复习之前必须说一个关键点 C++这门语言是强类型语言,非常的强调类型。
1. 关键字const
1.0 ) const 非指针
int main() { const int a = 1; int *p = (int *)&a; *p = 10; return 0; }
结果 a = 1 *p = 10
原因:a是常量,处在常量符号表中。在编译阶段,编译器检查到使用a这个常量时,会从常量符号表中取出这个值,而不是从该地址内存中取值。
当使用 &获取地址后,才会为这个地址申请内存。因此*p只是操作这个地址的值,不影响a的常量符号表中的值
1.1 ) const 指针
int main() { int a = 1; int b = 2; const int *p1 = (int *)&a; int *const p2 = (int *)&b; *p1 = 10; //错误 p1 = &b; //正确 *p2 = 20; //正确 p2 = &a; //错误 return 0; }
当const在 * 左侧,即代码中的指针p1,表示修饰的是p1指向的地址的内存,因此,修改p1指向地址是可以的,但修改p1指向地址的内存编译器会报错。
当const在 * 右侧,即代码中的指针p2,表示修饰的是p2指向的地址,因此,修改p2指向地址的内存是可以的,但是修改p2指向地址编译器会报错。
2. 关键字const 与define的区别
发生的时期不同。
define发生在预处理阶段,进行简单的文本替换,但不会进行类型检查作用域检查。
const在预处理之后的编译阶段,会对类型进行检查。
因此用define的时候要格外小心,特别是宏定义一些函数的时候,要考虑符号的优先级问题。
3. inline 内联函数
一般调用一个普通较短的函数,就是跳转到这个函数名(地址),在一些实时性要求高的程序里面,跳转就要有系统开销(保存现场、恢复现场),会降低实时性。因此可以使用inlne关键字来修饰函数,类似于define,就是原地展开函数。
内联是一种请求,如果函数过于复杂,编译器会拒绝内联,函数变回普通函数。
4. 函数重载
这是C++一个重要的特性。
void r1chie(void) { } void r1chie(int a) { } void r1chie(int a,int b) { } void r1chie(char a) { } void r1chie(char a,int b) { } int main() { int a; char c; r1chie(); r1chie(a); r1chie(a,c); r1chie(c); r1chie(c,a); return 0; }
我们反汇编上面这段函数
00000000004006ef <main>: 4006ef: 55 push %rbp 4006f0: 48 89 e5 mov %rsp,%rbp 4006f3: 48 83 ec 10 sub $0x10,%rsp 4006f7: e8 ba ff ff ff callq 4006b6 <_Z6r1chiev> //4006b6 4006fc: 8b 45 fc mov -0x4(%rbp),%eax 4006ff: 89 c7 mov %eax,%edi 400701: e8 b7 ff ff ff callq 4006bd <_Z6r1chiei> //4006bd 400706: 0f be 55 fb movsbl -0x5(%rbp),%edx 40070a: 8b 45 fc mov -0x4(%rbp),%eax 40070d: 89 d6 mov %edx,%esi 40070f: 89 c7 mov %eax,%edi 400711: e8 b1 ff ff ff callq 4006c7 <_Z6r1chieii> //4006c7 400716: 0f be 45 fb movsbl -0x5(%rbp),%eax 40071a: 89 c7 mov %eax,%edi 40071c: e8 b3 ff ff ff callq 4006d4 <_Z6r1chiec> //4006d4 400721: 0f be 45 fb movsbl -0x5(%rbp),%eax 400725: 8b 55 fc mov -0x4(%rbp),%edx 400728: 89 d6 mov %edx,%esi 40072a: 89 c7 mov %eax,%edi 40072c: e8 af ff ff ff callq 4006e0 <_Z6r1chieci> // 4006e0 400731: b8 00 00 00 00 mov $0x0,%eax 400736: c9 leaveq 400737: c3 retq
可以看到,虽然函数名是一样的,但是地址却是不一样的。
那么重载是根据什么呢?
4.1 )参数的数量
4.2 )参数的类型
4.3) 参数的顺序
4.4) 同一作用域
5. 关键字new和delete
malloc函数与关键字new。
5.1)malloc是库函数,需要包含头文件。new需要编译器支持。
5.2)malloc需要指定大小。new是编译器计算。
5.3)malloc返回的是void *类型指针,还需要进行强转。new返回的是对象类型指针,因此不需要强转。
使用new申请内存,如果内存不需要了就记得释放
class r1chie { public: int a; int b; private: int c; }; int main() { int *p1 = new int(); int *p2 = new int(10); int *p3 = new int[10]; char *p4 = new char(); r1chie *p5 = new r1chie; int **p6 = new int*[10]; delete p1; delete p2; delete[] p3; delete p4; delete p5; delete[] p6[10]; return 0; }
6. namespace 命名空间
#include <iostream> namespace r1chie { int i = 1; } namespace r1chie_2 { int i = 10; } using namespace std; int main() { int i = 100; cout << r1chie::i << endl; cout << r1chie_2::i << endl; cout << i << endl; return 0; }
输出结果:
1
10
100
7. C++的引用 &
7.0)引用的本质就是指针。
7.1)一个变量可以取多个别名
int main() { int a = 1; int& b = a; int& c = a; cout << a << endl; cout << b << endl; cout << c << endl; a = 2; cout << a << endl; cout << b << endl; cout << c << endl; return 0; }
输出结果:
1 1 1 2 2 2
7.2)引用必须初始化
int main() { int& b; // 编译器报错 return 0; }
7.3)引用只能初始化一次,不能改变为其它变量的引用
7.4)不能建立数组的引用。因为数组是一个由若干个元素所组成的集合,所以无法建立一个数组的别名
7.5)引用的类型要相同
int main() { int a = 1; float& b = a; //报错 return 0; }
7.6)常引用
7.6.1) 使用常引用,则不能使用引用对目标变量的值进行改变,从而使引用的目标成为了const。
int main() { int a = 1; const int& b = a; a = 2; //正确 b = 3; //报错 return 0; }
7.6.2)临时对象,临时对象都是const类型的
string fun1(); void fun2(string &s); fun2(fun1()); //报错 fun2("Hello world"); //报错
原因是将const类型转换成非const,C++是强类型的语言因此报错。
7.7)C++中引用的内部实现
int& a ---> int* const a void f(int& a) ----> void f(int* const a) { { a = 5; *a = 5; } }
关于引用这篇文章分析得很好 https://www.cnblogs.com/Mr-xu/archive/2012/08/07/2626973.html
8. C++的强制类型转换
这个又是一个比较重要的特性。
四种转换:
8.1)static_cast
变量和对象之间的转换 和 有继承关系的类对象指针转换,可通过父类对象去初始化子类对象(只会初始化父类部分)。
class Base { public: int a; Base(int i) { a = i; cout << "This is Base"<< endl; } }; class Child :public Base { public: int b; Child(int i) : Base(i) { b = i; cout << "This is Child" << endl; } }; int main() { int a = 1; char b = 'a'; a = static_cast<int>(b); Base *b1 = new Base(10); Child *c1 = static_cast<Child*>(b1); c1->b = 100; cout << "c1->a = " << c1->a << endl; cout << "c1->b = " << c1->b << endl; c1->a = 20; cout << "b1->a = " << b1->a << endl; return 0; }
输出结果
This is Base c1->a = 10 c1->b = 100 b1->a = 20
8.2)const_cast
去除类对象的属性,但必须要强制转换成引用或指针
int main() { const int a = 2; // int b = const_cast<int>(a); 报错 int& c = const_cast<int&>(a); int *p = const_cast<int*>(&a); cout << a << endl; cout << c <<endl; cout << *p << endl; c = 6; cout << endl; cout << a << endl; cout << c <<endl; cout << *p << endl; return 0; }
输出:
2 2 2 2 //从常量符号表取出,因此a的值依然是2 6 6
8.3)dynamic_cast
用于有继承关系的类指针(引用)间的转换
用于有交叉关系的类指针(引用)间的转换
具有类型检查的功能,编译时会去检查使用的方法是否正确,转换是否成功只有程序运行过程才知道
父类转子类时,父类中必须有虚函数支持(换句话说必须是多态)
class Base { public: Base() { cout << "This is Base"<<endl; } /* virtual ~Base() { cout << "This is ~Base" << endl; } */ }; class Child : public Base { }; int main() { Base *p = new Base; Child *pc = dynamic_cast<Child*>(p); //报错,原因父类没有虚函数 cout << p << endl; delete p; return 0; }
8.4)reinterpret_cast
用于指针之间的转换
int main() { int a = 1; char * b = reinterpret_cast<char*>(&a); char c = reinterpret_cast<int>(a); //报错 return 0; }