面试问题记录-C++
1.在main函数前调用函数
2.new/delete, new[]/delete[], malloc, free的联系与区别
malloc/free 是动态内存管理的入口,从动态内存里申请处一块内存给我们使用,但是这块内存并没有被初始化,在使用之前,我们还需要对这块内存进行手动的初始化。对于自己定义的类,手动初始化每个数据成员是比较繁琐的,所以在C++中就出现了new / delete 操作符(并不是函数),他们能够保证对象一被创建出来就被初始化,一出作用域就被清理干净。
- malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)。
- 它们都是动态管理内存的入口。
- malloc/free是C/C++标准库的函数,new/delete是C++操作符。
- malloc/free需要手动计算类型大小且返回值w为void*,new/delete可自动计算类型的大小,返回对应类型的指针。
- malloc/free管理内存失败会返回0,new/delete等的方式管理内存失败会抛出异常。
虽说new/delete可以申请和释放内存,但从系统层面来说,真正起作用的还是malloc和free,因为new/delete底层调用的正是malloc和new。所以称new/delete为 placement 版内存管理接口。
A * a=new A;实际上执行如下3个过程。
(1)调用operator new分配内存,operator new (sizeof(A)) ,实际上底层调用的是malloc函数
(2)调用构造函数生成类对象,A::A()
(3)返回相应指针
A* a[10] = new A[10];
(1)调用operator new分配内存,operator new (sizeof(A))
(2)调用构造函数生成类对象,A::A() , 共调用10次
(3)返回相应指针
delete a;
(1) 调用析构函数
(2) 调用operator delete
(3) 底层调用free 实现真正的内存释放
delete a[10];
(1) 调用析构函数,共10次
(2) 调用operator delete
(3) 底层调用free 实现真正的内存释放
注:我们传递给delete的指针指向的对象必须是动态分配的或者是空指针。
3.new/delete 和 new[]/delete[]
代码接口:
void operator new (size_t size); void operator delete (size_t size); void * operator new [](size_t size); void * operator delete[] (size_t size);
A* a = new A; delete a; // 释放一个对象的内存,调用一次析构函数 A* a = new A[10]; deletea[] a; //释放多个对象的内存,调用多次析构函数
在上边delete释放多个对象内存时大家可能会有疑问,为什么没有给delete[] 传递 释放几个对象内存 的参数,delete[] 怎么知道是释放10个呢?
这其实是因为编译器在 A* a = new A[];时返回的地址内存空间比对象真正占用的多4个字节,其中10就在这四个字节的位置存放。
4.vector 和 list
vector:
- 需要连续的内存;
- 支持随机访问,由于内存连续速度很快。
- 插入和删除时会造成这个点之后的元素移动,复杂度为O(n);
- vector拥有一段连续的内存空间,能很好的支持随机存取,因此vector<int>::iterator支持“+”,“+=”,“<”等操作符。
list:
- 不需要连续的内存;
- 不支持随机访问;
- 插入和删除高效;
- list的内存空间可以是不连续,它不支持随机访问,因此list<int>::iterator则不支持“+”、“+=”、“<”等
但是 vector<int>::iterator和list<int>::iterator都重载了“++”运算符。
5.C++虚函数和纯虚函数
虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。
- 在类成员函数前加virtual关键字,该函数则变为虚函数,只含有虚函数的类不能称为抽象类;纯虚函数不但需要virtual关键字,还需要让成员函数 = 0,含有纯虚函数的类称为抽象类,抽象类不可实例化。
- 虚函数可以在其子类中被重载(overload),或者覆盖(override),在声明的类中必须实现。纯虚函数不可在抽象类中实现(纯虚函数在基类中只有声明没有定义),只能在子类中实现。
- 两者都可以被子类实现,在子类中都是虚函数。
- 虚函数的父类和子类都有各自实现的版本,由多态的方式调用的时候动态绑定。如果一个类中含有纯虚函数,那么任何对这个类实例化的语句都会报错。因为抽象基类是不能被直接调用的,必须被子类继承重载以后,根据要求调用其子类的方法。
- 虚函数和纯虚函数都不能定义为static,因为static要求编译时绑定,而虚函数和纯虚函数都是运行时绑定的。
- 一般的我们不希望抽象基类的构造函数暴露,所以一般声明为protect。
6.static
- 修饰变量:静态全局变量,存在于整个程序的声明周期中
- 修饰成员函数:此成员函数为静态成员函数,它不属于任何一个对象,跟具体的对象没有关联,它只属于类本身。踩坑经验:https://www.cnblogs.com/qiang-wei/p/12305615.html
7.虚函数的实现
虚表
8.智能指针
9.lambda表达式
10.C语言如何实现面向对象编程
11.struct与class的区别
struct中成员默认是public的,而class中成员默认是private的。
12.struct与union的区别
13.struct中的字节对齐机制
14.C++四种类型转换符(static_cast, dynamic_cast, const_cast, reinterpret_cast)各自的作用
15.构造函数声明成虚函数时,如何调用?(陷阱题,构造函数不能被声明为虚函数)
16.类型兼容规则
17.vector的push_back()方法的实现
18.map, unordered_map的区别
map:内部存储结构为红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的。map的每一个元素都是红黑树的一个节点,因此对map的插入删除查找等操作就等同于对红黑树的操作,所以红黑树的效率决定了map的效率。
优点:
- 有序性
- 时间复杂度低。因为红黑树很多操作都是在logn的时间复杂度下完成的
缺点:
- 空间占有率高。因为红黑树每个节点都需要额外保存其父节点、孩子节点、红/黑性质,使得每个节点都占有较大的空间。
unordered_map:内部实现了哈希表(把关键表映射到哈希表中的某个位置,因此查找效率为O(n) ),内部元素的顺序是杂乱的无序的。
优点:
- 众所周知,哈希表查找速度很快,效率较高
缺点:
- 哈希表的建立耗费时间
19.左值与右值分别指什么?
20.malloc(0)会怎么样?
int* p = new int[0]; 是合法的,返回一个非空指针,保证此指针与new返回的其他指针都不相同。对于0长度数组的指针,相当于数组的尾后指针,就像其他尾后指针一样,但不能进行解引用,毕竟它不指向任何元素。
21.C++中哪些函数不能被声明为虚函数?(普通、内联、构造、友元、静态)
22.构造函数为什么不能被声明为虚函数?(派生类的对象在基类构造函数调用前还不存在)
23.友元函数可以被声明为虚函数吗?为什么?(不能,友元函数不是成员函数,不能被继承,不支持运行时多态)
24.基类的虚函数的指针在派生类的虚标中吗?为什么要这样做?
25.vector和list中,删除末尾的元素,其指针和迭代器如何变化?若删除的是中间的元素呢?
26.C++变成可执行文件的过程
27.堆和栈
28.拷贝构造函数和赋值函数
29.空类的sizeof大小
30.类A空,类B继承A,B中有一个虚函数,sizeof大小
31.类A空,类B中有一个虚函数和一个类A对象,sizeof大小
32.函数重载。如果返回值不同或者参数位置不同是否是重载
33.局部变量,全局变量,静态局部变量和静态全局变量
34.map和unordered_map的区别
35.用define写一个结构体类型的偏移地址
36.局部变量、全局变量、常量还有malloc开辟的内存变量分别放在哪个区
37.STL中Vector、List、Map底层实现
38.重载和重写的区别
39.全局变量的初始化位置
40.迭代器失效问题
41.main函数执行前后会执行什么代码