【C++面向对象程序设计】CH5继承与派生-创新互联

目录

创新互联建站一直秉承“诚信做人,踏实做事”的原则,不欺瞒客户,是我们最起码的底线! 以服务为基础,以质量求生存,以技术求发展,成交一个客户多一个朋友!为您提供网站建设、成都做网站、成都网页设计、小程序开发、成都网站开发、成都网站制作、成都软件开发、成都App制作是成都本地专业的网站建设和网站设计公司,等你一起来见证!

前言

一、继承与派生的概念

二、派生类的声明方式

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#includeusing 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.类型兼容规则举例
#includeusing 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#includeusing 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)单一继承时的构造函数举例
#includeusing 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#includeusing 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和name
			cout<< "年龄: "<< 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#includeusing 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 
{ //声明公用派生类Student1
	public:
		Student1(int n, string nam, int a): Student(n, nam)
//派生类构造函数
		{
			age = a;    //在此处只对派生类新增的数据成员初始化
		}
		void show( ) 
		{                 //输出num,name和age
			display();                      //输出num和name
			cout<< "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和name
			cout<< "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)单一继承时构造函数、析构函数举例
#includeusing 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#includeusing 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)派生类构造函数举例
#includeusing 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)多继承同名隐藏举例
#includeusing namespace std;

class B1 
{	//声明基类B1
	public:	//外部接口
		int nV;
		void fun()  
		{
			cout<< "Member of B1"<< endl;
		}
};

class B2 
{	//声明基类B2
	public:	//外部接口
		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)二义性问题举例(二) 
#includeusing 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();

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


分享标题:【C++面向对象程序设计】CH5继承与派生-创新互联
路径分享:http://ybzwz.com/article/icspc.html