个人能力有限,希望大家有意见或者建议,或者是有好的面试八股文题目都可以分享在评论区,谢谢。
31.C++标准库里优先队列是怎么实现的?
优先队列是利用堆来实现的,priority_queue是用于存储具有优先级顺序的元素的。默认情况下,priority_queue是一个最大堆,即根节点是队列中具有最高优先级的元素。通过比较函数,可以创建最小堆或者根据其他规则定义优先级。优先队列的插入弹出操作的时间复杂度是O(log n)。优先队列中默认的优先级设置是数字大或字典序大的优先级高。
32.C++ Coroutine?
Coroutine即协程是一个轻量级的线程,能够挂起执行流稍后恢复执行的函数。C++20协程是无栈的:它通过主动返回awaiter给caller来挂起执行流;恢复执行流继续执行所需要的数据将被存储在堆上。协程的一个重要优点就是能够将异步执行转化为同步执行(如各种回调,会导致代码割裂而难以阅读)
33.extern C有什么作用?
extern C的作用:
-
去掉函数名修饰:在 C++ 中,函数名可能会被编译器进行名称修饰,以区分不同的函数重载或命名空间中的函数。使用 extern C可以告诉编译器不对函数名进行修饰,使得函数名与 C 语言中的函数名保持一致。
-
在C++中调用C语言函数:将函数声明为 C 链接规则,以便在 C++ 中调用 C 语言编写的函数。
-
在编写动态链接库时,为了确保库的接口能够被 C 语言调用,通常会使用
extern "C"
来声明库的函数接口。 -
#ifdef __cplusplus extern "C" { #endif void myCFunction(); // 声明一个 C 风格的函数 #ifdef __cplusplus } #endif
34.怎么解决C++的菱形继承问题?
C++ 中的菱形继承问题(Diamond Inheritance)是由于多重继承中的一种特定情况造成的,即一个类同时继承自两个具有共同基类的类,导致最终派生类中包含了两份共同基类的成员。
解决菱形继承问题有3个方法:
-
虚继承:使用虚继承可以解决菱形继承问题。在共同基类前加上
virtual
关键字,确保只有一份共同基类的实例被派生类继承。通过虚继承,可以避免在最终派生类中出现两份共同基类的成员,从而避免内存空间浪费和二义性问题。 -
class Base { public: int data; }; class Derived1 : public virtual Base { // Derived1 类通过虚继承 Base }; class Derived2 : public virtual Base { // Derived2 类通过虚继承 Base }; class FinalDerived : public Derived1, public Derived2 { // FinalDerived 类继承 Derived1 和 Derived2 };
重写冲突函数:如果在菱形继承中出现了同名函数的二义性问题,可以在最终派生类中重写这些函数,明确指定调用哪个版本。通过重写冲突函数,可以消除二义性,确保程序能够正确调用所需的函数版本。
-
使用虚函数:在菱形继承中,如果共同基类中的函数需要被派生类重写,可以将这些函数声明为虚函数,以便在派生类中进行覆盖,实现动态绑定。使用虚函数可以保证派生类对基类函数的重写,避免二义性问题。
35.关键字override,final的作用?
override和final都是C++11中引入的新特性,用于增强代码的可读性、可维护性,并在一定程度上提高代码的安全性。
-
override是提示编译器,当前函数是对基类中的虚函数进行重写,如果当前函数并没有重写基类中的虚函数,编译器会产生一个错误。override帮助程序员在编码阶段就发现潜在的错误,例如拼写错误、参数列表不匹配等等。
-
final用于修饰类或虚函数,表示该类不能被继承,或者该虚函数不能被重写。final可以防止其他类继承该类,从而确保该类不会有任何子类。final修饰虚函数则表示这个虚函数在派生类中不能被修改。
-
class Base final { // 不能被继承的类 }; class Base { public: virtual void func() final { // 不能被重写的虚函数 } };
36.C++include双引号和尖括号的区别?
C++中包含头文件可以用include加双引号或尖括号。他们的区别在于:
-
使用双引号" "包含的头文件路径是相对于当前源文件所在目录的路径。编译器首先在当前源文件所在目录中查找指定的头文件,如果找不到,再到系统标准库目录中查找。可以用双引号包含自定义的头文件路径,通常用于包含项目内部的头文件,有时候会需要使用相对路径找到我们所需要引用的头文件。
-
使用尖括号< >包含的头文件路径是相对于系统标准库目录的路径。编译器只在系统标准库目录中查找指定的头文件,不会在当前源文件所在目录中查找。通常用于包含标准库的头文件或第三方库的头文件,这些头文件通常位于系统标准库目录中,都是加一个头文件名字即可,编译器一般会在你输入尖括号和库的前几个字母时候弹出选项。举个例子:
-
37.内联函数是怎么实现的,有什么优缺点?
内联函数是一种在编译时将函数调用处直接展开为函数体的特性。在函数定义或声明前加上inline关键字表示该函数是内联函数。编译器会尝试将内联函数的函数体插入到每个调用该函数的地方,而不是通过函数调用的方式来执行。编译器会根据函数的大小、复杂度等因素来决定是否将函数内联。(需要注意,有时候编译器会把一些没有inline关键字的函数也作为内联函数处理,此时需要调整编译器优化设置或者考虑宏进行替代或者修改代码使这个函数适合作为内联函数)。内联函数适合于函数体较小、频繁调用的情况。
内联函数的优点是内联函数可以减少函数调用的开销(函数调用时需要保存现场、传递参数、跳转等操作),而内联函数直接展开避免了这些开销。特别适用于短小的函数,可以提高程序的执行效率,降低程序运行时间。内联函数的缺点是可能导致代码膨胀,增加可执行文件的大小,增加程序的编译时间。
38.C++中的this指针?
C++中的this指针是隐藏在每一个非静态成员函数中的特殊指针,是一个右值,指向正在被该成员函数操作的那个对象。当一个对象调用成员函数时,先将对象的地址赋给this指针,然后调用成员函数。调用一个成员函数就会有一个隐藏的参数就是这个this指针,this指针的类型是const className* const,这说明this指针是一个常量指针,不能被修改,指向的对象也不能被修改。需要注意的是,在构造函数和析构函数中需要谨慎使用this指针。
39.深拷贝和浅拷贝的区别?
深拷贝和浅拷贝是C++中描述拷贝的2种方式:
-
深拷贝(Deep Copy):深拷贝是指在拷贝对象时,会复制对象的所有成员变量,并且对于动态分配的内存也会进行复制,而不是简单地复制指针。这样可以确保原对象和拷贝对象之间是完全独立的,互不影响。深拷贝通常需要自定义拷贝构造函数和赋值运算符重载函数来实现。
-
浅拷贝(Shallow Copy):浅拷贝是指在拷贝对象时,只是简单地复制对象的所有成员变量的值,包括指针变量的值,而不会复制指针所指向的内存。这样会导致原对象和拷贝对象共享同一块内存,当其中一个对象修改了内存中的数据,另一个对象也会受到影响。浅拷贝只需要使用默认的拷贝构造函数和赋值运算符重载函数即可实现。需要注意的是,浅拷贝在某些情况下可能会导致问题,如两个对象可能会尝试释放同一块内存,造成“双重释放”错误。
40.explicit关键字的作用?
explicit关键字用于修饰类内部的构造函数(该构造函数只有一个参数),禁止隐式调用拷贝构造函数和禁止类对象之间的隐式转换,从而提高代码的安全性和可读性。当一个构造函数被声明为explicit时,它将禁止隐式类型转换。这意味着只有在显式调用构造函数时才能创建对象,而不能通过隐式转换来创建对象。这可以避免一些意外的类型转换,提高代码的可靠性。举个例子:
#include <iostream>
class Distance {
public:
explicit Distance(int meters) : m_meters(meters) {}
void display() {
std::cout << "Distance: " << m_meters << " meters" << std::endl;
}
private:
int m_meters;
};
int main() {
// Distance obj = 1000; // 编译错误,禁止隐式类型转换
Distance obj(1000); // 显式调用构造函数
obj.display();
return 0;
}