C++ 基础知识点 八 第9章 继承性与派生类
2015-10-05 12:26
225 查看
第9章 继承性与派生类
【内容提要】
继承性与派生类的基本概念
派生类的声明和访问权限
派生类构造函数和析构函数的定义及使用
多重继承的声明、构造函数和析构函数的定义及使用
虚基类的作用、定义和使用
【重点与难点】
9.1 继承性与派生类的基本概念
继承是软件复用的一种形式,它是从现有类的基础上建立新类,新类继承了现有类的属性和方法,并且还拥有其特有的属性和方法。继承的过程称为派生,新建的类为派生类(或子类),原有的类称为基类(或父类)。继承可分为:单继承和多重继承。若派生类只有一个基类则称为单继承;若派生类有多个基类则称为多重继承。
9.2 派生类的声明与访问权限
9.2.1 派生类的声明
单继承中派生类的定义格式为:
class <派生类名>:<派生方式><基类名>
{
派生类新定义的成员声明;
};
说明:
①派生方式关键字为private、public和protected,分别表示私有继承、公有继承和保护继承。缺省的继承方式是私有继承。继承方式规定了派生类成员和类外对象访问基类成员的权限。
②派生类新定义的成员是指继承过程中新增加的数据成员和成员函数。通过在派生类中新增加成员实现功能的扩充。
9.2.2 派生类的访问权限
公有继承(public)
①继承后基类的公有成员、私有成员、保护成员在派生类中访问权限保持不变。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外只能通过派生类的对象访问基类的公有成员,无法通过派生类对象直接访问基类的私有成员和保护成员。
私有继承(private)
①继承后基类的所有成员在派生类中均为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
保护继承(protected)
①继承后基类的公有成员和保护成员在派生类中均为保护成员,基类的私有成员在派生类中仍为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
9.3 派生类构造函数和析构函数的定义及使用
在派生过程中,构造函数和析构函数不被继承。在创建一个派生类对象时,分别调用基类和派生类的构造函数,完成各自成员的初始化工作。当撤销一个派生类对象时,分别调用基类和派生类的析构函数完成善后处理工作。
在C++中对构造函数与析构函数的调用顺序有如下规定:
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。
②对于析构函数,先执行派生类的,再执行对象成员的,最后执行基类的。
派生类构造函数定义格式为:
<派生类名>::<派生类名>(参数总表):基类名(参数表),对象成员名1(参数表1),…,对象成员名n(参数表n)
{
//派生类新增成员的初始化语句
}
说明:
①派生类的构造函数名与派生类名相同。
②参数总表列出初始化基类成员数据、新增对象成员数据和派生类新增数据成员所需要的全部参数。
③冒号后列出需要使用参数进行初始化的基类的名字和对象成员的名字及各自的参数表,之间用逗号分开。对于使用缺省构造函数的基类或对象成员,可以不给出类名或对象名以及参数表。
④如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,保证在基类进行初始化时能获得所需的数据。
⑤如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责其间接基类的构造。
9.4 多重继承的声明、构造函数和析构函数的定义及使用
9.4.1 多重继承的声明
多重继承声明的格式为:
class <派生类名>:<派生方式1><基类名1>,…,<派生方式n><基类名n>
{
派生类成员声明;
};
说明:这里的派生方式以及访问权限定义与单继承中规则相同。
9.4.2 多重继承的构造函数与析构函数
多重继承中对构造函数和析构函数的调用顺序的规定:
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。多个基类构造函数的执行次序严格按照声明时从左到右的顺序来执行的,与定义派生类构造函数时指定的初始化表中的次序无关。多个对象成员所在类的构造函数的执行次序按照对象成员定义的顺序来执行。
②析构函数的调用顺序正好与构造函数的调用顺序相反。
定义多重继承构造函数的格式为:
<派生类名>::<派生类名>(参数总表):基类名1(参数表1),…,基类名n(参数表n),对象成员名1(对象成员参数表1),…,对象成员名m(对象成员参数表m)
{
//派生类新增成员的初始化语句
}
说明:单继承中构造函数定义的说明在多重继承构造函数中均适用。
9.5 虚基类的作用、定义和使用
9.5.1 多重继承中的二义性问题
问题的产生:
①当派生类继承的多个基类中存在同名成员时,派生类中就会出现来自不同基类的同名成员,就出现了标识符不唯一或二义性的情况,这在程序中是不允许的。
②当一个类从多个基类派生而来,这多个基类又有共同的基类,则在派生类中访问这个共同基类中的成员时会产生二义性。
解决办法:
对于第一种情况:
①使用作用域运算符“::”
②使用同名覆盖的原则
③使用虚函数(在下一章中介绍)
对于第二种情况:
使用虚基类
9.5.2 虚基类
如上图所示,对于非虚基类的情况,derived有两个基类base1和base2,这两个基类又有共同基类base,derived类中就有基类base的两个不同的拷贝。在derived要访问base
类中的成员时,就会产生二义性问题。虚基类就是为了解决这个问题而引入的。如上图所示,对于虚基类的情况,derived中的公共基类base就只有一个拷贝而不会出现二义性问题。
9.5.3 虚基类的定义
虚基类的声明是在派生类的声明过程中进行的,格式为:
class<派生类名>:virtual <派生方式><基类名>
说明:
①虚基类关键字的作用范围和派生方式与一般派生类的一样,只对紧跟其后的基类起作用。
②声明了虚基类以后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存拷贝。
9.5.4 虚基类的构造函数和初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的执行顺序不同:
①虚基类的构造函数在非虚基类的构造函数之前执行。
②若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的先后次序执行。
③若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数,再执行派生类的构造函数。
【典型例题】
例题1.请将下列类定义补充完整。
#include<iostream.h>
class base{
public:
void fun( )
{
cout<<"base::fun"<<endl;
}
};
class derived: public base {
public:
void fun( ) {
___________________//显式调用基类的fun函数
cout<<"derived::fun"<<endl;
}
};
解答:
本题考查在继承过程中,如果基类与子类有同名成员时,如何完成各自的引用。如果基类与子类有同名成员时,子类的同名成员会屏蔽基类的同名成员,所以要在自类中引用该成员则需要使用作用域区分符确定要调用谁的成员。因此,本题答案为:base::fun();
例题2.有如下程序:
#include<iostream.h>
class base
{
public:
void show(){cout<<"base:publicmember"<<endl;}
protected:
voidshow1(){cout<<"base:protected member"<<endl;}
private:
voidshow2(){cout<<"base:private member"<<endl;}
};
classderived:protected base
{
public:
void fn()
{
show1();//①
show2();//②
}
};
void main()
{
derived a;
a.fn();
a.show(); //③
a.show1(); //④
show(); //⑤
}
有语法错误的语句是( )。
(a)①②③④ (b)②③④⑤ (c)①③④⑤ (d)①②④⑤
解答:
本题主要考查各种派生中派生类的访问权限问题。这里derived采用保护继承的方式继承了base类。对于保护继承其访问权限有如下规则:
①继承后基类的公有成员和保护成员在派生类中均为保护成员,基类的私有成员在派生类中仍为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
对于语句①②是在派生类内部访问基类的保护成员函数和私有成员函数,无论哪种继承方式,基类的私有成员都不能被基类定义以外的任何地方直接访问,所以语句②是错误用法;而在派生类定义的内部访问基类的公有和保护成员是允许的,所以语句①正确。对于语句③④是在类定义以外通过子类的对象访问基类的公有成员函数和保护成员函数,因为保护继承后基类的公有和保护成员在子类中均为保护成员,所以在类外通过对象不能对其直接进行访问,所以③④语句是错误的用法。语句⑤执行后找不到相应的函数定义,因此是错误的。答案为:b
例题3.下列说法正确的是( )。
(a) 基类的构造函数和析构函数不能被派生类继承。
(b) 在派生类中用户必须自定义派生类构造函数。
(c) 析构函数与构造函数被调用的顺序是一致的。
(d) 在多重继承中,多个基类的构造函数的调用顺序由定义派生类构造函数时指定的初始化表中的次序决定。
解答:
如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,保证在基类进行初始化时能获得所需的数据。析构函数与构造函数被调用的顺序正好相反。在多重继承中,多个基类的构造函数的调用顺序由在定义派生类时基类的声明顺序决定。答案为:a
例题4.有如下程序:
#include<iostream.h>
class A {
public:
A( ) { cout << "A"; }
};
class B {
public:
B( ) { cout << "B"; }
};
class C :public A {
B b;
public:
C( ) { cout << "C"; }
};
int main( )
{
C obj;
return 0;
}
执行后的输出结果是()。
(a)CBA (b)BAC (c)ACB (d)ABC
解答:
本题主要考查继承中构造函数的调用顺序。对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。本程序中在主函数里创建C类对象obj,则首先执行类C的基类A的构造函数输出A,然后调用其对象成员b所在的B类的构造函数输出B,最后执行类C自己的构造函数输出C。答案为:d
【内容提要】
继承性与派生类的基本概念
派生类的声明和访问权限
派生类构造函数和析构函数的定义及使用
多重继承的声明、构造函数和析构函数的定义及使用
虚基类的作用、定义和使用
【重点与难点】
9.1 继承性与派生类的基本概念
继承是软件复用的一种形式,它是从现有类的基础上建立新类,新类继承了现有类的属性和方法,并且还拥有其特有的属性和方法。继承的过程称为派生,新建的类为派生类(或子类),原有的类称为基类(或父类)。继承可分为:单继承和多重继承。若派生类只有一个基类则称为单继承;若派生类有多个基类则称为多重继承。
9.2 派生类的声明与访问权限
9.2.1 派生类的声明
单继承中派生类的定义格式为:
class <派生类名>:<派生方式><基类名>
{
派生类新定义的成员声明;
};
说明:
①派生方式关键字为private、public和protected,分别表示私有继承、公有继承和保护继承。缺省的继承方式是私有继承。继承方式规定了派生类成员和类外对象访问基类成员的权限。
②派生类新定义的成员是指继承过程中新增加的数据成员和成员函数。通过在派生类中新增加成员实现功能的扩充。
9.2.2 派生类的访问权限
公有继承(public)
①继承后基类的公有成员、私有成员、保护成员在派生类中访问权限保持不变。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外只能通过派生类的对象访问基类的公有成员,无法通过派生类对象直接访问基类的私有成员和保护成员。
私有继承(private)
①继承后基类的所有成员在派生类中均为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
保护继承(protected)
①继承后基类的公有成员和保护成员在派生类中均为保护成员,基类的私有成员在派生类中仍为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
9.3 派生类构造函数和析构函数的定义及使用
在派生过程中,构造函数和析构函数不被继承。在创建一个派生类对象时,分别调用基类和派生类的构造函数,完成各自成员的初始化工作。当撤销一个派生类对象时,分别调用基类和派生类的析构函数完成善后处理工作。
在C++中对构造函数与析构函数的调用顺序有如下规定:
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。
②对于析构函数,先执行派生类的,再执行对象成员的,最后执行基类的。
派生类构造函数定义格式为:
<派生类名>::<派生类名>(参数总表):基类名(参数表),对象成员名1(参数表1),…,对象成员名n(参数表n)
{
//派生类新增成员的初始化语句
}
说明:
①派生类的构造函数名与派生类名相同。
②参数总表列出初始化基类成员数据、新增对象成员数据和派生类新增数据成员所需要的全部参数。
③冒号后列出需要使用参数进行初始化的基类的名字和对象成员的名字及各自的参数表,之间用逗号分开。对于使用缺省构造函数的基类或对象成员,可以不给出类名或对象名以及参数表。
④如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,保证在基类进行初始化时能获得所需的数据。
⑤如果派生类的基类也是派生类,则每个派生类只需负责其直接基类的构造,不负责其间接基类的构造。
9.4 多重继承的声明、构造函数和析构函数的定义及使用
9.4.1 多重继承的声明
多重继承声明的格式为:
class <派生类名>:<派生方式1><基类名1>,…,<派生方式n><基类名n>
{
派生类成员声明;
};
说明:这里的派生方式以及访问权限定义与单继承中规则相同。
9.4.2 多重继承的构造函数与析构函数
多重继承中对构造函数和析构函数的调用顺序的规定:
①对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。多个基类构造函数的执行次序严格按照声明时从左到右的顺序来执行的,与定义派生类构造函数时指定的初始化表中的次序无关。多个对象成员所在类的构造函数的执行次序按照对象成员定义的顺序来执行。
②析构函数的调用顺序正好与构造函数的调用顺序相反。
定义多重继承构造函数的格式为:
<派生类名>::<派生类名>(参数总表):基类名1(参数表1),…,基类名n(参数表n),对象成员名1(对象成员参数表1),…,对象成员名m(对象成员参数表m)
{
//派生类新增成员的初始化语句
}
说明:单继承中构造函数定义的说明在多重继承构造函数中均适用。
9.5 虚基类的作用、定义和使用
9.5.1 多重继承中的二义性问题
问题的产生:
①当派生类继承的多个基类中存在同名成员时,派生类中就会出现来自不同基类的同名成员,就出现了标识符不唯一或二义性的情况,这在程序中是不允许的。
②当一个类从多个基类派生而来,这多个基类又有共同的基类,则在派生类中访问这个共同基类中的成员时会产生二义性。
解决办法:
对于第一种情况:
①使用作用域运算符“::”
②使用同名覆盖的原则
③使用虚函数(在下一章中介绍)
对于第二种情况:
使用虚基类
9.5.2 虚基类
如上图所示,对于非虚基类的情况,derived有两个基类base1和base2,这两个基类又有共同基类base,derived类中就有基类base的两个不同的拷贝。在derived要访问base
类中的成员时,就会产生二义性问题。虚基类就是为了解决这个问题而引入的。如上图所示,对于虚基类的情况,derived中的公共基类base就只有一个拷贝而不会出现二义性问题。
9.5.3 虚基类的定义
虚基类的声明是在派生类的声明过程中进行的,格式为:
class<派生类名>:virtual <派生方式><基类名>
说明:
①虚基类关键字的作用范围和派生方式与一般派生类的一样,只对紧跟其后的基类起作用。
②声明了虚基类以后,虚基类的成员在进一步派生过程中和派生类一起维护同一个内存拷贝。
9.5.4 虚基类的构造函数和初始化
虚基类的初始化与一般的多继承的初始化在语法上是一样的,但构造函数的执行顺序不同:
①虚基类的构造函数在非虚基类的构造函数之前执行。
②若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的先后次序执行。
③若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数,再执行派生类的构造函数。
【典型例题】
例题1.请将下列类定义补充完整。
#include<iostream.h>
class base{
public:
void fun( )
{
cout<<"base::fun"<<endl;
}
};
class derived: public base {
public:
void fun( ) {
___________________//显式调用基类的fun函数
cout<<"derived::fun"<<endl;
}
};
解答:
本题考查在继承过程中,如果基类与子类有同名成员时,如何完成各自的引用。如果基类与子类有同名成员时,子类的同名成员会屏蔽基类的同名成员,所以要在自类中引用该成员则需要使用作用域区分符确定要调用谁的成员。因此,本题答案为:base::fun();
例题2.有如下程序:
#include<iostream.h>
class base
{
public:
void show(){cout<<"base:publicmember"<<endl;}
protected:
voidshow1(){cout<<"base:protected member"<<endl;}
private:
voidshow2(){cout<<"base:private member"<<endl;}
};
classderived:protected base
{
public:
void fn()
{
show1();//①
show2();//②
}
};
void main()
{
derived a;
a.fn();
a.show(); //③
a.show1(); //④
show(); //⑤
}
有语法错误的语句是( )。
(a)①②③④ (b)②③④⑤ (c)①③④⑤ (d)①②④⑤
解答:
本题主要考查各种派生中派生类的访问权限问题。这里derived采用保护继承的方式继承了base类。对于保护继承其访问权限有如下规则:
①继承后基类的公有成员和保护成员在派生类中均为保护成员,基类的私有成员在派生类中仍为私有成员。
②在派生类中可以直接访问基类的公有成员和保护成员,但对于私有成员的访问只能通过基类的非私有成员函数间接访问。
③在基类和派生类定义以外对基类的所有成员均无法直接访问也无法通过派生类的对象间接访问。
对于语句①②是在派生类内部访问基类的保护成员函数和私有成员函数,无论哪种继承方式,基类的私有成员都不能被基类定义以外的任何地方直接访问,所以语句②是错误用法;而在派生类定义的内部访问基类的公有和保护成员是允许的,所以语句①正确。对于语句③④是在类定义以外通过子类的对象访问基类的公有成员函数和保护成员函数,因为保护继承后基类的公有和保护成员在子类中均为保护成员,所以在类外通过对象不能对其直接进行访问,所以③④语句是错误的用法。语句⑤执行后找不到相应的函数定义,因此是错误的。答案为:b
例题3.下列说法正确的是( )。
(a) 基类的构造函数和析构函数不能被派生类继承。
(b) 在派生类中用户必须自定义派生类构造函数。
(c) 析构函数与构造函数被调用的顺序是一致的。
(d) 在多重继承中,多个基类的构造函数的调用顺序由定义派生类构造函数时指定的初始化表中的次序决定。
解答:
如果基类没有定义构造函数,派生类也可以不定义构造函数,全都采用缺省的构造函数。如果基类定义了带有形参表的构造函数,派生类就必须定义构造函数,保证在基类进行初始化时能获得所需的数据。析构函数与构造函数被调用的顺序正好相反。在多重继承中,多个基类的构造函数的调用顺序由在定义派生类时基类的声明顺序决定。答案为:a
例题4.有如下程序:
#include<iostream.h>
class A {
public:
A( ) { cout << "A"; }
};
class B {
public:
B( ) { cout << "B"; }
};
class C :public A {
B b;
public:
C( ) { cout << "C"; }
};
int main( )
{
C obj;
return 0;
}
执行后的输出结果是()。
(a)CBA (b)BAC (c)ACB (d)ABC
解答:
本题主要考查继承中构造函数的调用顺序。对于构造函数,先执行基类的,再执行对象成员的,最后执行派生类的。本程序中在主函数里创建C类对象obj,则首先执行类C的基类A的构造函数输出A,然后调用其对象成员b所在的B类的构造函数输出B,最后执行类C自己的构造函数输出C。答案为:d
相关文章推荐
- C++刷称号——2707: 素数与要素
- C++ 基础知识点 七 第8章 类和对象的创建
- C++ 基础知识点 五 第6章 指针、引用和动态空间管理
- Leetcode NO.274 H-Index
- kmp算法实现(C++版)
- C/C++中的const--常量指针与指针常量
- C/C++:用数组构造队列。
- Leetcode NO.53 Maximum Subarray
- C/C++ const变量的修改
- 面试复习(C++)之直接选择排序
- C++ 大数版的加减乘除代码实现总结
- C/C++ 注释规范
- 程序设计基石与实践系列之能让你成为Top程序员的十个C语言资源
- VC++ 中简单操作MP3音乐的方法,小结
- C语言:查找算法
- C++中的也能使用正则表达式
- C++可变参数模板的小知识
- C/C++ 拾遗
- c++组合模式和原型模式一起使用
- 带你玩转Visual Studio——带你发布自己的工程库