(一四八)多重继承

多重继承(MI,指的是派生类,同时继承两个或者多个类。

 

公有MI表示的是is-a关系,在继承的时候,需要用public来限制每一个基类。

 

私有和保护MI表示的has-a关系。MI和单继承在某些程度上很类似。

 

 

MI相对单继承来说,带来了很多问题,例如:

①从两个不同基类继承同名方法(例如基类A和基类B都有show()函数);

 

②从两个或更多的相关基类那里继承同一个类的多个实例(不懂)。

 

 

 

 

虚基类:

假如派生类D由两个基类BC继承而来,而BC都是A的派生类。那么D将同时包含2ABC各包含一个A);

 

但是,由于我们只需要一个ABC重复了A的部分)。因此,不能简单的使用Class D:public B,public C 这样的方式进行继承。

 

针对这个问题,于是需要使用 虚基类,关键字为:virtual

 

虚基类的意义在于,当MI的两个基类(BC)都是基类,且其都是A的派生类时(A在此时作为BC的虚基类,如class B :public virtual A),那么派生类D将只包含一个A,且在D的构造函数中,BC将不传递值给其基类A,需要D额外调用基类A的构造函数。

 

例如:

A的构造函数:

A();

 

A::A(const string & na, int m);

 

B的构造函数:

B::B() :A(), b(0)

{

}

 

B::B(const stringnaint aaint bb) : A(naaa), b(bb)

{

}

 

C的构造函数:

C::C() :A(), c(0)

{

}

 

C::C(const stringnaint Aint cc) : A(naA), c(cc)

{

}

 

D继承BC而来,D的构造函数;

D::D():A(),B(),C()

{

}

 

D::D(const string&naint aaint bbint cc) : A(naaa), B(naaabb), C(naaacc)

{

}

 

在派生类类D的第二个构造函数中,其中第一个和第二个参数naaa,作为基类B和基类C的参数,同时还调用了A的构造函数。

 

在非虚基类的情况下,这种方式是错误的,但是在遇见虚基类的情况下,这是唯一选择。

 

如代码:

类声明:

class A		//由于有纯虚函数,所以A为抽象基类
{
	string name;
	int a;
protected:
	virtual void get();	//设置名字和变量a
	virtual void data();	//输出名字和变量a
public:
	A();
	A::A(const string & na, int m);
	virtual ~A() = 0;	//纯虚析构函数A
	virtual void set() = 0;	//设置名字
	virtual void show() = 0;	//输出
};
class B :public virtual A
{
	int b;
protected:
	void get();	//设置名字和变量a
	void data();	//输出名字和变量a
public:
	B();
	B(const string& na, int aa, int bb);
	void set();	//设置名字
	void show();	//输出
};
class C :virtual public A
{
	int c;
protected:
	void get();	//设置名字和变量a
	void data();	//输出名字和变量a
public:
	C();
	C(const string& na, int aa, int cc);
	void set();	//设置名字
	void show();	//输出
};
class D :public B, public C
{
protected:
	void get();
	void data();
public:
	D();
	D(const string&na, int aa, int bb, int cc);
	void set();
	void show();
};

类方法:


A::A()
{
	name = "";
	a = 0;
}
A::A(const string & na, int m)
{
	name = na;
	a = m;
}
void A::get()
{
	cout << "请输入名字:";
	getline(cin, name);
	cout << "请输入a:";
	cin >> a;
	while (cin.get() != '\n')
		continue;		//直接放个分号也行呀,放continue是为了美观和看起来直接?
}
void A::data()
{
	cout << "名字:" << name << endl;
	cout << "a   :" << a << endl;
}

B::B() :A(), b(0)
{
}
B::B(const string& na, int aa, int bb) : A(na, aa), b(bb)
{
}
void B::get()
{
	cout << "请输入b:" << endl;
	cin >> b;
	while (cin.get() != '\n')
		continue;
}
void B::data()
{
	cout << "b   :" << b << endl;
}
void B::set()
{
	cout << "类B:" << endl;
	A::get();
	get();
}
void B::show()
{
	cout << "类B:" << endl;
	A::data();
	cout << "b   :" << b << endl;
}

C::C() :A(), c(0)
{
}
C::C(const string& na, int A, int cc) : A(na, A), c(cc)
{
}
void C::get()
{
	cout << "请输入c:" << endl;
	cin >> c;
	while (cin.get() != '\n')
		continue;
}
void C::data()
{
	cout << "c   :" << c << endl;
}
void C::set()
{
	cout << "类C:" << endl;
	A::get();
	get();
}
void C::show()
{
	cout << "类C:" << endl;
	A::data();
	cout << "c   :" << c << endl;
}

D::D():A(),B(),C()
{
}
D::D(const string&na, int aa, int bb, int cc) : A(na, aa), B(na, aa, bb), C(na, aa, cc)
{
}
void D::get()
{
	B::get();
	C::get();
}
void D::data()
{
	B::data();
	C::data();
}
void D::set()
{
	cout << "类D:" << endl;
	A::get();
	get();
}
void D::show()
{
	cout << "类D:" << endl;
	A::data();
	data();
}

测试程序:


</pre><pre name="code" class="cpp">int main()
{
	A* pr[4];	//声明一个A基类指针数组
	for (int i = 0; i < 4; i++)
	{
		cout << "选择第"<<i+1<<"个使用的类:\nb.B类\tc.C类\td.D类" << endl;
		char q;
		cin >> q;
		while (strchr("bcd", q) == nullptr)
		{
			cout << "输入错误,请输入b或者c或者d:";
			cin >> q;
		}
		if (q == 'b')
			pr[i] = new B;
		else if (q == 'c')
			pr[i] = new C;
		else if (q == 'd')
			pr[i] = new D;

		cin.get();
		pr[i]->set();		//指针调用函数使用->
	}


	for (int i = 0; i < 4; i++)
		(*pr[i]).show();

	system("pause");
	return 0;
}

测试:

选择第1个使用的类:
b.B类   c.C类   d.D类
a
输入错误,请输入b或者c或者d:b
类B:
请输入名字:aa
请输入a:1
请输入b: 1
选择第2个使用的类:
b.B类   c.C类   d.D类
c
类C:
请输入名字:b b
请输入a:2
请输入c: 2
选择第3个使用的类:
b.B类   c.C类   d.D类
d
类D:
请输入名字:CC
请输入a:3
请输入b: 3
请输入c: 3
选择第4个使用的类:
b.B类   c.C类   d.D类
d
类D:
请输入名字:qqee
请输入a:4
请输入b: 4
请输入c: 4
类B:
名字:aa
a   :1
b   :1
类C:
名字:b b
a   :2
c   :2
类D:
名字:CC
a   :3
b   :3
c   :3
类D:
名字:qqee
a   :4
b   :4
c   :4
请按任意键继续. . .


总结:

①虚基类:

虚基类是可能涉及重复的基类,在这里A作为虚基类。因为在派生类D之中,基类BCA派生而来(因此多重继承时,会在D中产生重复部分),于是,在声明类B和类C时,A既是公有继承,又是作为虚基类继承。

 

具体形式则是在声明类B和类C时,让A作为虚基类;

声明类D时,BC正常声明(但应分别声明为public,因为默认是private私有继承)

 

 

②另外,A不仅是虚基类,还是抽象基类(有纯虚函数),因此,指针是不能指向类A的(无法创建类A的对象)。

 

③方法strchr(字符串,字符) 的作用是检查字符在字符串中首次出现时的地址。

例如字符a在字符串"qqabb"中,首次出现的地址是字符串的地址+2char的宽度。如果字符没有在字符串中出现,则返回nullptr

因此,这个方法可以用来检测某字符是否是我们预想中的几个字符之一(如果不是,则返回null,用关系表达式可以作为判断条件)。

 

 

 

当基类和非基类混用时:

假如基类M分别派生出ABCD四个派生类,其中:

M对于AB,是用作虚基类;

M对于CD,是用作非虚基类(即普通继承);

而派生类N,由ABCD四个基类共同派生而来(普通继承)。

M(派生)

AM是虚基类)

(MI)

BM虚)

CM非虚)

DM非虚)

 

那么对于类N来说,从虚派生祖先(类A和类B)共同继承一个类M的子对象(如果还有更多虚派生祖先,例如EFGH都是M作为虚基类并且派生出N,那么也一样是只继承一个);

从每一个非虚派生祖先(类C和类D)分别继承一个类M的子对象。

于是,类N有三个类M的子对象(AB共同给一个,CD分别给一个)。

 

 

 

 

虚基类和支配:

关于二义性:

对于普通MI来说,假如有两个基类有同名方法,派生类无同名方法,那么不使用作用域解析运算符的话,就导致二义性(因为编译器不知道你要调用哪一个基类的方法)。

 

对于虚基类来说,

①首先,派生类的同名方法优先于基类的同名方法。例如AB的虚基类,都有方法show(),于是在B中用show()则指的是B的。BC的基类,C中没有方法show(),在C中用show()则默认指B::show()

 

②其次,在①的基础上,有类D是类A的派生类,且没有show()方法(于是D中的show()A::show())。

然后EDC的派生类(于是同时出现C类中的B::show()D类中的A::show()),

因为类B是由类A派生而来的,因此类B中的show()将优先于类A中的show()

 

也就是说:在有 虚基类 存在的 前提下 ,在一个派生类中,如果有两个同名方法,且一个方法所在的类(假设为类B)是由另一个方法所在的类(假设为类A,且应是作为B的虚基类)派生而来的,那么派生出来的那个类的方法,将优于其基类的方法。——不懂

 

 

那么假如类A不是虚基类,但类BC的虚基类,其结果有所变化么?

实验测试表明:

假如A派生BB(作为虚基类)派生C

A派生D

CD派生E

同名方法存在与AB之中,那么依然会导致二义性(通过E调用同名方法)。

 

只有当A作为虚基类分别派生出BD时(且方法存在于AB之中),以上流程才不会导致二义性。

像①无虚基类存在;②B是虚基类;③CD中一个或者两个作为虚基类;都会导致二义性。

 

推测(应该是没问题的):

当虚基类和其派生类中存在同名方法时,派生类的同名方法优先于虚基类且不会导致二义性,否则会导致二义性。

确认:

在使用非虚基类的情况下,必须使用作用域解析运算符来区分同名方法,否则会引起二义性(除非该派生类重新定义了同名方法,则自动调用该派生类的同名方法)。

 

 

总结:

①有间接虚基类的派生类包含直接调用间接基类构造函数的构造函数,这对于间接非虚基类来说是非法的;

(这个是指ABC的虚基类,BCD的基类,于是对于D来说,他的构造函数在调用BC的构造函数时,还必须同时调用A的构造函数)

(但如果A不是BC的虚基类,那么这样做是非法的。注意,如果AB的虚基类但不是C的虚基类,也是非法的)

 

②在面对虚基类时,通过优先规则来解决二义性。

 

总的来说,MI不是很明白,书上讲的略简单了一些,我觉得基础的方法用用还行。除非遇见复杂点的情况,然后仔细研究,才能搞懂MI了。

 

 


你可能感兴趣的:(MI,多重继承,虚基类)