继承与继承体系中派生类的对象模型

  继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。

继承格式


继承权限和访问限定符

继承权限和访问限定符
继承方式 基类public成员 基类protected成员 基类private成员

继承引起

的访问控制关系

变化概述

public继承 仍为public成员


扫描二维码关注公众号,回复: 1935880 查看本文章

仍为protected成员


不可见

基类的非私有成

员在子类的访问属性

都不变

protected继承 变为protected成员 仍为protected成员 不可见


基类的非私有成员

都成为子类的保护成员


private继承 变为private成员 变为private成员 不可见

基类的非私有成

员都成为子类的私有成员

【总结】

(1)基类private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。 可以看出保护成员限定符是因继承才出现的 

(2)public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象

解释:用到父类对象的地方均可用子类对象进行替换
(3)protected/private继承是一个

(3)protected/private继承是一个 实现继承 ,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承意味着is-implemented-in-terms-of(是根据……实 现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的  
(4)不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,
基类的私有成员存在但是在子类中不可见(不能访问)  
(5)使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式  

(6)在实际运用中一般使用都是public继承,极少场景下才会使用 protetced/private继承

派生类对象的构造与析构

继承体系下派生类和基类构造函数的调用次序

以下面的代码为例:

class Base
{
public:
	Base(int b):_b(b)
	{
		cout << "Base()" << endl;
	}
	
	~Base()
	{
		cout << "~Base()" << endl;
	}
	int _b;
};

class Derived :public Base
{
public:
	Derived(int d)
		:Base(2)
		,_d(d)
	{
		cout << "Derived()" << endl;
	}

	~Derived()
	{
		cout << "~Derived()" << endl;
	}
	int _d;
};

int main()
{
	Derived d(3);
	return 0;
}

若想构造一个派生类的对象,我们首先调用派生类的构造函数,在派生类的构造函数的初始化列表中调用基类的构造函数,先把基类构造好,然后返回到派生类中继续执行,直到把派生类构造好


【继承体系下派生类和基类析构函数的调用次序】

先调用派生类的析构函数,在派生类最后一条有效语句后调用基类的析构函数


继承体系下派生类的对象模型

【单继承】

单继承:子类只继承一个父类,如下:

class B
{
public:
	int _b;
};

class D :public B
{
public:
	int _d;
};

int main()
{
	D d;
	d._b = 1;
	d._d = 2;
	return 0;
}

派生类的对象模型:


【多继承】

多继承:一个派生类由两个基类继承而来,如下:

class B1
{
public:
	int _b1;
};

class B2
{
public:
	int _b2;
};

class D :public B1,public B2
{
public:
	int _d;
};

int main()
{
	D d;
	d._b1 = 1;
	d._b2 = 2;
	d._d = 3;
	return 0;
}

派生类D的对象模型


【菱形继承】

菱形继承:如图


代码如下:

菱形继承具有二义性:如上图,当派生类D要访问成员变量_b时,要指明是从类C1中继承下来的_b,还是从类C2中继承下来的_b,否则就会产生歧义

class B
{
public:
	int _b;
};

class C1:public B
{
public:
	int _c1;
};

class C2:public B
{
public:
	int _c2;
};


class D :public C1, public C2
{
public:
	int _d;
};

int main()
{
	D d;
	//d._b = 1;//具有二义性,是C1中的_b,还是C2中的_b
	d.C1::_b = 1;
	d.C2::_b = 2;
	d._c1 = 3;
	d._c2 = 4;
	d._d = 5;
	return 0;
}

派生类D的对象模型


【菱形虚拟继承】

由于上面的菱形继承,派生类D继承了两遍_b,要多开辟空间,还会造成二义性,菱形虚拟继承就解决了这个问题,菱形虚拟继承在派生类C1,C2的继承权限前面加上一个virtual关键字,这样就构成了菱形虚拟继承

如下代码:

class B
{
public:
	int _b;
};

class C1 :virtual public B
{
public:
	int _c1;
};

class C2 :virtual public B
{
public:
	int _c2;
};


class D :public C1, public C2
{
public:
	int _d;
};

int main()
{
	D d;
	d._b = 1;
	d._c1 = 2;
	d._c2 = 3;
	d._d = 4;
	return 0;
}

派生类D的对象模型



赋值兼容规则

在public继承权限下,子类和派生类对象之间有:

1)派生类对象可以赋值给基类对象,基类对象不能赋值给派生类对象

int main()
{
	B b;//定义基类对象b
	D d;	//定义派生类对象d
	b = d;//用派生类对象给基类对象b赋值
	//d = b;//不可以
	return 0;
}

解释:


2)基类的指针/引用可以指向派生类对象,派生类的指针/引用不可以指向基类对象

int main()
{
	B b1;//定义基类变量b1
	D d1;//定义派生类变量d1
	B& r = b1;//用基类的引用r指向派生类变量
	return 0;
}

解释:

此时的r是派生类中从基类继承过来部分的别名,r与d1中基类部分共享同一段内存单元,r与b1有相同的起始地址,如下图:


猜你喜欢

转载自blog.csdn.net/zimituanzi_/article/details/80911487