C++ 常见面试题

  1. 智能指针 方便管理一个对象的生命周期,在智能指针下,一个对象什么时候要析构什么时候受智能指针决定,scoped_ptr: 离开作用域,自动删除, shared_ptr:本质是引用计数(reference counting),也就是说shared_ptr是支持复制的,复制一个shared_ptr的本质是对这个智能指针的引用次数加1,而当这个智能指针的引用次数降低到0的时候,该对象自动被析构 但是shared_ptr存在着循环引用的弊端,就是在A类中存在B类的智能指针,B 类中存在A类的智能指针,最后会导致 资源无法释放。 但是weak_ptr可以解决这个问题,weak_ptr指向一个对象并不会增加计数,且weak_ptr要用lock()返回一个shared_ptr来使用该指针,没有重载->这个运算符

  2. 虚函数和虚表:虚函数: 允许基类指针来调用子类函数的函数。虚表存放在全局数据区,任何含有虚函数的类中,不论这个虚函数是继承的还是定义的,都有一个函数指针指向一个虚函数表,虚函数表是一个数组,数组中的每个元素都是一个虚函数指针。

    如果父类的虚函数没有被子类改写, 那么子类的虚函数表中的元素就是父类的对应的虚函数指针;相反,如果子类改写了父类的虚函数,那么对应的虚函数表中的元素就是自己的虚函数指针,决议这个指向的过程发生在运行时,就是所谓的动态绑定

    虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表

    构造函数不能是虚函数, 析构函数如果不设置虚函数则会造成内存泄漏

    虚继承解决了菱形继承中的二义性问题

  3. 析构函数在 对象生命周期结束时候,delete指向对象指针时候,对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。

  4. 构造函数 先调用基类构造函数,再调用成员对象构造函数,最糊调用派生类,析构函数的调用顺序则相反

  5. c++内存管理

    1. 内存对齐

      1. 变量起始地址能够被其对齐值整除,结构体变量的对齐值为最宽的成员大小。

      2. 结构体每个成员相对于起始地址的偏移能够被其自身对齐值整除,如果不能则在前一个成员后面补充字节。

      3. 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节。

    2. 分为静态存储区(全局变量 和 静态变量),堆(new malloc),栈(函数调用所传递的参数、函数的返回地址、函数的局部变量等)

      1. 栈和堆的区别堆动态分配,栈由编译器管理,堆有碎片,增长方向也不相同
    3. 局部静态变量: 内存地址早就占用了,但是会在你第一次执行到初始化的那行代码的时候初始化它 ,会有一位来判断是否已经初始化

  6. 引用: 申明引用时,要对其进行初始化,不能把该引用赋给其他对象 引用传参不需拷贝副本,省内存,代码阅性强

    1. 引用和指针的区别: 引用必须被初始化,引用不可改变, 不存在空的引用

    2. 函数返回为引用:不能返回局部变量的引用(被销毁),不能返回函数内部new分配的内存的引用(函数返回值可能只当作一个临时变量,那么这m个引用指向的内存就无法释放,内部泄漏),可以返回类成员的引用,流操作符重载返回值申明为“引用”的作用(因为要连续使用)

  7. C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

    对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

  8. C++ static

    1. 静态成员变量

      1. 静态成员变量是该类的所有对象所共有的

      2. 因为静态数据成员在全局数据区分配内存,由本类的所有对象共享,所以,它不属于特定的类对象,不占用对象的内存,不初始化 默认就是0

      3. 静态成员变量存储在全局数据区,静态成员变量必须初始化,而且只能在类体外进行,不能再类的内部初始化

    2. 静态成员函数

      1. 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用于某个对象。无法访问属于类对象的非静态成员变量和非静态成员函数,它只能调用其余的静态成员函数和静态成员变量,因为没有this指针
    3. 静态全局变量

      1. 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的自动初始化值是随机的)

      2. 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的

    4. 静态局部变量

      1. 静态局部变量在全局数据区分配内存;

      2. 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化

      3. 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;

      4. 静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

    5. 静态函数

      1. 静态函数不能被其它文件所用;
  9. 想使用外部文件,对模板类型,则必须在定义这些模板类对象和模板函数时使用export 普通变量类型使用extern即可

  10. C++中的多态性具体体现在运行和编译两个方面。运行时多态是动态多态,其具体引用的对象在运行时才能确定。编译时多态是静态多态,在编译时就可以确定对象使用的形式。 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。 C++中,实现多态有以下方法:虚函数,抽象类,覆盖,模板(重载和多态无关)

  11. sizeof 计算栈上的分配大小,所以无法计算static变量大小, 对函数使用sizeof,在编译阶段会被函数的返回值的类型代替, sizeof是一个运算符

  12. sizeo是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小

  13. 空指针:指针指向的地址为空的指针叫空指针(NULL指针)

野指针:是指向“垃圾”内存(不可用内存)的指针  产生原因:指针创建时未初始化。指针变量刚被创建时不会自动成为NULL指针,它会随机指向一个内存地址。

悬垂指针:指针所指向的对象已经被释放或者回收了,但是指向该对象的指针没有作任何的修改,仍旧指向已经回收的内存地址。 此类指针称为垂悬指针。
  1. 多态就是将基类类型的指针或者引用指向派生类型的对象。多态通过虚函数机制实现。
多态的作用是接口重用。
  1. 在 C++ 中,const 成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。
const 数据成员 只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象没被创建时,编译器不知道 const 数据成员的值是什么。成员引用变量也是一样
  1. 可以,可以用_onexit 注册一个函数,它会在main 之后执行;

  2. 调用函数时要进行参数压栈,一般情况下顺序是从最右边参数往左压栈。

  3. 字符串会在末尾自动添加空字符

  4. 虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组。而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键。

  5. 只要基类在定义成员函数时已经声明了 virtue关键字,在派生类实现的时候覆盖该函数时,virtue关键字可加可不加,不影响多态的实现。子类的空间里有父类的所有变量(static除外)

  6. vector push_back 平均时间复杂度了 为 nm / m - 1

  7. vector 适合随机存储,不适合高频率插入删除, list不适合随机存储,但是适合高频率插入删除(原理是一个双向列表)

  8. 解决哈希的方法:

  9. 开放定址法 从冲突的那个位置开始往下查找到第一个没有冲突的

    1. 线性 定址法中最简单的冲突处理方法,它从发生冲突的单元起,依次判断下一个单元是否为空,当达到最后一个单元时,再从表首依次判断。直到碰到空闲的单元或者探查完全部单元为止。

    2. 平方探测: 方探查法即是发生冲突时,用发生冲突的单元d[i], 加上 1²、 2²等。即d[i] + 1²,d[i] + 2², d[i] + 3²...直到找到空闲单元。

    3. 双散列探测法: 其中hl和前面的h一样,以关键字为自变量,产生一个0至m—l之间的数作为散列地址;h2也以关键字为自变量,产生一个l至m—1之间的、并和m互素的数(即m不能被该数整除)作为探查序列的地址增量(即步长)

  10. 链地址法

    1. 是将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行
  11. 再哈希法 即再进行一次哈希运算

  12. Stl 迭代器失效

  13. 序列式容器就是数组式容器,删除当前的iterator会使后面所有元素的iterator都失效(因为其他数据的位置随之发生了变化)

  14. 树形结构 erase元素时候 要用erase(iter++)

  15. vector使用了push_back 导致扩容进行了深拷贝,导致迭代器失效

  16. STL允许在一个容器上的多线程读取和不同容器上的多线程写入,除此之外不能依赖任何库自带的线程安全性

  17. c++静态多态 : 使用模版,静态多态是在编译期完成的,因此效率较高,编译器也可以进行优化; 动态多态: 对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。

  18. 模板实例化*是生成采用特定模板参数组合的具体类或函数(实例) 有显式实例化 swap< int >(a,b) ,隐式实例化 swa(a,b)

  19. 显式具体化将不会使用Swap()模板来生成函数定义,而应使用专门为该特定类型显式定义的函数类型。有两种定义形式,如下,其中job为用户自定义类 template <> void Swap(job &a, job &b) template <> void Swap< job >(job &a, job &b) 显式具体化在声明后,必须要有具体的实现,这是与显示实例化不同的地方。

  20. c++模板函数的声明与定义通常放在头文件中,而普通的函数通常是声明放在头文件中,定义放在源文件中,而对模板函数来说,原因是 首先明确,模板函数是在编译器遇到使用模板的代码时才将模板函数实例化的,若将模板函数声明放在tem.h,模板定义放在tem.cpp,在main.cpp中包含头文件,调用add,按道理说应该实例化int add(int,int)函数,即生成add函数的相应代码,但是此时仅有声明,找不到定义,因此此时,它只会实例化函数的符号,并不会实例化函数的实现,即这个时候,在main.o编译单元内,它只是将add函数作为一个外部符号,这就是与普通函数的区别,对普通函数来说,此时的add函数已经由编译器生成相应的代码了,而对模板函数来说,此时并没有生成add函数对应的代码,同时也提高了编译的效率

  21. override 重写 是指派生函数覆盖基类函数,是C++多态的表现。Overload重载:功能相近的几个函数用相同的名字表示,但参数不同(包括类型、顺序不同),即函数重载。

  22. override保留字表示当前函数重写了基类的虚函数

在函数比较多的情况下可以提示读者某个函数重写了基类虚函数,表示这个虚函数是从基类继承,不是派生类自己定义的;强制编译器检查某个函数是否重写基类虚函数,如果没有则报错。在类的成员函数参数列表后面添加该关键字既可。

你可能感兴趣的:(C++ 常见面试题)