本文的重点是C++面经问题和串讲复习,C和C++的区别,可以参考看看,个人见解。
问题:简述C和C++的区别?
- C++是面向对象的;C是面向过程的
面向过程:完成一件事情,按照步骤一步步的实现。做饭举例:要先买菜,洗菜,切菜,炒菜。一步步完成
面向对象:将不同的事物都看成一个对象,依靠对象之间的相互联系,相互操作,完成一定的功能。做饭举例:菜、刀、锅。这都是对象,对象之间相互作用,相互联系。最终实现成品菜这个对象。
- C++输入输出流不同
- C++有命名空间namespace
- C++有缺省函数
缺省函数:就是对每个函数的参数一个初值。
缺省函数分为:
全缺省函数:每个参数都有一个初值
半缺省函数:部分函数有初值。要求:从右往左进行赋初值,不能隔着给。
注意:
缺省函数的声明和定义不能分开;定义的初值不一样,编译器就会乱。
缺省值必须是常量或者全局变量。
- C++有函数重载
函数重载:函数名相同,参数列表不同(参数的个数、类型、顺序)
注意:
函数的返回值不一样不属于重载。
在后面的知识:构造函数可以重载;析构函数不可以重载。(原因后面叙述!)
C++可以进行函数重载的原因:函数名字修饰不同;这个过程是在编译阶段。
名字修饰这个过程:是编译器为了区分各个函数,将函数通过某种算法,重新修饰为一个全局唯一的名称。
C修饰函数名字:函数名前面加上下划线。例如:int add(int a, int b) 函数名=> _add
C++修饰函数名字:更加复杂,它会带上返回值,带上参数列表。具体:修饰后名字是:?(问号)开头,接着函数名,随后@结尾,命名空间@类名@,函数调用类型+返回值+参数@,Z结尾。
这里映射出两个新的问题:
第一个问题:因为名字修饰在编译阶段,那么程序运行几个阶段?每个阶段都是干什么的?
答:四个阶段:预处理,编译,汇编,链接。
- 预处理:主要展开包含的头文件,宏定义替换,去掉无用代码,注释等操作(.c—.i)注意:这时候可能有新的问题:说说宏的优缺点,可以用什么替换?
- 编译:这个阶段编译器主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言(.i—.s)转换为汇编语言文件
- 汇编:汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。(.s—.o)得到机器语言
- 链接:链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
具体参考:程序运行几个阶段详解
第二个问题:如果我要按C风格编译怎么办?什么时候会使用?
答:在函数前加extern "C",在C++使用编译好的C语言库的时候会使用。
这又会有新的问题:你都用过那些C语言库?extern这个关键字怎么用?
第一个问题:这个就自己积累,或者查查,不过一般不问。
第二个问题:C++关键字总结
映射出的问题:宏的优缺点,可以用什么替换?
答:
宏的优点:1.增强代码的复用性。 2.提高性能
宏的缺点:1.没有安全检查。 2.代码可读性差,不利于维护。 3.不方便调试。(因为预处理进行的替换)
替换技术:1.替换函数——>用内联函数 2.替换常量——>用const修饰
新问题:const关键字的用法?内联函数是什么?
- 内联函数:这也是C++区别于C的一点
问:什么是内联函数?答:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。
- inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
- inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找 不到。
这里解释上面的构造函数为什么可以重载?析构函数不可以重载?
答:因为构造函数作用是构造不同的对象,不可能构造出全部都是一模一样的对象。学生举例子,也分为小学生,中学生,大学生。对象的多样性所以要重载。
析构函数不能重载是因为:析构函数没有参数。(至于为什么没有参数……我也不知道,问了就说不知道吧,感兴趣可以查查。)
- C++有引用
引用:引用就是个变量起一个别名。共用一个内存空间。
注意:
- 引用必须初始化。
- 多个引用可以引用一个变量,但是一个引用只能引用一个变量。最根本的原因是共用一个内存空间。
传值、传址、传引用速率:传值 < 传地址 < 传引用
传地址和传引用的异同点:(经常问)
共同点:底层都是按传地址的方式实现的
不同点:
- 1.引用必须初始化,但是指针开始要指向NULL,否则有野指针问题。
- 2.引用没有NULL,指针有NULL。
- 3.有多级指针,没有多级引用。
- 4.引用只能引用一个对象,指针可以指任何别的对象。
- 5.指针有自己的内存空间,引用是和被引用的共用一个内存空间。指针有自己空间所以里面可以放所指向的地址。(这一点必须说出来,解释了3,4)
- 6.指针的取地址是人手工操作的,引用的取地址是编译器操作的。
- 7.还有sizeof后的值,这个要熟悉sizeof和strlen函数,不熟悉不用说。
传引用的使用场景:
- 做参数:这没啥说的和指针一样。
- 做返回值:只是后要注意引发函数栈帧问题。(注意自己好好了解一下。)
函数栈帧:如果函数返回时,离开函数作用域后,其栈上空间已经还给系统,因此不能用栈上的空间作为引用类型返回。如果以引用类型返回,返回值的生命周期必须不受函数的限制(即比函数生命周期长)。
这里引申出一个问题:传值、传地址到底是怎么传递参数的,一般不问,要注意。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实 参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回 值类型非常大时,效率就更低。
这里还有一个:在新的C++11的标准中有右值引用。跟着部分是没有关系的,说的时候不用自己扩展,知道就行,在问你:C++11你知道哪些?你就可以回答C++11中有右值引用。(我后续博客会总结C++11中的东西)
- C++和C的动态内存规划不一样
C中的内存规划是malloc和free。而C++中是new和delete。然后就是:new/delete和malloc/freee的区别。重点:(调用析构函数和调构造函数。一个是函数、一个是操作符。不熟悉的可以看看:C++动态内存管理)
这里新的问题:内存的划分?在C++动态内存管理中会说。
- C++有智能指针
智能指针部分可以看这个:智能指针详解。后面面经部分我也会讲。
- C++有类型转换符
参考博客:C++强制类型转换
- C++有STL(你要是提STL,就会问你那你知道哪些STL,这时候会说很多,所以问你区别一般不会说这么多,只会问你你知道哪些STL)
STL的总结最常问的是:vecto和list的区别。这个非常容易问。
vector和list的区别。(重点:连续、不连续、方便随机访问、方便插入删除、为什么)
(一般我的回答:vector是一段连续的空间,list不是连续的空间,因为vecotr连续空间,所以方便随机访问,知道首地址,因为连续,知道位置便可以访问,list方便插入删除,因为不连续,所以访问的时候要先遍历整个双向循环链表,确定位置后,才可以访问,但是因为每个节点中放着下一个节点地址,所以方便插入删除,只用让他指向上一个,下一个结点的位置就好。)
STL我后续博客会总结。
注意:千万别说C++有封装、继承和多态!!!!这是面向对象语言的特性,C++是一种面向对象语言!!!这不是C++才有的。
下面的部分是C++面经问题+总复习!!!依旧串讲拓展形式。
问:面向对象的三大特性?答:封装、继承、多态。
封装的概念:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
继承:一般不会问你什么继承。要知道概念:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,继承呈现了面向对象程序设计的层次结构,继承是类设计层次的复用。
三大继承方式:public、protected、private
这个地方有个点:问题:C++中struct和class的区别是什么?
答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。 和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。
派生类的默认成员函数?(主要是:派生类的构造函数、析构函数调用规则?)
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
- 派生类的operator=必须要调用基类的operator=完成基类的复制。
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类 对象先清理派生类成员再清理基类成员的顺序。
- 派生类对象初始化先调用基类构造再调派生类构造。
- 派生类对象析构清理先调用派生类析构再调基类的析构。
总结:基类部分调用基类,派生类调用派生类的。1、5、6是关键。
这时候新的问题:你知道友元吗?
友元:
- 友元函数可访问类的私有成员,但不是类的成员函数;
- 友元函数不能用const修饰;
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
- 一个函数可以是多个类的友元函数;
- 友元函数的调用与普通函数的调用和原理相同;
- 友元关系不能继承。
虚继承:目的就是解决菱形继承。
这里的D就是多继承。对于多继承一般不问。
菱形继承的问题:数据的二义性。现在 B 中有 A 中数据a,C 中也有 A 的数据a;D 中一定有从B,C得到的数据a;那不就有A中的两个a,那 a 到底来自 B 还是 C 就不知道。
虚继承解决:需要注意的是,虚拟继承不要在其他地方去使用。(这一点为什么……我也不知道,感兴趣的可以自己查查,评论一下)
虚拟继承的原理:D对象中将A::a放到了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向一张表。这两个指针叫虚基表指针,这表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。(说的直白一点:只有一个数据a,是A的,但是同时属于B和C;B,C要找通过偏移量;放B,C的叫虚基表,里面放的B,C是虚基表指针。)
多态:(一般不直接问什么是多态,他会问你什么是虚函数。答:虚函数就是virtual修饰的函数,它实现的就是的多态。通过什么条件,进行虚函数的重写,体现不同对象对一件事情的不同表现形式。)
问:什么多态?答:不同对象对一件事情的不同表现形式。
多态的条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)
原理:有虚函数表,里面放着虚函数指针,需要的时候对应查询虚函数表。虚函数代码还是在代码段。
这里一个典型问题:构造函数可以定义为虚函数吗?析构函数呢?
参考这篇博客:构造函数、析构函数可以是虚函数吗
抽象类:
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。
因为如果定义一个动物类?难道生成一个动物对象?犬科动物继承动物特性后,重写,生成哈士奇,就很合理。
另外:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中。
还有就是类的六个基本函数:这里问的是:构造函数、析构函数的重载,虚函数的判断。
这里还有主要一个问题:拷贝构造函数中深浅拷贝问题。
参考博客:深浅拷贝/写时拷贝
还有STL问题,后续博客会说明。