前言
面试总结第二波,主要针对自己在看别人面经时,总结和C++面试相关的问题。
C++面试题合集
目录
C++面试题合集
1、C和C++的区别?
2、C++多态
3、引用和指针的区别?
4、函数中值传递、引用传递、指针传递有什么区别?
5、static关键字的作用
6、const关键字的作用
7、memset、memcpy、strcpy有什么区别?
8、析构函数有哪些特点?
9、虚函数的作用?
10、分别给出bool、int、float、指针变量和0比较的if语句
11、内联函数与普通函数有什么区别?
12、拷贝构造函数自动调用的情况
13、函数模板和函数重载异同?
14、C++中explicit关键字有什么作用?
15、C++中restrict关键字有什么作用?
16、面向对象的三大特征
17、在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
18、头文件中的ifndef/define/endif有什么作用?
19、为什么构造函数不能为虚函数?
20、堆和栈的区别?
21、malloc/free和new/delete的区别
22、常见的内存错误及对策
23、为什么要使用字节对齐
24、四种强制类型转换
25、C++中的内存分配
26、平衡二叉树和红黑树的区别
1、C和C++的区别?
- C语言是面向过程的语言,面向过程即加工的是一个一个的函数;而C++是面向对象的语言,面向对象即加工的是一个一个的类。
- C语言中,表达式的结果放在寄存器,是一个数;C++中,表达式返回的变量的本身。
- C语言中const是一个冒牌货,是一个只读的变量,有自己的存储空间,通过指针可以修改;C++中const放在符号表中,当取地址时,会单独开辟一个内存空间(编译器编译期间)。
- C++对C函数进行了扩展,一个是inline函数代替了宏代码,另一个是函数重载(名称相同,参数不一样)。
2、C++多态
C++多态分:静态多态和动态多态
静态多态:函数重载、函数模板,都是在编译器间完成的,函数重载是因为C++编译器在编译时,会对函数名进行扩展,比如fun(int,int)——>fun_int_int,以此来区分不同的函数;函数模板是因为C++编译器会进行两次编译,对于第二次编译会按照不同的函数参数类型,生成多个对应的函数,以此来区分。
动态多态:指程序在运行期间,根据父类指针指向的对象,来判定会执行哪个对象的虚函数。
实现多态三个条件:
- 要有继承
- 要有虚函数重写
- 用父类指针(引用)指向子类对象
虚析构函数
通过父类指针释放所有子类资源,则需要把所有父类的析构函数写成虚析构函数。
重载重写重定义
重载:必须在同一个类中进行
重写:必须保证发生在子类与父类之间,加virtual就是多态,不加就是重定义
定义一个父类对象,遇到func函数,如果不会发生多态,则只执行父类中的函数;
定义一个子类对象,遇到func函数,如果子类已存在,则不会去父类查找,只会在子类中查找,子类没有则再向父类查找
多态原理
用类定义对象的时候,C++编译器会在对象中添加一个vptr指针,用来指向虚函数表(是一个存储类成员的函数指针的数据结构),C++编译器根本不需要区分是子类对象还是父类对象,父类对象和子类对象都有vptr指针,然后去找各自对应的虚函数表,再找对应的函数入口地址。
vptr指针是分步初始化的
构造函数中,调用虚函数能发生多态吗?不能,当执行父类构造函数时,子类的vptr指针是指向父类的虚函数表的,当父类的构造函数执行完后,会把子类的vptr指针指向子类的虚函数表。
把所有函数都声明成虚函数好吗?
不好,vptr调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数,出于效率考虑,没有必要将所有函数都声明成虚函数。
纯虚函数和抽象类
抽象类不能被实例化。
3、引用和指针的区别?
引用不是一个变量,而指针是一个变量。引用实质上是一个常量指针,而C++中的常量是存在一个符号表中的,不是一个变量,变量是存储空间的别名。而由于引用也是一个指针所以它和指针一样所占的存储空间大小一样,和类型无关。
4、函数中值传递、引用传递、指针传递有什么区别?
- 值传递:会为形参分配内存,然后将实参的值拷贝给形参,形参值的改变不会影响实参,函数结束后,形参内存释放;
- 引用传递:不会为形参分配内存,因为引用就是原实参变量的一个别名,所以改变形参就是改变实参,函数结束后,形参不会被释放;
- 指针传递:形参为指针变量,传递进来的是实参的内存地址,可以改变实参的值,调用时为形参指针分配内存,结束时释放指针变量。
5、static关键字的作用
- 函数体内的static变量只在该函数类可以使用且只分配一次内存,其值调用时维持上次的值。
- 在一个模块内全局static变量只被该文件的所有函数使用,不能被其他文件的函数使用;函数也一样
- 在一个类中的static变量,只属于整个类,对类的对象只有一份拷贝
- 在一个类中的static函数,只属于整个类,不接受this指针,所以只能访问static变量
6、const关键字的作用
- 对于一个变量来说,如果不想被改变,可以定义成const,需要初始化。
- 对于一个指针来说,可以定义指针本身是const,即该指针不能改变指向的对象,也可以定义所指对象是const,即不能通过*p来改变所指的对象值,或者二者都可以是const
- 对于类的成员函数定义成const,则不能改变类中的成员变量
- 对于类的成员函数,有时候需要定义其返回值是const,以使得其返回值不是左值
7、memset、memcpy、strcpy有什么区别?
- memset:用来对一段内存空间全部赋值为某一个值
- memcpy:用来做内存拷贝,可以拷贝任何数据对象,还可以指定拷贝的数据长度
- strcpy:只能拷贝字符串,遇到\0截至
8、析构函数有哪些特点?
特殊的类成员函数,没有返回类型,没有参数,没有重载;public、protected、private等权限对于析构函数无效;析构函数不能手动调用,只能在类对象结束生命周期时,由系统自动调用释放放在构造函数中分配的资源。
9、虚函数的作用?
- 虚函数就是使子类用同样的函数可以对父类进行覆盖,并且通过父类指针调用时,如果覆盖则调用子类覆盖后的函数,如果没有覆盖,则调用父类中的函数,实现多态的效果
- 如果是纯虚函数,则纯粹为了子类函数进行覆盖时统一函数名而已,子类必须覆盖纯虚函数,否则子类也是抽象类
- 含有纯虚函数的类是抽象类,不能实例化,常用作接口
10、分别给出bool、int、float、指针变量和0比较的if语句
bool:if(!var)
int:if(var==0)
float:const float EPSINON=1e-6; if((x>=-EPSION)&&(x<=EPSINON))
指针变量:if(var==NULL)
11、内联函数与普通函数有什么区别?
- 声明和函数体必须一起
- C++编译器直接将函数体插入到被调用的地方(没有额外的压栈、跳转、返回的开销)
- 函数体必须比较简单,没有过多的for、if语句
12、拷贝构造函数自动调用的情况
- 当类的一个对象去初始化另一个对象时
- 如果函数的形参是一个类对象时,实参赋值给形参时
- 返回一个类对象时
13、函数模板和函数重载异同?
- 函数重载指几个函数,函数名字相同,函数参数类型不同或参数个数不同的函数
- 函数模板是指几个函数具体算法相同,但参数类型不同
- 模板函数可以减少重载函数,但也可能引发错误
14、C++中explicit关键字有什么作用?
和构造函数一起用,表示这个函数必须显式调用,防止不必要的隐式调用类型转换构造函数。
15、C++中restrict关键字有什么作用?
用来优化,修饰指针的,限制多个指针指向同一个地址。
16、面向对象的三大特征
- 封装:就是把一个客观的事物抽象成一个类,然后用不同的修饰属性:public、protected、private对数据和方法进行修饰,对可信的类或者对象进行操作,对不可信的进行信息隐藏;
- 继承:可以使用现有类的所有功能,无需重新编写原先类的一些功能,可以直接进行扩展,通过继承创建的类称为子类或者派生类,被继承的类称为父类或基类。
- 多态:通过父类指针根据所指的对象,所表示出不同的特性。
17、在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
C++支持函数重载,而C不支持重载,C++编译器编译后的函数名和C是不同的,包含了函数名、函数参数数量及类型信息,C++就是靠这种机制实现重载的。被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,所以这个声明的真实目的就是解决名字匹配问题,实现C++和C的混合编程。
18、头文件中的ifndef/define/endif有什么作用?
这是C++预编译头文件保护符,保证即使文件被多次包含,头文件也只定义一次。
19、为什么构造函数不能为虚函数?
虚函数是采用一种虚调用方法,虚调用是一种可以在只有部分信息情况下工作的机制。但是创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。
20、堆和栈的区别?
- 栈:有系统自动分配和释放内存,速度快,栈是向低地址扩展的数据结构,大小有限。
- 堆:由程序员自己申请和释放内存,一般较慢,且容易产生碎片,堆是向高地址扩展的不连续内存区域,空间大且相对灵活。
21、malloc/free和new/delete的区别
new会自动执行类的构造函数,delete会自动执行类的析构函数,而malloc和free不会。
22、常见的内存错误及对策
- 内存还没分配,就已经使用:在使用内存前应该判断下是否不为空
- 内存虽然分配成功,但没有初始化就使用了它:就是创建变量时,记得赋初值
- 内存分配成功也初始化了,但超过了内存边界:写程序自己注意
- 忘记释放内存,导致内存泄漏
- 释放了内存却继续使用了它
23、为什么要使用字节对齐
在可接受的空间浪费前提下,尽可能提高对相同元素的快速处理,就是为了提高CPU访问数据的效率。
24、四种强制类型转换
- static_cast:隐式类型转换
- reinterpret_cast:强制类型转换
- dynamic_cast:用于继承中父类向子类转换
- const_cast:去掉const属性
25、C++中的内存分配
- 全局/静态存储区域:全局变量和静态变量
- 栈:局部变量、函数参数
- 堆:new分配的内存块,需要程序员手动调用delete进行释放
- 常量存储区:存储常量,不能被修改
- 代码区:存放代码(如函数),不允许修改,但可以执行
26、平衡二叉树和红黑树的区别
- 平衡二叉树:严格意义上的平衡二叉树,即左右子树高度差不会超过1,插入和删除过于复杂,便于查找
- 红黑树:弱平衡二叉树,只会保证没有一个分支的高度大于另一个分支的两倍,插入和删除效率较高
27、分布式的基本原理
28、操作系统相关问题(进程怎么维护已打开的文件描述符)
29、锁的实现?信号的详细实现?TCP状态的切换?Time-Wait的作用?
30、
31、
Linux启动原理、Linux日志3/4分别是什么意思等
31、内联函数、构造函数、静态成员函数可以是虚函数吗?
不可以
内联函数:在编译时展开,必须有实体;
静态成员函数:是属于这个类的,也必须要有实体
构造函数:如果构造函数也是虚函数,那么它也是存在于虚函数表中,需要通过vptr指针进行查表可得,但构造函数本身都不存在,创建不了实例,class的一些成员函数是不能被访问的(除静态成员函数外)。