71、静态变量什么时候初始化?
- 初始化只有一次,但是可以多次赋值,在主程序之前,编译器已经为其分配好了内存;
- 静态局部变量和全局变量一样,数据都存放在全局区域,所以在主程序之前,编译器已经为其分配好了内存;
C和C++中静态局部变量的初始化节点又有点不太一样:
- 在C中,初始化发生在代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到在C语言中无法使用变量对静态局部变量进行初始化,在程序运行结束,变量所处的全局内存会被全部回收。
- 在C++中,初始化是在执行相关代码时才会进行初始化;
- 主要是由于C++引入对象后,要进行初始化必须执行相应构造函数和析构函数,在构造函数或析构函数中经常会需要进行某些程序中需要进行的特定操作,并非简单地分配内存。
- 所以C++标准定为全局或静态对象是有首次用到时才会进行构造;
- 在程序结束,按照构造顺序反方向进行逐个析构。
- 所以在C++中是可以使用变量对静态局部变量进行初始化的。
72、从汇编层去解释一下引用
9: int x = 1;
00401048 mov dword ptr [ebp-4],1
10: int &b = x;
0040104F lea eax,[ebp-4]
00401052 mov dword ptr [ebp-8],eax
x的地址为ebp-4,b的地址为ebp-8,因为栈内的变量内存是从高往低进行分配的,所以b的地址比x的低。
lea eax,[ebp-4] 这条语句将x的地址ebp-4放入eax寄存器。
mov dword ptr [ebp-8],eax 这条语句将eax的值放入b的地址。
ebp-8中上面两条汇编的作用即:将x的地址存入变量b中,这不和将某个变量的地址存入指针变量是一样的吗?
所以从汇编层次来看,的确引用是通过指针来实现的。
73、new和malloc的区别
- new/delete是C++关键字,需要编译器支持;
- malloc/free是库函数,需要头文件支持;
- 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸;
- new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
- new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。
- new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。
- delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
- malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
74、delete p、delete [] p、allocator都有什么作用?
- 动态数组管理new一个数组时,[]中必须是一个整数;- new动态数组返回的并不是数组类型,而是一个元素类型的指针;
- delete[] 时,数组中的元素按构造逆序的顺序进行销毁;
- new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起;delete也是将对象析构和内存释放组合在一起的;
- allocator将这两部分分开进行,allocator申请一部分内存,不进行初始化对象,只有当需要的时候才进行初始化操作。
75、new和delete的实现原理, delete是如何知道释放内存的大小的?
new和delete的实现原理
new:
-
new简单类型直接调用operator new分配内存,对于简单类型,new[]计算好大小后调用operator new;
-
而对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;
-
对于复杂数据结构,new[]先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数,针对复杂类型,new[]会额外存储数组大小;
- new表达式调用一个名为operator new(operator new[])函数,分配一块足够大的、原始的、未命名的内存空间;
- 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
- 对象被分配了空间并构造完成,返回一个指向该对象的指针。
delete:
- delete简单数据类型默认只是调用free函数;
- 复杂数据类型先调用析构函数再调用operator delete;
- 针对简单类型,delete和delete[]等同;
- 假设指针p指向new[]分配的内存。因为要4字节存储数组大小,实际分配的内存地址为[p-4],系统记录的也是这个地址;delete[]实际释放的就是p-4指向的内存。
- 而delete会直接释放p指向的内存;
delete是如何知道释放内存的大小的?
- 需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。
76、malloc申请的存储空间能用delete释放吗?
不能!
- malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的;
- malloc /free的操作对象都是必须明确大小的,而且不能用在动态类上;
- new 和delete会自动进行类型检查和大小,malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的;
当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。
77、C++中新增了string,它与C语言中的 char *有什么区别吗?它是如何实现的?
- string继承自basic_string,其实是对char*进行了封装,封装的string包含了char *数组,容量,长度等等属性。
- string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2^n),然后将原字符串拷贝过去,并加上新增的内容。
78、malloc、realloc、calloc的区别?
1、malloc函数
void* malloc(unsigned int num_size);
int *p = malloc(20*sizeof(int));申请20个int类型的空间;
malloc申请的空间的值是随机初始化的。
2、calloc函数
void* calloc(size_t n,size_t size);
int *p = calloc(20, sizeof(int));
calloc申请的空间的值是初始化为0的。
3、realloc函数
void realloc(void *p, size_t new_size);
给动态分配的空间分配额外的空间,用于扩充容量。
79、类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?
1、类成员初始化方式
- 赋值初始化,通过在函数体内进行赋值初始化;
- 列表初始化,在冒号后使用初始化列表进行初始化。
两种方式的主要区别在于:
- 对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
- 列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员,只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
2、为什么用成员初始化列表会快一些?
80、有哪些情况必须用到成员列表初始化?作用是什么?
必须使用成员初始化的四种情况:
- 该类的成员变量是个引用
- 该类的成员变量是const类型
- 该类是继承一个基类,并且基类中有构造函数,构造函数里有参数;
- 该类的成员变量类型是类类型,而该类的构造函数带参数时;
成员初始化列表细节探究:
- 初始化列表中的代码可以看作是被编译器安插到构造函数体中的,只是这些代码有些特殊。
- 这些代码 是在任何用户自己的构造函数体代码之前被执行的。所以大家要区分开构造函数中的用户代码 和 编译器插入的 初始化所属的代码。
- 这些列表中变量的初始化顺序是 定义顺序,而不是在初始化列表中的顺序。