1.多重继承派生类
- 除去一个类从一个基类派生, C++还支持一个派生类同时继承多个基类。
这样理解:子女既可以继承父亲的特点,也可以继承母亲的特点 - 多重继承派生类的定义
如果已经定义了多个基类, 那么定义多重继承的派生类的形式为:
class 派生类名:访问标号1 基类名1,访问标号2 基类名2,...
{ //类体
成员列表
};
class A { };
class B : public A { }; //A→B,类A派生出类B
class C : public A { }; //A→C
class D : public B,public C { }; //B→D,C→D
- 多重继承派生类的构造函数
多重继承派生类的构造函数形式与单一继承时的构造函数形式基本相同, 只是在派生类的构造函数初始化列表中调用多个基类构造函数。
一般形式为:
派生类名(形式参数列表) : 基类名1(基类1构造函数实参列表),
基类名2(基类2构造函数实参列表),
…,
子对象名1(子对象1属类构造函数实参列表),
…,
派生类初始化列表
{
派生类初始化函数体
}
-
其调用顺序是:
①调用基类构造函数, 各个基类按定义时的次序先后调用;
②调用子对象构造函数, 各个子对象按声明时的次序先后调用;
③执行派生类初始化列表;
④执行派生类初始化函数体; -
eg:
#include <iostream>
using namespace std;
class Base1
{
private:
int b1;
public:
//构造函数的重载
Base1()
{
b1=0;
cout<<"默认构造Base1: "<<"b1= "<<b1<<endl;
}
Base1(int i)
{
b1=i;
cout<<"构造Base1: "<<"b1= "<<b1<<endl;
}
};
class Base2
{
private:
int b2;
public:
Base2()
{
b2=0;
cout<<"默认构造Base2: "<<"b2= "<<b2<<endl;
}
Base2(int j)
{
b2=j;
cout<<"构造Base2: "<<"b2= "<<b2<<endl;
}
}
class Base3
{
public:
Base()
{
cout<<"默认构造Base3: "<<endl;
}
};
class Derive:public Base1,public Base2,public Base3
{
private://有三个子对象
Base1 memBase1;
Base2 memBase2;
Base3 memBase3;
public:
Derive()
{
cout<<"默认构造Derive"<<endl;
}
Derive(int a,int b,int c,int d):
//没有显示的调用base3的构造函数,而是系统自动调用base3构造函数,因为它只有一个不带
//参数的构造函数
//先对base1,base2,base3调用构造函数,再对子对象memBase1,memBase2,memBase3
//调用构造函数
Base1(a),Base2(b),memberBase1(c),memBase2(d)
{
cout<<"默认构造Derive."<<endl;
}
}
int main()
{
cout<<"\n创建派生类对象obj1: "<<endl;
Derive obj1;//调用不带参数的构造函数,即:从基类开始,先调用base1基类的构造函数,再调用
//base2基类的构造函数,再调用base3基类的构造函数,再调用子对象的构造函数
//最后调用派生类的构造函数
cout<<"\n创建派生类对象obj2(1,2,3,4): "<<endl;
Derive obj2(1,2,3,4);//构造函数的调用顺序:基类的构造函数,子对象的构造函数,派生类的构造函数
return 0;
}
运行结果:
创建派生类对象obj1:
默认构造Base1: b1=0
默认构造Base2: b2=0
默认构造Base3:
默认构造Base1: b1=0
默认构造Base2: b2=0
默认构造Base3:
默认构造Derive.
创建派生类对象obj2(1,2,3,4):
构造Base1: b1=1
构造Base2: b2=2
默认构造Base3:
构造Base1: b1=3
构造Base2: b2=4
默认构造Base3:
构造Derive.
2.二义性问题
- 多重继承时, 多个基类可能出现同名的成员。
在派生类中如果使用一个表达式的含义能解释为可以访问多个基类的成员, 则这种对基
类成员的访问就是不确定的, 称这种访问具有二义性。
C++要求派生类对基类成员的访问必须是无二义性的。 - eg:
class A
{
public:
void fun()
{
cout<<"a.fun"<<endl;
}
};
class B
{
public:
void fun()
{
cout<<"b.fun"<<endl;
}
void gun()
{
cout<<"b.gun"<<endl;
}
};
class C:public A,public B//类C有5个成员,因为有继承的呀
{
public:
//派生类的gun覆盖了基类的gun
void gun()//重写gun(),这里不会出现二义性,因为相当于重写了gun成员
{
cout<<"c.gun"<<endl;
}
void hun()
{
fun();//出现二义性,继承了A的fun成员和继承了B的fun成员,无法区分
}
};
int main()
{
C c,*p=&c;
return 0;
}
- 使用成员名限定可以消除二义性, 例如:
c.A::fun(); //成员名限定消除二义性,表示A类里面的fun成员
c.B::fun(); //成员名限定消除二义性
p->A::fun(); //成员名限定消除二义性
p->B::fun(); //成员名限定消除二义性
- 基本形式为:
对象名.基类名::成员名
对象指针名->基类名::成员名
- 名字支配规则
(1)C++对于在不同的作用域声明的名字, 可见性原则是: 如果存在两个或多个具有包含关系的作用域, 外层声明了一个名字, 而内层没有再次声明相同的名字, 那么外层名字在内层可见; 如果在内层声明了相同的名字, 则外层名字在内层不可见, 这时称内层名字隐藏(或覆盖) 了外层名字, 这种现象称为隐藏规则(和全局变量和局部变量一样去理解)
(2)在类的派生层次结构中, 基类的成员和派生类新增的成员都具有类作用域, 二者的作用域是不同的: 基类在外层, 派生类在内层。
(3)如果派生类声明了一个和基类成员同名的新成员, 派生的新成员就覆盖了基类同名成员, 直接使用成员名只能访问到派生类的成员。
(4)如果派生类中声明了与基类成员函数同名的新函数, 即使函数的参数不同, 从基类继承的同名函数的所有重载形式也都会被覆盖。
(5)如果要访问被覆盖的成员, 就需要使用基类名和作用域限定运算符来限定。
(6)派生类D中的名字N覆盖基类B中同名的名字N, 称为名字支配规则。
如果一个名字支配另一个名字, 则二者之间不存在二义性, 当选择该名字时, 使用支配者的名字, 如:
(7)如果要使用被支配者的名字, 则应使用成员名限定, 例如:
(6)的eg:
c.gun();//使用C::gun
(7)的eg:
c.B::gun(); //使用B::gun,使用基类的名字,而不是派生类定义的名字