继承的了解与学习
目录
1. 继承的概念及定义
1.1 继承的概念
1.2继承的名称
1.3继承方式
1.4继承类模板
2.基类和派生类之间的转化
3.继承中的作用域
4.派生类的默认成员函数
5.继承与友元
6.继承与静态函数
7.多继承与其菱形继承问题
8.虚继承
9.继承和组合
1. 继承的概念及定义
1.1 继承的概念
在C++中,继承(Inheritance) 是面向对象编程(OOP)的核心概念之一,它允许一个类(派生类/子类)基于另一个类(基类/父类)来构建,继承其成员变量和成员函数,并可以扩展或修改其行为。它是is-a的关系,就是什么事什么的关系。比如dog是动物。
在下列代码中,我们就是继承关系。
#include<iostream>
using namespace std;
class dongwu
{
public:dongwu(){cout << "666";}
};
class gou:public dongwu
{
public:gou(){cout << "777";}
};
int main()
{gou d;return 0;
}
1.2继承的名称
被继承的叫做父类或者基类,就是上述代码中的dongwu类,主动继承的类叫做子类或者派生类,就是上述代码中的gou类。
1.3继承方式
继承的方式在派生类后加:进行选择,其中总体来说,有点类似于权限只能缩小不能放大,父类中private成员不管选择哪种继承方式继承过来都是private类,类似的父类中public成员如果选择public继承就是public成员,选择protected就是protected成员,private继承就是private成员。
下面给出具体的细则:


1.4继承类模板
namespace bit
{
//template<class T>
//class vector
//{};
// stack和vector的关系,既符合is-a,也符合has-a
template<class T>
class stack : public std::vector<T>
{
public:
void push(const T& x)
{
// 基类是类模板时,需要指定⼀下类域,
// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了
// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到
vector<T>::push_back(x);
//push_back(x);
}
void pop()
{
vector<T>::pop_back();
}
const T& top()
{
return vector<T>::back();
}
bool empty()
{
return vector<T>::empty();
}
};
2.基类和派生类之间的转化

Student sobj ;
// 1.派⽣类对象可以赋值给基类的指针/引⽤
Person* pp = &sobj;
Person& rp = sobj;
// ⽣类对象可以赋值给基类的对象是通过调⽤后⾯会讲解的基类的拷⻉构造完成的
Person pobj = sobj;
//2.基类对象不能赋值给派⽣类对象,这⾥会编译报错
sobj = pobj;
3.继承中的作用域
在继承体系中基类和派生类都有独立的作用域,当派生类和基类有同名函数和同名成员变量时,基类的变量会被自动屏蔽,基类和派生类中都含有a当我们打印时不指定作用域它会默认打印派生类中的5,如果我们想访问基类中的a需要指定作用域
using namespace std;
class dongwu
{
public:dongwu(){cout << "666";}int a=5;
};
class gou:public dongwu
{
public:int a=10;gou(){cout << "777";cout << a;cout<<dongwu::a;}};
4.派生类的默认成员函数
我们在学习类中,学习了构造函数,拷贝构造函数,还有析构函数。那么在派生类和基类中,它们的默认成员函数有什么区别呢?
首先我们的派生类继承的基类中的变量不需要在派生类中实现构造,如果我们不显示调用,编译器会自动调用基类的构造来实现,就是在派生类中要把基类当做一个整体来看待,关于构造的顺序是先构造基类,再构造派生类,因为派生类中可能会有用到基类的地方,这时如果基类未被构造实例化会出现问题,析构的时候我们先析构派生类,再析构基类,这同样是因为派生类中可能会有用到基类的地方,先把基类析构会报错。
在C++中,派生类对象的构造过程必须包含基类部分的构造。这句话的核心是:如果基类没有默认构造函数(即无参构造函数),派生类必须在自己的构造函数初始化列表中显式调用基类的某个构造函数,否则会导致编译错误。
operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域。
如果不指定作用域会出现递归,导致一直调用派生类的=,导致栈溢出崩掉。
Person& operator=(const Person& p )
{
cout<<"Person operator=(const Person& p)"<< endl;
if (this != &p)
_name = p ._name;
return *this ;
}
这里我们再看一个问题就是派生类的析构函数能显示调用基类的析构函数吗?乍一看是没有问题的,但是我们再细想一下,析构时派生类先析构,派生类析构要调派生类的析构函数,但是如果我们再在派生类的析构显示调用,那么派生类析构会调用一次,派生类析构完成又会调用一次,导致二次析构可以看见下面的显示调用导致的二次析构,所以我们不需要显示调用析构,编译器会自动处理,先析构派生类再去析构基类。
class dongwu
{
public:dongwu(){cout << "666";}int a=5;~dongwu(){cout << "~dongwu";}
};
class gou:public dongwu
{
public:int a=10;gou(){cout << "777"<<endl;cout << a<<endl;cout << dongwu::a<<endl;}~gou(){dongwu::~dongwu();cout << "~gou";}
};
如果类不想被继承加final关键字就好。关键字加在基类之后。
5.继承与友元
友元关系不能被继承。也就是说基类友元不能访问派⽣类私有和保护成员。
6.继承与静态函数



class dongwu
{
public:static int b;dongwu(){cout << "666";}int a=5;~dongwu(){cout << "~dongwu";}
};
int dongwu::b = 5;
class gou:public dongwu
{
public:int a=10;gou(){cout << "777"<<endl;cout << a<<endl;cout << dongwu::a<<endl;}~gou(){dongwu::~dongwu();cout << "~gou";}
};
int main()
{gou d;gou a;gou b;cout << &(a.b) << " " << &(b.b) << " " << &(d.b);return 0;
}
注意:静态成员只是影响生命周期并不影响访问权限的改变。有的可能访问不了,这个在类模范板块可以进一步进行理解。
7.多继承与其菱形继承问题
8.虚继承
比雅尼·斯特劳斯特鲁普为了解决数据冗余和二义性的问题,为此设计出了虚继承,哪里有在哪里加virtual。
9.继承和组合
继承是is-a的关系,是狗是动物,组合是has-a的关系,是汽车包含轮胎。
继承的耦合性高,它被父类影响很大,一些功能可能必须去依靠父类去实现,这时我们就不能轻易去改变父类。
组合的耦合性低,它主要是通过父类提供的接口进行实现的,和类的封装一样,外界只知道接口,不知道底层的实现细节。而继承的细节派生类就能观察到,一定程度上破坏了封装。