本篇文章连问面试时经常会遇到的类和继承相关25个问题,看看你能回答出几道题呀。
还是先看一下思维导图,如下:
1. c++的三大特性是什么
c++的三大特性,说白了其实就是面向对象的三大特性,是指:封装、继承、多态,简单说明如下:
- 封装是一种技术,它使类的定义和实现分离,也就是隐藏了实现细节,只留下接口给他人调用,另外封装还有一层意义是它把某种事物具现出属性和方法并形成了一个整体,就像一个人,同时具有身高和身体等等这些,才是完整的人,如果不封装,那这个人就相当于四分五裂了;
- 继承,所谓继承,其实就是真实意义上讲的继承了某些东西,放到c++的类里面,其实就是实现了代码的重用,即派生类要使用基类的属性和方法,就不用再重新编写代码,这种可以算是实现继承。还有一种就是继承了某样东西,但是派生类需要重新实现一下,也就是接口继承,下面第三点要讲的多态就是接口继承的典型代表;
- 多态,多种形态,就是我们使用基类的指针或者引用调用基类的某个函数时,编译期并不知道到底是要调用哪个函数,因为我们不能确定这个指针或者引用到底指向基类对象还是派生类对象,直到运行时才能确定,这个就叫多态。
2. c++继承的优点和缺点
优点:根据第1点中讲的,其实继承优点就是实现了代码的重用和接口的重用;
缺点:子类会继承父类的部分行为,父类的任何改变都可能影响子类的行为,也就是说,如果继承下来的实现不适合子类的问题,那么父类必须重写或者被其他的类替换,这种依赖关系限制了灵活性。
从以上对比看,同一种属性既可以是优点,也可以是缺点,就看个人在编程过程中的灵活运用了。
3. 派生类调用构造函数和析构函数的顺序
看代码:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B:public A
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
int main()
{
B b;
return 0;
}
输出结果如下:
A()
B()
~B()
~A()
根据结果,可知顺序如下:
-
派生类对象定义时,先调用基类的构造函数,再调用派生类的构造函数;
-
派生类对象销毁时,先调用派生类的析构函数,再调用基类的析构函数。
4. c++中多态有什么作用
个人理解,其实就是实现了接口的重用,同样的接口,派生类与基类不同的实现。
5. 多态的实现原理
一般来讲多态分为编译时多态和运行时多态,编译时多态就是指的重载哪些,我们通常默认多态是运行时的多态。
运行时多态简单来讲就是:使用基类指针或者引用指向一个派生类对象,在非虚继承的情况下,派生类直接继承基类的虚表指针,然后使用派生类的虚函数去覆盖基类的虚函数,这样派生类对象通过虚表指针访问到的虚函数就是派生类的虚函数了。
更详细的说明请看之前写的这篇文章:c++头脑风暴-多态、虚继承、多重继承内存布局
6. 类成员函数的重载、覆盖和隐藏的区别
重载即为函数重载,重载的特征:
- 相同的范围,也就是在同一个类中;
- 函数名字相同;
- 函数参数不同;
- virtual关键字无影响。
覆盖是指派生类函数覆盖基类函数,覆盖的特征:
- 不同的范围,即函数分别位于派生类和基类中;
- 函数名字相同;
- 函数参数相同;
- 基类函数必须有virtual关键字。
隐藏是指派生类的函数屏蔽了与其同名的基类函数,特征如下:
- 如果派生类的函数与基类的函数同名,但是参数不同,此时不论有没有virtual关键字,基类的函数都将被隐藏;
- 如果派生类的函数与基类的函数同名,参数也相同,但是基类函数没有virtual关键字,此时,基类的函数将被隐藏;
总结:函数名相同,参数也相同的情况下,如果基类函数有virtual关键字,则是多态,否则就是隐藏;函数名相同,参数不同的情况下,如果函数位于同一个类中,则是重载,否则就是隐藏。
7. 析构函数是否可以为虚函数?如果可以,有什么作用?
析构函数可以是虚函数,因为它是对象结束时才调用,不影响虚表构建。
那么析构函数作为虚函数有什么作用呢,看这样一段代码:
#include <iostream>
using