此份笔记建议在完整阅读郑莉老师、董渊老师、何江舟老师所编写的《C++语言程序设计(第4版)》后食用,风味更佳!
最后,由于本人水平有限,笔记中仍存在错误但还没有被检查出来的地方,欢迎大家批评与指正。
类的继承,是新的类从已有类那里得到已有的特性。
从另一个角度来看这个问题,从已有类产生新类的过程就是类的派生。
由原有的类产生新类时,新类便包含了原有类特征,同时也可以加入自己所特有的新特性。
class 派生类名:继承方式 基类名1,继承方式 基类名2,...,继承方式 基类名n
{
派生类成员声明;
}
(1)单继承:一个派生类只有一个直接基类
(2)多继承:一个派生类,同时有多个基类
派生类成员是指除了从基类继承来的所有成员之外 ,新增加的数据和函数成员。
派生新类的三个步骤:吸收基类成员、改造基类成员、添加新的成员。
吸收基类成员就是一个重用的过程,而对基类成员进行调整、改造以及添加新成员就是原有代码的扩充过程,二者是相辅相成的。
派生类继承了基类的全部数据成员和除了构造、析构函数之外的全部函数成员;但是这些成员的访问属性在派生的过程中是可以调整的。
从基类继承的成员,其访问属性由继承方式控制。
基类的自身成员可以对基类中任何一个其他成员进行访问,但是通过基类的对象 ,就只能访问该类的公有成员。
当类的继承方式为公有继承时,基类的公有成员和保护成员的访问属性在派生类中不变,而基类的私有成员不可直接访问。
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可直接访问。
保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可直接访问。
class B{
...
};
class D:public B{
...
};
B b1,*pb1;
D d1;
//派生类对象可以隐含转换为基类对象,即用派生类对象中从基类继承来的成员,逐个赋值给基类对象的成员
b1 = d1;
//派生类的对象也可以初始化基类对象的引用
B &rb = d1;
//派生类对象的地址也可以隐含转换为指向基类的指针
pb1 = &d1;
#include
using namespace std;
class Base1{
public:
void display() const {
cout << "Base1::display()" << endl;
}
};
class Base2:public Base1{
public:
void display() const {
cout << "Base2::display()" << endl;
}
}
class Derived:public Base2{
public:
void display() const {
cout << "Derived::display()" << endl;
}
}
void fun(Base1 *ptr)
{
ptr->display();
}
int main()
{
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
return 0;
}
//输出结果
Base1::display()
Base1::display()
Base1::display()
派生类的构造函数只负责对派生类新增的成员进行初始化,对所有从基类继承下来的成员,其初始化工作还是由基类的构造函数完成。
同样,对派生类对象的扫尾、清理工作也需要加入新的析构函数。
在构造派生类的对象时,会首先调用基类的构造函数,来初始化它们的数据成员;然后按照构造函数初始化列表中指定的方式初始化派生类新增的成员对象,最后才执行派生类构造函数的函数体。
派生类名::派生类名(参数表):基类名1(基类1初始化参数表),...,基类名n(基类n初始化参数表),成员对象名1(成员对象1初始化参数表),...,成员对象n(成员对象n初始化参数表)
{
派生类构造函数的其他初始化操作;
}
(1)调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
(2)对派生类新增的成员对象初始化,调用顺序按照它们在类中声明的顺序。
(3)执行派生类的构造函数体中的内容。
构造函数初始化列表中基类名、对象名之间的次序无关紧要,它们各自出现的顺序可以是任意的,无论它们的顺序怎样安排,基类构造函数的调用和各个成员对象的初始化顺序都是确定的 。
实例:
#include
using namespace std;
class Base1{
public:
Base1(int i){
cout << "Constructing Base1: " << i << endl;
}
};
class Base2{
public:
Base2(int j){
cout << "Constructing Base2: " << j << endl;
}
};
class Base3{
public:
Base3(){
cout << "Constructing Base3: *" << endl;
}
};
class Drived:public Base1,public Base2,public Base3{
Drived(int a,int b,int c,int d):Base1(a),menber2(d),menber1(c),Base2(b)
{
}
//下面这样的也行
// Drived(int a,int b,int c,int d):Base1(a),Base2(b),menber1(c),menber2(d)
// {
//
// }
private:
Base1 menber1;
Base2 menber2;
Base3 menber3;
}
假设,Drived 类是Base类的派生类,Drived 类的复制构造函数形式如下:
Drived::Drived(const Drived &d):Base(d){
...
}
派生类析构函数的声明方法与没有继承关系的类中析构函数的声明方法完全相同,只要在函数体中负责把派生类新增的非对象成员的清理工做好就够了,系统会自己调用基类及对象成员的析构函数来对基类及对象成员进行清理。
(1)唯一标识问题
(2)可见性问题
我们只能访问一个能够唯一标识的可见成员。如果通过某一个表达式能引用的成员不只一个,称为有二义性。
(1)可见性原则
如果存在两个或多个具有包含关系的作用域,外层声明了一个标识符,而内层没有再次声明同名标识符,那么外层标识符在内层仍然可见
(2)隐藏原则
如果在内层声明了同名标识符,则外层标识符在内层不可见,这时称内层标识符隐藏了外层同名标识符,这种现象称为隐藏规则。
(3)在派生类中讨论
(1)派生类的多个基类拥有同名成员,同时派生类中也有新增同名成员
(2)派生类没有新增同名成员
(3)总结
(1)将一个作用域引入另一个作用域
#include
using namespace std;
class Base1{
public:
int var;
void fun(){
...
}
};
class Base2{
public:
int var;
void fun(float i){
...
}
}
class Drived:public Base1,public Base2{
public:
void fun(int j){
...
}
}
int main()
{
Drived d;
d.var = 1; //编译报错,有二义性
d.fun(); //编译报错,有二义性
}
这时,我们想直接通过派生类 Drived 的基类 Base1 的数据成员 var 和函数成员 fun( ),怎么做呢?使用 using。将 Derived 类的定义修改如下:
class Drived:public Base1,public Base2{
public:
using Base1::var;
void fun(int j){
...
}
}
那么我们就可以在主程序中这样直接访问 Base1 的数据成员 var了
int main()
{
Drived d;
d.var = 1;
}
(2)实现“派生类同名函数”隐藏作用,实现基类同名函数与派生类同名函数的重载
将上面的 Derived 类的定义再修改如下:
class Drived:public Base1,public Base2{
public:
using Base1::var;
using Base1::fun();
void fun(int j){
...
}
}
这样,在主函数就实现了函数的重载
int main()
{
Drived d;
d.var = 1;
d.fun();
d.fun(10);
}
如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类来进行限定。
当某类的部分或全部直接基类是从另一个共同基类派生而来时,在这些直接基类中从上一级共同基类继承来的成员就拥有相同的名称。在派生类的对象中,这些同名数据成员在内存中同时拥有多个副本,同一个函数名会有多个映射。
class 派生类名:vitrual 继承方式 基类名
#include
using namespace std;
class Base0{
public:
int var0;
void fun0(){
cout << "Member of Base0" << endl;
}
};
class Base1:virtual public Base0{
public:
int var1;
};
class Base2:virtual public Base0{
public:
int var2;
};
class Drived:public Base1,public Base2{
public:
int var;
void fun(){
cout << "Member of Drived" << endl;
}
};
int main()
{
Drived d;
d.var=2;
d.fun();
return 0;
}
//输出结果
Member of Base0
#include
using namespace std;
class Base0{
public:
Base0(int var):var0(var){}
int var0;
void fun0(){
cout << "Member of Base0" << endl;
}
};
class Base1:virtual public Base0{
public:
Base1(int var):Base0(var){}
int var1;
};
class Base2:virtual public Base0{
public:
Base2(int var):Base0(var){}
int var2;
};
class Drived:public Base1,public Base2{
public:
Drived(int var):Base0(var),Base1(var),Base2(var){}
int var;
void fun(){
cout << "Member of Drived" << endl;
}
};
int main()
{
Drived d(1);
d.var=2;
d.fun();
return 0;
}
(1)如果该类有直接或间接的虚基类 ,则先执行虚基类的构造函数。
(2)如果该类有其他基类 ,则按照它们在继承声明列表中出现的次序,分别执行它们的构造函数,但构造过程中,不再执行它们的虚基类的构造函数。
(3)按照在类定义中出现 的顺序,对派生类中新增的成员对象进行初始化。 对于类类型的成员对象,如果出现在构造函数初始化列表中,则以其中指定的参数执行构造函数,如未出现,则执行默认构造函数;对于基本数据类型的成员对象,如果出现在构造函数的初始化列表中,则使用其中指定的值为其赋初值,否则什么也不做。
(4)执行构造函数的函数体。