C++——继承与派生

2021/5/13 14:57:30

本文主要是介绍C++——继承与派生,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

C++-继承与派生学习笔记

整理者:严超

1.基础知识

1.1什么是继承与派生?

在 C++ 中,当定义一个新的类 B 时,如果发现类 B 拥有某个已写好的类 A 的全部特点,此外还有类 A 没有的特点,那么就不必从头重写类 B,而是可以把类 A 作为一个“基类”(也称“父类”),把类 B 写为基类 A 的一个“派生类”(也称“子类”)。这样,就可以说从类 A “派生”出了类 B,也可以说类 B “继承”了类 A。简单来说:继承就是保留利用原有的类的功能;派生就是添加新的成员和功能。

1.2 派生类的定义

派生类的声明:

class 派生类名:[继承方式] 基类名

{

    派生类成员声明;

};

如果一个派生类同时有多个基类,称为多重继承;如果派生类只有一个基类,称为单继承。

 

1.3继承方式

继承方式规定了如何访问基类继承的成员。不同的继承方式的影响主要体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限。

继承方式有public、 private,、protected。
1.公有继承(public):基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
2.私有继承(private):基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类的私有成员。
3.保护继承(protected):基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类的私有成员。
不同继承方式下父类成员的访问级别如下:

2.对基本知识的应用

2.1公用继承

如果采用公用继承,基类中的访问属性在派生类中保持不变,即:

基类中的私有属性——派生类中私有属性

基类中的共用属性——派生类中共用属性

基类中的受保护属性——派生类中受保护属性

实例1:

在这个例子中,派生类Student1中的私有成员为

int num;

char name;

char sex;

int age;

共有成员为:

myshow();

myshow1();

2.2私有继承

如果采用私有继承,基类中的访问属性在派生类中发生如下变化,即:

基类中的私有属性——派生类中不可访问

基类中的共用属性——派生类中私有属性

基类中的受保护属性——派生类中私有属性

实例2:

在这个例子中,派生类Student1中的私有成员为:

int age;//子类新增

myshow();//继承自父类

共有成员为:

myshow1(); //子类新增

子类不可访问

int num;

char name;

char sex;

2.2保护继承

受保护的成员不能被类外访问,这一点类似私有成员,可以认为保护尘缘对类的用户来说是私有的。但是有一点与私有成员不同:受保护成员可以被派生类的成员函数引用

如果采用保护继承,基类中的访问属性在派生类中发生如下变化,即:

基类中的私有属性——派生类中不可访问

基类中的共用属性——派生类中受保护属性

基类中的受保护属性——派生类中受保护属性

实例3:

其实这里这个例子没有什么意义,一个类数据私有,函数受保护就没有与外界的接口了,在这里仅仅作为例子。

在这个例子中,派生类Student1中的私有成员为:int age;

共有成员为:myshow1();

受保护成员为:myshow();

子类不可访问:

int num;

char name;

char sex;

 

 

3.知识的扩展应用

3.1多继承

派生类的定义:

class 派生类名:继承方式1 基类名1, 继承方式2 基类名2, 。。。。。  {

成员声明;

};

实例4:

3.2菱形继承

类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B-->D 这条路径,还是来自 A-->C-->D 这条路径。

实例5:

这段代码实现了上图所示的菱形继承,但是程序中试图直接访问成员变量 m_a,结果发生错误,因为类 B 和类 C 中都有成员变量 m_a,编译器不知道选用哪一个,所以产生了歧义。

为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:

void seta(int a){ B::m_a = a; }

同理,这样表示使用 B 类的 m_a。当然也可以使用 C 类的:

void seta(int a){ C::m_a = a; }

3.3虚继承

了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
即在继承方式前面加上virtual关键字就是虚继承。

3.3虚继承

了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
即在继承方式前面加上virtual关键字就是虚继承。

实例6:

这段代码使用虚继承重新实现了菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

3.4派生类的构造函数

派生类新增成员:派生类定义构造函数初始化;

继承来的成员:自动调用基类构造函数进行初始化;

派生类的构造函数需要给基类的构造函数传递参数。

派生类的构造函数语法如下:

派生类名::派生类名(参数总表)

:基类名(参数表),本类成员初始化列表

{

     //其他初始化;

}

 

子类构造函数必须对继承来的成员进行初始化,可以直接通过初始化列表或赋值方式进行,也可以调用父类构造函数进行初始化。
实例7:

运行结果为:小明的年龄是14,成绩是88。

People(name, age)就是调用基类的构造函数,并将 name 和 age 作为实参传递给它,m_score(score)是派生类的参数初始化表,它们之间以逗号隔开。其中People(name, age)和m_score(score)位置可互换。

另外,函数头部是对基类构造函数的调用,而不是声明,所以括号里的参数是实参,它们不但可以是派生类构造函数参数列表中的参数,还可以是局部变量、常量等,例如:

Student::Student(char *name, int age, float score): People("小明", 14), m_score(score){ }

3.5基类构造函数调用规则

通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。换句话说,定义派生类构造函数时最好指明基类构造函数;如果不指明,就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,那么编译失败。

实例8:

运行结果:xxx的年龄是0,成绩是0。小明的年龄是14,成绩是88。

创建对象 stu1 时,执行派生类的构造函Student::Student(),它并没有指明要调用基类的哪一个构造函数,从运行结果可以很明显地看出来,系统默认调用了不带参数的构造函数,也就是People::People()。

创建对象 stu2 时,执行派生类的构造Student::Student(char *name, int age, float score),它指明了基类的构造函数。

如果将基类 People 中不带参数的构造函数删除,那么会发生编译错误,因为创建对象 stu1 时需要调用 People 类的默认构造函数, 而 People 类中已经显式定义了构造函数,编译器不会再生成默认的构造函数。

3.6虚继承时的构造函数

在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

 

实例9:

运行结果:

m_a=1, m_b=2

m_a=3, m_c=4

m_a=5, m_b=6, m_c=7, m_d=8

在最终派生类 D 的构造函数中,除了调用 B 和 C 的构造函数,还调用了 A 的构造函数,这说明 D 不但要负责初始化直接基类 B 和 C,还要负责初始化间接基类 A。在以往的普通继承中,派生类的构造函数只负责初始化它的直接基类,再由直接基类的构造函数初始化间接基类,用户尝试调用间接基类的构造函数将导致错误。

现在采用了虚继承,虚基类 A 在最终派生类 D 中只保留了一份成员变量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在调用 A 的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化 m_a。

为了避免出现这种矛盾的情况,C++ 规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。在最终派生类 D 的构造函数中,调用 B 的构造函数时试图将 m_a 初始化为 9,调用 C 的构造函数时试图将 m_a 初始化为 10,但是输出结果有力地证明了这些都是无效的,m_a 最终被初始化为 5,这正是在 D 中直接调用 A 的构造函数的结果。

 

3.7派生类的析构函数

和构造函数类似,析构函数也不能被继承。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。

另外析构函数的执行顺序和构造函数的执行顺序也刚好相反:

创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。

而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数

实例10:

运行结果:
A constructor
B constructor
C constructor
C destructor
B destructor
A destructor

4.注意事项

1.使用class关键字定义派生类时,默认继承方式为private。

使用struct关键字定义派生类时,默认继承方式为public。

C++工程项目中,通常只使用public继承方式。

2.多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。

5.参考网址:

1.C++语言学习——继承与派生

https://blog.51cto.com/u_9291927/2147902#h16

2.C++继承和派生简明教程

http://c.biancheng.net/view/2264.html

 



这篇关于C++——继承与派生的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程