面试总结之C与C++基础
C和C++是比较基础编程语言,无论是前端开发或者是后端开发,多多少少都会涉及到C和C++的一些基础知识,这里就为大家总结一下常见的考点。总结的部分是结合了教科书、其他面经以及个人的面试经历,相信能够帮助到大家。如果有不足的地方,欢迎大家指出!
本博客长期更新,欢迎大家收藏、订阅!
vector扩容原理
- 迭代器失效:
push_back()
操作时,一旦引起空间重新配置(deep copy),指向原vector的迭代器失效;- 使用
erase()
函数的时候,也会导致迭代器失效。it
本身失效,it
还可能越界
- 扩容方式:不同编译器的扩容方式不同,VS2015以1.5倍扩容,GCC以2倍扩容。
- 为什么成倍增长?:
- 一次增加固定值大小:假设有
n
个元素,每次增加k
个元素,push_back()
操作的复杂度是$O(n)$ - 成倍增长:假设有
n
个元素,倍增m
,push_back()
操作的复杂度是$O(1)$
- 一次增加固定值大小:假设有
STL常用结构总结
map、set、multimap、multiset
- 底层实现:红黑树(RB Tree)
- 插入复杂度:$O(logN)$
- 查看复杂度:$O(logN)$
- 删除复杂度:$O(logN)$
hash_map、hash_set、hash_multimap、hash_multiset
- 底层实现:哈希表
- 插入复杂度:$O(1)$,最坏$O(N)$
- 查看复杂度:$O(1)$,最坏$O(N)$
- 删除复杂度:$O(1)$,最坏$O(N)$
各种结构的用途
list
:支持快速插入、删除($O(1)$),查找比较费时($O(N)$)vector
:查找速度快,插入和删除比较费时map
:查找速度比较快($O(logN)$)hash_map
:如果不碰撞的话,查找时间常数级($O(1)$)
虚函数virtual function
- 作用:多态,对象带额外信息,用于在运行时确定该对象调用哪一个虚函数。
- 实现原理:每个对象存储一个
vptr
(virtual table pointer,虚函数表指针),该指针指向vtbl
(virtual table,虚函数表)的首地址。 - 调用过程:调用虚函数时,对象通过本身存储的vptr,在vtbl中找到与调用对象匹配的函数指针,再通过这个函数指针调用该函数,实现运行时多态。
- 细节:
vtbl
是类共用的,vptr
是对象独享的;- 对象的内存空间存储着
vptr
(4bytes),指向vtbl
的地址入口; vtbl
保存该类所有虚函数的地址,是一个函数指针数组。
struct结构体内存对齐
- 原因:不用机器硬件对内存空间的处理不同,因此编译器应该提供统一的内存处理形式。
- 原则:
- 存储地址从0开始;
- 每个成员按其大小和指定参数
n
中较小的一个进行对齐; - 结构体总长度必须为所有对齐参数的整数倍。
- 自定义对齐参数:加上一行代码
# pragma pack(n)
,n
为自定义的对齐参数
例子1:
1 | struct A { |
分析:每个short类型占2个字节大小,A的内存地址是0-5,共6个字节。
例子2:
1 | struct B { |
分析:long类型占4个字节,a1的内存地址是0-3,short占两个字节,内存地址是4-5,因为结构体总长度必须是所有参数的整数倍,也就是要是4的倍数,所以结构体的内存地址是0-7,共8个字节
例子3:
1 | struct A { |
分析:int类型占4个字节,a的内存地址是0-3;char占1个字节,内存地址是4;short占2个字节,所以要跳过5,从6开始,内存地址是6-7。整个结构体的内存地址是0-7,共8个字节。
例子4:
1 | struct B { |
分析:char类型占1个字节,b的内存地址是0;int类型占4个字节,所以要跳过1-3,从4开始,内存地址是4-7;short类型占2个字节,内存地址是8-9。因为结构体总长度必须为所有对其参数的整数倍,所以必须是4的倍数。即结构体的内存地址是0-11,共12个字节。
可以看出,例子3和例子4两个结构存储的内容都是一样的,但是由于声明的顺序不同,导致结构体最终的大小是不一样的,因此我们在编程的过程中,定义struct的时候应该将内存较大的元素放在靠前的位置,尽可能地节省空间。
new和malloc的区别
最本质区别:new是运算符(operator),而malloc是函数(function)。
- 分配内存的区域
- new:从自由存储区(free store)中分配内存,自由存储区可以是堆(heap),也可以是静态存储区(static area)
- malloc:只能从堆(heap)中分配内存
- 返回类型的安全性
- new:返回对象类型的指针,类型严格与对象匹配,类型安全
- malloc:返回
void*
,要强制类型转换
- 内存分配失败
- new:抛出
bac_alloc
异常,不会返回NULL - malloc:返回NULL,需要用户做判断
- new:抛出
- 指定内存大小
- new:用户不需要制定内存块的大小,编译器会根据类型信息自行计算
- malloc:用户需要显式地指出所需内存的尺寸
- 构造和析构
- new操作符:
- 调用
operator new()
函数,分配内存空间 - 调用对象构造函数,传入初值
- 返回指向该对象的指针
- 调用
- delete操作符:
- 调用对象的析构函数
- 调用
operator delete()
函数,释放内存空间
- malloc函数:只分配内存空间,返回
void*
- free函数:只释放内存空间
- new操作符:
- 支持数组
- new、delete支持数组格式,会对数组中每一个对象进行构造或析构
- malloc、free不支持构造或析构
- new,malloc调用
- new的实现可基于malloc
- 重载
- new、delete:可重载
- malloc、free:不可重载
- 重新分配内存
- malloc分配后,可以用realloc进行内存重新分配。先判断当前指针所指向的空间是否有足够的连续空间:
- 如果有,在原地址的基础上扩大内存地址,返回原指针
- 如果没有,分配新的空间,将原数据copy到新的空间中,然后释放掉原来的空间,返回新地址的指针。
- malloc分配后,可以用realloc进行内存重新分配。先判断当前指针所指向的空间是否有足够的连续空间:
智能指针shared_prt和unique_ptr
作用:更好的内存管理,防止指针问题带来的内存泄露。
重点:智能指针是一个类(class),而不是一种新的数据类型(type)。
共同点:
- 可以为空,两者都可以直接声明,而不给任何初值。
- 都有判断条件,给定智能指针
p
,通过如果p
指向了对象,则为true - 通过解引用获得数据,给定智能指针
p
,通过*p
获得所指对象 - 访问成员变量:给定智能指针
p
,通过p->mem
或(*p).mem
访问成员变量 - 获得保存的指针:给定智能指针
p
,通过p.get()
获取p
中保存的指针 - 交换两个指针
- share_ptr:
swap(p, q)
- unique_ptr:
p.swap(p, q)
- share_ptr:
static的作用
- 隐藏:在一个文件中定义的static变量,在其他源文件中无法访问,实现了对其他源文件的隐藏,因此可以在不同的源文件使用同名的变量。
- 变量内容持久:类似于声明了一个全局的变量。
- 默认初始化为0:static变量的声明默认值是0(NULL)
const成员函数
类中的每一个成员函数前实际上都有一个this
指针,在一个类的成员函数后加const
关键字,表明这个函数不能改变类中的成员变量,实际上这个const是对this
修饰的。
explicit关键字
修饰构造函数,防止由构造函数定义的隐式转换。