说在前面:本文是我收集的一些c++面试的面经以及自己做出的一些回答。
1. 介绍下面向对象的三大特征
答:面向对象的特征包括封装,继承和多态。封装是将数据和操作数据的方法隐藏起来,具体形式比如类,结构体。封装的好处就是隐藏的数据的内部实现,提高了代码的可维护性和安全性。继承就是指一个类继承另一个类的特性和行为,被继承的类称为父类,继承的类称为子类。继承的好处在提高代码的复用性。多态就是说对象在运行时会表现出不同的行为状态,多态包括编译时多态和运行时多态,编译时多态是说这个对象的行为是在编译时确定的,比如函数重载,函数模板和类模板。运行时多态是指对象的行为是在运行时确定的,比如常说的父类的指针指向子类的对象。多态的好处就在于提高代码灵活性和扩展性。
(回答模板:分别回答每个特点的定义和这些特点对程序本身的意义,这里还可以扩展说下多态是怎样实现的)
2. 当子类和父类具有相同的成员名字时会存在什么问题?
答:当父类和子类具有相同的成员定义时,不管是成员变量还是成员函数都会产生成员隐藏,也就是说不能通过子类直接访问到父类的成员(同名),如果想要访问的话需要通过作用域解析运算符 :: 。
3. 菱形继承时子类访问父类的成员会出现什么问题?如何解决这个问题?
答:菱形继承是说一个类D继承了另外两个类B和C,这两个类又继承了同一个类A。当类D去访问类A的成员时,会出现歧义,因为编译器无法确定应该使用哪一条继承路径。解决菱形继承的问题可以通过虚继承,确保类A的实例只在继承结构中创建一次,而不是每个子类都创建一次,从而消除歧义。
4. 介绍下虚函数表和虚表指针?
答:当一个类包含虚函数或从包含虚函数的类继承时,编译器会为该类创建一个虚函数表。这个表是一个静态数组,数组里面就是指向这个类的虚函数的指针。虚表指针是在对象初始化的时候创建的,用于指向对应的虚函数表。这两个是实现多态的关键,当通过基类的指针或引用调用虚函数时,运行时将根据对象实际的类类型,使用该对象的虚函数表来确定调用哪个函数实现。
5. 析构函数为什么需要是虚函数?
答:如果析构函数不是虚函数,那么在多态的场景下,父类的指针不能去调用子类的析构函数,那么可能就会造成子类的资源不能正确释放,从而导致内存泄漏。
(这里补充一点:析构函数为什么默认不是虚函数呢?这是因为并不是所有类都存在继承关系和虚函数,为了避免额外的资源消耗)
6. 什么时候会生成默认拷贝构造?
答:当类中没有定义任何拷贝构造函数的时候;当类中没有定义移动构造或者移动赋值的时候。
7. 介绍下左值引用和右值引用的区别?
答:左值引用就是对左值的引用,左值一般就是指具名对象,或者说可以取地址的对象。左值引用也就是常说的引用,通过引用的方式进行参数传递,函数返回可以减少拷贝带来的开销。右值引用就是对右值的引用,右值就是指没有名称的临时对象,或者无法取地址的对象。右值引用主要用于移动语义和完美转发。
8. 介绍下std::move()和std::forward()的区别?
答:std::move()用于将其参数转换为右值引用。它实际上不移动任何东西,只是执行一个类型转换。std::forward()的主要作用是在泛型编程中进行完美转发,将传入的参数以原有的引用类型传递给其它函数。
9. vector的内存分配方式?
答:vector是一个动态数组,当它的size超过当前的capacity的时候就会触发扩容,这种扩展通常是成倍扩容,为了避免频繁的内存分配。为了避免频繁的内存分配,我们也可以使用reserve函数提前为vector指定需要的容量。
10. 前置++和后置++的区别?
答:前置++是先对操作数进行递增操作,返回的是递增后的值的引用;后置++是先生成一个操作数的副本,然后将操作数递增,最后返回操作数的副本。
11. 栈和堆的区别?
答:栈是一种线性的数据结构,它具有先进后出的特点,栈的内存分配和释放是系统自动完成的,通常只有几M大小,函数的调用就是在栈上完成的。
堆是一种树形的数据结构,它是程序员手动分配和释放的,效率比较低,管理比较复杂。
12. make_shared函数做了什么?
答:make_shared是一个函数模板,用于创建一个新的对象,并返回这个对象的共享指针,它封装了对象的创建和智能指针的创建。