参考:
https://zhuanlan.zhihu.com/p/37331092
https://blog.csdn.net/MUMU23333/article/details/80105446
C++虚函数
1 . 在有继承关系的父子类中,构建和析构一个子类对象时,父子构造函数和析构函数的执行顺序分别是怎样的?
2. 在有继承关系的类体系中,父类的构造函数和析构函数一定要申明为virtual吗?如果不申明为virtual会怎样?
构造函数不能是虚函数,析构函数可以是虚函数且推荐最好设置为虚函数。
首先,我们已经知道虚函数的实现则是通过对象内存中的vptr来实现的。而构造函数是用来实例化一个对象的,通俗来讲就是为对象内存中的值做初始化操作。那么在构造函数完成之前,也即还没有进行初始化,此时vptr是没有值的,也就无法通过vptr找到作为构造函数和虚函数所在的代码区,所以构造函数只能以普通函数的形式存放在类所指定的代码区中。
而对于析构函数,当我们delete(a)的时候,如果析构函数不是虚函数,那么调用的将会是基类base的析构函数。而当继承的时候,通常派生类会在基类的基础上定义自己的成员,此时我们当然希望可以调用派生类的析构函数对新定义的成员也进行析构。
3. 什么是C++多态?C++多态的实现原理是什么?
多态性(polymorphism)可以简单地概括为“一个接口,多种方法”,它是面向对象编程领域的核心概念。
多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
编译时多态性(静态多态):通过重载函数实现:先期联编 early binding
运行时多态性(动态多态):通过虚函数实现 :滞后联编 late binding
C++运行时多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(Override),或者称为重写。
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
多态的目的:封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
多态最常见的用法就是声明基类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是固定的,因此将始终调用到同一个函数,这就无法实现“一个接口,多种方法”的目的了
参考:https://zhuanlan.zhihu.com/p/37340242
4. 什么是虚函数?虚函数的实现原理是什么?
虚函数
定义:用virtual修饰的成员函数称为虚函数;
重写(覆盖):
当在子类中定义了一个与父类完全相同的虚函数时,则称这个子类的函数重写(或覆盖)了父类的函数;
总结:
完全相同指函数名、参数列表和返回值相同;
存在一种特殊情况:协变,子类虚函数和父类虚函数的返回值分别为子类指针和父类指针;
只有类的成员函数才能定义为虚函数;
静态成员函数不能定义为虚函数,因为static成员函数不属于任何对象;
如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual;
构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但最好不要这样做,因为使用时容易引起混淆;
内联函数不能为虚函数,如果内联函数被virtual修饰,计算机会忽视inline将它变成纯粹的虚函数;
不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为;
最好把基类的析构函数定义为虚函数;(如果没有定义为虚函数,当基类指针指向派生类,并且删除指针时,会析构基类而不会析构派生类,造成内存泄漏)
5. 虚函数 vs 纯虚函数,如何选用?
当基类中的某个成员方法,在大多数情形下都应该由子类提供个性化实现,但基类也可以提供缺省备选方案的时候,该方法应该设计为虚函数。
当基类中的某个成员方法,必须由子类提供个性化实现的时候,应该设计为纯虚函数。
6. 什么是虚表?虚表的内存结构布局如何?虚表的第一项(或第二项)是什么?
7. 菱形继承(类D同时继承B和C,B和C又继承自A)体系下,虚表在各个类中的布局如何?如果类B和类C同时有一个成员变了m,m如何在D对象的内存地址上分布的?是否会相互覆盖?
6,7回答
https://blog.csdn.net/MUMU23333/article/details/80105446
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base construct" << endl;
}
//~Base()
//{
// cout << "Base destruct" << endl;
//}
virtual ~Base()
{
cout << "Base destruct" << endl;
}
public:
void func()
{
cout << "normal func, inside class Base" << endl;
}
virtual void vir_func()
{
cout << "virtual func, inside class Base" << endl;
}
private:
};
class Derive_A :public Base
{
public:
Derive_A()
{
cout << "Derive_A construct" << endl;
}
~Derive_A()
{
cout << "Derive_A destruct" << endl;
}
public:
void func()
{
cout << "normal func, inside class Derive_A" << endl;
}
virtual void vir_func()
{
cout << "virtual func, inside class Derive_A" << endl;
}
private:
};
class Derive_B :public Base
{
public:
Derive_B()
{
cout << "Derive_B construct" << endl;
}
~Derive_B()
{
cout << "Derive_B destruct" << endl;
}
public:
void func()
{
cout << "normal func, inside class Derive_B" << endl;
}
virtual void vir_func()
{
cout << "virtual func, inside class Derive_B" << endl;
}
private:
};
int main(int argc, char* argv[])
{
Base* base = new(Base);
Base* a = new(Derive_A);
Base* b = new(Derive_B);
base->func(); a->func(); b->func();
cout << "===========================" << endl;
Derive_A* aa = new(Derive_A);
aa->func();
delete aa;
cout << "===========================" << endl;
base->vir_func(); a->vir_func(); b->vir_func();
cout << "===========================" << endl;
((Derive_A*)b)->func();
((Derive_A*)b)->vir_func();
delete base;
delete a;
delete b;
return 0;
}
在上述例子中,我们首先定义了一个基类base,基类有一个名为vir_func的虚函数,和一个名为func的普通成员函数。类A,B都是由类base派生的子类。然后我们定义三个base类型的指针Base、a、b分别指向类base、A、B。可以看到,当使用这三个指针调用func函数时,调用的都是基类base的函数。而使用这三个指针调用虚函数vir_func时,调用的是指针指向的实际类型的函数。最后,我们将指针b做强制类型转换,转换为A类型,然后分别调用func和vir_func函数,发现普通函数调用的是类A的函数,而虚函数调用的是类B的函数。以上,我们可以得出结论“当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的实际类型决定”。