C++面试知识点
C++内存分配
包括:栈,堆,常量区,代码区,全局区
- 栈:存放函数调用过程中的局部变量,由编译器自动管理,无内存泄漏
- 堆:存放动态生成的对象,需要程序员手动释放或用智能指针管理
- 常量区:存不能修改的只读数据,字符串字面量,全局 const 变量
- 全局区:存全局变量,静态变量static
- 代码区:代码编译后的机器指令分配的内存空间
new和delete
ew 和 delete 必须配对,new[] 和 delete[] 必须配对。 new[] 和 delete[]用于处理数组。
智能指针
- unique_ptr:独占指针,一个对象同时只能被一个指针所指,若指针被销毁或指向其他,对象就被销毁,但是可以移交拥有权
- shared_ptr:共享指针,一个对象可以同时被多个共享指针所指,内部有个计数器,计数器为0后销毁,即最后一个指向它的指针被销毁后销毁
- weak_ptr:用于解决环形引用的问题
环形引用:两个对象各有一个 对方的shared_ptr指针,且都指向了对方,此时main中销毁掉外部的指针后,两个对象内部的指针还是互相指着,计数器都为1,不会被销毁。
虚函数与虚表
- 虚函数:实现多态,当基类指针或引用指向派生类对象时,调用 virtual 修饰的函数,会根据对象的真实类型调用对应版本,而不是根据指针/引用的静态类型决定
- 虚表:虚函数地址表,只要一个类含有虚函数,编译器通常会为这个类生成一张虚表,表里存放该类各个虚函数的函数地址。对象内部通常会多一个隐藏指针,叫 vptr,指向自己所属类的虚表。
- 构造函数不能是虚函数:虚函数调用需要对象里已经有可用的 vptr,并通过 vptr 找到虚表。但构造函数执行前,对象还没有被完整初始化,不能依赖一个完整对象的虚表机制来决定“该调用哪个构造函数”。 构造函数负责建立对象,虚函数机制依赖对象已经建立。
- 析构函数可以是虚函数:只要一个类可能被当作基类,并通过基类指针删除派生类对象,就应该把析构函数声明为 virtual。因为C++ 只有在函数是 virtual 时,才会根据运行时类型做动态分派。析构函数如果不是虚函数,delete p 就按指针的静态类型 Base* 来生成析构调用。会导致子类中的一些东西泄露
虚继承与菱形继承解决
-
菱形继承:最底层子类会继承到两份共同基类对象,导致数据冗余和访问二义性。
-
虚继承:中间层写成class B : virtual public A ,使最终派生类中只保留一份虚基类子对象 虚基类的构造由最底层派生类负责 如何实现:B 和 C 不再各自内嵌一份 A,而是通过额外的指针/偏移信息,共同指向同一个虚基类 A 子对象。此时访问的是同一个值
-
虚继承构造函数:虚基类由最底层派生类负责初始化。
模板
把类型或常量参数化,让同一份代码适配多种类型 模板和虚函数的核心区别:模板是编译期多态,虚函数是运行期多态 要写在每一个模板函数或模板类定义前面
类模板中可以使用虚函数
一个虚函数还能是模板吗:不能。虚函数本身不能是函数模板,因为虚表布局必须在类定义被编译时就固定,而模板实例化是按使用点、按翻译单元触发的,不是先全程序收集完再统一生成虚表。
this指针
成员函数内部自动存在的一个指针,指向当前调用该函数的对象 只能在非静态成员函数中使用 this 是一个“指针常量”。 用途:
- 链式调用:p.SetName(“Tom”).SetAge(18);
- 链表节点删除自己
const
被 const 修饰(在 const 后面)的值不可改变 const指针不可改变指向
static
普通成员属于对象,static 成员属于类;局部 static 变量生命周期贯穿整个程序;文件级 static 用来限制作用域。
inline
直接将函数内容放到调用位置,省去函数调用开销 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
C++ 中 struct 和 class
区别:默认访问权限,struct是public,class是private
using
引入命名空间
接口
接口就是一组“必须实现的能力约定”,C++ 里通常用纯虚函数抽象类来表达。
对象内存布局
对象里真正占空间的是非静态成员变量、虚表指针以及对齐填充;成员函数本身不在对象里。
placement new
只负责“在已有内存上构造对象”。