第八章 继承
继承的概念:在已有类的基础上创造新类的过程.
如果一个B类继承A类,或称从类A派生类B
类 A 称为基类(父类),类B称为派生类(子类)
类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
* 不论种方式继承基类,派生类都不能直接使用基类的私有成员
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
* 不论种方式继承基类,派生类都不能直接使用基类的私有成员
派生类的生成过程经历了三个步骤:
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
在C++的继承机制中,派生类吸收基类中除构造函数和析构函数之外的全部成员。
●改造基类成员
通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
●添加派生类新成员
●添加派生类新成员
仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现。
基类对象:a , b
子类对象:a , b ,c
总之:子类对象空间总是不小于基类对象
class Father{ int a,b;public: // 成员函数};
class Son:public Father
{
int c;public: // 成员函数
};
#include <iostream> using namespace std; class A{ public: int a; int b; private: int c; protected: int d; }; class B: public A { int c; }; main( ){ cout << ” size of A is” << sizeof(A); cout << ” size of B is” << sizeof(B); }
基类 | 派生类 |
private 成员 | ~ |
protected 成员 | protected 成员 |
public 成员 | public 成员 |
~ | private 成员 |
~ | protected 成员 |
~ |
public 成员
|
#include<iostream> using namespace std ; class A { public : void get_XY() { cout << "Enter two numbers of x, y : " ; cin >> x >> y ; } void put_XY() { cout << "x = "<< x << ", y = " << y << '\n' ; } protected: int x, y ; }; class B : public A { public : int get_S() { return s ; }; void make_S() { s = x * y ; }; // 使用基类数据成员x,y protected: int s; }; class C : public B { public : void get_H() { cout << "Enter a number of h : " ; cin >> h ; } int get_V() { return v ; } void make_V() { make_S(); v = get_S() * h ; } // 使用基类成员函数 protected: int h, v; };
重名成员:
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
题型
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
题型
class base { public : int a , b ; } ; class derived : public base { public : int b , c ; } ; void f () { derived d ; d . a = 1 ; d . base :: b = 2 ; d . b = 3 ; d . c = 4 ; };派生类中访问静态成员
*基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
* 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
* 根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
*派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问 对象名 . 成员
派生类构造函数和析构函数的定义规则
类名 :: 成员
或通过对象访问 对象名 . 成员
派生类构造函数和析构函数的定义规则
1.基类的构造函数和析构函数不能被继承
2.如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数
3.如果基类无无参的构造函数,派生类必须定义构造函数
4.如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造
5.派生类是否定义析构函数与所属的基类无关
派生类的构造函数的定义
派生类的数据成员既包括基类的数据成员,也包括派生类新增数据成员。
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
派生类析构函数
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
派生类析构函数
(1)当派生类中不含对象成员时
a.在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
b.在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
a.在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
b.在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
多继承
a.在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
b.在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数。
(2)当派生类中含有对象成员时
a.在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
b.在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
多继承
一个类有多个直接基类的继承关系称为多继承
多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
多继承的派生类构造和访问
*多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
*执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
*一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承的构造函数
多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
多继承的派生类构造和访问
*多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
*执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
*一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承的构造函数
派生类名(参数总表):基类名1(参数表1),基类名2(参数表2),…,基类名n(参数表n)
{
// 派生类新增成员的初始化语句
}
多继承方式下构造函数的执行顺序:
{
// 派生类新增成员的初始化语句
}
多继承方式下构造函数的执行顺序:
●先执行所有基类的构造函数
●再执行对象成员的构造函数
●最后执行派生类的构造函数
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序
●再执行对象成员的构造函数
●最后执行派生类的构造函数
处于同一层次的各基类构造函数的执行顺序取决于定义派生类时所指定的基类顺序
与派生类构造函数中所定义的成员初始化列表顺序没有关系。
内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致
内嵌对象成员的构造函数执行顺序与对象在派生类中声明的顺序一致
多继承的析构函数
析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
a.功能是在派生类中对新增的有关成员进行必要的清理工作。
b.析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
a.功能是在派生类中对新增的有关成员进行必要的清理工作。
b.析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
赋值兼容规则
赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
赋值兼容的可行性
通过公有继承
派生类得到了除了构造、析构函数以外的所有成员
且这些成员的访问控制属性也和基类完全相同。
这样,它便具备了基类的所有功能。
通过公有继承
派生类得到了除了构造、析构函数以外的所有成员
且这些成员的访问控制属性也和基类完全相同。
这样,它便具备了基类的所有功能。
利用赋值兼容规则
a 派生类的对象可以赋给基类对象(强制类型转换)
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
a 派生类的对象可以赋给基类对象(强制类型转换)
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
例如,下面声明的两个类:
class Base{
…
};
class Derived:public Base{
…
};
class Base{
…
};
class Derived:public Base{
…
};
根据赋值兼容规则, 以下几种情况是合法的:
(1) 可以用派生类对象给基类对象赋值。例如:
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
(2) 可以用派生类对象来初始化基类的引用。例如:
Derived d;
Base &br=d;
(3) 可以把派生类对象的地址赋值给指向基类的指针。例如:
Derived d;
Base *bptr=&d;
这种形式的转换,是在实际应用程序中最常见到的。
(4) 可以把指向派生类对象的指针赋值给指向基类对象的指针。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
赋值兼容规则的特点
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
Base b;
Derived d;
b=d;
这样赋值的效果是,对象b中所有数据成员都将具有对象d中对应数据成员的值。
(2) 可以用派生类对象来初始化基类的引用。例如:
Derived d;
Base &br=d;
(3) 可以把派生类对象的地址赋值给指向基类的指针。例如:
Derived d;
Base *bptr=&d;
这种形式的转换,是在实际应用程序中最常见到的。
(4) 可以把指向派生类对象的指针赋值给指向基类对象的指针。例如:
Derived *dptr,obj; dptr=&obj;
Base *bptr=dptr;
赋值兼容规则的特点
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
赋值兼容应注意的问题
(1)声明为指向基类的指针可以指向它的公有派生类的对象,但不允许指向它的私有派生类的对象。例如:
class B {…};
class D:private B {…};
B b1,*pbl;D d1;
pb1=&b1; //合法,基类B的对象b1和B类的指针
pb1=&d1; //非法,不允许将基类指针指向它的私有派生类对象
(2)允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
(3) 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。
个人心得体会:
class B {…};
class D:private B {…};
B b1,*pbl;D d1;
pb1=&b1; //合法,基类B的对象b1和B类的指针
pb1=&d1; //非法,不允许将基类指针指向它的私有派生类对象
(2)允许将一个声明为指向基类的指针指向其公有派生类对象,但是不能将一个声明为指向派生类对象的指针指向其基类的一个对象。
(3) 声明为指向基类对象的指针,当其指向公有派生类对象时,只能用它来直接访问派生类中从基类继承来的成员,而不能直接访问公有派生类的定义的成员。
个人心得体会:
学习完继承,我的第一感觉就是终于可以减少代码的行数了,还可以将代码简单化,比如说写一个ATM或者图书管理系统,每个类里面都需要用到时间类,那么我们不妨将时间类作为基类,其他类作为子类,那么就可以直接行使时间类里面的功能了,又比如图书管理系统中的记录类,每个数据类都需要调用记录类,如果把记录类作为子类,就可以在数据类里面直接用记录类(不包括基类的私有成员),减少多次写记录类的繁琐,从而使代码简单化。我们现在写的代码基本上都是几百行,如果我们用继承写的化,就会大大地减少代码的量,所以我感觉继承对我们写代码是很有帮助的。
我们学习写代码已经有一年了,我们应该懂得如何去优化代码,去用很少的行数实现更完整的功能,我相信如果我们在写代码的时候可以经常记得我们的目的是去优化代码,是以很短的代码去实现代码的更大的功能,那么我们肯定是一位合格的计算机专业的学生,以后也将是一位优秀的人。