一、继承和派生的介绍
C++ 中的继承是类与类之间的关系,拿现实世界中的关系举例,就好像是儿子继承父亲的财产。
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。
从另一方面来说,派生(Derive)和继承是一个概念,被继承的类称为父类或基类,继承的类称为子类或派生类。“子类”和“父类”通常放在一起,“基类”和“派生类”通常放在一起。
一个基类可以有很多个派生类,一个派生类也可以有很多个基类。
派生类拥有基类的成员,同时也可以给自己定义新成员,以增强类的功能。
以下是两种典型的使用继承的场景:
-
当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。
-
当你需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员。
继承的写法:
class 派生类名:[继承方式] 基类名{
派生类的成员
};
二、三种继承方式
和类中的三个属性相同,继承方式也分为public(共有)、private(私有)、和protected(保护)继承方式。
类成员的访问权限由高到低依次为 public --> protected --> private,不同的继承方式会影响基类成员在派生类中的访问权限。
和类中的三个属性有所不同的是,protected 成员和 private 成员类似,不能通过对象访问。但在继承的条件下,派生类可以调用基类中的protected成员,但还是不能调用基类中的private成员。
通过不同的继承方式,基类成员在派生类对象中的访问权限如下图所示:
1、不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)
2、一般选择继承方式时,选择共有继承,最大限度的使用基类中的成员。
先看一段代码:
#include<iostream>
using namespace std;
class Father
{
private:
int m_car;
public:
Father();
int m_money;
protected:
int m_room;
};
class Son :public Father
{
public:
void fun();
};
Father::Father()
{
m_car = 1;
m_money = 100;
m_room = 5;
}
void Son::fun()
{
cout << this->m_money<<endl<<this->m_room << endl;
}
int main()
{
Son s1;
cout<<s1.m_money<<endl;
s1.fun();
system("pause");
return 0;
}
上面代码选择的是共有继承。
在派生类的fun
函数中,可以调用基类中的public和protected成员。
而创建的派生类对象s1
中,对象可以调用基类中的共有属性成员,其他属性成员则需要在派生类函数中调用。
如果在一个子类继承多个父类容易出现二义性,即同样的的数据或函数在这个子类所继承的多个父类当中都有这样一个成员,子类在调用这个同名的成员的时候就不知道是在调用哪一个父类的成员,所以容易导致二义性,解决方法就是使用作用域,在调用的时候明确说明调用是这个父类。<子类对象.父类::数据\成员>
三、继承中的构造和析构
1、继承单个类时的构造析构
1、子类会继承父类的成员属性,成员函数,但是不会继承构造和析构,只有父类能够构造析构自己的属性,子类不可以。
2、构造顺序是:先构造父类,然后子类;析构顺序时,先析构子类,然后父类,这个是一个入栈和出栈得过程
3、子类不会继承父类得构造和析构,但是在子类对象构造或析构的时候会调用父类的构造或析构,所以在构造和析构子类对象的时候要确保父类里面的有构造和析构可用
#include<iostream>
using namespace std;
class Father
{
private:
int m_car;
public:
Father(int a,int b,int c);
int m_money;
protected:
int m_room;
};
class Son :public Father
{
public:
Son(int a,int b,int c);
void fun();
};
Father::Father(int a,int b,int c):m_car(a),m_money(b),m_room(c){}
Son::Son(int a,int b,int c):Father(a,b,c){}
void Son::fun()
{
cout << this->m_money<<endl<<this->m_room<<endl;
}
int main()
{
Son s1(5,8,10);
s1.fun();
system("pause");
return 0;
}
因为类的构造函数不能被继承,所以在上面代码Son::Son(int a,int b,int c):Father(a,b,c){}
中,Father(a,b,c)
调用父类的构造函数,对基类进行构造,通过这种方式,可对继承过来的成员变量的初始化。
2、继承多个类的构造析构
基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同,如:
class D: public A, private B, protected C{
//类中成员
}
在调用基类的构造函数时,不管是
D(形参列表): A(实参列表), B(实参列表), C(实参列表){
//其他操作
}
还是
D(形参列表): B(实参列表), C(实参列表), A(实参列表){
//其他操作
}
都是先构造A,再构造B,最后构造C.
四、多继承(菱形继承)
菱形继承的继承关系如上图所示,类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。
如果A中包含成员a,在D中想要调用a,就会产生路径问题,系统不知道该通过 A–>B–>D 还是A–>C–>D 方式来访问a。所以要解决这个问题,引入了虚继承的概念。
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
#include<iostream>
using namespace std;
class A
{
public:
int m_a;
};
class B:virtual public A
{
public:
int m_b;
};
class C :virtual public A
{
public:
int m_c;
};
class D:public B,public C
{
public:
void SetA(int a);
};
void D::SetA(int a)
{
m_a = a;
}
int main()
{
D d;
system("pause");
return 0;
}
转载:https://blog.csdn.net/qq_45930294/article/details/106135798