多继承
定义
多继承,顾名思义就是继承多个父类。以往派生类只有一个基类,叫作单继承。派生类有两个或者两个以上的基类,就叫作多继承。
多继承的语法也很简单,将多个基类用逗号隔开。
例如已声明了类A、类B和类C,那么可以这样来声明派生类D:
class D: public A, private B, protected C
{
//类D新增加的成员
}
可以看到一个细节,可以分别用不同的方式继承基类,比如public, private, protected。但我们学过继承(可以看之前的博文),继承方式尽量只选public。
派生类初始化多个基类中的成员变量
派生类初始化基类中的private成员变量,都需要借助初始化列表的初始化方式,并且调用基类中的构造函数,间接的去初始化基类中的成员变量。
class Person
{
int m_age;
public:
Person(int age): m_age(age){
cout << "Person::Person age = " << m_age << endl;
}
};
class Student
{
int m_score;
public:
Student(int score):m_score(score){
cout << "Student::Student m_score = " << m_score << endl;
}
};
class Undergraduate : public Person , public Student
{
int m_grade;
public:
// 由于在工程中 m_age和m_score大多数是私有的,所以不可以直接对其进行赋值,下方代码报错
Undergraduate(int age, int score, int grade): m_age(age),m_score(score),m_grade(grade){}
// 我们知道,子类构造函数可以调用父类的构造函数,且必须通过初始化列表
// 所以,子类初始化父类中的成员变量,用子类构造函数调用父类构造函数的方法可以实现
Undergraduate(int age, int score, int grade) : Person(age), Student(score), m_grade(grade){
cout << "Undergraduate::Undergraduate m_grade = " << m_grade << endl;
}
};
所以我们在主函数中,实例Undergraduate对象时,(将对象放在栈空间或者堆空间)就要这样实例化:传三个参数,调用Undergraduate类中的有参构造函数,分别使得age=20;score=100;grade=4
int main()
{
Undergraduate ug(20,100,4);
Undergraduate *p_ptr = new Undergraduate(20, 100, 4);
// 这个的字节存储顺序是,先继承谁,谁的变量就放在前面
// class Undergraduate : public Person , public Student 先继承的Person类,所以m_age就放在了这块内存的前面
getchar();
return 0;
}
对象所在的内存空间,都有什么,怎么排列?
ug对象所在的内存里分别有m_age、m_score、m_grade,总共占12个字节。
这个的字节存储顺序是,先继承谁,谁的变量就放在前面。
由于是(class Undergraduate : public Person , public Student )先继承的Person类,所以m_age就放在了这块内存的前面
多继承时,派生类与基类同名函数问题
假设基类和派生类中存在同名的函数(不包括重载函数)。那派生类对象调用函数时,会出现什么情况呢?
class Person
{
int m_age;
public:
// 同名函数
void eat()
{
cout << "Person::eat()" << endl;
}
};
class Student
{
int m_score;
public:
// 同名函数
void eat()
{
cout << "Student::eat()" << endl;
}
};
class Undergraduate : public Person , public Student
{
int m_grade;
public:
//同名函数
void eat()
{
cout << "Undergraduate::eat()" << endl;
}
};
我们知道,继承会把基类中的函数一并继承下来。到时候派生类Undergraduate 里有三个eat函数,Ug.eat();会调用哪个函数呢?
结果是调用 Undergraduate类下的eat函数, 这无可厚非
但我想要调用父类中的eat怎么办呢? 看代码:
int main()
{
// 同名函数问题。
Undergraduate Ug;
Ug.eat(); //调用同名函数,此时调用 Undergraduate类下的eat 这无可厚非
// 但我想要调用父类中的eat怎么办呢? 下方代码可以解决
Ug.Person::eat();
Ug.Student::eat();
getchar();
return 0;
}
指定作用域,就可以指定去调用哪个类的函数,这是多继承时需要掌握的知识点
Ug.Person::eat();
Ug.Student::eat();
多继承时,派生类与基类同名成员变量问题
基类和派生类存在同名的成员变量,那派生类对象调用变量时,会出现什么情况呢?
class Person
{
int m_age;
public:
int m_same;
};
class Student
{
int m_score;
public:
int m_same;
};
class Undergraduate : public Person , public Student
{
int m_grade;
public:
int m_same;
};
不说废话了,调用规则和同名成员函数一样,需要指定作用域。
int main()
{
// 同名成员变量问题
Undergraduate UG;
UG.m_same = 10; //访问同名成员变量,此时访问Undergraduate类下m_same,这无可厚非
// 但我想要访问父类中的m_same怎么办呢? 下方代码可以解决
UG.Person::m_same = 20;
UG.Student::m_same = 30;
getchar();
return 0;
}
在汇编眼里,这三个同名变量,是不同名的。
子类想要创建无参构造函数,父类如果有构造函数,最起码得有无参构造函数
这里温习一个知识点,构造函数那块的知识点。子类想要创建无参构造函数,父类如果有构造函数,最起码得有无参构造函数。为什么突然温习这个知识点呢,因为我写代码时,在这里出错了,一时间找不到问题所在,然后翻了翻构造函数的文章,才想起来。
例子是这样的:有一个基类Student,和一个Undergraduate 类派生于Student。派生类想要初始化父类中的成员变量m_score,就必须通过调用基类中有参的构造函数初始化,到这里没问题。派生类存在有参构造函数,基类中也存在有参构造函数。但我此时有一个应用场景,希望创建对象时,调用Undergraduate 的无参构造函数,因此我在Undergraduate 类中声明了这个无参构造函数
class Student
{
int m_score;
public:
Student(int score):m_score(score){
cout << "Student::Student m_score = " << m_score << endl;
}
};
class Undergraduate : public Student
{
int m_grade;
public:
int m_same;
Undergraduate(){}//无参构造函数:子类想要创建无参构造函数,父类如果有构造函数,最起码得有无参构造函数
Undergraduate(int age, int grade) : Student(score), m_grade(grade){
cout << "Undergraduate::Undergraduate m_grade = " << m_grade << endl;
}
};
结果就报错了:类Student中没有默认的构造函数
我们知道,子类创建的构造函数会默认的调用父类中的无参构造函数。如果父类中没有构造函数,那我就不调用。如果父类中存在有参的构造函数,且没有无参构造函数,子类中的构造函数就必须显式的调用有参构造函数。
我这个程序出错的地方在于,子类中无参的构造函数,在父类没有无参构造函数的前提下,没有显式的调用有参构造函数。
解决方案:显式的调用有参构造函数(初始化列表的方式),或者在父类中声明无参的构造函数,那子类中的构造函数无需做任何操作,就可以默认的调用父类中无参的构造函数。
多继承总结
- 书写语法就是,继承多个基类,用逗号隔开
- 同名函数和同名变量的调用需要指定作用域
- 不建议使用,因为太过复杂,但要了解