本文主要介绍了C++中面向对象三大特性之一的多态的相关概念,包含了单继承、多继承、菱形继承以及虚拟继承,最后比较了继承和组合两种类之间的关系。
继承机制是面向对象程序设计使代码可以复用的有效手段。它允许程序员在原有类基础上进行扩展,增添新的功能,这样产生的类,叫做派生类/子类,其中的原有类叫做基类/父类。(派生类对应基类,子类对应父类,在使用时尽量对应使用)
之前我们了解的代码复用,比如模板类,模板函数等都属于函数复用,而继承属于类设计层次的复用。
继承后父类的成员(成员变量+成员函数)都变成子类的一部分,子类复用了父类的成员。
继承方式和访问限定符所使用的关键字是一样的,但他们的功能不同。
可以用下面的代码实际感受一下这三种继承方式。
class Person
{
public:
void Print()
{
cout << _name << endl;
}
protected:
string _name; // 姓名
private:
int _age; // 年龄
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:
int _stunum; // 学号
};
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public:
int _No; // 学号
};
void Test()
{
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
}
基类::基类成员
这种方式显示访问。例子:
class Person
{
protected:
string _name = "张三"; // 姓名
int _num = 666; // 身份证号
};
class Student : public Person
{
public:
void Print()
{
cout << " 姓名:" << _name << endl;
cout << " 身份证号:" << Person::_num << endl;
cout << " 学号:" << _num << endl;
}
protected:
int _num = 999; // 学号
};
void Test()
{
Student s1;
s1.Print();
};
int main()
{
Test();
return 0;
}
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" << i << endl;
}
};
void Test()
{
B b;
b.fun(10);
};
注意:基类和派生类的fun函数并不构成重载,因为他们在不同的作用域,他们是隐藏关系
派生类构造函数必须调用基类的构造函数初始化基类那一部分成员,如果基类没有默认构造函数,派生类就必须在初始化列表处显示的调用基类构造函数。
派生类对象初始化时,会先调用基类构造函数,再调用派生类构造函数。
必须调用基类的拷贝构造完成基类部分的拷贝构造。
必须调用基类的赋值运算符重载完成基类部分的赋值运算符重载。
派生类额析构函数会在调用结束后自动调用基类的析构函数清理基类成员,确保先清理派生类的成员再清理基类的成员的析构顺序。
派生类对象析构先调用派生类析构函数再调用基类析构函数。
编译器会对析构函数的函数名进行特殊处理,即派生类和基类的析构函数名都会被处理为destructor()。因此派生类和基类的析构函数回构成隐藏。
友元关系不能继承,即基类的友元不能访问派生类的private和protected成员
基类的静态成员,在整个继承体系中都只有一个这样的成员,即无论有多少个派生类,都只有一个static实例。
由于C++支持多继承,因此可能会出现菱形继承(一个派生类继承的多个基类拥有共同的基类)的情况。
如图,类D同时继承了类B和类C,类B和类C继承了相同的基类类A,则类A的成员在类D中会出现两份,会造成数据冗余和二义性的问题。
为了解决菱形继承造成的数据冗余和二义性的问题,我们引入了虚拟继承。(注意虚拟继承只能用在菱形继承中)
虚拟继承解决问题的原理:
简单来说是将D类中的A类成员放到所有成员的最下面,此时这一份A同时属于B和C。
那么B和C如何找到A呢?通过B和C中的两个指针分别指向的两张表,这两个指针叫做虚基表指针,这两张表叫做虚基表。虚基表中存放着A成员偏移量。(虚基表,实际上是一个数组)通过偏移量可以找到下面的A。
1.一般不要设计出多继承,否则可能会产生菱形继承,就需要使用虚拟继承,就会导致问题便得更加复杂。
2.继承和组合
以上就是今天要讲的内容,本文介绍了C++中继承的相关概念。本文作者目前也是正在学习C++相关的知识,如果文章中的内容有错误或者不严谨的部分,欢迎大家在评论区指出,也欢迎大家在评论区提问、交流。
最后,如果本篇文章对你有所启发的话,希望可以多多支持作者,谢谢大家!