1、linux中大小端对齐的问题
2、extern "C" {}用法
3、类型安全和内存安全
4、c++的构造函数类型
默认构造函数、初始化构造函数、拷贝构造函数、移动构造函数
特别:
委托构造函数:
转换构造函数:
5、浅拷贝:拷贝指针,而不是拷贝一份新数据;若原空间内存被释放了,调用拷贝的对象会出现错误。
深拷贝:拷贝数据并开辟新的内存
6、public:类内部外部均可访问 protect:类内和派生类可访问;private:类内可访问
7、大小端存储问题:
大端存储:数据的高子节存储在低地址中
小段存储:数据的高子节存储在高地址中(LInux系统使用的是小端存储)
网络传输字节序为大端存储
检测大小段存储方式可以使用强制类型转换,将int类型截断为char类型,然后看保存的是高子节还是低字节。
8、三种不同类型的new
plain new 普通的也是最常使用的new,如果分配内存失败会抛出异常
nothrow new 不抛出异常,失败返回null
placement new 允许在一块已经分配好的内存位置上,重新构造对象;内部实际上不分配内存,而是直接调用构造函数。palcement new的对象不能使用delete来销毁,而是应该显式的调用析构函数,因为placement new的对象实际大小并不一定等于原来的内存大小
9、static作用
隐藏:对于不同的文件的全局变量,static可以限定适用范围
改变生命周期:局部变量使用static修饰,可以延长生命周期
默认初始化:static类型数据和全局数据默认初始化为0
修饰函数:改为类的属性而不关联类的实例
类的static修饰函数不接受this指针,因此只能访问static类型变量
10、const修饰指针
顶层指针:表示指针本身指向的地址不可更改,即本身是const属性,但是可以通过*来读写指向的内容
int* const p;
底层指针:表示指针指向的是一个常量,不可修改,可以改变指针指向的地址,但是无法通过指针修改指向的内容
const int* p;
int const *p;
11、静态局部变量、全局变量都存放在全局区,在首次使用时进行初始化(C语言为程序开始时就初始化)
12、const对象只能调用const成员函数
13、delete[]时,元素按照逆序进行销毁
14、allocator:指分配内存,先不初始化,在需要的时候才会进行初始化
15、malloc和free只能用于明确大小的对象,不能用于动态对象。malloc开辟的内存不能用delete释放。
16、malloc底层函数:brk(堆顶地址上移,小于128k)、mmap(文件映射区,大于128k执行)
17、列表初始化方式,是在给成员变量分配内存空间时就进行初始化,因此速度比较快
18、引用成员和常量成员必须使用列表初始化的方法。
19、避免内存泄漏的方法:可以增加一个全局变量记录new的次数,释放数组一定要delete[],发生继承的时候,一定要将基类的析构函数写为虚函数。
20、内存泄漏检测工具:Valgrind(Linux)
21、对象复用,本质上是一种设计模式--享元模式。通过将对象存储到‘对象池’中实现对象的重复利用。
22、零拷贝:避免cpu将数据从一个位置拷贝到另一个位置,零拷贝可以实现减少拷贝次数和操作总线的次数。
23、使用push_back()函数需要调用拷贝构造函数和转移构造函数,而使用emplace_back()插入的元素原地构造,不需要触发拷贝构造和转移构造,效率更高
24、列表初始化成员函数方式对于内置数据类型效率相当,但是对于自定义数据类型,少了一次构造函数的调用,效率更快。
25、四种类型转换
reinterpret_cast<type-id>() :强制类型转换
const_cast<type-id>(): 修改对象的const或者volatile属性
dynamic_cast<type-id>():有类型检查,基类向派生类转换比较安全,派生类转为基类不安全。
static_cast<type-id>():普通的类型转换,但是没有类型检查,也不能修改const或volatile属性
26、C++函数压栈过程,函数发生调用,先将本函数的执行状,参数等进行入栈。函数的调用顺序:从栈空间分配内存-->从实参的存储空间复制到形参
27、coredump:程序运行异常而终止爆出的错误,会产生一个core文件,里面记录了运行内存,寄存器,内存指针和函数堆栈等信息。
28、拷贝构造函数和移动构造函数
拷贝构造函数将原对象的数据完整复制一份,指针类型必须采用深层复制(即新开辟内存空间并复制内容);
移动构造函数起到资产转移的作用,将类a的资产转移给类b,转移后要将右值的资源控制权解除,比如指针设置为null,防止释放。
29、静态类型:编译时已经确认
动态类型:例如指针类型,运行时才会确认的类型。
动态绑定和静态绑定:动态绑定依赖于指向的对象的动态类型,静态绑定依赖于指向对象的静态类型,编译时已经确定。
静态类型函数不具有动态类型,只有虚函数才具有动态类型。
30、为什么指针可以改变指向对象,但是引用却不能?----符号表的问题,指针存储的是指针实参的地址,引用存储的则是引用对象的原地址。
31、类如何实现只能静态分配和只能动态分配(堆)?
静态分配办法:将new和delete重载为private属性
动态分配办法:将new和delete重载为protect属性
32、向上类型转换和向下类型转换
向上类型转换是说将派生类指针类型转换为基类,是安全的(因为基类指针可以指向子类对象)
向下类型转换,不安全。
33、函数指针:特殊的指针类型:
return_type (*ptr) (param_type);
// 例子:
int (*p)(int);
// ------------------举例------------------------
int (*ptr)(int);
int func1(int a)
{
cout << a << endl;
return a;
}
int main() {
ptr = &func1;
(*ptr)(5);
return 0;
}
34、函数调用过程栈的变化,返回值和参数变量哪个先入栈?
1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调
函数所需要的参数压入栈;
2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作
隐含在call指令中);
3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp),然后再保存调用者函数的栈顶地址,即:当前
被调函数的栈底地址(mov ebp,esp);
4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的
顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈
35、函数参数扫描从后向前,函数压栈出栈,因此printf函数可以得到一个正常顺序的参数。
36、cout不是函数,而是ostream的全局对象:
37、重载运算符为成员函数的时候,默认将this对象绑定到运算符左侧
38、explicit修饰构造函数,可以禁止隐式类型转换
39、你知道strcpy和memcpy的区别是什么吗?
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。 2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。 3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
40、动态与静态绑定
41、防止类被实例化的方法:
将构造函数设置为private,可以实现单例模式
写一个纯虚函数,类变为抽象类,无法实例化
42、memset(this,0, sizeof(*this)
该方法在this类对象只含简单数据类型的时候很好,可以做到直接初始化为0,但是如果·含有虚函数、复杂数据类型、其他类型对象的时候会破坏他们的内存。
43、一致性哈希算法的解答:一致性哈希
44、c++从代码到可执行程序的过程:
预处理:删除注释,宏定义展开,替换文本;处理include的文件(递归进行).i文件
编译:生成.s文件,词法分析,语法分析,语义分析、编译器优化、生成目标文件
汇编:转换为汇编语言--->.o
链接:将不同的源文件进行链接,生成最终的可执行文件
45、链接分为动态链接和静态链接:
动态链接:把程序按照模块分为各个独立的部分,在程序执行时才会去把他们连接到一起生成一个可执行文件;
灵活,省空间,但是影响性能;
静态链接:所有函数和数据被编译进一个二进制文件,运行时执行;
速度快,浪费空间,不好更新(修改其中一个库函数,需要对整个文件进行重新编译)
46、友元函数-->使用时要传递类的引用或者指针;友元类:所有成员都是另一个类的友元函数
注:友元关系不可继承
47、读写锁:多个读者可以同时读,但是写锁只能一个持有,且写锁优先读锁。
48、delete和delete[]区别,delete只调用一次析构函数,delete[]调用每个元素的析构函数。
49、构造函数调用顺序:
基类的构造函数,如果有多个基类,则按照类派生表的顺序构造;
成员类对象的构造函数
派生类广构造函数
析构函数的调用顺序:----》与构造循序相反。
50、拷贝构造函数为什么必须传递引用?
拷贝构造函数本身就是将引用对象复制一份产生新对象,如果传递值的话会出现递归调用的情况。
51、静态函数定义为虚函数是没有意义的,因为静态函数本身不含有this指针,而虚指针又是跟对象绑定的,因此静态函数内部无法访问虚指针。
52、虚函数表到底有几个?每个类都有一个虚函数表吗?虚指针和虚函数表的运行原理到底是什么样的?
53、继承的原理,底层实现;菱形继承的问题,内存布局。
对于多重继承来讲,一个类继承了两个基类,但是这两个基类中存在同名的成员,在使用该类的时候就会造成访问的二义性,解决办法是在访问的成员前加上限定域。
菱形继承:
菱形基层是比较特殊的存在,即一个类派生出两个子类,两个子类又被一个类继承。这个时候初始化最下面的子类,开始的基类会被分配两次内存,浪费空间且也会产生歧义;解决办法,将共同继承的基类声明为虚基类。