C++ 对象
demo.cpp
#include <stdio.h>
//通过class关键字类定义类
class Student{
public:
//类包含的变量
char *name;
int age;
float score;
//类包含的函数
void say(){
printf("%s的年龄是 %d,成绩是 %f\n", name, age, score);
}
};
int main(){
//通过类来定义变量,即创建对象
class Student stu1; //也可以省略关键字class
//为类的成员变量赋值
stu1.name = "小明";
stu1.age = 15;
stu1.score = 92.5f;
//调用类的成员函数
stu1.say();
return 0;
}
gcc命令在链接时默认使用C的库,只有添加了-lstdc++选项才会使用 C++ 的库, 或者使用g++.
gcc demo.cpp -lstdc++ -o demo
g++ demo.cpp -o demo
但是让用户指定参数是一种不明智的行为,不但增加了学习成本,还使得操作更加复杂,所以后来 GCC 又针对不同的语言推出了不同的命令,例如g++命令用来编译 C++,gcj命令用来编译 Java,gccgo命令用来编译Go语言。
C++ 命名空间:
namespace 是C++中的关键字,用来定义一个命名空间,语法格式为:
namespace name{
//variables, functions, classes
}
#例如
namespace Li{ //小李的变量定义
FILE fp = NULL;
}
namespace Zhang{ //小张的变量定义
FILE fp = NULL
}
使用方式一:
Li :: fp = fopen(“one.txt”, “r”); //使用小李定义的变量 fp
使用方式二:
using Li :: fp;
fp = fopen(“one.txt”, “r”); //使用小李定义的变量 fp
使用命名空间下的类定义变量:
Diy::Student stu1;
c++ 输入输出
#include<iostream>
using namespace std;
int main(){
int x;
float y;
cout<<"Please input an int number:"<<endl;
cin>>x;
cout<<"The int number is x= "<<x<<endl;
cout<<"Please input a float number:"<<endl;
cin>>y;
cout<<"The float number is y= "<<y<<endl;
return 0;
}
cin表示标准输入、cout表示标准输出、cerr表示标准错误。
iostream 是 Input Output Stream 的缩写,意思是“输入输出流”。
cout 和 cin 都是 C++ 的内置对象,而不是关键字
endl 最后一个字符是字母“l”,而非阿拉伯数字“1”,它是“end of line”的缩写。这段代码中也可以用\n来替代endl
使用 cout 进行输出时需要紧跟<<运算符,使用 cin 进行输入时需要紧跟>>运算符,这两个运算符可以自行分析所处理的数据类型,因此无需像使用 scanf 和 printf 那样给出格式控制字符串。
C++ 与 C const 区别
同样的一份代码, 保存为demo.c (gcc demo.c), demo.cpp (g++ demo.cpp) 执行后,得到的结果不同.
#include<stdio.h>
int main(){
const int n = 10;
int *p = (int*) &n; // 必须强制类型转换
*p = 99;
printf("%d\n", n);
return 0;
}
变量是要占用内存的,即使被 const 修饰也不例外。m、n 两个变量占用不同的内存,int n = m;表示将 m 的值赋给 n,这个赋值的过程在C和C++中是有区别的。
在C语言中,编译器会先到 m 所在的内存取出一份数据,再将这份数据赋给 n;而在C++中,编译器会直接将 10 赋给 m,没有读取内存的过程,和int n = 10;的效果一样。C++ 中的常量更类似于#define命令,是一个值替换的过程,只不过#define是在预处理阶段替换,而常量是在编译阶段替换。
C++ 对 const 的处理少了读取内存的过程,优点是提高了程序执行效率,缺点是不能反映内存的变化,一旦 const 变量被修改,C++ 就不能取得最新的值。
C++中全局 const 变量的可见范围是当前文件, 可以通过添加 extern 关键字来增大 C++ 全局 const 变量的可见范围
内存操作
在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。
int *p = (int*) malloc( sizeof(int) * 10 ); //分配10个int型的内存空间
free(p); //释放内存
在C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存。
int *p = new int; //分配1个int型的内存空间
delete p; //释放内存
int *p = new int[10]; //分配10个int型的内存空间
delete[] p;
和 malloc() 一样,new 也是在堆区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收。
C++ inline内联函数
为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数。
将函数声明为内联函数
//内联函数,交换两个数的值
inline void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
函数宏定义 可以使用 内联函数来代替,好处是 不用考虑宏展开时括号的问题。但是宏定义函数,可以省略参数类型的问题。
C++ 函数重载
C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading)。借助重载,一个函数名可以有多种用途。
//交换 int 变量的值
void Swap(int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
//交换 float 变量的值
void Swap(float *a, float *b){
float temp = *a;
*a = *b;
*b = temp;
}
参数列表不同包括参数的个数不同、类型不同或顺序不同,仅仅参数名称不同是不可以的。函数返回值也不能作为重载的依据。
C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_int,void Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。
C++函数重载过程中的二义性和类型转换
发生函数调用时编译器会根据传入的实参的个数、类型、顺序等信息去匹配要调用的函数,这在大部分情况下都能够精确匹配.但当实参的类型和形参的类型不一致时情况就会变得稍微复杂,例如函数形参的类型是int,调用函数时却将short类型的数据交给了它,编译器就需要先将short类型转换为int类型才能匹配成功。
C++ 标准规定,在进行重载决议时编译器应该按照下面的优先级顺序来处理实参的类型:
▽优先级 | 包含的内容 | 举例说明 |
---|---|---|
精确匹配 | 不做类型转换,直接匹配 | (暂无说明) |
只是做微不足道的转换 | 从数组名到数组指针、从函数名到指向函数的指针、从非 const 类型到 const 类型。 | |
类型提升后匹配 | 整型提升 | 从 bool、char、short 提升为 int,或者从 char16_t、char32_t、wchar_t 提升为 int、long、long long。 |
小数提升 | 从 float 提升为 double。 | |
使用自动类型转换后匹配 | 整型转换 | 从 char 到 long、short 到 long、int 到 short、long 到 char。 |
小数转换 | 从 double 到 float。 | |
整数和小数转换 | 从 int 到 double、short 到 float、float 到 int、double 到 long。 | |
指针转换 | 从 int * 到 void *。 |
C++ 标准还规定,编译器应该按照从高到低的顺序来搜索重载函数,首先是精确匹配,然后是类型提升,最后才是类型转换;一旦在某个优先级中找到唯一的一个重载函数就匹配成功,不再继续往下搜索。
如果在一个优先级中找到多个(两个以及以上)合适的重载函数,编译器就会陷入两难境地,不知道如何抉择,编译器会将这种模棱两可的函数调用视为一种错误,因为这些合适的重载函数同等“优秀”,没有一个脱颖而出,调用谁都一样。这就是函数重载过程中的二义性错误。
#include <iostream>
using namespace std;
//1号函数
void func(char ch){
cout<<"#1"<<endl;
}
//2号函数
/*
void func(int n){
cout<<"#2"<<endl;
}
*/
//3号函数
void func(long m){
cout<<"#3"<<endl;
}
//4号函数
void func(double f){
cout<<"#4"<<endl;
}
int main(){
short s = 99;
float f = 84.6;
func('a'); //不需要类型转换,调用func(char)
func(s); //将short转换成int,调用func(int)
func(49); //不需要类型转换,调用func(int)
func(f); //将float转换成double,调用func(double)
return 0;
}
这段代码在编译时发生了错误,大概的意思是:func(s)和func(49)这两个函数发生调用错误,它们可以匹配三个重载函数中的任何一个,编译器不知道如何抉择。
在设计重载函数时,参数类型过少或者过多都容易引起二义性错误,因为这些类型相近,彼此之间会相互转换。