C++继承的构造函数、多重继承、虚继承

一、继承

继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义。利用类的继承和派生实现了更高层次的代码可重用性,符合现代软件开发的思想。

C++语言同时支持单一继承和多重继承。单一继承是指派生类只从一个基类继承而来;相应的,多重继承指派生类同时从两个或更多的基类继承而来。

1. 继承的特性:

(1)定义派生类关键字可以是class或者是struct,两者区别是:用class定义派生类,默认的继承方式是private,用struct定义派生类,默认的继承方式为public。新增加的成员默认属性也是class对应private属性,struct对应public属性。

(2)基类不能被派生类继承的两类函数是构造函数(默认构造、拷贝构造、移动构造)和析构函数。

实例:单一继承

#include"stdafx.h"
#include<iostream>
using namespace std;
 
class Other
{
public:
    Other()
    {
        cout<<"constructing Other class"<<endl;
    }
    ~Other()
    {
        cout<<"destructing Other class"<<endl;
    }
};
 
class Base
{
public:
    Base()
    {
        cout<<"constructing Base class"<<endl;
    }
    ~Base()
    {
        cout<<"destructing Base class"<<endl;
    }
};
 
class Derive:public Base
{
private:
    Other ot;
public:
    Derive()
    {
        cout<<"constructing Derive class"<<endl;
    }
    ~Derive()
    {
        cout<<"destructing Derive class"<<endl;
    }
};
 
int main()
{
    Derive d;
 
    return 0;
}

运行结果:
在这里插入图片描述

可以看到定义派生类对象时,构造函数的调用顺序:

  1. 先调用基类的构造函数
  2. 然后调用派生类对象成员所属类的构造函数(如果有对象成员)
  3. 最后调用派生类的构造函数

析构函数的调用顺序正好与构造函数调用顺序相反。

二、多重继承

1. 多重继承概述

多重继承:常规情况,一个类只有一个基类,而C++支持多重继承,即一个类可以继承自多个类。

人(Person)可以吃饭和睡觉,既可以是作家也可以是程序员,作家可以写文章,程序员可以写程序,

即是作家又是程序员的人能够写文章和写程序。

多重继承的优缺点:

多重继承的优点很明显,就是对象可以调用多个基类中的接口;

多重继承的缺点是什么呢?如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性。

对于二义性,通常有两个解决方案:

(1)加上全局符确定调用哪一份拷贝。

(2)使用虚拟继承,使得多重继承类只拥有基类类的一份拷贝。

2. 静态成员变量

在C++中(以及其他一些语言,如 C#,Java 等面向对象的语言中)类的成员变量被声明为static(称为静态成员变量),意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见。

静态成员变量特性:

  1. 静态成员变量属于整个类所有;
  2. 静态成员的生命周期不依赖于任何对象(程序包运行的整个周期);
  3. 可以通过类名直接访问公有静态成员变量;
  4. 所有对象共享类的静态成员变量;
  5. 可以通过对象名访问公有静态成员变量;
  6. 在定义时直接通过static关键字修饰;
  7. 静态成员变量需要在类外单独分配空间;
  8. 静态成员变量在程序内部位于全局数据区(但是文件间无法共享)。

父类的static变量和函数在派生类中依然可用,但是受访问性控制(比如,父类的private域中的就不可访问),而且对static变量来说,派生类和父类中的static变量是共用空间的,这点在利用static变量进行引用计数的时候要特别注意。

static函数没有“虚函数”一说。因为static函数实际上是“加上了访问控制的全局函数”,全局函数哪来的什么虚函数?

派生类的friend函数可以访问派生类本身的一切变量,包括从父类继承下来的protected域中的变量。但是对父类来说,他并不是friend的。

3. 静态成员函数

  1. 静态成员函数是类中的特殊的成员函数;
  2. 静态成员函数没有隐藏的this指针;
  3. 静态成员函数可以通过类名直接访问;
  4. 静态成员函数可以通过对象访问;
  5. 静态成员函数只能直接访问静态成员变量(函数),而不能直接访问普通成员变量(函数)。

4. 派生类构造函数与析构函数

在定义一个派生类的对象时,在派生类中新增加的数据成员当然用派生类的构造函数初始化,但是对于从基类继承来的数据成员的初始化工作就必须由基类的构造函数完成,这就需要在派生类的构造函数中完成对基类构造函数的调用。同样,派生类的析构函数只能完成派生类中新增加数据成员的扫尾、清理工作,而从基类继承来的数据成员的扫尾工作也应有基类的析构函数完成。由于析构函数不能带参数,因此派生类的析构函数默认直接调用了基类的析构函数。

派生类构造函数与析构函数的特点:

(1)一般情况下,基类名后面的参数表中的实际参数来自前面派生类构造函数形式参数总表,当然也可能是与前面形式参数无关的常量;

(2)多层次继承中,每一个派生类只需要负责向直接基类的构造函数提供参数;如果一个基类有多个派生类,则每个派生类都要负责向该基类的构造函数提供参数。

定义派生类对象时,构造函数的调用顺序:

(1)先调用基类的构造函数

(2)然后调用派生类对象成员所属类的构造函数(如果有对象成员)

(3)最后调用派生类的构造函数

析构函数的调用顺序正好与构造函数调用顺序相反。

5. 从多个父类继承构造函数

在多重继承中,派生类有多个平行的基类,这些处于同一层次的基类构造函数的调用顺序,取决于声明派生类时所指定的各个基类的顺序,而与派生类构造函数的成员初始化列表中调用基类构造函数的顺序无关。

实例:多重继承

#include"stdafx.h"
#include<iostream>
using namespace std;
 
class Grand
{
    int g;
public:
    Grand(int n):g(n)
    {
        cout<<"Constructor of class Grand g="<<g<<endl;
    }
    ~Grand()
    {
        cout<<"Destructor of class Grand"<<endl;
    }
};
 
class Father:public Grand
{
    int f;
public:
    Father(int n1,int n2):Grand(n2),f(n1)
    {
        cout<<"Constructor of class Father f="<<f<<endl;
    }
    ~Father()
    {
        cout<<"Destructor of class Father"<<endl;
    }
};
 
class Mother
{
    int m;
public:
    Mother(int n):m(n)
    {
        cout<<"Constructor of class Mother m="<<m<<endl;
    }
    ~Mother()
    {
        cout<<"Destructor of class Mother"<<endl;
    }
};
 
class Son:public Father,public Mother
{
    int s;
public:
    Son(int n1,int n2,int n3,int n4):Mother(n2),Father(n3,n4),s(n1)
    {
        cout<<"Constructor of class Son s="<<s<<endl;
    }
    ~Son()
    {
        cout<<"Destructor of class Son"<<endl;
    }
};
 
int main()
{
    Son s(1,2,3,4);
    return 0;
}

运行结果:
在这里插入图片描述
可以看到,与单一继承不同的是:在多重继承中,派生类有多个平行的基类,这些处于同一层次的基类构造函数的调用顺序,取决于声明派生类时所指定的各个基类的顺序,而与派生类构造函数的成员初始化列表中调用基类构造函数的顺序无关。

三、虚基类、虚继承(虚派生)

1、虚基类

如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。

在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如

c1.A::display( )。

在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的。C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员。

注意:

虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。

声明虚基类的一般形式为

class 派生类名: virtual 继承方式

基类名

经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,该派生类只继承该基类一次。

需要注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。否则仍然会出现对基类的多次继承。

如果在派生类B和C中将类A声明为虚基类,而在派生类D中没有将类A声明为虚基类,则在派生类E中,虽然从类B和C路径派生的部分只保留一份基类成员,但从类D路径派生的部分还保留一份基类成员。

虚基类的初始化如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化表对虚基类进行初始化。

在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类。

对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

2、虚继承

虚继承是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
在这里插入图片描述

1)D继承了B,C也就继承了两个虚基类指针

2)虚基类表存储的是,虚基类相对直接继承类的偏移(D并非是虚基类的直接继承类,B,C才是)

3)虚基类时,D直接初始化A类。如果D类有了子类,将由其子类初始化A类,即由最底层的派生类来初始化

4)初始化顺序:先初始化虚基类部分,然后再按照派生列表中出现的顺序来初始化其他类。

5)如果有多个虚基类,会按照派生列表中的直接基类来回追溯,看是否这些直接基类含有虚基类,追溯到哪个虚基类,就先构造哪个虚基类的子内容,析构顺序相反。
实例:

#include<iostream>
using namespace std;
 
class A  //大小为4
{
public:
	int a;
};
class B :virtual public A  //大小为12,变量a,b共8字节,虚基类表指针4
{
public:
	int b;
};
class C :virtual public A //与B一样12
{
public:
	int c;
};
class D :public B, public C //24,变量a,b,c,d共16,B的虚基类指针4,C的虚基类指针4
{
public:
	int d;
};
 
int main()
{
	A a;
	B b;
	C c;
	D d;
	cout << sizeof(a) << endl;
	cout << sizeof(b) << endl;
	cout << sizeof(c) << endl;
	cout << sizeof(d) << endl;
	system("pause");
	return 0;
}

运行结果分析:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值