C++程序设计【五】类的继承与派生

第五章 类的继承与派生

第一节 类的继承与类的派生

  • 继承和派生是人们认识客观世界的过程。在程序设计方法中,人们追求代码复用(这是提高软件开发效率的重要手段),将继承和派生用于程序设计方法中,从而有了面向对象程序设计的重要特点。C++对代码复用有很强的支持,“继承”就是支持代码复用的机制之一。
  • 通过已有的类建立新类的过程,叫作类的派生。原来的类称为基类,也称为父类或一般类;新类称为派生类,也称为子类或特殊类。
  • 派生类派生自基类,或继承于基类,也可以说基类派生了派生类。派生机制是C++语言及面向对象程序设计方法的重要特征之一。派生类可以再作为基类派生新的派生类,由此基类和派生类的集合称作类继承层次结构。

一、继承的概念

  • 使用基类派生新类时,构造函数和析构函数外,基类的所有成员自动成为派生类的成员,包括基类的成员变量和成员函数。同时,派生类可以增加基类中没有的成员,这同样是指成员变量和成员函数。可以重新定义或修改基类中已有的成员,包括可以改变基类中成员的访问权限。当然派生类需要定义自己的构造函数和析构函数。
  • 使用基类成员是一个重用的过程,在基类之上进行调整,不论是添加新成员还是改造已有的,都是扩充的过程。
  • 若派生类中定义了一个与基类中同名的成员,则会出现基类与派生类有同名成员的情况,这是允许的。同名的成员既可以是成员变量,也可以是成员函数。这种情况下,若在派生类的成员函数中访问这个同名成员,或通过派生类对象访问这个同名成员时,除非特别指明,访问的就是派生类中的成员,这种情况叫“覆盖”,即派生类的成员覆盖基类的同名成员。覆盖也称为重定义或是重写。
  • 对于成员函数来说,派生类既继承了基类的同名成员函数,又在派生类中重写了这个成员函数。这称为函数重定义,也称为同名隐藏。
    • “隐藏”的意思是指,使用派生类对象调用这个名字的成员函数时,调用的是派生类中定义的成员函数,即隐藏了基类中的成员函数。

二、派生类的定义

1、基类派生 派生类的定义

class 派生类名: 继承方式说明符 基类名
{
  类体
};

  • 继承方式说明符指明如何控制基类成员在派生类中的访问属性,通常有3种方式,分别是public(公有继承)、private(私有继承)和protected(保护继承)。一般情况下都使用public。
//基类定义
class BaseClass
{
	int v1,v2;
};
//派生类定义
class DerivedClass:public BaseClass
{
	int v3;
}
  • 空类也可以作为基类,也就是说,空类可以派生子类。派生类可以改变基类中成员的访问权限
2、虚基类的定义

class 派生类名:virtual 派生方式 基类名
{
  派生类体
};

  • 声明虚函数后,派生类对象的地址可以赋值给基类指针,也就是基类指针可以指向派生类对象。
  • 当程序运行到该语句时,如果基类指针指向的是一个基类对象,则调用基类的虚函数;
  • 如果基类指针指向的是一个派生类对象,则调用派生类的虚函数。

例1:声明一个交通工具(vehicle)基类,具有maxspeed、weight成员变量,run、stop成员函数(简单输出提示”正在行进”,“停止”),同时编写vehicle类的构造函数和析构函数。由此派生出自行车类(bicycle)、汽车类(motorcar),自行车类有高度(height)属性,汽车(motorcar)类有座位数(seatnum)。从bicycle和motorcar派生出摩托车类(motor-cycle),在继承过程中注意把vehicle设置为虚基类。

#include <iostream>
using namespace std;

class vehicle
{
private:
	int maxspeed,weight;
public:
	void run(){	cout<<"正在行进"<<endl;	};
	void stop(){	cout<<"停止"<<endl;	};

	vehicle(int m,int w):maxspeed(m),weight(w){	cout<<"vehicle构造函数"<<endl;	};
	~vehicle(){	cout<<"vehicle析构函数"<<endl;	};
};

class bicycle:virtual public vehicle
{
private:
	double hegiht;
public:
	bicycle(int m,int w,double h):vehicle(m,w),hegiht(h)
	{	
		cout<<"bicycle构造函数"<<endl;
	};

};

class motorcar:virtual public vehicle
{
private:
	int seatnum;
public:
	motorcar(int m,int w,int s):vehicle(m,w),seatnum(s)
	{
		cout<<"motorcar构造函数"<<endl;
	}
};

class motorCycle:public bicycle,public motorcar
{
public:
	motorCycle(int m,int w,double h,int s):bicycle(m,w,h),motorcar(m,w,s),vehicle(m,w)
	{
		cout<<"motorCycle构造函数"<<endl;
	}
};

例2:写一个程序,定义一个抽象类Shape,由它派生3个类:Square(正方形)、Trapezoid(梯形)和Triangle(三角形)。用虚函数分别计算几种图形面积、并求它们的和。要求用基类指针数组,使它每一个元素指向一个派生类对象。
在这里插入图片描述

#include <iostream>
using namespace std;
class Shape
{
public:
	virtual double area()const =0;
};

class Square:public Shape
{
private:
	double side;
public:
	Square(double s):side(s){};
	double area()const
	{
		return side*side;
	}
};

class Trapezoid:public Shape
{
private:
	double a,b,h;
public:
	Trapezoid(double a1,double a2,double a3):a(a1),b(a2),h(a3){};
	double area()const
	{
		return ((a+b)*h/2);
	};
};

class Triangle:public Shape
{
private:
	double w,h;
public:
	Triangle(double a1,double a2):w(a1),h(a2){};
	double area()const
	{
		return (w*h/2);
	};
};

void main()
{
	Square square(5);
	Trapezoid trapezoid(5,10,2);
	Triangle triangle(1,2);

	//要求用基类指针数组,使它每一个元素指向一个派生类对象。
	Shape *p[5];
	p[0] = &square;
	p[1] = &trapezoid;
	p[2] = &triangle;

	//用虚函数分别计算几种图形面积、并求它们的和
	double sumArea = 0;
	for (int i=0;i<3;i++)
	{
		sumArea += p[i]->area();
	}
	cout<<"总面积是:"<<sumArea<<endl;
}

例3:利用LOCATION类,使用继承定义圆类CIRCLE,圆由圆心和半径构成。提供得到圆心坐标和半径的成员函数、以及计算圆的周长和面积的成员函数。在主程序中创建两个圆A和B,圆心坐标分别为(0,3)、(3,7),按如下格式输出两个圆的圆心坐标、周长和面积,并计算和输出两个圆的圆心之间的距离。
  A:(x1,y1,r1),Girth=g1,Area=a1
  B:(x2,y2,r2),Girth=g2,Area=a2
  A(xl,y1),B(x2,y2),Distance=d

三、派生类的大小

  • 派生类对象中包含基类成员变量,而且基类成员变量的存储位置位于派生类对象新增的成员变量之前。派生类对象占用的存储空间大小,等于基类成员变量占用的存储空间大小加上派生类对象自身成员变量占用的存储空间大小
  • 对象占用的存储空间包含对象中各成员变量占用的存储空间。出于计算机内部处理效率的考虑,为变量分配内存时,会根据其对应的数据类型,在存储空间内对变量的起始地址进行边界对齐。可以使用sizeof( )函数计算对象占用的字节数。
  • 对象的大小与普通成员变量有关,与成员函数和类中的静态成员变量无关,即普通成员函数、静态成员函数、静态成员变量、静态常量成员变量等均对类对象的大小没有影响。

四、继承关系的特殊性

C++的继承性允许派生类继承基类的部分成员(除基类的构造函数和析构函数外),并允许增加新的成员或重定义基类的成员。

  • 如果基类有友元类或友元函数,则其派生类不会因继承关系而也有此友元类或友元函数。
  • 如果基类是某类的友元,则这种友元关系是被继承的。即被派生类继承过来的成员函数,如果原来是某类的友元函数,那么它作为派生类的成员函数仍然是某类的友元函数。
  • 总之,基类的友元不一定是派生类的友元;基类的成员函数是某类的友元函数,则其作为派生类继承的成员函数仍是某类的友元函数
#include <iostream>
using namespace std;
//前向引用声明
class another;

//基类
class Base
{
private:
	float x;
public:
	void print(const another &K);
};

//派生类
class Derived:public Base
{
private:
	float y;
};

//其他类
class another
{
private:
	int aaa;
public:
	another():aaa(100){};
	//类内声明友元函数:目的——让其他类中的成员函数访问本类中的private成员变量
	friend void Base::print(const another &K);
};
//类外定义友元函数
void Base::print(const another &K)
{
	cout<<"Base:"<<K.aaa<<endl;
};

int main()
{
	Base a;
	Derived b;
	another c;

	a.print(c);	//输出为:Base:100
	b.print(c);	//输出为:Base:100

	return 0;
};
  • 如果基类中的成员是静态的,则在其派生类中,被继承的成员也是静态的,即其静态属性随静态成员被继承。
  • 如果基类的静态成员是公有的或是保护的,则它们被其派生类继承为派生类的静态成员。访问这些成员时,通常用“<类名>::<成员名>”的方式引用或调用。无论有多少个对象被创建,这些成员都只有一个拷贝,它为基类和派生类的所有对象所共享。
#include <iostream>
using namespace std;
class Base
{
private:
	float x;
public:
	//注:类中静态变量,必须类内声明,类外定义
	static int staV;
	Base()
	{
		cout<<"Base:staV++之前="<<staV<<endl;
		staV++;
		cout<<"Base:staV++之后="<<staV<<endl;		
	}
};
int Base::staV=0;
//派生类
class Derived:public Base
{
private:
	float y;
public:
	Derived()
	{
		cout<<"Derived:staV++之前="<<staV<<endl;
		staV++;
		cout<<"Derived:staV++之后="<<staV<<endl;
	}
};

int main()
{
	Base a;
	cout<<a.staV<<endl;	//输出1
	
	Derived d;	//先调用基类的构造函数,再调用派生类的构造函数
	cout<<d.staV<<endl;	//输出3

	return 0;
};

在这里插入图片描述

五、有继承关系的类之间的访问

  • 派生类和基类中都可以定义自己的成员变量和成员函数,派生类中的成员函数可以访问基类中的公有成员变量,但不能直接访问基类中的私有成员变量。也就是说,不能在派生类的函数中,使用“基类对象名.基类私有成员函数(实参)”,或是“基类对象名.基类私有成员变量”,或是“基类名::基类私有成员”的形式访问基类中的私有成员。
  • 在类的派生层次结构中,基类的成员和派生类新增的成员都具有类作用域。二者的作用范围不同,是相互包含的两个层,派生类在内层,基类在外层。
  • 如果派生类声明了一个和基类某个成员同名的新成员,派生的新成员就隐藏了外层同名成员,直接使用成员名只能访问到派生类的成员。
  • 如果派生类中声明了与基类成员函数同名的新函数,即使函数的参数表不同,从基类继承的同名函数的所有重载形式也都会被隐藏。如果要访问被隐藏的成员,就需要使用基类名和作用域分辨符来限定。
#include <iostream>
using namespace std;
class CB
{
public:
	int a;
	CB(int x):a(x){};
	void showa()
	{
		cout<<"Class CB::a="<<a<<endl;
	}
};

class CD:public CB
{
public:
	int a;	//与基类a同名
	CD(int x,int y):CB(x),a(y){};

	void showa()	//与基类showa同名
	{
		cout<<"Class CD:a="<<a<<endl;
	};

	void print2a()
	{
		cout<<"CD::a="<<a<<endl;
		cout<<"CB::a="<<CB::a<<endl;
	}
}

int main()
{
	CB CBobj(12);
	CBobj.showa();

	CD CDobj(666,888);
	//访问CB、CD中的同名a——方式一:
	CDobj.showa();		//访问派生类的showa()
	CDobj.CB::showa();	//访问基类的showa()

	//访问CB、CD中的同名a——方式二:
	CDobj.print2a();
}

六、protected访问范围说明符

  • 定义类时,类成员可以使用protected访问范围说明符进行修饰,从而成为“保护成员”。保护成员的访问范围比私有成员的访问范围大,能访问私有成员的地方都能访问保护成员。此外,基类中的保护成员可以在派生类的成员函数中被访问
  • 在基类中,一般都将需要隐藏的成员说明为保护成员而非私有成员。将基类中成员变量的访问方式修改为protected后,在派生类中可以直接访问

七、多重继承

  C++允许从多个类派生一个类,即一个派生类可以同时有多个基类。这称为多重继承。相应地,从一个基类派生一个派生类的情况,称为单继承或单重继承
  一个类从多个基类派生的一般格式如下:
class派生类名:继承方式说明符 基类名1,继承方式说明符 基类名2,…,继承方式说明符 基类名n
{
  类体
};

  • 派生类继承了基类名1、基类名2、……、基类名n的所有成员变量和成员函数,各基类名前面的继承方式说明符用于限制派生类中的成员对该基类名中成员的访问权限,其规则与单继承情况一样。
  • 多重继承情况下如果多个基类间成员重名时,按如下方式进行处理:对派生类而言,不加类名限定时默认访问的是派生类的成员;而要访问基类重名成员时,要通过类名加以限定。
#include <iostream>
using namespace std;
class CB1
{
public:
	int a;
	CB1(int x):a(x){};
	void showa()
	{
		cout<<"Class CB1::a="<<a<<endl;
	}
};

class CB2
{
public:
	int a;
	CB2(int x):a(x){};
	void showa()
	{
		cout<<"Class CB2::a="<<a<<endl;
	}
};

//多重继承,两个基类
class CD:public CB1,public CB2
{
public:
	int a;			//与两个基类成员变量a重名
	CD(int x,int y,int z):CB1(x),CB2(y),a(z){};

	void showa()	//与两个基类成员函数showa()重名
	{
		cout<<"Class CD::a="<<a<<endl;
	};

	void print3a()
	{
		cout<<"CD::a="<<a<<endl;
		cout<<"CB1::a="<<CB1::a<<endl;
		cout<<"CB2::a="<<CB2::a<<endl;
	};
};

int main()
{
	CB1 CB1obj(10);
	CB1obj.showa();

	CD CDobj(100,200,300);
	//调用派生类的showa()
	CDobj.showa();		
	//调用基类CB1的showa()
	CDobj.CB1::showa();
	//访问派生类CB2成员a
	CDobj.CB2::a;
}
  • 如果派生类中新增了同名成员,则派生类成员将隐藏所有基类的同名成员。使用“派生类对象名.成员名”或“派生类对象指针->成员名”的方式可以唯一标识和访问派生类新增成员。这种情况下,不会产生二义性。
  • 如果派生类中没有新增同名成员,当满足访问权限时,使用“派生类对象名.成员名”或“派生类对象指针->成员名”方式时,系统无法判断到底是调用哪个基类的成员,从而产生二义性。为了避免二义性,必须通过基类名和作用域分辨符来标识成员
  • 当要访问派生类对象中的某个变量时,添加“基类::”作为前缀,指明需要访问从哪个基类继承来的,从而可以排除二义性。

第二节 访问控制

设计继承类时,需要使用继承方式说明符指明派生类的继承方式。继承方式说明符可以是public(公有继承)、private(私有继承)或protected(保护继承)

一、公有继承

在这里插入图片描述

二、兼容性规则

  • 类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,也称为赋值兼容规则
  • 在公有派生的情况下,有以下3条类型兼容规则。
    • 派生类的对象可以赋值给基类对象
    • 派生类对象可以用来初始化基类引用
    • 派生类对象的地址可以赋值给基类指针,即派生类的指针可以赋值给基类的指针
  • 上述3条规则反过来是不成立的。例如,不能把基类对象赋值给派生类对象。在进行替代之后,派生类对象就可以作为基类的对象使用了,但只能使用从基类继承的成员。

  如果类B为基类,类D为类B的公有派生类,则类D中包含了基类B中除构造函数、析构函数之外的所有成员。这时,根据类型兼容规则,在基类B的对象可以出现的任何地方,都可以用派生类D的对象来替代。假设有以下的声明:
    class B{…}
    class D : public B{…}
    B b1,*pb1;
    D d1;
  这时,派生类对象可以隐含转换为基类对象,即用派生类对象中从基类继承来的成员变量的值,逐个为基类对象的成员变量的值进行赋值。
  b1=d1;
  派生类的对象也可以用来初始化基类对象的引用,即
  B &rb=d1;
  派生类对象的地址可以隐含转换为指向基类的指针,即派生类对象的地址赋给基类指针:
  pb1=&d1;
  由于类型兼容规则的引入,对于基类及其公有派生类的对象,可以使用相同的函数统一进行处理。因为当函数的形参为基类的对象(或引用、指针)时,实参可以是派生类的对象(或指针),从而没有必要为每一个类设计单独的模块,大大提高了程序的效率。

#include <iostream>
using namespace std;
class A
{
	int an;
public:
	A(){};
	A(int n):an(n){};
	void print()
	{
		cout<<"A的对象:an:"<<an;
	}
	void print(int k)
	{
		cout<<"an:"<<an;
	}
};
//公有派生
class B:public A
{
	int bn;
public:
	B(int n):A(2*n),bn(n){};
	void print()
	{
		cout<<"\n B的对象:";
		A::print(1);
		cout<<",bn"<<bn<<endl;
	};
};

int main()
{
	A a(10);
	B b(20);
	a.print();	//A的对象:an:10
	b.print();	//B的对象:an:40,bn=20
	//派生类对象赋值给基类对象
	a=b;
	a.print();	//A的对象:an:40
	b.print();	//B的对象:an:40,bn=20

	return 0;
}

三、私有继承

第一级派生类中第二级派生类中基类与派生类外
基类的公有成员直接访问不可访问不可访问
基类的保护成员直接访问不可访问不可访问
基类的私有成员调用公有函数访问不可访问不可访问

四、保护继承

  • 保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可以直接访问。这样,派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但在类外通过派生类的对象无法直接访问它们。

第三节 派生类的构造函数和析构函数

  派生类并不继承基类的构造函数,所以需要在派生类的构造函数中调用基类的构造函数,以完成对从基类继承的成员变量的初始化工作。具体来说,派生类对象在创建时,除了要调用自身的构造函数进行初始化外,还要调用基类的构造函数初始化其包含的基类成员变量。
  在执行一个派生类的构造函数之前,总是先执行基类的构造函数。派生类对象消亡时,先执行派生类的析构函数,再执行基类的析构函数。

一、构造函数和析构函数

定义派生类构造函数的一般格式如下:
  派生类名::派生类名(参数表):基类名1(基类1 初始化参数表),…,基类名m(基类m初始化参数表),成员对象名1(成员对象1 初始化参数表),…,成员对象名n(成员对象n 初始化参数表)
  {
    类构造函数函数体,可用来进行其他初始化操作
  }

#include <iostream>
using namespace std;
//基类
class BaseClass
{
protected:
	int v1,v2;
public:
	BaseClass();
	BaseClass(int,int);	
	~BaseClass();	//声明析构函数
};
BaseClass::BaseClass()
{
	cout<<"BaseClass无参构造函数"<<endl;
};
BaseClass::BaseClass(int m,int n)
{
	v1=m;v1=n;
	cout<<"BaseClass2个参数构造函数"<<endl;
};
//定义析构函数
BaseClass::~BaseClass()
{
	cout<<"BaseClass析构函数"<<endl;
};

//定义公有继承的派生类
class DerivedClass:public BaseClass
{
	int v3;
public:
	DerivedClass(){cout<<"DerivedClass无参构造函数"<<endl;};
	DerivedClass(int k):v3(k){cout<<"DerivedClass1个参数构造函数"<<endl;};
	DerivedClass(int m,int n,int k):BaseClass(m,n),v3(k){cout<<"DerivedClass3个参数构造函数"<<endl;};
	~DerivedClass(){cout<<"DerivedClass析构函数"<<endl;};
};

int main()
{
	cout<<"无参对象的创建"<<endl;
	BaseClass test1;	//基类对象
	//创建派生类对象时:基类构造函数 -> 派生类构造函数 -> 派生类析构函数 -> 基类析构函数
	DerivedClass test2;	//派生类对象

	return 0 ;
};

在这里插入图片描述

  • 派生类构造函数执行的一般次序如下:
    • ① 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)
    • ② 对派生类新增的成员变量初始化,调用顺序按照它们在类中声明的顺序
    • ③ 执行派生类的构造函数体中的内容。
  • 构造函数初始化列表中基类名、对象名之间的次序无关紧要,它们各自出现的顺序可以是任意的,无论它们的顺序怎样安排,基类构造函数的调用和各个成员变量的初始化顺序都是确定的。

二、复制构造函数

  • 对于一个类,如果程序中没有定义复制构造函数,则编译器会自动生成一个隐含的复制构造函数,这个隐含的复制构造函数会自动调用基类的复制构造函数,对派生类新增的成员对象执行复制。
  • 如果要为派生类编写复制构造函数,一般也需要为基类相应的复制构造函数传递参数,但并不是必须的。
#include <iostream>
using namespace std;

class A
{
private:
	int i;
public:
	//默认构造函数
	A():i(100){	cout<<"类A默认构造函数"<<endl;	};
	//复制构造函数
	A(const A &s):i(s.i){	cout<<"类A复制构造函数"<<endl;	};

	int getValue();		//取值
	void setValue(int);	//设置值
};
int A::getValue(){	return i;	};
void A::setValue(int k){	i=k;	};

//公有派生类
class B:public A
{
private:
	float f;
public:
	//默认构造函数
	B():f(20.1){	cout<<"类B默认构造函数"<<endl;	};
	//复制构造函数
	B(const B &v):A(v),f(v.f){	cout<<"类B复制构造函数"<<endl;	};

	float getValue();
	int getValue1(){	return A::getValue();	};
};
//重写基类函数,改变了返回值类型
float B::getValue(){	return f;	};

int main()
{
	A a;		//调用A类默认构造函数
	B b;		//调用A类默认构造函数 -> B类默认构造函数
	B bb(b);	//调用A类复制构造函数 -> B类复制构造函数

	return 0;
}

在这里插入图片描述

#include <iostream>
using namespace std;
class CBase
{
public:
	CBase(){};
	CBase(CBase &c){	cout<<"CBase::复制构造函数"<<endl;	}
	CBase & operator=(const CBase &b)
	{
		cout<<"CBase::operator="<<endl;
		return *this;
	};
};

class CDerived:public CBase
{
public:
	CDerived()
	{
		cout<<"CDrived::复制构造函数"<<endl;
	};
};

int main()
{
	CDerived d1,d2;
	//d3初始化过程中会调用CBase的复制构造函数
	CDerived d3(d1);

	d2=d1;	//会调用类CBase重载的“=”运算符

	return 0;
}

在这里插入图片描述

三、多重继承的构造函数和析构函数

  • 当创建有多个基类的派生类的对象时,按照类定义中给出的基类的顺序,依次调用它们的构造函数,再调用派生类的构造函数。对象消亡时,按照构造函数调用的次序的逆,调用析构函数。

例:class DerivedClass : public BaseClass1, public BaseClass2
  先执行基类BaseClass1的构造函数,再执行基类BaSeClass2的构造函数,然后执行派生类DerivedClass的构造函数

第四节 类之间的关系

  • 使用已有类编写新的类有两种方式:继承和组合。这也形成类和类之间的两种基本关系:继承关系和组合关系(组合关系也就是第三章第六节提到的包含关系)。
    • 继承关系也称为“is a”关系或“是”关系。
    • 组合关系也称为“has a”关系或“有”关系,表现为封闭类,即一个类以另一个类的对象作为成员变量。

1、封闭类的派生

如果一个类的成员变量是另一个类的对象,则为封闭类。定义封闭类构造函数的一般形式如下:
  类名::类名(形参表):内嵌对象1(形参表),内嵌对象2(形参表),…
  {
    类体
  }
其中,
“内嵌对象1(形参表),内嵌对象2(形参表),…”是初始化列表,其作用是对内嵌对象进行初始化。

2、互包含关系的类

  • 在处理相对复杂的问题而需要考虑类的组合时,很可能遇到两个类相互引用的情况,这种情况称为循环依赖。举例如下:
//类A的定义
class A
{
public:
	//以类B对象为形参的成员函数
	void f(B b);
}//类B的定义
class B
{
public:
	//以类A对象为形参的成员函数
	void g(A a);
}

第五节 多层次的派生

  • 在C++中,派生可以是多层次的。例如,类CStudent派生类CGraduatedStudent,而后者又可以派生CDoctorStudent等。总之,类A派生类B,类B可以再派生类C,类C又能够派生类D,以此类推。在这种情况下,称类A是类B的直接基类,类B是类C的直接基类,类A是类C的间接基类。当然,类A也是类D的间接基类。
    • 在定义派生类时,只需写直接基类,不需写间接基类。派生类沿着类的层次自动向上继承它所有的直接和间接基类的成员。在C++中,类之间的继承关系具有 传递性
  • 派生类的成员包括派生类自己定义的成员、直接基类中定义的成员及所有间接基类中定义的全部成员。
  • 当生成派生类的对象时,会从最顶层的基类开始逐层往下执行所有基类的构造函数,最后执行派生类自身的构造函数;当派生类对象消亡时,会先执行自身的析构函数,然后自底向上依次执行各个基类的析构函数

第六节 基类与派生类指针的互相转换

  • 在公有派生的情况下,因为派生类对象也是基类对象,所以派生类对象可以赋给基类对象。
  • 对于指针类型,可以使用基类指针指向派生类对象,也可以将派生类的指针直接赋值给基类指针。但即使基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类中没有而仅在派生类中定义的成员函数。
#include <iostream>
using namespace std;

class CBase
{
protected:
	int n;
public:
	CBase(int i):n(i){};
	void print(){	cout<<"CBase:n="<<n<<endl;	};
};

class CDerived:public CBase
{
public:
	int v;
	CDerived(int i):CBase(i),v(2*i){};
	void Func(){};
	void print()
	{
		cout<<"CDerived:n="<<n<<endl;
		cout<<"CDerived:v="<<v<<endl;
	};
};

int main()
{
	CDerived objDerived(3);
	CBase objeBase(5);
	//使用基类指针指向派生类对象
	CBase *pBase = &objDerived;

	cout<<"使用派生类指针调用函数"<<endl;
	//写法二: CDerived *pDerived;pDerived=&objDerived;
	CDerived *pDerived = &objDerived;
	//调用的是派生类中的函数:n=3,v=6
	pDerived->print();	

	cout<<"使用基类指针调用函数"<<endl;
	//基类指针 = 派生类指针(√);派生类指针=基类指针(×):pDerived = pBase
	pBase = pDerived;	
	pBase->print();		//调用的是基类中的函数:n=3(注:而不是n=5)
	/** 注:但即使基类指针指向的是一个派生类的对象,
		也不能通过基类指针访问基类中没有而仅在派生类中定义的成员函数。
		pBase -> Func();	//×,通过基类指针不能调用派生类函数
	*/

	pDerived = (CDerived*)pBase;	//强制类型转换,派生类指针=基类指针
	cout<<"使用派生类指针调用函数"<<endl;
	pDerived->print();	//调用的是派生类中的函数

	return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值