C++常见问题整理
1.关键字
1.1 const
底层const指针:可以修改指向,不可以改值。
1 |
|
底层const指针:可以改值,不可以改指向。
1 |
|
成员函数指针:表示这个函数不会修改成员变量的内容,mutable关键字修饰的成员变量除外,同时该函数不能被static关键字修饰。
1 |
|
1.2 static
static本质与全局符号的概念一致,只是在语法层面存在细微差别。
- 修饰全局变量:语法层面限制该变量只在当前文件内有效。
- 修饰全局函数:语法层面限制该函数只在当前文件内有效。
- 修饰局部变量:语法层面用来扩展栈变量的生命周期。
- 修饰成员变量:语法层面表示多个对象中共享数据,可以通过类名访问,需要在类外初始化,可以被普通成员函数访问。
- 修饰成员函数:语法层面可以被类名直接调用,不传递this指针,不能访问普通成员变量。
1.3 virtual
用于声明一个类成员函数为虚函数。存在虚函数的类对象,首个成员固定为一个指针,指向该类的虚函数表。
当父类指针指向子类对象,子类重写父类虚函数,此时虚函数会以指针寻址的方式动态调用,构成多态性。
构造函数中完成虚表指针的初始化,所以构造函数不能被定义为虚函数。
析构函数声明为虚函数的原因:
1 |
|
如果析构函数不为虚函数,上面的代码会只调用父类的析构函数,子类的对象没有被正确释放。
当析构函数声明为虚函数时,会在子类虚函数表中找到子类的析构函数进行调用,先析构子类再析构父类,整个派生类被完全释放。
1.4 volatile
告诉编译器,这个变量的值需要每次都从内存中去读取,不允许暂存到寄存器中,可以用在多线程对某个全局标志的访问时。
1 |
|
上面代码在Release版编译下,会死循环,汇编如下:
在将g_Flag使用volatile修饰后,不会产生死循环,汇编代码如下:
1.5 explict
用于修饰类构造函数,不允许对象发生隐式类型转换。
1 |
|
2.字节对齐
2.1 规则
- 结构体成员:第一个成员放在offset为0的位置,之后的每个数据成员按照#pragma pack指定的数值和sizeof()中的较小值进行对齐。
- 结构体总体:总大小按照#pragma pack指定的数值和sizeof()中的较小值进行对齐。
2.2 原因
平台原因:某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:访问未对齐的内存,处理器需要作两次访问;而访问对齐的内存仅需要一次访问。
如int类型的四字节存储在0x1地址上,cpu需要首先从0x0位置读4字节,舍去0x0这1个字节,再从0x4位置读4字节,舍去0x5开始的3个字节,最终拼接得到最终结果。
3.类型转换
const_cast
用于对指针或引用去const属性
1
2
3
4
5
6
7
8
9
10int a = 5;
const int b = 10;
const int* pCa = &a;
// *p = 10; // 错误
// b= 20; // 错误
int* pa = const_cast<int*>(pCa); // 指针去const
int& rb = const_cast<int&>(b); // 引用去const
*pa = 10; // 正确
rb = 20; // 正确static_cast
与C风格强制类型转换基本一样,不存在运行时类型检查。
1
2float fVal1 = 3.1415926f;
int dwVal2 = static_cast<int>(fVal1);dynamic_cast
具有运行时类型检查,可以防止在具有虚函数的派生关系中,将父类对象地址交给子类指针,会返回NULL。
1
2
3
4
5
6Base *base = new Base();
Sub *pSub = dynamic_cast<Sub*>(base); // 转换失败,此时pSub为NULL
if(pSub != NULL)
{
// do something.
}reinterpret_cast
对要转换的二进制流重新解释,常用于指针与整数之间的转换。
1
2
3
4
5unsigned short Hash(void *p)
{
unsigned int val = reinterpret_cast<unsigned int>(p);
return (unsigned short)(val ^ (val >> 16));
}