基类、派生类、派生类成员变量的构造和析构顺序

本文深入探讨了C++中虚函数的概念、作用、语法及其在实现多态性中的应用。从虚函数的定义、作用、语法入手,详细解释了如何通过虚函数实现动态多态性,并通过实例展示了虚析构函数、纯虚函数和抽象类的概念。文章旨在帮助读者理解和掌握C++中的多态性机制。
#include <iostream>

class A
{
public:
	A() { std::cout << "A" << std::endl; }
	virtual ~A() { std::cout << "~A" << std::endl; }
	virtual void Test() { std::cout << "A::Test" << std::endl; }
};

class B
{
public:
	B() { std::cout << "B" << std::endl; }
	~B() { std::cout << "~B" << std::endl; }
};

class C : public A
{
public:
	C() { std::cout << "C" << std::endl; }
	~C() { std::cout << "~C" << std::endl; }
	void Test() { std::cout <<"C::Test" << std::endl; }
private:
	B _b;
};

int main(void)
{
C c;
return 0;
}
实例化一个派生类对象:
构造
1.执行基类的构造函数;
2.初始化派生类的成员变量,由于B是一个类类型,所以会调用B的默认构造函数,此时如果B没有默认构造函数会报错;
3.执行派生类的构造函数。
析构
与构造顺序相反
执行结果:
A
B
C
~C
~B

~A

int main(void)
{
A *pa = new C();
pa->Test();
delete pa;

return 0;
}
构造和析构顺序与上类似,不过需要注意的是基类A的析构函数必须声明为virtual,否则派生类得不到析构
执行结果:
A
B
C
C::Test
~C
~B
~A

 

虚函数表

   虚函数 
  C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的 编译器厂商都选择此方法)。
  类的虚函数表是一块连续的内存,每个 内存单元中记录一个JMP指令的地址。
  注意的是,编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员占据虚函数表中的一行。如果类中有N个虚函数,那么其虚函数表将有N*4字节的大小。
  虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的 指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
  编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是 多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中 函数指针,并调用相应的函数。

 

虚析构函数

 

虚析构函数  虚析构函数是为了解决这样的一个问题:基类指针指向派生类对象,并用基类的指针删除派生类对象。

  如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。因为它会为类增加一个虚函数表,使得对象的体积翻倍,还有可能降低其可移植性。

  所以基本的一条是:无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。

  抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。

虚析构函数举例

  这里是一个例子:

  class awov { // awov = "abstract w/o

  // virtuals"

  public:

  virtual ~awov() = 0; // 声明一个纯虚析构函数

  };

  这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:

  awov::~awov() {} // 纯虚析构函数的定义

  这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。

  注意:如果声明虚析构函数为inline,将会避免调用它们时产生的开销,但编译器还是必然会在什么地方产生一个此函数的拷贝。

 

纯虚函数

一、定义  纯虚函数是一种特殊的虚函数,它的一般格式如下:

  class <类名>

  {

  virtual <类型><函数名>(<参数表>)=0;

  …

  };

  在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。这就是纯虚函数的作用。

  纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做抽象类。这种类不能声明对象,只是作为基类为派生类服务。除非在派生类中完全实现基类中所有的的纯虚函数,否则,派生类也变成了抽象类,不能实例化对象。

二、引入原因

  1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数

  2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

  为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;)。若要使派生类为非抽象类,则编译器要求在派生类中,必须对纯虚函数予以重写以实现多态性。同时含有纯虚函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

三、相似概念

  1、多态性

  指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性

  a.编译时多态性:通过重载函数实现

  b 运行时多态性:通过虚函数实现。

  2、虚函数

  虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载

  3、抽象类

  包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。

 

虚函数

定义

  定义:在某 基类中声明为 virtual 并在一个或多个 派生类中被重新定 义的 成员函数 [1]
  语法:virtual 函数返回类型 函数名(参数表) { 函数体 }
  用途 实现 多态性,通过指向派生类的基类 指针,访问派生类中同名覆盖成员函数
  虚函数必须是基类的非 静态成员函数,其访问权限可以是protected或public,在基类的类定义中定义虚函数的一般形式:
  class 基类名{
  .......
  virtual 返回值类型 将要在派生类中 重载的函数名( 参数列表);
  };

作用

  虚函数的作用是实现 动态联编,也就是在程序的运行阶段动态地选择合适的 成员函数,在定义了虚函数后,可以在 基类派生类中对虚函数重新定义,在派生类中重新定义的函数应与虚函数具有相同的 形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
  当程序发现虚函数名前的关键字virtual后,会自动将其作为动态联编处理,即在程序运行时动态地选择合适的成员函数。虚函数是C++ 多态的一种表现。
  例如:子类继承了父类的一个函数(方法),而我们把父类的 指针指向子类,则必须把父类的该函数(方法)设为virtual(虚函数)。
  ([2010.10.28] 注:下行语义容易使人产生理解上的偏差,实际效果应为:
  如存在:Base -> Derive1 -> Derive2 及它们所拥有的虚函数func()
  则在访问派生类Derive1的实例时,使用其基类Base及本身类型Derive1,或被 静态转换的后续派生类Derive2的指针或引用,均可访问到Derive1所实现的func()。)
  动态联编规定,只能通过指向基类的指针或基类对象的引用来调用虚函数,其格式:
  1、指向基类的指针变量名->虚函数名( 实参表)
  2、基类对象的引用名. 虚函数名(实参表)
  使用虚函数,我们可以灵活的进行 动态绑定,当然是以一定的开销为代价。如果父类的函数(方法)根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数(方法)设为virtual 函数名=0 我们把这样的函数(方法)称为 纯虚函数
  如果一个类包含了纯虚函数,称此类为 抽象类

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值