880 likes | 966 Views
第 9 章 继 承 性. 9.1 基类和派生类 9.2 单 继 承 9.3 多 继 承 9.4 虚 基 类. 继承性是面向对象程序设计的一种重要功能,是实现代码复用的一种形式。继承可以使程序设计人员在一个已存在类的基础上很快建立一个新的类,而不必从零开始设计新类。新设计类能够具有原有类的属性和方法,并且为了使新类具有自己独特的功能,新类还要添加新的属性和方法。 当一个类被其他的类继承时,被继承的类称为基类,又称为父类、超类。继承其他类属性和方法的类称为派生类,又称为子类、继承类。. 9.1 基类和派生类. 9.1.1 派生类的定义
E N D
第9章 继 承 性 • 9.1 基类和派生类 • 9.2 单 继 承 • 9.3 多 继 承 • 9.4 虚 基 类
继承性是面向对象程序设计的一种重要功能,是实现代码复用的一种形式。继承可以使程序设计人员在一个已存在类的基础上很快建立一个新的类,而不必从零开始设计新类。新设计类能够具有原有类的属性和方法,并且为了使新类具有自己独特的功能,新类还要添加新的属性和方法。继承性是面向对象程序设计的一种重要功能,是实现代码复用的一种形式。继承可以使程序设计人员在一个已存在类的基础上很快建立一个新的类,而不必从零开始设计新类。新设计类能够具有原有类的属性和方法,并且为了使新类具有自己独特的功能,新类还要添加新的属性和方法。 • 当一个类被其他的类继承时,被继承的类称为基类,又称为父类、超类。继承其他类属性和方法的类称为派生类,又称为子类、继承类。
9.1 基类和派生类 • 9.1.1 派生类的定义 • 派生能用从派生类到基类的箭头图形表示,箭头指向基类表示派生类引用基类中的函数和数据,而基类则不能访问派生类,如图9-1所示。任何一个类均可作为基类。仅从一个基类派生的继承称为单继承。
基类 子类 图9-1 派生
单继承声明语句的一般形式为: • class <派生类名> : <继承方式> <基类名> • { • 数据成员和成员函数声明 • }
基类可分为两类:直接基类和间接基类。如果某个基类在基类列表中提及,则称它是直接基类。例如:基类可分为两类:直接基类和间接基类。如果某个基类在基类列表中提及,则称它是直接基类。例如: • class A • { }; • class B:public A //类A为直接基类。 • { };
间接基类可写为: • class A • { }; • class B:public A • { }; • class C:public B //类A是间接基类,可扩展到任意级数 • { };
9.1.2 继承方式 • 继承方式有3种:公有继承方式(public)、私有继承方式(private)和保护继承方式(protected)。
9.1.2.1 公有继承 • 在公有派生类中: • (1)基类的公有成员在派生类中仍是公有成员。 • (2)基类的保护成员在派生类中仍是保护成员。 • (3)基类的私有成员在派生类中是不可访问的。
9.1.2.2 私有继承 • 在私有派生类中: • (1)基类的公有成员在派生类中是私有成员。 • (2)基类的保护成员在派生类中是私有成员。 • (3)基类的私有成员在派生类中仍是不可访问的。
9.1.2.3 保护继承 • 在保护派生类中: • (1)基类的公有成员在派生类中是保护成员。 • (2)基类的保护成员在派生类中是保护成员。 • (3)基类的私有成员在派生类中仍是不可访问的。
9.2 单 继 承 • 9.2.1 单继承中的成员访问权限 • (1)公有成员:一个类的公有成员允许本类的成员函数、本类的对象、公有派生类的成员函数、公有派生类的对象访问。
(2)私有成员:一个类的私有成员只允许本类的成员函数访问。(2)私有成员:一个类的私有成员只允许本类的成员函数访问。 • (3)保护成员:具有私有成员和公有成员的特征。一个类的保护成员允许本类的成员函数、公有派生类的成员函数访问。本类的对象、公有派生类的对象不能访问。
【例9.1】成员访问权限举例。 • class A //基类 • { • private: • int privA; • protected: • int protA; • public: • int pubA; • };
class B : public A //派生类 • { • public: • void fn() • { • int a; • a = privA; //错误:不可访问 • a = protA; //有效 • a = pubA; //有效 • } • };
void main() • { • A a; //基类对象 • a.privA = 1; //错误:不可访问 • a.protA = 1; //错误:不可访问 • a.pubA = 1; //有效 • B b; //派生类对象 • b.privA = 1; //错误:不可访问 • b.protA = 1; //错误:不可访问 • b.pubA = 1; //有效 • }
【例9.2】分析下面的程序。 • #include "iostream.h" • class A • { • private: • int x; • public: • void f1(int a); • int f2(); • };
class B:public A • { • private: • int y; • public: • void g1(int a); • int g2(); • };
void A::f1(int a) • { • x=a; • } • int A::f2() • { • return x; • }
void B::g1(int a) • { • y=a; • } • int B::g2() • { • return y+f2(); • }
void main() • { • B b; • b.f1(10); • b.g1(10); • cout<<b.g2()<<endl; • } • 运行程序,输出结果为:20
9.2.2 构造函数和析构函数 • 9.2.2.1 构造函数 • 派生类的数据是由基类中的数据和在派生类中新定义的数据组成。由于构造函数不能够继承。因此,在定义派生类的构造函数时,除了对自己新定义的数据成员进行初始化外,还必须调用基类的构造函数使基类的数据成员得以初始化。
【例9.3】分析下面的程序。 • class Base • { • protected: • int a; • public: • Base(){ a = 0;} //默认构造函数 • Base(int c) { a = c;} • //单参数构造函数 • };
class Derived: public Base • { • public: • Derived():Base(){}; //默认构造函数 • Derived(int c):Base(c){}; //单参数构造函数 • };
【例9.4】分析下面的程序。 • class engine • { • private: • int num; • public: • engine(int s) { num = s; } • };
class jet • { • private: • int jt; • engine eobj; //这里声明一个对象 • public: • jet(int x, int y): eobj(y) • { • jt = x; • } • };
总结派生类的构造函数的调用顺序如下: • (1)基类的构造函数; • (2)子对象类的构造函数(如果子对象存在); • (3)派生类的构造函数。
9.2.2.2 析构函数 • 析构函数的调用顺序与构造函数相反,析构函数首先为派生类调用,然后为子对象类的析构函数调用,最后调用基类的析构函数。仅当派生类的构造函数通过动态内存管理分配内存时,才定义派生类的析构函数。如果派生类的构造函数不起任何作用或派生类中未添加任何附加数据成员,则派生类的析构函数可以是一个空函数。
【例9.5】分析下面的程序。 • #include "iostream.h" • class A • { • private: • int x; • public: • A(){x=0;}
A(int xx){x=xx;} • ~A(){cout<<"A Destructor called."<<endl;} • void display(){cout<<x<<" ";} • };
class B:public A • { • private: • int b1; • A b2; //这里声明一个对象 • public: • B(){b1=0;} • B(int i,int j):b2(j){b1=i;}
B(int i, int j,int k): A(i),b2(j),b1(k){} • ~B(){cout<<"B Destructor called."<<endl;} • void print() • { • display(); • cout<<b1<<" "; • b2.display(); • cout<<endl; • } • };
void main() • { • B obj1; • B obj2(5,6); • B obj3(7,8,9); • obj1.print(); • obj2.print(); • obj3.print(); • }
程序运行结果为: • 0 0 0 • 0 5 6 • 7 9 8 • B Destructor called. • A Destructor called. • A Destructor called. • B Destructor called. • A Destructor called. • A Destructor called. • B Destructor called. • A Destructor called. • A Destructor called.
9.2.2.3 调用成员函数 • 派生类中的成员函数与基类中的成员函数可以有相同的名称。当使用基类的对象调用函数时,基类的函数被调用。当使用派生类对象的名称时,派生类的函数被调用。如果派生类的成员函数要调用相同名称的基类函数,它必须使用作用域运算符::。基类中的函数既可使用基类的对象,也可使用派生类的对象调用。如果函数存在于派生类而不是基类中,那么它只能被派生类的对象调用。
【例9.6】分析下面的程序。 • class Base • { • protected: • int ss; • public: • int func() {return ss;} • void print(){cout<<ss;} • };
class Derived: public Base • { • public: • int func() { return Base::func(); } • };
void main() • { • Base b1; //基类对象 • b1.func(); //调用基类函数func • Derived a1; //派生类对象 • a1.func(); //调用派生类对象func • }
9.3 多 继 承 • 9.3.1 多继承的概念 • 从多个基类派生的继承称为多继承,或称多重继承,即一个派生类可以有多个直接基类。
多继承声明语句的一般形式为: • class <派生类名>:< 继承方式> <基类名1>,< 继承方式> <基类名2>,··· • { • 数据成员和成员函数声明 • };
例如: • class A • { • ··· • }; • class B • { • ··· • }; • class C:public A,public B • { • ··· • };
【例9.7】分析下面的程序。 • #include "iostream.h" • class A • { • public: • void printA(){cout<<"Hello ";} • };
class B • { • public: • void printB(){cout<<"C++ ";} • }; • class C: public A,public B • { • public: • void printC(){cout<<"World!\n";} • };
void main() • { • C obj;; • obj.printA(); • obj.printB(); • obj.printC(); • }
程序执行结果为: • Hello C++ World!
9.3.2 多继承的构造函数和析构函数 • 多基派生类的构造函数的一般形式为: • <派生类名>::<派生类名>(〈参数表1〉,〈参数表2〉,···):<基类名1>(〈参数表1〉),<基类名2>(〈参数表2〉),··· • { • <派生类成员> • }
多重继承的构造函数按照下面的原则被调用: • (1)先基类,后自己。 • (2)如果在同一层上有多个基类,按照派生时定义的先后顺序执行。 • 多重继承的析构函数的执行顺序与多重继承的构造函数的执行顺序相反。
【例9.8】分析下列程序的输出结果。 • #include "iostream.h" • class Base1 • { • private: • int a1; • public:
Base1(int i){a1=i;cout<<"Constructor Base1 called "<<a1<<endl;} • ~Base1(){cout<<"Desstructor Base1 called"<<endl;} • };
class Base2 • { • private: • int a2; • public: • Base2(int j){a2=j;cout<<"Constructor Base2 called "<<a2<<endl;} • ~Base2(){cout<<"Desstructor Base2 called"<<endl;} • };