在C++中,可重用性是通过继承这一机制来实现的,因此,继承是C++中一个重要的部分。
1.派生类的声明
声明一个派生类的一般格式为:
class 派生类名:继承方式 基类名 { //派生类新增的数据成员和成员函数 };
从已有类派生出新类时,可以在派生类内完成以下功能:
(1)可以增加新的数据成员
(2)可以增加新的成员函数
(3)可以对基类的成员进行重定义
(4)可以改变基类成员在派生类中的访问属性
2.基类成员在派生类中的访问属性
从基类继承来的成员在派生类中的访问属性是由继承方式控制的,下面我们来看看类的继承方式。
类的继承方式有public(公有继承),protected(保护继承),private(私有继承)3种,不同的继承方式导致不同访问属性的基类成员在派生类中的访问属性也不同。
(1)公有继承的访问规则
基类成员 | 私有成员 | 公有成员 | 保护成员 |
类内访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 可访问 | 不可访问 |
(2)保护继承的访问规则
基类成员 | 私有成员 | 公有成员 | 保护成员 |
类内访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
(3)私有继承的访问规则
基类成员 | 私有成员 | 公有成员 | 保护成员 |
类内访问 | 不可访问 | 可访问 | 可访问 |
对象访问 | 不可访问 | 不可访问 | 不可访问 |
根据上面三个表格我们不难看出:
a.基类中的私有成员:
无论哪种继承方式,都不允许派生类继承,即在派生类中是不可以直接访问的。
b.基类中的公有成员:
公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现
私有继承时,基类中的所有公有成员在派生类中都是以私有成员的身份出现
保护继承时,基类中的所有公有成员在派生类中都是以保护成员的身份出现
c.基类中的保护成员:
公有继承时,基类中的所有公有成员在派生类中仍以保护成员的身份出现
私有继承时,基类中的所有公有成员在派生类中都是以私有成员的身份出现
保护继承时,基类中的所有公有成员在派生类中都是以保护成员的身份出现
下面我们通过实例来看看公有继承:
#include<iostream> using namespace std; class B { public: void geta(int a1) { a = a1; } void showa() { cout << "a=" << a << endl; } private: int a; }; class D :public B { public: void getab(int a1, int b1) { geta(a1); b = b1; } void showab() { cout << "a=" << a << endl;//错误,a在类中为不可直接访问成员 cout << "b=" << b << endl; } private: int b; }; void Funtest() { D d; d.getab(10, 24); d.showa(); d.showab(); } int main() { Funtest(); system("pause"); return 0; }
上面程序中在D内访问了a是错误的,在这里再次说明:派生类公有继承了基类,但是不代表派生类可以访问基类私有成员,企图访问是非法的。
3.派生类的构造函数和析构函数:
我们先通过一个例子来看看派生类的构造函数和析构函数的调用顺序吧:
#include<iostream> using namespace std; class B { public: B(int n) { cout << "B()" << endl; i = n; } ~B() { cout << "~B()" << endl; } void showi() { cout << i << endl; } private: int i; }; class D :public B { public: D(int n, int m) :B(m) { cout << "D()" << endl; j = n; } ~D() { cout << "~D()" << endl; } void showj() { cout << j << endl; } private: int j; }; void Funtest() { D d(50, 60); d.showi(); d.showj(); } int main() { Funtest(); system("pause"); return 0; }
大家分析一下这段代码的结果是什么呢?
从结果中可以看到:
当创建派生类对象时,首先调用基类的构造函数,然后再调用派生类的构造函数,当撤销派生类对象时,则先调用派生类的析构函数,随后调用基类的析构函数。
那么为什么调用析构函数时顺序是相反的呢?
对于析构函数来说,基类的构造函数并不了解子类的结构,所以子类必须先于基类完成清理工作,一步一步向上推进。
4.派生类的构造函数与析构函数
在上面的程序中我们可以看到派生类内有自定义的构造函数,并且带了参数。派生类不能继承基类中的构造函数和析构函数。当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径。
C++中,派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表)
{
//派生类新增数据成员的初始化语句
}
基类构造函数的参数通常来源于派生类构造函数的参数总表
说明:
(1)基类没有缺省的构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表
(2)基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数
(3)基类定义了带有形参表的构造函数,派生类就一定定义构造函数(可以看上面那段程序帮助理解哦)
5.继承体系中的作用域
(1)在继承体系中基类和派生类是两个不同的作用域(这也是为什么基类的私有成员无论被哪种方式继承时都不可以在派生类中直接访问的原因)
(2)派生类和基类中有同名成员时,派生类成员将屏蔽基类对成员的直接访问。在派生类成员函数中,可通过下面这种方式访问:
基类::基类成员
(3)当然,在实际中,我们最好不要使用同名成员来定义对象
6.赋值兼容规则:
(1)子类对象赋值给父类对象:
Base b; Derived d; b=d;
(2)子类对象可初始化父类对象的引用:
B &br = d;
(3)父类指针可以指向子类对象:
B *pb = &d;
(4)若函数形参为父类对象或对象的引用时,调用函数时可以用子类对象作实参
class B { public: int i; //…… }; class D:public B { }; void fun(B &bb) { cout<<bb.i<<endl; } 在调用函数fun()时可以用子类的对象d作为实参。输出子类D的对象d赋给父类的数据成员i的值。
7.友元与继承
友元关系不能继承,即基类友元不能访问派生类私有和保护成员
8.单继承、多继承、菱形继承、
(1)单继承:一个子类只有一个直接父类时称这个继承关系为单继承
(2)多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
多重继承的一般形式为:
class类名l:访问控制类名2,访问控制类名3,…访问控制类名n
(
…//定义派生类自己的成员
};
多继承构造函数的调用顺序与单继承构造函数的调用顺序相同,也是遵循先调用基类的构造函数,再调用对象成员的构造函数,最后调用派生类构造函数的原则。
(3)菱形继承
例如,B为类C1、C2的直接父类,C1、C2又同时是D的父类。
因为菱形继承存在二义性和数据冗余的问题,所以引出了下面的虚拟继承
9.虚拟继承
虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类C1、C2,而类C1、C2都继承自类B,因此在类D中两次出现类B中的变量和函数。为了节省内存空间,可以将C1、C2对B的继承定义为虚拟继承,而B就成了虚拟基类。
虚基类的声明:
class 派生类名:virtual 继承方式 类名
{
// ……
}
说明:关键字virtual与继承方式关键字的先后顺序无关,它只是说明是“虚拟继承”。
下面我们通过例子来深度理解一下虚拟继承是怎么一回事:
#include<iostream> using namespace std; class B { public: B() { a = 1; cout << "B a = " << a << endl; } protected: int a; }; class B1 :public B { public: B1() { a += 10; cout << "B1 a = " << a << endl; } }; class B2 :public B { public: B2() { a += 20; cout << "B2 a = " << a << endl; } }; class D :public B1, public B2 { public: D() { cout << "B1::a = " << B1::a << endl; cout << "B2::a = " << B2::a << endl; } }; void Funtest() { D d; } int main() { Funtest(); system("pause"); return 0; }
程序运行结果如下:
由于在类D中同时存在着类B1、B2的数据成员a,因此在D中的构造函数中输出a时必须加上“类名::”,指出是哪一个数据成员a,否则就会出现二义性。如果将上面的子类D改成下面形式便会出错:
class D :public B1, public B2 { public: D() { cout << "D a = " << a << endl;//错误 } };
下面使用关键字来看这个程序:
#include<iostream> using namespace std; class B { public: B() { a = 1; cout << "B a = " << a << endl; } protected: int a; }; class B1 : virtual public B { public: B1() { a += 10; cout << "B1 a = " << a << endl; } }; class B2 :virtual public B { public: B2() { a += 20; cout << "B2 a = " << a << endl; } }; class D :public B1, public B2 { public: D() { cout << "D a = " << a << endl; //cout << "B1::a = " << B1::a << endl; //cout << "B2::a = " << B2::a << endl; } }; void Funtest() { D d; } int main() { Funtest(); system("pause"); return 0; }
程序结果运行如下:
上述程序中使用了关键字,这样的话,从B1、B2派生出的类D指继承基类B一次,就是说基类B的数据成员a只保留一份。
虚拟继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和空间浪费的问题。
这是我对于继承的理解,如有不足,还请各位多多指教
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。