主讲教师:全红艳 hyquan@sei.ecnu.edu.cn
第十一章 继承和派生
本章主要内容 类之间的关系 基类和派生类 基类的初始化 继承的应用实例 多继承 继承时的构造函数
相关知识 或者: class 类名{ private: 私有的数据和成员函数; public: 公有的数据和成员函数; }
说明 class是定义类的关键字。 <类名>是一个标识符,用于唯一标识一个类。 类的说明部分,说明该类的所有成员。 类的成员包括数据成员和成员函数两部分。 类的成员从访问权限上分有以下三类: 公有的(public) 私有的(private) 保护的(protected) 其中默认为private权限。
公有的成员(public) 私有的成员(private) 保护的成员(protected) 可以被程序中的任何代码访问; 只能被类本身的成员函数及友元类的成员函数访问,其他类的成员函数,包括其派生类的成员函数都不能访问它们; 保护的成员(protected) 与私有成员类似,只是除了类本身的成员函数和说明为友元类的成员函数可以访问保护成员外,该类的派生类的成员也可以访问。
传递性 高等植物、蕨类植物、芒萁都是植物,具有植物的共同特征 11.1 类之间的关系 植物 低等植物 高等植物 藻类 菌类 地衣类 苔藓类 蕨类 裸子类 被子类 地钱 小金发草 毛蕨 芒萁 杉木 柏木 荔枝 橘子 传递性 高等植物、蕨类植物、芒萁都是植物,具有植物的共同特征 不具有对称性 不是所有植物都属于蕨类
1.继承是指一个类从另一个或另一些类获得了一些基本性质,并在此基础上增加了自身的一些特殊性质。 11.1 类之间的继承关系 1.继承是指一个类从另一个或另一些类获得了一些基本性质,并在此基础上增加了自身的一些特殊性质。 2.继承是建立在类的一般化结构(通用——特殊结构)基础上的。也即特殊类继承了一般类的性质。 一般类——基类,或超类。 特殊类——派生类,或子类。 派生类是基类的具体化,而基类则是派生类的抽象
11.1 类之间的关系 3. 继承的关键成分 例如,正方形具有与四边形相同的性质。 例如,正方形的四条边必须等长。 (1)实体所具有的共性 11.1 类之间的关系 3. 继承的关键成分 (1)实体所具有的共性 例如,正方形具有与四边形相同的性质。 (2)实体间的区别,也即实体自身所具有的个性。 例如,正方形的四条边必须等长。 (3)实体之间存在一般—特殊关系,从而可以建立层次结构。
11.1 类之间的关系 继承与派生问题举例
11.1 类之间的关系 继承与派生问题举例 猫科
11.1 类之间的关系 继承与派生问题举例
11.1 类之间的关系 继承 是类之间定义的一种重要关系 一个 B 类继承A类,或称从类 A 派生类 B 11.1 类之间的关系 继承 是类之间定义的一种重要关系 一个 B 类继承A类,或称从类 A 派生类 B 类 A 称为基类(父类),类 B 称为派生类(子类) B1,B2 的基类 A A 的派生类 C1,C2,C3 的基类 A 的派生类(单继承) C3的基类 B1 B2 B1 的派生类 C1 C2 C3 B1,B2 的派生类(多继承)
11.2 基类和派生类 类继承关系的语法形式 class student1:public student { 11.2 基类和派生类 类继承关系的语法形式 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; class student1:public student { void display_1() { cout<<“age:”<<age<<endl; cout<<“address:”<< address <<endl;} Private: int age; string addr; }
11.2 基类和派生类 类继承关系的语法形式 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; 基类名表 构成 11.2 基类和派生类 类继承关系的语法形式 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; 基类名表 构成 访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
11.2 基类和派生类 类继承关系的语法形式 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; 基类名表 构成 11.2 基类和派生类 类继承关系的语法形式 class 派生类名 : 基类名表 { 数据成员和成员函数声明 }; 基类名表 构成 访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n 访问控制 表示派生类对基类的继承方式,使用关键字: public 公有继承 private 私有继承 protected 保护继承
基类 派生类 11.2 基类和派生类 派生类的构成 Student类 Student1类 int num; char name[10]; 11.2 基类和派生类 派生类的构成 基类 派生类 Student类 Student1类 数 据 成 员 int num; char name[10]; char sex; int num; char name[10]; char sex; 继承 void display(); void display(); 成员 函数 int age; char addr[30]; 新 增 void display();
11.2.1 访问控制 派生类对基类成员的使用,与继承访问控制和基类中成员性质有关 公有继承 基类的公有成员派生类的公有成员 7.2.1 访问控制 11.2.1 访问控制 派生类对基类成员的使用,与继承访问控制和基类中成员性质有关 公有继承 基类的公有成员派生类的公有成员 基类的保护成员派生类的保护成员 私有继承 基类的公有成员和保护成员派生类的私有成员 保护继承 基类的公有成员和保护成员派生类的保护成员 不论种方式继承基类,派生类都不能直接使用基类的私有成员
继承方式 基类成员的访问权限 派生类对基类成员的访问权限 public 公有继承 private protected 不可见 私有继承 保护继承
1.公有继承 基类 派生类 private 成员 protected 成员 protected 成员 public 成员 public 成员 7.2.1 访问控制 1.公有继承 public 成员 public 成员 protected 成员 protected 成员 private 成员 public 成员 protected 成员 基类 派生类
例 公有继承的测试 #include<iostream.h> class A { public : 7.2.1 访问控制 例 公有继承的测试 #include<iostream.h> 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 int get_S() { return s ; }; void make_S() { s = x * y ; }; // 使用基类数据成员x,y protected: int s; class C : public B 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; class A class C : public B class B : public A
例 公有继承的测试 void main() { A objA ; B objB ; C objC ; 7.2.1 访问控制 void main() { A objA ; B objB ; C objC ; cout << "It is object_A :\n" ; objA.get_XY() ; objA.put_XY() ; cout << "It is object_B :\n" ; objB.get_XY() ; objB.make_S() ; cout << "S = " << objB.get_S() << endl ; cout << "It is object_C :\n" ; objC.get_XY() ; objC.get_H(); objC.make_V() ; cout << "V = " << objC.get_V() << endl ; } 例 公有继承的测试 x y objA.y objA.x objA s objB.s objB.y objB.x objB h v objC.h objC.v objC.s objC.y objC.x objC
7.2.1 访问控制 2.私有继承 public 成员 protected 成员 private 成员 基类 派生类
例 私有继承的测试 #include<iostream.h> class A { public : 7.2.1 访问控制 #include<iostream.h> class A { public : void get_XY() { cout << "Enter two numbers of x and y : " ; cin >> x >> y ; } void put_XY() { cout << "x = "<< x << ", y = " << y << '\n' ; } protected: int x, y ; }; class B : private A int get_S() { return s ; } void make_S() { get_XY(); s = x * y ; } private: int s ; void main() { B objB ; cout << "It is object_B :\n" ; objB.make_S() ; cout << "S = " << objB.get_S() << endl ; } 例 私有继承的测试 class B : private A class A objB.s objB.y objB.x objB private成员
例 私有继承的测试 #include<iostream.h> class A { public : 7.2.1 访问控制 #include<iostream.h> class A { public : void get_XY() { cout << "Enter two numbers of x and y : " ; cin >> x >> y ; } void put_XY() { cout << "x = "<< x << ", y = " << y << '\n' ; } protected: int x, y ; }; class B : private A int get_S() { return s ; } void make_S() { get_XY(); s = x * y ; } private: int s ; void main() { B objB ; cout << "It is object_B :\n" ; objB.make_S() ; cout << "S = " << objB.get_S() << endl ; } 例 私有继承的测试 class B : private A class A objB.s objB.y objB.x objB private成员
例 私有数据成员的测试 #include<iostream.h> class A { public: A(){ x=1; } 7.2.1 访问控制 #include<iostream.h> class A { public: A(){ x=1; } int out() {return x ; } void addX() { x++; } private: int x ; } ; class B : public A { public: B(){ y=1; } int out() {return y ; } void addY() { y++; } private: int y ; void main() { A a ; B b ; b.addX() ; b.addY() ; cout << "a.x=" << a.out() << endl ; cout << "b.x=" << b.A::out() << endl ; cout << "b.y=" << b.out() << endl ; } 例 私有数据成员的测试 class B : public A class A 基类的私有数据成员 不能在派生类中直接访问 但派生类对象建立私有数据空间 b.x a.x b.y
例 私有数据成员的测试 #include<iostream.h> class A { public: A(){ x=1; } 7.2.1 访问控制 #include<iostream.h> class A { public: A(){ x=1; } int out() {return x ; } void addX() { x++; } private: int x ; } ; class B : public A { public: B(){ y=1; } int out() {return y ; } void addY() { y++; } private: int y ; void main() { A a ; cout << "a.x=" << a.out() << endl ; B b ; b.addX() ; b.addY() ; cout << "b.x=" << b.A::out() << endl ; cout << "b.y=" << b.out() << endl ; } 例 私有数据成员的测试 class B : public A class A b.x a.x b.y
7.2.1 访问控制 3.保护继承 public 成员 protected 成员 private 成员 基类 派生类
11.2.2 重名成员 派生类定义了与基类同名的成员,在派生类中访问同名成员 时屏蔽了基类的同名成员 7.2.2 重名成员 11.2.2 重名成员 派生类定义了与基类同名的成员,在派生类中访问同名成员 时屏蔽了基类的同名成员 在派生类中使用基类的同名成员,显式地使用类名限定符: 类名 :: 成员
1.重名数据成员 例: base a b class base { public : int a , b ; derived a b b c 7.2.2 重名成员 1.重名数据成员 base a b 例: class base { public : int a , b ; } ; class derived : public base int b , c ; void f () { derived d ; d . a = 1 ; d . base :: b = 2 ; d . b = 3 ; d . c = 4 ; }; derived a b b c 4 3 2 1 derived d 基类成员的作用域延伸到所有派生类 派生类的重名成员屏蔽基类的同名成员
2.重名成员函数 //例 #include<iostream.h> class A { public: int a1, a2 ; 7.2.2 重名成员 //例 #include<iostream.h> class A { public: int a1, a2 ; A( int i1=0, int i2=0 ) { a1 = i1; a2 = i2; } void print() { cout << "a1=" << a1 << '\t' << "a2=" << a2 << endl ; } }; class B : public A int b1, b2 ; B( int j1=1, int j2=1 ) { b1 = j1; b2 = j2; } void print() //定义同名函数 { cout << "b1=" << b1 << '\t' << "b2=" << b2 << endl ; } void printAB() { A::print() ; //派生类对象调用基类版本同名成员函数 print() ; //派生类对象调用自身的成员函数 } void main() { B b ; b.A::print(); b.printAB(); } 2.重名成员函数
类型兼容规则 一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在: 通过基类对象名、指针只能使用从基类继承的成员 派生类的对象可以被赋值给基类对象。 派生类的对象可以初始化基类的引用。 指向基类的指针也可以指向派生类。 通过基类对象名、指针只能使用从基类继承的成员
例 类型兼容规则举例 #include <iostream> using namecpace std; 例 类型兼容规则举例 #include <iostream> using namecpace std; class B0 //基类B0声明 { public: void display(){cout<<"B0::display()"<<endl;} //公有成员函数 };
class B1: public B0 { public: void display(){cout<<"B1::display()"<<endl;} }; class D1: public B1 void display(){cout<<"D1::display()"<<endl;} void fun(B0 *ptr) { ptr->display(); //"对象指针->成员名" }
int main() //主函数 { B0 b0; //声明B0类对象 B1 b1; //声明B1类对象 D1 d1; //声明D1类对象 B0 *p; //声明B0类指针 p=&b0; //B0类指针指向B0类对象 fun(p); p=&b1; //B0类指针指向B1类对象 p=&d1; //B0类指针指向D1类对象 } 运行结果: B0::display()
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 ) 11.3 基类的初始化 建立一个类层次后,通常创建某个派生类的对象,包括使用基类的数据和函数 C++提供一种机制,在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据 派生类构造函数声明为 派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 ) … 对象成员n ( 变元表 ) ; 构造函数执行顺序:基类 对象成员 派生类
// 例 调用构造函数顺序测试,构造函数无参数 #include < iostream.h > class Base 7.3 基类的初始化 // 例 调用构造函数顺序测试,构造函数无参数 #include < iostream.h > class Base { public : Base ( ) { cout << "Base created.\n" ; } } ; class D_class : public Base { public : D_class ( ) { cout << "D_class created.\n" ; } void main ( ) { D_class d1 ; }
例 带参数构造函数调用顺序测试 #include <iostream.h> class parent_class { int data1 , data2 ; public : parent_class ( int p1 , int p2 ) { data1 = p1; data2 = p2; } int inc1 () { return ++ data1; } int inc2 () { return ++ data2 ; } void display () {cout << "data1=" << data1 << " , data2=" << data2 << endl ; } }; class derived_class : private parent_class { int data3 ; parent_class data4 ; public: derived_class ( int p1 , int p2 , int p3 , int p4 , int p5 ): parent_class ( p1 , p2 ) , data4 ( p3 , p4 ) { data3 = p5 ; } int inc1 ( ) { return parent_class :: inc1 ( ) ; } int inc3 ( ) { return ++ data3 ; } void display ( ) { parent_class :: display ( ) ; data4.display ( ) ; cout << "data3=" << data3 << endl ; } } ; void main ( ) { derived_class d1 ( 17 , 18 , 1 , 2 , -5 ) ; d1 . inc1 ( ) ; d1 . display ( ) ; } 7.3 基类的初始化 例 带参数构造函数调用顺序测试
11.4 继承的应用实例 例 考察一个点、圆、圆柱体的层次结构 Point Circle Cylinder
{ friend ostream &operator<< (ostream &, const Point &); public: 7.4 继承的应用实例 class Point { friend ostream &operator<< (ostream &, const Point &); public: Point( int = 0, int = 0 ) ; // 带默认参数的构造函数 void setPoint( int, int ) ; // 对点坐标数据赋值 int getX() const { return x ; } int getY() const { return y ; } protected: int x, y; // Point类的数据成员 }; class Circle : public Point { friend ostream &operator<< (ostream &, const Circle &); // 友员函数 Circle(double r=0.0, int x=0, int y=0); // 构造函数 void setRadius(double); /*置半径*/ double getRadius() const; /*返回半径*/ double area() const; // 返回面积 protected: double radius; // 数据成员,半径 class Cylinder:public Circle { friend ostream & operator<<(ostream &, const Cylinder &); // 友员函数 Cylinder(double h=0.0, double r=0.0, int x=0, int y=0); // 构造函数 void setHeight(double); /* 置高度值*/ double getHeight() const; /* 返回高度值*/ double area() const; /* 返回面积*/ double volume() const; /* 返回体积*/ protected: double height; // 数据成员,高度
// Point 类的成员函数 // 构造函数,调用成员函数对 x,y作初始化 Point::Point ( int a, int b ) 7.4 继承的应用实例 // Point 类的成员函数 // 构造函数,调用成员函数对 x,y作初始化 Point::Point ( int a, int b ) { setPoint ( a , b ) ; } // 对数据成员置值 void Point :: setPoint ( int a, int b ) { x = a ; y = b ; } // 重载插入算符,输出对象数据 ostream &operator<< ( ostream &output , const Point &p ) { output << '[' << p.x << "," << p.y << "]" ; return output ; }
// Circle 类的成员函数 // 带初始化式构造函数,首先调用基类构造函数 7.4 继承的应用实例 // Circle 类的成员函数 // 带初始化式构造函数,首先调用基类构造函数 Circle::Circle( double r, int a, int b ): Point( a, b ) { setRadius ( r ); } // 对半径置值 void Circle::setRadius ( double r ) { radius = ( r >= 0 ? r : 0 ); } // 返回半径值 double Circle::getRadius() const { return radius; } // 计算并返回面积值 double Circle::area() const { return 3.14159 * radius * radius ; } // 输出圆心坐标和半径值 ostream & operator<< ( ostream &output, const Circle &c) { output << "Center = " << '[' << c.x << "," << c.y << "]" << "; Radius = " << setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << c.radius ; return output ; }
// Cylinder 类的成员函数 // 带初始化式构造函数,首先调用基类构造函数 7.4 继承的应用实例 // Cylinder 类的成员函数 // 带初始化式构造函数,首先调用基类构造函数 Cylinder::Cylinder(double h, double r, int x, int y):Circle(r,x,y) { setHeight(h); } // 对高度置值 void Cylinder::setHeight(double h) { height = ( h >= 0 ? h : 0 ); } // 返回高度值 double Cylinder::getHeight() const { return height; } // 计算并返回圆柱体的表面积 double Cylinder::area() const { return 2*Circle::area()+2*3.14159*radius*height; } // 计算并返回圆柱体的体积 double Cylinder::volume() const { return Circle::area()*height; } // 输出数据成员圆心坐标、半径和高度值 ostream &operator<< ( ostream &output, const Cylinder &cy ) { output << "Center = " << '[' << cy.x << "," << cy.y << "]" << "; Radius = " << setiosflags(ios::fixed|ios::showpoint) << setprecision(2) << cy.radius << "; Height = " << cy.height << endl ; return output; }
#include <iostream.h> #include <iomanip.h> void main() { Point p ( 72, 115 ) ; //定义点对象并初始化 cout << "The initial location of p is " << p << endl ; p.setPoint ( 10, 10 ) ; //置点的新数据值 cout << "\nThe new location of p is " << p << endl ; //输出数据 Circle c ( 2.5, 37, 43 ) ; //定义圆对象并初始化 cout<<"\nThe initial location and radius of c are\n"<<c<<"\nArea = "<<c.area()<<"\n" ; //置圆的新数据值 c.setRadius ( 4.25 ) ; c.setPoint ( 2, 2 ) ; //输出圆心坐标和圆面积 cout<<"\nThe new location and radius of c are\n"<<c<<"\nArea = "<<c.area()<< "\n" ; Cylinder cyl ( 5.7, 2.5, 12, 23 ) ; //定义圆柱体对象并初始化 //输出圆柱体各数据和表面积,体积 cout << "\nThe initial location, radius ang height of cyl are\n" << cyl << "Area = " << cyl.area() << "\nVolume = " << cyl.volume() << '\n'; //置圆柱体的新数据值 cyl.setHeight ( 10 ) ; cyl.setRadius ( 4.25 ) ; cyl.setPoint ( 2, 2 ) ; cout << "\nThe new location, radius ang height of cyl are\n" << cyl << "Area = " << cyl.area() << "\nVolume = "<<cyl.volume()<< "\n" ; } 7.4 继承的应用实例
11.5 多继承 单重继承与多重继承
11.5 多继承 一个类有多个直接基类的继承关系称为多继承 多继承声明语法 7.5 多继承 11.5 多继承 一个类有多个直接基类的继承关系称为多继承 多继承声明语法 class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n { 数据成员和成员函数声明 };
class C : public A ,public B 7.5 多继承 11.5 多继承 类 C 可以根据访问控制同时继承类 A 和类 B 的成员,并添加 自己的成员 class A class B class C : public A ,public B
11.5.1 多继承的派生类构造和访问 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员 7.5.1 多继承的派生类构造和访问 11.5.1 多继承的派生类构造和访问 多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员 执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
多继承的简单应用 class Base1 { public: Base1(int x) { value = x ; } 7.5.1 多继承的派生类构造和访问 多继承的简单应用 class Base1 { public: Base1(int x) { value = x ; } int getData() const { return value ; } protected: int value; }; class Base2 Base2(char c) { letter=c; } char getData() const { return letter;} char letter; class Derived : public Base1, public Base2 { friend ostream &operator<< ( ostream &, const Derived & ) ; public : Derived ( int, char, double ) ; double getReal() const ; private : double real ; };
多继承的简单应用 void main() { Base1 b1 ( 10 ) ; Base2 b2 ( 'k' ) ; 7.5.1 多继承的派生类构造和访问 多继承的简单应用 class Base1 { public: Base1(int x) { value = x ; } int getData() const { return value ; } protected: int value; }; class Base2 Base2(char c) { letter=c; } char getData() const { return letter;} char letter; class Derived : public Base1, public Base2 { friend ostream &operator<< ( ostream &, const Derived & ) ; public : Derived ( int, char, double ) ; double getReal() const ; private : double real ; void main() { Base1 b1 ( 10 ) ; Base2 b2 ( 'k' ) ; Derived d ( 5, 'A', 2.5 ) ; : return ; } 'K' 2.5 'A' 5 10 value letter real Basc1 b1 Basc2 b2 Derived d
11.6 继承时的构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数。 11.6 继承时的构造函数 基类的构造函数不被继承,派生类中需要声明自己的构造函数。 声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。 派生类的构造函数需要给基类的构造函数传递参数
11.6.1 单一继承时的构造函数 派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表) { 11.6.1 单一继承时的构造函数 派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表) { 本类成员初始化赋值语句; };
举例 11.6.1 单一继承时的构造函数 #include<iostream> using namespace std; class B{ public: B(); B(int i); ~B(); void Print() const; private: int b; };
cout<<"B's default constructor called."<<endl; } B::B() { b=0; cout<<"B's default constructor called."<<endl; } B::B(int i) { b=i; cout<<"B's constructor called." <<endl; B::~B() { cout<<"B's destructor called."<<endl; } void B::Print() const { cout<<b<<endl; } 55
class C:public B { public: C(); C(int i,int j); ~C(); void Print() const; private: int c; }; 56
cout<<"C's default constructor called."<<endl; } C::C() { c=0; cout<<"C's default constructor called."<<endl; } C::C(int i,int j):B(i) { c=j; cout<<"C's constructor called."<<endl; C::~C() { cout<<"C's destructor called."<<endl; } void C::Print() const { B::Print(); cout<<c<<endl; } int main() { C obj(5,6); obj.Print(); } 57
11.6.2 多继承时的构造函数 派生类名::派生类名(基类1形参,基类2形参,...基类n形参,本类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数) { 本类成员初始化赋值语句; };
11.6.2 派生类与基类的构造函数 当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数。 11.6.2 派生类与基类的构造函数 当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数。 若基类中未声明构造函数,派生类中也可以不声明,全采用默认形式构造函数。 当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类构造函数。
11.6.3 多继承且有内嵌对象时的构造函数 派生类名::派生类名(基类1形参,基类2形参,...基类n形参,本类形参):基类名1(参数), 基类名2(参数), ...基类名n(参数),对象数据成员的初始化 { 本类成员初始化赋值语句; };
11.7 构造函数的调用顺序 1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。 11.7 构造函数的调用顺序 1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。 2. 调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。 3. 派生类的构造函数体中的内容。
拷贝构造函数 若建立派生类对象时调用默认拷贝构造函数,则编译器将自动调用基类的默认拷贝构造函数。 若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。例如: C::C(C &c1):B(c1) {…}
例 派生类构造函数举例 #include <iostream> using namespace std; class B1 //基类B1,构造函数有参数 {public: B1(int i) {cout<<"constructing B1 "<<i<<endl;} }; class B2 //基类B2,构造函数有参数 B2(int j) {cout<<"constructing B2 "<<j<<endl;} class B3 //基类B3,构造函数无参数 B3(){cout<<"constructing B3 *"<<endl;}
class C: public B2, public B1, public B3 { public: //派生类的公有成员 C(int a, int b, int c, int d): B1(a),memberB2(d),memberB1(c),B2(b) {} private: //派生类的私有对象成员 B1 memberB1; B2 memberB2; B3 memberB3; }; int main() { C obj(1,2,3,4); } 运行结果: constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 65
继承时的析构函数 析构函数也不被继承,派生类自行声明 声明方法与一般(无继承关系时)类的析构函数相同。 不需要显式地调用基类的析构函数,系统会自动隐式调用。 析构函数的调用次序与构造函数相反。
例 派生类析构函数举例 #include <iostream> using namecpace std; class B1 //基类B1声明 { public: B1(int i) {cout<<"constructing B1 "<<i<<endl;} ~B1() {cout<<"destructing B1 "<<endl;} }; class B2 //基类B2声明 {public: B2(int j) {cout<<"constructing B2 "<<j<<endl;} ~B2() {cout<<"destructing B2 "<<endl;} class B3 //基类B3声明 B3(){cout<<"constructing B3 *"<<endl;} ~B3() {cout<<"destructing B3 "<<endl;}
class C: public B2, public B1, public B3 {public: C(int a, int b, int c, int d): B1(a),memberB2(d),memberB1(c),B2(b){} private: B1 memberB1; B2 memberB2; B3 memberB3; }; int main() { C obj(1,2,3,4); } 68
例 运行结果 constructing B2 2 constructing B1 1 constructing B3 * constructing B1 3 constructing B2 4 destructing B3 destructing B2 destructing B1
同名隐藏规则 当派生类与基类中有相同成员时: 若未强行指名,则通过派生类对象使用的是派生类中的同名成员。 如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。
例 多继承同名隐藏举例 #include <iostream> using namespace std; 例 多继承同名隐藏举例 #include <iostream> using namespace std; class B1 //声明基类B1 { public: //外部接口 int nV; void fun() {cout<<"Member of B1"<<endl;} }; class B2 //声明基类B2 void fun(){cout<<"Member of B2"<<endl;} class D1: public B1, public B2 { public: int nV; //同名数据成员 void fun(){cout<<"Member of D1"<<endl;} //同名函数成员
int main() { D1 d1; d1.nV=1; //对象名.成员名标识, 访问D1类成员 d1.fun(); d1.B1::nV=2; //作用域分辨符标识, 访问基类B1成员 d1.B1::fun(); d1.B2::nV=3; //作用域分辨符标识, 访问基类B2成员 d1.B2::fun(); }
二义性问题 在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数或同名隐藏规则来解决。 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。 C B1 B2 B3 A
二义性问题举例(一) class C: public A, piblic B { public: void g(); void h(); }; 如果声明:C c1; 则 c1.f(); 具有二义性 而 c1.g(); 无二义性(同名覆盖) class A { public: void f(); }; class B void f(); void g()
二义性的解决方法 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f() 解决方法二:同名覆盖 在C 中声明一个同名成员函数f(),f()再根据需要调用 A::f() 或 B::f()
二义性问题举例(二) class C : public B1,public B2 { public: int f(); private: int d; } class B { public: int b; } class B1 : public B { private: int b1; class B2 : public B int b2; };
派生类C的对象的存储结构示意图: b b1 C类对象 b2 d 有二义性: 无二义性: C c; c.B1::b c.b c.B2::b 77
11.5.2 虚基类 如果一个派生类从多个基类派生,而这些基类又有一个共同 的基类,则在对该基类中声明的名字进行访问时,可能产生 二义性 7.5.2 虚基类 11.5.2 虚基类 如果一个派生类从多个基类派生,而这些基类又有一个共同 的基类,则在对该基类中声明的名字进行访问时,可能产生 二义性
例如: 有: class B { public : int b ;} ; 7.5.2 虚基类 例如: class B { public : int b ;} ; class B1 : public B { private : int b1 ; } ; class B2 : public B { private : int b2 ; } ; class C : public B1 , public B2 { public : int f ( ) ; private : int d ; } ; class B { b } class B { b } class B1 { b1 } class B2 {b2} 有: C c ; c . B ; // error c . B :: b ; // error,从哪里继承的? c . B1 :: b ; // ok,从B1继承的 c . B2 :: b ; // ok ,从B2继承的 class C { f () , d }
例如: class B { public : int b ;} ; 7.5.2 虚基类 例如: class B { public : int b ;} ; class B1 : public B { private : int b1 ; } ; class B2 : public B { private : int b2 ; } ; class C : public B1 , public B2 { public : int f ( ) ; private : int d ; } ; class B { b } class B { b } class B1 { b1 } class B2 {b2} class C { f () , d } #include<iostream.h> void main () { C c ; c . B1 :: b = 5 ; c . B2 :: b = 10 ; cout << "path B1==> " << c . B1 :: b << endl ; cout << "path B2==> " << c . B2 :: b << endl ; }
例如: class B { public : int b ;} ; 7.5.2 虚基类 例如: class B { public : int b ;} ; class B1 : public B { private : int b1 ; } ; class B2 : public B { private : int b2 ; } ; class C : public B1 , public B2 { public : int f ( ) ; private : int d ; } ; class B { b } class B { b } class B1 { b1 } class B2 {b2} class C { f () , d } 多重派生类 C 的对象的存储结构示意 建立 C 类的对象时,B 的 构造函数将被调用两次:一次 由B1调用,另一次由 B2 调用, 以初始化 C 类的对象中所包含 的两个 B 类的子对象 c.b c.b1 c.b2 c.d B B1 B2 C
B1 B2 B3 A C 11.5.2 虚基类 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处 7.5.2 虚基类 11.5.2 虚基类 如果在多条继承路径上有一个公共的基类,那么在继承路径的某处 汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象 要使这个公共基类在派生类中只产生一个子对象,必须对这个基类 声明为虚继承,使这个基类成为虚基类。 虚继承声明使用关键字 virtual
例如: 有: class B { public : int b ;} ; 7.5.2 虚基类 例如: class B { public : int b ;} ; class B1 : virtual public B { private : int b1 ; } ; class B2 : virtual public B { private : int b2 ; } ; class C : public B1 , public B2 { private : float d ; } ; class B { b } class B1 { b1} class B2 {b2} 有: C cc ; cc . b // ok class C { d } 由于类 C 的对象中只有一个 B 类子对象,名字 b 被约束到该子对象上, 所以,当以不同路径使用名字 b 访问 B 类的子对象时,所访问的都是 那个唯一的基类子对象。即 cc . B1 :: b 和 cc . B2 :: b 引用是同一个基类 B 的子对象
带有虚基类的派生类 C 的对象的存储结构示意 7.5.2 虚基类 例如: class B { public : int b ;} ; class B1 : virtual public B { private : int b1 ; } ; class B2 : virtual public B { private : int b2 ; } ; class C : public B1 , public B2 { private : float d ; } ; class B { b } class B1 { b1} class B2 {b2} 带有虚基类的派生类 C 的对象的存储结构示意 class C { d } c.b1 c.b2 c.d c.b B B1 B2 C
例 虚继承的测试 #include < iostream.h > class A { public : 7.5.2 虚基类 例 虚继承的测试 #include < iostream.h > class A { public : A ( ) { cout << "class A" << endl ; } } ; class B : public A B ( ) {cout << "class B" << endl ; } class C : public A C ( ) {cout << "class C" << endl ; } class D : public B , public C D ( ) {cout << "class D" << endl ; } void main ( ) { D dd ; } class D class B class C class A 两次调用 A的构造函数
例 虚继承的测试 #include < iostream.h > class A { public : 7.5.2 虚基类 例 虚继承的测试 #include < iostream.h > class A { public : A ( ) { cout << "class A" << endl ; } } ; class B : public A B ( ) {cout << "class B" << endl ; } class C : public A C ( ) {cout << "class C" << endl ; } class D : public B , public C D ( ) {cout << "class D" << endl ; } void main ( ) { D dd ; } class D class B class C class A class D class B class C class A : virtual public A : virtual public A 一次调用 A的构造函数
11.6 静态联编 联编是指一个程序模块、代码之间互相关联的过程。 静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。 11.6 静态联编 联编是指一个程序模块、代码之间互相关联的过程。 静态联编,是程序的匹配、连接在编译阶段实现,也称为早期匹配。 重载函数使用静态联编。 动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编。 switch 语句和 if 语句是动态联编的例子。
11.6.1 虚函数和动态联编 冠以关键字 virtual 的成员函数称为虚函数 实现运行时多态的关键首先是要说明虚函数,另外,必须用 11.6.1 虚函数和动态联编 冠以关键字 virtual 的成员函数称为虚函数 实现运行时多态的关键首先是要说明虚函数,另外,必须用 基类指针调用派生类的不同实现版本
虚函数和基类指针 基类指针虽然获取派生类对象地址,却只能访问派生类从基类继承的成员 解决办法:基类定义虚函数
随着p指向不同对象,this指针作类型转换 #include<iostream.h> class Base { public : Base(char xx) { x = xx; } virtual void who() { cout << "Base class: " << x << "\n" ; } protected: char x; } ; class First_d : public Base { public : First_d(char xx, char yy):Base(xx) { y = yy; } void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; } protected: char y; class Second_d : public First_d { public : Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; } void who() { cout<<"Second derived class: "<<x<<", "<<y<<", "<<z<<"\n" ; } protected: char z; void main() { Base B_obj( 'A' ) ; First_d F_obj( 'T', 'O' ) ; Second_d S_obj( 'E', 'N', 'D' ) ; Base * p ; p = & B_obj ; p -> who() ; p = &F_obj ; p -> who() ; p = &S_obj ; p -> who() ; } 例 演示基类指针的移动 由于who()的虚特性 随着p指向不同对象,this指针作类型转换 执行不同实现版本 void who() { cout<<"Second derived class: "<<x<<", "<<y<<", "<<z<<"\n" ; }
纯虚函数和抽象类 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本 纯虚函数为各派生类提供一个公共界面 纯虚函数说明形式: virtual 类型 函数名(参数表)= 0 ; 一个具有纯虚函数的基类称为抽象类。
例 简单图形类 class figure #include<iostream.h> //figure.h class figure { protected : double x,y; public: void set_dim(double i, double j=0) { x = i ; y = j ; } virtual void show_area() = 0 ; }; class triangle : public figure { public : void show_area() { cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of "<<x*0.5*y<<"\n"; } class square : public figure { public: { cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of "<<x*y<<"\n"; } class circle : public figure { cout<<"Circle with radius "<<x; cout<<" has an area of "<<3.14*x*x<<"\n"; } 例 简单图形类 #include<iostream.h> #include"figure.h" void main() { triangle t ; //派生类对象 square s ; circle c; t.set_dim(10.0,5.0) ; t.show_area(); s.set_dim(10.0,5.0) ; s.show_area() ; c.set_dim(9.0) ; c.show_area() ; } virtual void show_area() = 0 ; //纯虚函数 void show_area() { cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of " <<x*0.5*y<<"\n"; } void show_area() { cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of " <<x*y<<"\n"; } void show_area() { cout<<"Circle with radius "<<x; cout<<" has an area of "<<3.14*x*x<<"\n"; }
#include<iostream.h> #include"figure.h" void main() { figure *p; // 声明抽象类指针 triangle t; square s; circle c; p=&t; p->set_dim(10.0,5.0); // triangle::set_dim() p->show_area(); p=&s; p->set_dim(10.0,5.0); // square::set_dim() p=&c; p->set_dim(9.0); // circle::set_dim() } //figure.h class figure { protected : double x,y; public: void set_dim(double i, double j=0) { x = i ; y = j ; } virtual void show_area() = 0 ; }; class triangle : public figure { public : void show_area() { cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of "<<x*0.5*y<<"\n"; } class square : public figure { public: { cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of "<<x*y<<"\n"; } class circle : public figure { cout<<"Circle with radius "<<x; cout<<" has an area of "<<3.14*x*x<<"\n"; } virtual void show_area() = 0 ; //纯虚函数 void show_area() { cout<<"Triangle with high "<<x<<" and base "<<y <<" has an area of " <<x*0.5*y<<"\n"; } void show_area() { cout<<"Square with dimension "<<x<<"*"<<y <<" has an area of " <<x*y<<"\n"; } void show_area() { cout<<"Circle with radius "<<x; cout<<" has an area of "<<3.14*x*x<<"\n"; }
虚函数与多态的应用 虚函数和多态性使成员函数根据调用对象的类型产生不同的动作 多态性特别适合于实现分层结构的软件系统,便于对问题抽象时 定义共性,实现时定义区别
一个实例 计算雇员工资 抽象类 计件工人类 管理人员类 计时工人类 Employee HourWorker Manager 一个实例 计算雇员工资 抽象类 提供一般属性,共同操作界面 计件工人类 提供特殊属性,操作实现 管理人员类 提供特殊属性,操作实现 计时工人类 提供特殊属性,操作实现 Employee HourWorker Manager PieceWorker
例 计算雇员工资 Employee HourWorker Manager PieceWorker //Employee.h 例 计算雇员工资 Employee HourWorker Manager PieceWorker //Employee.h class Employee { public: Employee(const long,const char* ); virtual ~Employee(); const char * getName() const; const long getNumber() const; virtual double earnings() const=0; virtual void print() const; protected: long number; // 编号 char * name; // 姓名 }; //虚析构函数
例 计算雇员工资 Employee HourWorker Manager PieceWorker // Manager.h 例 计算雇员工资 Employee HourWorker Manager PieceWorker // Manager.h class Manager : public Employee { public: Manager(const long , const char *, double =0.0); ~Manager() { } void setMonthlySalary(double); virtual double earnings() const; virtual void print() const; private: double monthlySalary ; };
例12-8 计算雇员工资 Employee HourWorker Manager PieceWorker // HourlyWorker.h 例12-8 计算雇员工资 Employee HourWorker Manager PieceWorker // HourlyWorker.h class HourlyWorker : public Employee { public: HourlyWorker(const long, const char *, double=0.0, int =0 ); ~HourlyWorker(){} void setWage(double); void setHours(int); virtual double earnings() const; virtual void print() const; private: double wage; double hours; };
例12-8 计算雇员工资 Employee HourWorker Manager PieceWorker // PieceWorker.h 例12-8 计算雇员工资 Employee HourWorker Manager PieceWorker // PieceWorker.h class PieceWorker : public Employee { public: PieceWorker(const long , const char *, double =0.0, int =0 ); ~PieceWorker() { } void setWage ( double ) ; void setQuantity ( int ) ; virtual double earnings() const; virtual void print() const; private: double wagePerPiece; int quantity; };
调用函数语句形式相同 void test1() { cout << setiosflags(ios::fixed|ios::showpoint) << setprecision(2) ; Manager m1 ( 10135, "Cheng ShaoHua", 1200 ) ; Manager m2 ( 10201, "Yan HaiFeng"); m2.setMonthlySalary ( 5300 ) ; HourlyWorker hw1 ( 30712, "Zhao XiaoMing", 5, 8*20 ) ; HourlyWorker hw2 ( 30649, "Gao DongSheng" ) ; hw2.setWage ( 4.5 ) ; hw2.setHours ( 10*30 ) ; PieceWorker pw1 ( 20382, "Xiu LiWei", 0.5, 2850 ) ; PieceWorker pw2 ( 20496, "Huang DongLin" ) ; pw2.setWage ( 0.75 ) ; pw2.setQuantity ( 1850 ) ; // 使用抽象类指针,调用派生类版本的函数 Employee *basePtr; basePtr=&m1; basePtr->print(); basePtr=&m2; basePtr->print(); basePtr=&hw1; basePtr->print(); basePtr=&hw2; basePtr->print(); basePtr=&pw1; basePtr->print(); basePtr=&pw2; basePtr->print(); } 调用函数语句形式相同