目录
2.1.3在1G内存的计算机中能否malloc(1.2G)?为什么?⭐⭐
2.1.7头文件声明时加extern,但是定义时不要加,因为extern可以多次声明,但只有一个定义⭐⭐⭐⭐
2.1.8函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
2.1.15const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
2.1.17有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
2.1.20 c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
2.1.22 select、epoll的区别?使用场景?⭐⭐⭐⭐⭐
2.2.3被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
2.2.5对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
2.2.6析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
目录
2.1.3在1G内存的计算机中能否malloc(1.2G)?为什么?⭐⭐
2.1.7头文件声明时加extern,但是定义时不要加,因为extern可以多次声明,但只有一个定义⭐⭐⭐⭐
2.1.8函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
2.1.15const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
2.1.17有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
2.1.20 c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
2.1.22 select、epoll的区别?使用场景?⭐⭐⭐⭐⭐
2.2.3被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
2.2.5对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
2.2.6析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
2.2.14成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
2.2.15如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐
2.2.14成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
2.2.15如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐
2.1 c和c++
-
2.1.1 new和malloc的区别⭐⭐⭐⭐⭐
答:
1)new、delete是C++中独有的操作符,而malloc和free是C/C++中的标准库函数。
2)使用new创建对象在分配内存的时候会自动调用构造函数,同时也可以完成对对象的初始化,同理要记得delete也能自动调用析构函数。因为malloc和 free是库函数而不是运算符,不在编译器控制范围之内,所以不能够自动调用构造函数和析构函数。也就是mallloc只是单纯地为变量分配内存,free也只是释放变量的内存。
3)new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而malloc返回的是void*类型,我们需要强行将其转换为实际类型的指针,并且需要指定好要申请内存的大小,malloc不会自动计算的。
4)C++允许重载new/delete操作符,而malloc和free是一个函数,并不能重载。
5)new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。
6)内存区域:先了解自由存储区和堆,两者不相等于的。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
特征 | new/delete | malloc/free |
分配内存的位置 | 自由存储区 | 堆 |
内存分配失败 | 抛出异常 | 返回NULL |
分配内存的大小 | 编译器根据类型计算得出 | 显式指定字节数 |
处理数组 | 有处理数组的new版本new[] | 需要用户计算数组大小后进行内存分配 |
已分配内存的扩张 | 不支持 | 使用realloc完成 |
分配内存时内存不足 | 可以指定处理函数或重新制定分配器 | 无法通过代码进行处理 |
是否可以重载 | 可以 | 不可以 |
构造函数与析构函数 | 调用 | 不调用 |
-
2.1.2 malloc的底层实现⭐⭐⭐⭐
解析:回顾进程的空间模型,如图2-1所示,
图2-1进程空间示意图
与1.1.5节的图相比,多了一个program break指针,Linux维护一个break指针,这个指针指向堆空间的某个地址。从堆起始地址到break之间的地址空间为映射好的,可以供进程访问;而从break往上,是未映射的地址空间,如果访问这段空间则程序会报错。我们用malloc进行内存分配就是从break往上进行的。
图2-2堆内部机制
获取了break地址,也就是内存申请的初始地址,下面是malloc的整体实现方案:
malloc函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。 调用malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。 调用free函数时,它将用户释放的内存块连接到空闲链表上。 到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc()函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。
-
2.1.3在1G内存的计算机中能否malloc(1.2G)?为什么?⭐⭐
malloc能够申请的空间大小与物理内存的大小没有直接关系,仅与程序的虚拟地址空间相关。程序运行时,堆空间只是程序向操作系统申请划出来的一大块虚拟地址空间。应用程序通过malloc申请空间,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
-
2.1.4指针与引用的相同和区别;如何相互转换?⭐⭐⭐⭐⭐
1. 指针是一个实体,而引用仅是个别名
2. 指针和引用的自增(++)运算意义不一样,指针是对内存地址的自增,引用是对值的自增;
3. 引用使用时无需解引用(*),指针需要解引用;
4. 引用只能在定义时被初始化一次,之后不可变;指针可变;
5. 引用不能为空,指针可以为空;
6.引用没有const,指针有const;(本人当初看到这句话表示疑问,这里解释一下:指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)
7. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32位系统指针变量一般占用4字节内存。
1)指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。
2)引用转指针:把引用类型的对象用&取地址就获得指针了。
1. 1. int a = 0;
2. 2.
3. 3. int *pA = &a;
4. 4.
5. 5. void fun(int &va){}
6. 6.
7. //此时调用: fun(*pA);
8.// pA是指针,加个*号后可以转换成该指针指向的对象,此时fun的形参是一个引用值,pA指针指向的对象会转换成引用va。
9.
-
2.1.5 C语言检索内存情况 内存分配的方式⭐⭐⭐
检索内存:顾名思义,对某段内存进行遍历搜索。
内存分配:
1、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
2、在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3、从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
-
2.1.6 extern”C” 的作用⭐⭐⭐
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;
而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
-
2.1.7头文件声明时加extern,但是定义时不要加,因为extern可以多次声明,但只有一个定义⭐⭐⭐⭐
-
2.1.8函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
_stdcall和_cdecl都是函数调用约定关键字
_stdcall:参数由右向左压入堆栈;堆栈由函数本身清理
_cdecl:参数也是由右向左压入堆栈;但堆栈由调用者清理
-
2.1.9重写memcpy()函数需要注意哪些问题⭐⭐
需要考虑地址重叠问题
-
2.1.10数组到底存放在哪里⭐⭐⭐
实际的数组元素被存储在堆中,数组引用变量是一个引用类型的变量,被存储在栈中
-
2.1.11 struct和class的区别 ⭐⭐⭐⭐⭐
-
2.1.12 char和int之间的转换;⭐⭐⭐
-
2.1.13 static的用法(定义和用途)⭐⭐⭐⭐⭐
-
2.1.14 static的用法(定义和用途)⭐⭐⭐⭐⭐
1)定义静态全局变量
在普通全局变量前加关键字static就声明成了静态全局变量。如果没有初始化,则其默认值为0。二者的存储方式一样,均存储在静态存储区。然而二者的作用域发生了变化。非静态的全局变量作用域是整个源程序,比如说一个源程序中包含多个文件,则非静态的全局变量在各个文件中均有效。而static全局变量则限制了其作用域只能在定义了该变量的文件内,在其他文件中不能使用它。(其他文件不可以通过将它定义为extern而使用它)。
2)定义静态局部变量
在局部变量前面加上关键字static,该局部变量就成了静态局部变量。如果没有初始化,则其默认值为0。在函数内以static声明的变量虽然与自动局部变量的作用域相同(即作用域都只限于函数内),但存储空间是以静态分配而非默认的自动分配方式获取的。静态局部变量只能初始化一次,这是由编译器来保证实现。
3)定义静态成员变量
在C++中,在类的定义中以static声明的成员变量属于类变量,也即在所有类实例中共享。普通成员变量每个类实例有一份,而静态成员变量一个类只有一份,被所有类实例共享。静态数据成员在定义时需要分配空间,所以不能在类中进行初始化。静态数据成员没有this指针。
静态成员变量本质上还是全局变量。
4)定义静态函数
在函数的返回类型上加上关键字static,函数就被定义成静态函数,函数的作用域被限制在当前文件下。
5)定义静态成员函数
在C++中,在类的定义中以static声明的成员函数属于类函数。静态成员函数不具体作用于某个类实例,所以静态成员函数内部不能访问非静态成员变量,也不能调用非静态成员函数静态成员函数。非静态数据成员属于特定的类实例。主要用于对静态数据成员的操作。静态成员函数没有this指针。
-
2.1.15const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
1)就起作用的阶段而言:#define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
2)就起作用的方式而言:#define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
4)从代码调试的方便程度而言:const常量可以进行调试的,#define是不能进行调试的,因为在预编译阶段就已经替换掉了。
-
2.1.16 volatile作用和用法 ⭐⭐⭐⭐⭐
首先volatile修饰的变量,作用在编译阶段,影响编译出的结果,其修饰的变量是随时可能被修改的,volatile告诉编译器,这个变量是重要人物,不要偷懒的去走捷径,每次认认真真的去从内存拿值。
回答两个问题:
1.一个变量可以同时被const和volatile修饰吗?
2.一个指针可以是volatile的吗?如果可以那有什么意义?
第一个问题的答案是可以的,因为volatile只是告诉编译器,每次都要去内存取值,而不是用缓存的数据,然而const修的变量只是说明,该变量在本程序内部是可读的,但是是可以在程序外部的东西修改,不如外部的状态寄存器的值等,因此,const保证内部不修改,volatile保证每次都从内存取直。
第二个问题,指针可以是volatile的,例如,一个子服务修改指向一个BUFF的指针。
volatile关键点:
1)告诉编译器不做任何优化。
1. eg:给某一个地址输送两个指令
2. int *addr = 0xxxx;
3. *addr=1;
4. *addr=2;
5. 以上可能会被优化成:
6. *addr=…
7. *addr=2
8. 第一条指令可能会丢失。
9. 而:
10. volatile int *addr = 0xxxx;
11. *addr=1;
12. *addr=2;
13. 编译器就不会对其做优化:
14. *addr=1
15. *addr=2
16.
17.
2)定义的volatile修饰的变量是会在程序外被改变的,所以每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
1. eg:
2. volatile int flag;
3. falg = 0;
4. while(flag==0)
5. {
6. sleep(1);
7. }
8. go_on();
9.
如果没有volatile ,那么外部对flag的置位程序将不敏感,导致死循环。
-
2.1.17有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
常量指针:指向常量的指针,const int *p(int const*p,const在*之前),其本质是一个指针,指针指向常量,指针指向可以改,指针指向的值不可以改;
指针常量:本质是个常量,int *const p(const在*之后),指针指向不可以改,但是指针指向的值可以改;
常量引用:格式为 const 变量类型 &变量名,当声明该引用时,不可通过引用对其目标变量的值进行修改,变量自身可以修改,可用于保证函数内形参不可更改,也就是保证传入的实参为常量。
-
2.1.18指针和引用的区别⭐⭐⭐
指针是一个变量,存储的是一个地址,只想内存中的一个存储单元,引用只是变量的别名;
指针可以有多级,引用只有一级;
指针可以不用初始化,引用必须初始化;
指针可以为空,引用不可以;
指针初始化后可以改变,引用不可以;
引用的大小是变量的大小,指针的大小是本身的大小,4字节。
-
2.1.19c/c++中变量的作用域⭐⭐⭐⭐⭐
类型 | 作用域 | 生命周期 |
auto变量 | 一对{}内 | 当前函数 |
static局部变量 | 一对{}内 | 整个程序运行期 |
extern变量 | 整个程序 | 整个程序运行期 |
static全局变量 | 当前文件 | 整个程序运行期 |
extern函数 | 整个程序 | 整个程序运行期 |
static函数 | 当前文件 | 整个程序运行期 |
register变量 | 一对{}内 | 当前函数 |
全局变量 | 整个程序 | 整个程序运行期 |
-
2.1.20 c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
-
2.1.21 数组和链表的区别,使用场景?⭐⭐⭐
比较项 | 数组 | 链表 |
逻辑结构 | (1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素 | (1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存 |
内存结构 | 数组从栈上分配内存,使用方便,但是自由度小 | 链表从堆上分配内存,自由度大,但是要注意内存泄漏 |
访问效率 | 数组在内存中顺序存储,可通过下标访问,访问效率高 | 链表访问效率低,如果想要访问某个元素,需要从头遍历 |
越界问题 | 数组的大小是固定的,所以存在访问越界的风险 | 只要可以申请得到链表空间,链表就无越界风险 |
使用场景:
比较项 | 数组使用场景 | 链表使用场景 |
空间 | 数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据 | 链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储 |
时间 | 数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构 | 链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构 |
-
2.1.22 select、epoll的区别?使用场景?⭐⭐⭐⭐⭐
1、select的句柄数目有限,默认为1024个fd;而epoll没有限制,它的限制是最大的打开文件句柄数目,适用于高并发场景。
2、select采用轮询机制,每次遍历所有的文件描述符集合,效率低下,数据结构类似数组;而epoll是基于事件驱动的模型,维护一个队列,通过回调函数只处理活跃的文件描述符,效率更高,epoll不会随着fd数目增加而降低效率,epoll效率不一定比select高(可能要维护队列复杂)。
3、select只能检测是否可读写文件,无法区分是写入还是读取,无法保证数据的完整性;而epoll只有当状态发生变化时才会触发事件,能够保证数据完整性。
4、select是系统调用,每次调用都需要把全部描述符集合从用户态传输到内核态,并且需要判断每个描述符是否就绪;epoll 则是内核空间与用户空间共享一块内存,只需要在内核空间注册一个事件表,然后等待事件的通知即可,不需要对已注册的文件描述符进行遍历。
-
2.1.23 堆、栈、数据段、代码段分别存放哪些数据
栈是先进后出的,其内存空间是向下增长的;堆是先进先出的,其内存空间是向上增长的。
堆:动态申请内存(malloc),堆的申请释放由程序员控制
栈:存放函数调用相关参数和函数地址,局部变量,以及在任务切换的上下文信息,栈区由操作系统分配和管理
数据段:存储显式初始化的全局变量或者静态变量(.data段)
存储未初始化的全局变量和静态变量(.bss段)
代码段:存放二进制代码(.text段)
存放常量(.rodata段)
2.2 继承、多态⭐⭐⭐⭐⭐
-
2.2.1继承和虚继承 ⭐⭐⭐⭐⭐
-
2.2.2多态的类,内存布局是怎么样的 ⭐⭐⭐⭐⭐
-
2.2.3被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
-
2.2.4多态实现的三个条件、实现的原理 ⭐⭐⭐⭐⭐
-
2.2.5对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
-
2.2.6析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
-
2.2.7什么情况下会调用拷贝构造函数(三种情况)⭐⭐⭐
-
2.2.8析构函数一般写成虚函数的原因⭐⭐⭐⭐⭐
-
2.2.9构造函数为什么一般不定义为虚函数⭐⭐⭐⭐⭐
-
2.2.10什么是纯虚函数⭐⭐⭐⭐⭐
-
2.2.11静态绑定和动态绑定的介绍⭐⭐⭐⭐
-
2.2.12 C++所有的构造函数 ⭐⭐⭐
-
2.2.13重写、重载、覆盖的区别⭐⭐⭐⭐⭐
-
2.2.14成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
-
2.2.15如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐