多继承 -- 能不使用的时候尽量不要使用
1)语法
Father类:
class Father {
public:
Father();
Father(const char* name = "无", int age = 0);
protected: // 子类中可以直接访问
string name;
int age;
};
Mother类:
class Mother {
public:
Mother();
Mother(const char* name = "无", int age = 0 ,const char* dance = "跳舞");
protected: // 为了能够在子类中访问
std::string name;
int age;
std::string dance;
};
Son类:
class Son :public Father,public Mother {
public:
Son();
Son(const char* name, int age,const char* dance,int play);
private:
int play;
};
代码通过儿子同时继承父类和母类来实现多继承的关系。
语法: 在子类的类名后加 : 继承方式 基类1, 继承方式 基类2 ... ,这样就可以实现多继承了。
2)多继承的内存分布(和继承的类似,就是由继承一个类变成多个类了)
3)多继承时,数据的初始化(构造函数的调用)
Son::Son(const char* name, int age,const char* dance, int play):
Father(name,age),Mother(dance){ // Father类已经初始化了name和ageMother类就不需要了
this->play = play;
}
和单继承一样,调用基类的构造函数来初始化从基类继承来的属性。
上面name和age已经使用Father初始化了,所以Mother只需要初始化dance就行了。这样写的前提是Mother中其它的属性有默认构造参数。
那么构造函数的调用顺序 :
class Son :public Father,public Mother。取决于子类定义的时候,那个继承的基类谁写在前面,就先调用谁的构造函数。
和后面子类构造函数定义时调用,基类的构造函数的书写顺序。
4)多继承的严重缺陷 -- 访问的二义性
从内存看,子类同时继承了父亲的name和age属性和母亲的name和age属性,当我们直接访问name和age的时候,编译器是不知道我们访问的是从母亲哪里继承的属性,还是父亲哪里继承的属性。
看代码:
getBaseMessage是定义在子类中的函数,我们在这个函数中直接访问,Son继承来的属性,name和age,会发现上面是错误的。
从内存分布我们知道,你直接通过子类访问name和age编译器不知道你访问的是从Father和Mother哪个里面的数据。就会出现访问的二义性,出现问题。
解决方式:
1. 我们之前在作用域的名称覆盖中说到过,只要我们在子类中定义了与基类相同的变量或者函数,那么就会覆盖外界的属性。 所以,我们可以在子类的内部定义一个name和age,这样就可以直接访问了,访问的是自己内部定义的属性。
2. 在访问的时候,指明访问的是哪个基类继承的属性就行。
上面,访问Father类继承的name,访问Mother类继承的age。
3. 第三种其实是一种封装的思想(对于基类共有函数)
我们在两个基类中定义了,同名函数cook(),与变量相同,子类同时继承了两个基类的函数,如果直接访问也会出现二义性,所以需要指明,类具体访问的是哪个类当中的。
但是,我们在main函数中这样访问不是很好,因为每次都要写类名。对于这种情况,我们可以将对基类函数的调用封装到子类当中,封装成一个函数。 --- 这样在main函数中调用就简化了。
问题: 那为什么要调用父类的函数呢? 在开发中,可能需要再子类中为了实现或者使用父类中函数的功能来效用父类的函数。