【C++面向对象程序设计】CH5 继承与派生
创始人
2024-02-27 01:34:02
0

目录

前言

一、继承与派生的概念

二、派生类的声明方式

1.格式

2.【例】假定已经声明一个基类student,在它基础上通过单继承建立一个派生类student1。

三、派生类的构成

四、派生类成员的访问属性

前言

1.公有继承

(1)公有基类在派生类中的访问属性

(2)【例5.1】派生类访问公有继承的基类成员

(3)【例】公有继承举例

2.私有继承

(1)私有基类在派生类中的访问属性

(2)【例5.2】将例5.1中公有继承改为私有继承

3.保护成员和保护继承

(1)保护基类在派生类中的访问属性

(2)【例5.3】在派生类中访问保护成员

4.多级派生时的访问属性

(1)介绍

(2)【例5.4】多级派生类的访问属性。如果声明了以下的了类。

五、类型兼容规则

1.规则

2.类型兼容规则举例

3.基类与派生类的对应关系

4.多继承时派生类的声明

5.多继承举例

6.继承与派生的目的

六、派生类的构造函数和析构函数

前言——派生类的构造函数 

1.简单的派生类的构造函数

(1)介绍

(2)【例5.5】简单派生类的构造函数

(3)单一继承时的构造函数举例

2.有子对象的派生类的构造函数

(1)介绍

(2)【例】

3.多层派生时的构造函数

(1)介绍

(2)举例

4.派生类构造函数的特殊形式

5.派生类的析构函数

(1)介绍

(2)单一继承时构造函数、析构函数举例

七、多重继承

1.声明多重继承的方法

(1)介绍

(2)【例5.8】

2.多重继承派生类的构造函数

(1)派生类构造函数举例

(2)同名隐藏规则

(3)多继承同名隐藏举例

3.多重继承引起的二义性问题

(1)介绍

(2)二义性问题举例(一)

(3)二义性的解决方法

(4)二义性问题举例(二) 

(5)多重继承的同名问题


前言

        面向对象程序设计有四个主要特点:抽象、封装、继承和多态性。本文主要介绍有关继承的知识,在下一篇文章介绍多态性。

一、继承与派生的概念

        C++的继承机制实现软件可重用。有时两个类的内容基本相同或有一部分相同。例如已声明了类student:

class Student
{ private :int num;string name;char sex; public:void display( ){cout<<"num: "<

        如果另一个部门除了需要已有的数据外,还需要地址信息,你可以再声明另一个类:

class Student1
{private :int num;string name;char sex;char addr[20]; public:void display(){ cout<<"num: "<

        可以看到新类中大部分成员是原来已有的。人们自然会想到能否利用原来声明的类student,加上新内容即可,以减少重复的工作。这就引出了C++的继承机制。

        所谓继承是在已存在的类A的基础上建立一个新类B。类A称为基类或父类,类B称为派生类或子类。子类从父类获得其已有的特性,这种现象称作类的继承。从另一个角度看从父类产生子类,称作类的派生

        一个基类可以派生出多个派生类,每个派生类又可以作为基类再派生新的派生类。一个派生类只从一个基类派生,称作单继承

        一个派生类也可从多个基类派生,也就是说一个派生类可以有两个或多个基类。一个派生类有两个或多个基类的称为多重继承。基类和派生类的关系可以表述为:派生类是基类的扩充,而基类是派生类的抽象

二、派生类的声明方式

1.格式

        使用派生类要先声明,声明的格式为:

class  派生类名: [继承方式] 基类名
{      派生类新增成员声明    
};

        继承方式包括:public、private、protected。如果省略,默认为private。

2.【例】假定已经声明一个基类student,在它基础上通过单继承建立一个派生类student1。

class Student1: public Student
{  private:int age;string addr;public:void display_1(){  cout <<"age: "<

三、派生类的构成

        派生类中的成员包括从基类继承过来的成员和自己增加的成员。继承基类成员体现了同一基类的派生类都具有的共性,而新增加的成员体现了派生类的个性

  • 从基类接受成员:派生类将基类除构造函数和析构函数外的所有成员接收过来
  • 调整从基类接受的成员:一方面可以通过继承方式改变基类成员在派生类中的访问属性,另一方面可以在派生类中声明一个与基类成员同名的成员屏蔽基类的同名成员,注意如是成员函数不仅要函数名相同,而且函数的参数也要相同,屏蔽的含义是用新成员取代旧成员
  • 在声明派生类时增加成员:它体现了派生类对基类功能的补充
  • 在声明派生类时,还要自己定义派生类的构造函数

四、派生类成员的访问属性

前言

        派生类中包含了基类成员和派生类成员,就产生了这两部分成员的关系和访问属性的问题。这个关系由基类成员的访问属性和派生类的继承方式组合决定。

1.公有继承

(1)公有基类在派生类中的访问属性

        当派生类的继承方式为public(公有)属性时,在派生类中,基类的公有成员和保护成员在派生类中的访问属性没有变化,即分别作为派生类的公有成员和保护成员,派生类的成员可以直接访问它们。但是,派生类的成员无法直接访问基类的私有成员。保护私有成员是一条重要的原则。

(2)【例5.1】派生类访问公有继承的基类成员

class Student1: public Student
{private:int age;string addr; public:void get_value_1(){cin>>age>>addr;}void display_1(){  //cout<<"num: "<

【解释】

        由于基类的私有成员对派生类说是不能访问的,所以派生类的成员函数display_1不能直接访问基类的私有成员,只能通过基类的公有成员函数访问基类的私有成员。

        因为是公有继承,基类的公有成员在派生类中仍是公有,所以派生类的对象可以通过基类的公有成员函数访问基类的私有数据成员,也可以在派生类的成员函数中调用基类的公有成员函数,访问基类的私有数据成员。

方法一:

int main()
{	Student1 stud1;…  … stud1.display();stud1.display_1();return 0;
} 

方法二:

void display_1()
{ 	display();    //派生类成员调用基类公有成员cout<<"age: "<

(3)【例】公有继承举例

class Point 
{	//基类Point类的声明public:	//公有函数成员void InitP(float xx = 0, float yy = 0) {X = xx;Y = yy;}void Move(float xOff, float yOff) {X += xOff;Y += yOff;}float GetX() {return X;}float GetY() {return Y;}private:	//私有数据成员float X, Y;
};class Rectangle: public Point 
{ //派生类声明public:	//新增公有函数成员void InitR(float x, float y, float w, float h) {InitP(x, y);    //调用基类公有成员函数W = w;H = h;}float GetH() {return H;}float GetW() {return W;}private:	//新增私有数据成员float W, H;
};
#include 
#include 
using namespace std;int main() 
{Rectangle rect;rect.InitR(2, 3, 20, 10);//通过派生类对象访问基类公有成员rect.Move(3, 2);cout << rect.GetX() << ','<< rect.GetY() << ','<< rect.GetH() << ','<< rect.GetW() << endl;return 0;
}

2.私有继承

(1)私有基类在派生类中的访问属性

        在派生类中,基类的公有成员和保护成员作为派生类的私有成员,派生类的成员可以直接访问他们,而派生类的成员无法直接访问基类的私有成员。

        在派生类的外部,派生类的对象无法访问基类的全部对象。

        私有继承之后,全部基类成员在派生类中都成为了私有成员或不可访问的成员,无法进一步派生。私有成员一般很少使用。

(2)【例5.2】将例5.1中公有继承改为私有继承

class Student 
{private :int num;string name;char sex;public:void display( ) {cout << "num: " << num << endl;cout << "name: " << name << endl;cout << "sex: " << sex << endl;}
};class Student1: private Student 
{private:int age;string addr;public:void display_1() {display();cout << "age: " << age << endl;       //  正确cout << "address: " << addr << endl;	//  正确}
};int main() 
{Student1 stud1;stud1.display_1();return 0;
}

3.保护成员和保护继承

(1)保护基类在派生类中的访问属性

        当派生类的继承方式为protected继承属性时,在派生类中,基类的公有成员和保护成员均作为派生类的保护成员,派生类的成员可以直接访问他们,而派生类的成员无法访问基类的私有成员。

        在派生类的外部,派生类的对象无法访问基类的全部成员。

        如果基类只进行了一次派生,则保护继承和私有继承的功能完全相同,但保护继承可以进一步派生,而私有继承则不可以,两者具有实质性差别。

(2)【例5.3】在派生类中访问保护成员

class Student 
{              //    声明基类protected :                   //    基类保护成员int num;string name;char sex;public:                          //    基类公用成员void display( );
};class Student1: protected Student 
{private:int age;string addr;public:void display1( );
};void Student1::display1( ) 
{cout << "num: " << num << endl; //引用基类的保护成员cout << "name: " << name << endl;cout << "sex: " << sex << endl;cout << "age: " << age << endl;cout << "address: " << addr << endl;
}

        派生类的成员函数访问基类的保护成员是合法的。基类的保护成员对派生类的外界来说是不可访问的(例如,num是基类student的保护成员,由于派生类是保护继承,所以它在派生类中仍受保护,外界不能用stud1.num形式访问它)对照【例5.2】可以看到:保护成员和私有成员的区别在于把保护成员的访问范围扩展到派生类中

4.多级派生时的访问属性

(1)介绍

        以上介绍了只有一级派生的情况,实际上常常有多级派生的情况,如果有图5.9所示的派生关系:类A为基类,类B是类A的派生类,类C是类B的派生类,则类C也是类A的派生类。类B是类A的直接派生类,类C是类A的间接派生类。类A是类B的直接基类,是类C的间接基类

(2)【例5.4】多级派生类的访问属性。如果声明了以下的了类。

class A 
{             //    基类private:int ka;public:int ia;protected:void fa( );int ja;
};class B: public A 
{         //   public方式private:int mb;public:void fb1( );protected:void fb2( );
};class C: protected B 
{    //   protected方式private:int nc;public:void fc1( );
};

        类B公有继承类A,类C保护继承类B。各个成员在不同类中的访问属性如下:

五、类型兼容规则

1.规则

        一个公有派生类的对象在使用上可以被当作基类的对象,反之则禁止。具体表现在:

  • 派生类的对象可以被赋值给基类对象
  • 派生类的对象可以初始化基类的引用(向下兼容)
  • 指向基类的指针也可以指向派生类
  • 通过基类对象名、指针只能使用从基类继承的成员

2.类型兼容规则举例

#include 
using namespace 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 
{public: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类对象fun(p);p = &d1;	//  B0类指针指向D1类对象fun(p);return 0;
}

3.基类与派生类的对应关系

  • 单继承:派生类只能从一个基类派生
  • 多继承:派生类从多个基类派生
  • 多重派生:由一个基类派生出多个不同的派生类
  • 多层派生:派生类又作为基类,继续派生新的类

4.多继承时派生类的声明

class 派生类名:继承方式1  基类名1,继承方式2  基类名2,...
{成员声明;
}

 【注】每一个“继承方式”,只用于限制紧随其后的基类的继承

5.多继承举例

class A 
{public:void setA(int);void showA();private:int a;
};class B 
{public:void setB(int);void showB();private:int b;
};class C : public A, private B 
{public:void setC(int, int, int);void showC();private:int c;
};void  A::setA(int x) 
{a = x;
}void B::setB(int x) 
{b = x;
}void C::setC(int x, int y, int z) 
{//派生类成员直接访问基类的//公有成员setA(x);setB(y);c = z;
}//其它函数实现略
int main() 
{C obj;obj.setA(5);obj.showA();obj.setC(6, 7, 9);obj.showC();
// obj.setB(6);  错误
// obj.showB(); 错误return 0;
}

6.继承与派生的目的

  • 继承的目的:实现代码重用
  • 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造

六、派生类的构造函数和析构函数

前言——派生类的构造函数 

        基类的构造函数不被继承,派生类中需要声明自己的构造函数。

        声明构造函数时,只需要对本类中新增成员进行初始化,调用基类构造函数对继承来的基类成员初始化。

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

1.简单的派生类的构造函数

(1)介绍

        简单派生类只有一个基类,而且只有一级派生,在派生类的数据成员中不包含基类的对象(即子对象)。

        在定义派生类的构造函数时除了对自己的数据成员进行初始化外,还必须用基类的构造函数初始化基类的数据成员。构造函数格式如下:

派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(基类参数表)
{本类成员初始化赋值语句;
};

        派生类名后的参数表分别列出基类和派生类构造函数的形参(有类型和形参变量)。

        基类参数表列出传递给基类构造函数的实参,是派生类构造函数总参数表中的参数。

        用派生类构造函数的形参作基类构造函数的实参。

(2)【例5.5】简单派生类的构造函数

#include 
#include 
using namespace std;class Student 
{                            //声明基类public:                                  //公用部分Student(int n, string nam, char s ) {   //基类构造函数num = n;name = nam;sex = s;}~Student( ) { }protected:                               //保护部分int num;string name;char sex ;                            //基类析构函数
};class Student1: public Student 
{ //  声明公用派生类public:Student1(int n, string nam, char s, int a, char ad[ ] ): Student ( n, nam, s) { //   派生类构造函数age = a;     //  只对派生类新增的数据成员初始化addr = ad;}void show( );private:                 //  派生类的私有部分int age;string addr;
};void Student1::show() 
{cout << "num: " << num << endl;cout << "name: " << name << endl;cout << "sex: " << sex << endl;cout << "age: " << age << endl;cout << "address: " << addr << endl;
}int main( ) 
{Student1 stud1(10010, "Wang-li", 'f', 19, "115 Beijing Road, Shanghai");Student1 stud2(10011, "Zhang-fun", 'm', 21, "213 Shanghai Road,Beijing");stud1.show( );            // 输出第一个学生的数据stud2.show( );            // 输出第二个学生的数据return 0;
}

        在建立一个对象时,执行构造函数的顺序是:派生类构造函数先调用基类构造函数;再执行派生类构造函数本身(即派生类构造函数的函数体)。按上面的例子说,先初始化num,name,sex,然后再初始化age,addr。

        释放派生类对象时,先执行派生类析构函数,再执行其基类的析构函数。

(3)单一继承时的构造函数举例

#include 
using namespace std;class B 
{private:int b;public:B();B(int i);void Print() const;
};B::B() 
{b = 0;cout << "调用B的默认构造函数." << endl;
}B::B(int i) 
{b = i;cout << "调用B的构造函数." << endl;
}void B::Print() const 
{cout << b << endl;
}class C: public B 
{private:int c;public:C();C(int i, int j);void Print() const;
};C::C() 
{c = 0;cout << "调用C的默认构造函数." << endl;
}C::C(int i, int j): B(i) 
{c = j;cout << "调用C的构造函数." << endl;
}void C::Print() const 
{B::Print();cout << c << endl;
}int main() 
{C obj(5, 6);obj.Print();return 0;
}

2.有子对象的派生类的构造函数

(1)介绍

        类的数据成员除了是标准类型或系统提供的类型如string,还可以是类类型,如声明一个类时包含类类型的数据成员:Student s1;Student是已声明过的类名,s1是该类的对象,我们称s1为子对象。以【例5.5】为例,除了可以在派生类student1中增加age、address成员外,还可以增加班长一项,而班长本身也是学生,它属于student类型,有学号和姓名等基本数据,班长这项就是派生类中的子对象。

        怎样对子对象初始化?由于类是一种数据类型,不能带具体的值,何况每个派生类对象的子对象一般是不同的(如学生A,B,C的班长是A,而学生D,E,F的班长是F)。所以不能在声明派生类时对子对象初始化,系统在建立派生类对象时调用派生类构造函数对子对象进行初始化。

        派生类构造函数的任务包括:

  • 对基类数据成员初始化
  • 对子对象的数据成员初始化
  • 对派生类的数据成员初始化

        派生类构造函数一般形式为:

派生类名::派生类名 (总参数表):基类名(实参表 ), 子对象名(参数表)
{派生类新增成员的初始化语句;
} 

        执行派生类构造函数的顺序是:

  • 调用基类构造函数,初始化基类数据成员
  • 调用子对象构造函数,初始化子对象数据成员
  • 执行派生类构造函数,初始化派生类数据成员 

        编译系统在此根据参数名(而不是参数的顺序)决定各参数表中参数之间的传递关系。如果多个子对象,要逐个列出子对象及其参数表。

(2)【例】

#include 
#include 
using namespace std;class Student 
{                            //声明基类public:                                  //公用部分Student(int n, string nam ) {    //基类构造函数num = n;name = nam;}void display() {cout << "学号:" << num << endl << "姓名:" <<  name << endl;}protected:                               //保护部分int num;string name;char sex ;                            //基类析构函数
};class Student1: public Student 
{ //   public继承方式private:                           //  派生类的私有数据Student monitor;           //  定义子对象(班长)int age;string addr;public:
//下面是派生类构造函数Student1(int n, string nam, int n1, string nam1, int a, string ad): Student(n, nam), monitor(n1, nam1) {age = a;               //    在此处只对派生类addr = ad;            //    新增的数据成员初始化}void show( ) {cout << "这个学生是:" << endl;display();            // 输出num和namecout << "年龄: " << age << endl;cout << "地址: " << addr << endl << endl;}
//  输出子对象的数据成员void show_monitor() {cout << endl << "班长是:" << endl;monitor.display();  //调用基类成员函数}};int main( ) 
{Student1 stud1(10010, "王力", 10001, "李军", 19, "上海市北京路115号 ");stud1.show( );             //  输出第一个学生的数据stud1.show_monitor();     //  输出子对象的数据return 0;
}

3.多层派生时的构造函数

(1)介绍

        一个类可以派生出一个派生类,派生类和可以继续派生,形成派生的层次结构。多层派生时怎样写派生类的构造函数?现有如图所示的多层派生类:

        可以按照前面派生类构造函数的规则逐层写出各个派生类的构造函数。

        基类的构造函数首部:

Student(int n, string nam );

         派生类student1的构造函数首部:

Student1(int n,string nam,int a):Student(n,nam);

        派生类student2的构造函数首部:

Student2(int n,string nam,int a,int s):Student1(n,nam,a);

         写派生类构造函数的规则是:只须调用其直接基类的构造函数即可,不要列出每一层派生类的构造函数。在声明Student2类对象时,调用Student2构造函数,在执行Student2构造函数时,先调用Student1构造函数,在执行Student1构造函数时,先调用基类Student构造函数。初始化的顺序是:

  • 先初始化基类的数据成员num和name
  • 再初始化Student1的数据成员age
  • 最后初始化Student2的数据成员score

(2)举例

#include 
#include 
using namespace std;class Student 
{                            //声明基类public:                                  //公用部分Student(int n, string nam ) {          //基类构造函数num = n;name = nam;}void display() {                         //输出基类数据成员cout << "num:" << num << endl;cout << "name:" << name << endl;}protected:                                //保护部分int num;                                //基类有两个数据成员string name;
};class Student1: public Student 
{ //声明公用派生类Student1public:Student1(int n, string nam, int a): Student(n, nam)
//派生类构造函数{age = a;    //在此处只对派生类新增的数据成员初始化}void show( ) {                 //输出num,name和agedisplay();                      //输出num和namecout << "age: " << age << endl;}private:                                   //派生类的私有数据int age;                                  //增加一个数据成员
};class Student2: public Student1//声明间接公用派生类student2
{public://下面是间接派生类构造函数Student2(int n, string nam, int a, int s): Student1(n, nam, a) {score = s;}void show_all() {   //  输出全部数据成员show();               //  输出num和namecout << "score:" << score << endl; //输出age}private:int score;                                   //增加一个数据成员
};int main( ) 
{Student2 stud(10010, "李明", 17, 89);stud.show_all( );  //输出学生的全部数据return 0;
}

4.派生类构造函数的特殊形式

(1)当不需要对派生类新增成员进行初始化时,派生类构造函数的函数体可以为空。如【例5.6】程序中派生类Student1的构造函数改写成:

Student1(int n,string nam,int n1,string nam1 ) : Student(n,nam),monitor(n1,nam1)

        在调用派生类构造函数时不对派生类的数据成员初始化。

(2)如果在基类里没有定义构造函数,或定义了没有参数的构造函数,在定义派生类构造函数时可以不写基类的构造函数。因为此时派生类构造函数没有向基类构造函数传递参数的任务。在调用派生类构造函数时,系统地自动首先调用基类的默认构造函数。

(3) 如果在基类和子对象的类中都没有定义带参数的构造函数,也不需要对派生类自己的数据成员进行初始化,可以不定义派生类的构造函数

(4)如果在基类或子对象的类声明里定义了带参数的构造函数,就必须定义派生类构造函数,并在派生类构造函数中写出基类或子对象类的构造函数及其参数表

(5)如果在基类既定义了无参数的构造函数也定义了有参数的构造函数,在定义派生类构造函数时,既可以包含基类构造函数及其参数,也可以不包含基类构造函数

5.派生类的析构函数

(1)介绍

        类派生时,派生类不能继承基类的析构函数,撤销派生类对象时,需要派生类的析构函数去调用基类的析构函数。首先执行派生类自己的析构函数,清理派生类新增加的成员,然后调用子对象类的析构函数清理子对象,最后调用基类析构函数清理基类的成员。

(2)单一继承时构造函数、析构函数举例

#include 
using namespace std;class B 
{private:int b;public:B();B(int i);~B();void Print() const;
};B::B() 
{b = 0;cout << "调用B的默认构造函数." << endl;
}B::B(int i) 
{b = i;cout << "调用B的构造函数." << endl;
}B::~B() 
{cout << "调用B的析构函数." << endl;
}void B::Print() const 
{cout << b << endl;
}class C: public B 
{private:int c;public:C();C(int i, int j);~C();void Print() const;
};C::C() 
{c = 0;cout << "调用C的默认构造函数." << endl;
}C::C(int i, int j): B(i) 
{c = j;cout << "调用C的构造函数." << endl;
}C::~C() 
{cout << "调用C的析构函数." << endl;
}void C::Print() const 
{B::Print();cout << c << endl;
}int main() 
{C obj(5, 6);obj.Print();return 0;
}

七、多重继承

1.声明多重继承的方法

(1)介绍

        假定已声明了类A,类B和类C,由它们派生出新类D,声明的形式可以是:

Class D: public A,private B, protected C{  D类新增的成员声明 }

        多重继承派生类的构造函数在初始化表中包含多个基类构造函数,假定派生类有三个基类,它的构造函数形式是:

派生类构造函数名(总参数表):基类1构造函数(参数表),基类2构造函数(参数表),基类3构造函数(参数表){ 派生类新增成员初始化语句 }

        各基类的排列顺序不分先后,系统调用基类构造函数的顺序就是声明派生类时基类的出现顺序。如在前面中声明派生类D时,基类出现的顺序是A,B,C。系统先调用A的构造函数,再掉用B的构造函数,最后调用C的构造函数。

(2)【例5.8】

声明一个教师类和一个学生类,用多重继承的方式声明一个研究生类。教师包括name,age,title数据成员,学生类包括name1,sex,score。在定义派生类对象时给出初始化数据,输出这些数据。

#include 
#include 
using namespace std;class Teacher 
{                      //  声明Teacher(教师)类public:                                  //  公用部分Teacher(string nam, int a, string t) {  //  构造函数name = nam;age = a;title = t;}void display() {                        //  输出教师有关数据cout << "name:" << name << endl;cout << "age" << age << endl;cout << "title:" << title << endl;}protected:                               //  保护部分string name;int age;string title;                           //  职称
};class Student 
{                          //  声明类Student(学生)public:Student(string nam, char s, float sco) {name1 = nam;sex = s;score = sco;}                         //  构造函数void display1() {                     //  输出学生有关数据cout << "name:" << name1 << endl;cout << "sex:" << sex << endl;cout << "score:" << score << endl;}protected:                               //  保护部分string name1;char sex;float score;                            //  成绩
};class Graduate: public Teacher, public Student 
{public:Graduate(string nam, int a, char s, string t, float sco, float w):Teacher(nam, a, t), Student(nam, s, sco), wage(w) {}void show( ) {                               //  输出人员的有关数据cout << "name:" << name << endl;cout << "age:" << age << endl;cout << "sex:" << sex << endl;cout << "score:" << score << endl;cout << "title:" << title << endl;cout << "wages:" << wage << endl;}private:float wage;                                  //  工资
};int main( ) 
{Graduate grad1("Wang-li", 24, 'f', "assistant", 89.5, 1234.5);grad1.show( );return 0;
}

【注】从这个程序中可以发现,在多重继承时,从不同的基类中会继承重复的数据成员,例如本例中姓名就是重复的数据成员。 

2.多重继承派生类的构造函数

(1)派生类构造函数举例

#include 
using namespace std;class B1 
{	//基类B1,构造函数有参数public:B1(int i) {cout << "构造 B1 " << i << endl;}
};class B2 
{	//基类B2,构造函数有参数public:B2(int j) {cout << "构造 B2 " << j << endl;}
};class B3 
{	//基类B3,构造函数无参数public:B3() {cout << "构造 B3 *" << endl;}
};class C: public B2, public B1, public B3 
{public:	//派生类的公有成员C(int a, int b, int c, int d):B1(a), B2(b), memberB1(c), memberB2(d)  {}private:	//派生类的私有对象成员B1 memberB1;B2 memberB2;B3 memberB3;
};int main() 
{C obj(1, 2, 3, 4);return 0;
}

【注】从结果看到多重继承时,系统先调用基类的构造函数,再掉用子对象的构造函数,最后调用派生类的构造函数。

(2)同名隐藏规则

        当派生类与基类中有相同成员时:

  • 若未强行指明,则通过派生类对象使用的是派生类中的同名成员
  • 如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定

(3)多继承同名隐藏举例

#include 
using namespace std;class B1 
{	//声明基类B1public:	//外部接口int nV;void fun()  {cout << "Member of B1" << endl;}
};class B2 
{	//声明基类B2public:	//外部接口int nV;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();return 0;
}

3.多重继承引起的二义性问题

(1)介绍

        多继承最常见的问题是派生类继承基类同名成员而产生的二义性问题。

        在多重继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数或同名隐藏规则来解决。

        当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。

(2)二义性问题举例(一)

class A
{public:void  f();
};
class B
{public:void f();void g()
};
class C: public A, piblic B
{         public:void g();void h();
};

        如果声明:C c1;则c1.f()具有二义性,而c1.g()无二义性(同名覆盖原则)

(3)二义性的解决方法

  • 解决方法一:用类名来限定c1.A::f()或c1.B::f()
  • 解决方法二:同名覆盖。在C中声明一个同名成员f(),f()再根据需要调用A::f()或B::f()

(4)二义性问题举例(二) 

#include 
using namespace std;class B 
{public:B(int i ) {b = i;}int b;
};class B1 : public B 
{public:B1(int b, int bx): B(b) {b1 = bx;}private:int b1;
};class B2 : public B 
{public:B2(int b, int bx): B(b) {b2 = bx;}private:int b2;
};class C: public B1, public B2 
{public:C(int x1, int x2, int x3, int x4): B1(x1, x2), B2(x1, x3) {d = x4;}private:int d;
};int main(int argc, char *argv[]) 
{C cc(1, 2, 3, 4);cout << cc.B1::b << endl;cout << cc.B2::b << endl;//cout<

        派生类C的对象的存储结构示意图:

//有二义性
C cc;
cc.b;
cc.B::b;
//无二义性
cc.B1::b;
cc.B2::b;

(5)多重继承的同名问题

        如果类A和类B都有成员函数display和数据成员a,类c是类A和类B的直接派生类。分别讨论下下买你的三种情况:

  • 两个基类有同名成员,如上图所示
Class  A 
{public:int a;void display( );
};Class  B 
{public:int a;void display( );
};
Class  C :public A, public B 
{public:int b;void show( );
};

        如果在main函数中定义C类对象c1,并写出如下语句:

C c1;
c1.a=3;
c1.display();

        由于基类A和基类B都有数据成员a和成员函数display,系统无法辨别要访问的是A类还是B类,程序编译报错。解决这个问题可以用基类名限定,如:

c1.A::a=3;
c1.A::display();

        如果在派生类C中通过派生类成员函数show访问A的display和a,可以这样写:

A::a=3;
A::display();

         为了清楚起见,上图应该用下图的形式表示:

  • 两个基类和派生类三者都有同名成员。将前面的C类声明改为:
Class  C : public A, public B
{ public:int a;void display( );
};

        如果在main函数中定义C类对象c1:

C c1;
c1.a=3;
c1.display();

        程序可以通过编译,也能正常运行,在语法上认为这样访问的是派生类的成员a和成员函数display,规则是:派生类屏蔽基类的同名成员对于带参数的成员函数除了要函数名相同外,参数的个数和类型都要相同才符合屏蔽条件。如果在派生类外访问基类A的成员,要指明作用域A:。

c1.A::a=3;
c1.A::display();
  • 如果类A和类B又是从同一个基类派生的:
Class N 
{public :int a;Void display(){ cout<<"N::a="<

        类A和类B分贝从类N继承了数据成员a和成员函数display,在类A和类B中存在同名数据成员a和成员函数display。类A和类B中的同名数据成员a分别为不同的内存单元。在程序中类A和类B的构造函数调用基类N的构造函数分别对类A和类B的数据成员a初始化。对象c1怎样访问类A从基类N继承来的成员呢?访问格式如下:

c1.A::a=3;
c1.A::display();

 

相关内容

热门资讯

常用商务英语口语   商务英语是以适应职场生活的语言要求为目的,内容涉及到商务活动的方方面面。下面是小编收集的常用商务...
六年级上册英语第一单元练习题   一、根据要求写单词。  1.dry(反义词)__________________  2.writ...
复活节英文怎么说 复活节英文怎么说?复活节的英语翻译是什么?复活节:Easter;"Easter,anniversar...
2008年北京奥运会主题曲 2008年北京奥运会(第29届夏季奥林匹克运动会),2008年8月8日到2008年8月24日在中华人...
英语道歉信 英语道歉信15篇  在日常生活中,道歉信的使用频率越来越高,通过道歉信,我们可以更好地解释事情发生的...
六年级英语专题训练(连词成句... 六年级英语专题训练(连词成句30题)  1. have,playhouse,many,I,toy,i...
上班迟到情况说明英语   每个人都或多或少的迟到过那么几次,因为各种原因,可能生病,可能因为交通堵车,可能是因为天气冷,有...
小学英语教学论文 小学英语教学论文范文  引导语:英语教育一直都是每个家长所器重的,那么有关小学英语教学论文要怎么写呢...
英语口语学习必看的方法技巧 英语口语学习必看的方法技巧如何才能说流利的英语? 说外语时,我们主要应做到四件事:理解、回答、提问、...
四级英语作文选:Birth ... 四级英语作文范文选:Birth controlSince the Chinese Governmen...
金融专业英语面试自我介绍 金融专业英语面试自我介绍3篇  金融专业的学生面试时,面试官要求用英语做自我介绍该怎么说。下面是小编...
我的李老师走了四年级英语日记... 我的李老师走了四年级英语日记带翻译  我上了五个学期的小学却换了六任老师,李老师是带我们班最长的语文...
小学三年级英语日记带翻译捡玉... 小学三年级英语日记带翻译捡玉米  今天,我和妈妈去外婆家,外婆家有刚剥的`玉米棒上带有玉米籽,好大的...
七年级英语优秀教学设计 七年级英语优秀教学设计  作为一位兢兢业业的人民教师,常常要写一份优秀的教学设计,教学设计是把教学原...
我的英语老师作文 我的英语老师作文(通用21篇)  在日常生活或是工作学习中,大家都有写作文的经历,对作文很是熟悉吧,...
英语老师教学经验总结 英语老师教学经验总结(通用19篇)  总结是指社会团体、企业单位和个人对某一阶段的学习、工作或其完成...
初一英语暑假作业答案 初一英语暑假作业答案  英语练习一(基础训练)第一题1.D2.H3.E4.F5.I6.A7.J8.C...
大学生的英语演讲稿 大学生的英语演讲稿范文(精选10篇)  使用正确的写作思路书写演讲稿会更加事半功倍。在现实社会中,越...
VOA美国之音英语学习网址 VOA美国之音英语学习推荐网址 美国之音网站已经成为语言学习最重要的资源站点,在互联网上还有若干网站...
商务英语期末试卷 Part I Term Translation (20%)Section A: Translate ...