2020年秋招期间自己归纳总结的牛客上有关C++常见的面试考点。通过秋招面试情况来看,大部分问题确实有被问到,对自己也有非常大的帮助。不建议直接看我写的回答,而是带着问题去总结自己的回答。相信坚持就会有好结果。
58个问题,共计一万五千多字。
1、 c++重载(overload)和重写(override)的区别
重载是一组函数名相同但是参数特征标不同的方法,特征:
方法名必须相同,参数列表必须不同,返回参数可以不同。其中返回参数可以不同,体现在函数重载是在编译阶段完成,是一个静态编译过程,而c++调用函数是可以忽略返回值的,所以不能作为判断依据。(构造函数,模板函数)(静态)
重写是派生类重写基类的虚函数,特征:
只有虚方法和抽象方法才能够被重写,必须具备有相同的函数名,参数列表和相同的返回值类型。返回参数在一种情况下可以不同,即返回类型协变,体现在可以用基类的指针去指向派生类对象,所以返回的可以是基类指针也可是派生类指针,是一个动态的绑定过程。所以重写是在运行阶段检查的。(动态)
2、 c++多态的体现方式
同一种操作对于不同的对象,可以有不同的解释,产生不同的执行结果
主要有两种类别:
一种是编译时的多态(静态编译,静态绑定),通过重载实现。系统根据参数列表不同在一组同一函数名的方法中选择一个执行;
另一种是运行时的多态(动态编译,动态绑定),通过重写实现。主要是因为我们可以用基类指针指向派生类对象,就需要代码在运行时确定指向的对象具体是哪一种对象,再执行相应的操作。
3、 关键字const
Const作用:
定义常量,编译期间可以进行静态数据类型的安全检查;
修饰函数的传入参数:经常与引用一起使用,常用在修饰用户自定义的数据类型时,能够避免在传参数调用构造,复制和析构函数,加快运行效率,同时也能避免原始数据被修改。Const形参可以传入非const和const实参;
修饰函数的返回值时:返回值不能被直接修改,而且只能复制给const对象;
修饰类成员函数时:任何不会修改数据成员的成员函数都应该设置为const,形式比较特殊,放在函数的末尾。
4、 const和#define的区别
宏定义没有数据类型,只做文本替换。Const有数据类型,且会进行安全检查;
5、 static关键字
static修饰的变量为静态变量,只被初始化一次,在函数体内的static变量值依据上一次的结果,访问方式为类名.变量名;
在函数体外部定义的静态变量,可以被该文件下的所有函数调用,无外部链接性,是一个全局变量;
Static修饰的函数,可以被模块内的其他函数调用;
此外,定义在类中的静态变量和函数不是类的成员,没有this指针,不占用类的内存,并且要在类的外部进行初始化;
Static只能修饰内部类
6、 static与普通的全局变量有什么区别
static定义的全局变量只能被初始化一次,无外部连接性,只能被该文本下的所有函数使用。而普通全局变量,一般声明在头文件中,可以被所有包含该头文件的文件函数初始化并调用。
Static定义的函数,与普通函数的区别除了同变量相同之外。Static定义的函数内存中只有一份,而普通函数,调用一次就会有一个副本。
7、 内存泄漏解决办法
存在两个方面:手动开辟的内存空间没有进行释放,其次含有派生类的基类析构函数不是虚函数
解决办法:记得每次手动释放,使用智能指针,含有派生类的基类析构函数设置为虚函数。还可以选用智能指针
8、 智能指针
智能指针是作为内存管理工具。为了防止在动态开辟和释放内存的过程中,由于人为疏漏忘记释放内存而导致的内存泄漏问题。智能指针本质上是一个类,当作用域超过范围时,会自动的调用析构函数,释放内存空间,防止内存泄露。
auto_ptr:一种独占式拥有,当将一个对象的指针赋值给另一个变量时,会导致所有权的转让(即 一个对象只能有一个智能指针指向)但是不会报错。因此之前的指针变量会指向不定向,当访问其内存时就会出现问题。
unique_ptr:也是一个非常严格的独占式拥有,即一个对象只能自己拥有,但解决了auto_ptr的问题,在编译器会直接报错。其次可以将unique_ptr临时的右值赋值给unique_ptr对象。或者用move()函数实现所有权转让。
shared_ptr:实现共享式拥有,即一个对象可以被多个该指针指向。只有当指向该对象的所有指针都销毁时,该对象才会被释放。内部函数有use_count()返回指向的个数;unique()判断是否独占;swap()交换两个指向对象;对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
weak_ptr是一种不控制生命周期的智能指针。指向一个shared_ptr管理的对象,目的是配合shared_ptr进行对象管理。他的创建和消灭不会引起use_count()的增加。是为了解决shared_ptr相互引用造成的死锁问题。
9、 C++四种类型转换
const_cast:用于将const转换成非const
static_cast:用于各种隐式转换,非const转const。能用于多态向上转换,向下转换也行但是不安全;
dynamic_cast:动态类型转换,只能用于含有虚函数的类,用于类层次间的向上或者向下转换。只能转指针或者引用。向下转换如果返回是指针返回null,如果是引用则抛出异常。
reinterpret_cast:万能转换符,因为万能所以容易出错,尽量少用。
10、 指针和引用有什么区别
指针有自己的一块空间而引用只是变量的一个别名,因此用sizeof求空间大小的时候指针为4,而引用是引用对象的大小,因此使用++运算符含义也不同;
指针初始化时候可以设置为null,而引用必须初始化一个确定对象,因此指针可以指向不同的对象,引用不能中途易主;
可以有const指针,但是没有const引用;
如果函数返回动态分配的内存空间时,必须使用指针,引用可能引起内存泄漏。
第二次补充:是什么(本质),怎么用及问题(初始化(引用好于指针(野指针)),函数传入参数(两个都好),返回值(指针好于引用(内存泄漏))),适用场景
11、 什么是野指针,怎样预防野指针
两种情况:申明未初始化的指针变量,指向已删除对象的指针变量
预防:初始化时赋初值,指向删除对象的指针设为null
12、 什么是函数指针
函数指针就是指向函数的指针,编译时每个函数都有一个入口地址,函数指针就是指向该入口地址的指针。
可用作调用函数或者做函数的参数,比如回调函数
13、 虚函数表具体是怎样实现多态的
多态,对同一输入和表达式得到多种形态的输出。具体来说,就是通过父类指针和虚函数和重写机制去调用子类的成员函数。
因此实现多态必须有:继承和虚函数机制(或者说多态是通过虚函数实现的)
什么是虚函数表:对于含有虚函数的类都会有一个虚指针(属于类的空间)指向虚函数表(属于类外空间),用于在运行期间动态的绑定成员函数。
虚函数表在不同继承情况下是否不同:
对于单继承:子类虚表指针所指向的虚函数表的内容首先是父类的虚函数地址,再排布子类的虚函数地址。如果子类虚函数有对父类虚函数的重写,则用子类该虚函数覆盖掉父类的虚函数地址。
对于多继承(非菱形继承):有多少个父类就会在子类空间中形成几个虚表指针,排布顺序按照声明顺序。同时会将子类虚函数指针排布在第一个续表后面,如果子类虚函数对父类虚函数重写,则用子类该虚函数覆盖掉所有父类的虚函数地址。
对于虚继承(解决多重继承中的冲突问题):父类的成员变量以及虚函数都放在子类内存空间的最后。父类的虚表只会出现一次
https://www.cnblogs.com/jerry19880126/p/3616999.html
https://blog.csdn.net/li1914309758/article/details/79916414
14、 malloc和new的区别
class A {…};
A* Ptr = new A;
A* Ptr = (A*)malloc(sizeof(A));
new和delete是C++关键字,需要编译器支持,而malloc为标准库函数;
malloc开辟空间时需要显示指定内存大小,而new根据对象大小自动分配;
new分配成功的时候是严格返回的对象类型的指针,而malloc是返回的(void*)需要进行强制的类型转换。
new分配失败的时候会返回异常bac_alloc,而malloc分配失败时会返回null。因此在使用malloc时候,会经常在其后使用是否为NULL判断语句,用来判断内存是否正常开辟;
是否调用构造函数和析构函数:使用new操作符来分配对象内存会有三个步骤:首先调用operator new函数,分配一个足够大的未命名空间来存储特定类型的对象。然后,编译器运行构造函数,并为其传入初始值。