目录
继承的概念及定义
继承(inheritance)机制是面向对象程序设计中使代码能够复用的最重要手段。它允许我们在保持原有类特性的基础上进行扩展,通过添加新的方法(成员函数)和属性(成员变量)来创建新的类,这种新类被称为子类。继承呈现了面向对象程序设计的层次结构,并体现了从简单到复杂的认知过程。与以往接触的函数级别的复用不同,继承实现了类设计层面的复用。
-
概念:
- 继承允许我们基于已有的类(父类或基类)创建新的类(子类或派生类)。
- 子类继承父类的特性,并可以添加自己的特性。
-
示例:
- 假设我们有两个类
Student
和Teacher
,它们都有共同的成员变量(如姓名、地址、电话、年龄)以及共同的方法(如身份验证)。为了减少代码冗余,我们可以将这些共有的成员放入一个新的类Person
中。 Student
和Teacher
分别继承info
类,这样它们就可以复用 info类中的成员,而无需重复定义。
- 假设我们有两个类
首先,我们看如果不用继承的话,该咋写:
非继承方法:
//学生
class Student : public info {
public:
Student() {
_name = "张三";
_number = "123456789";
_tey = 18;
}
protected:
string _name;//姓名
string _number;//电话
int _tey;
};
//教师
class Techer :public info {
public:
Techer() {
_name = "李四";
_number = "8888888888888";
_tit = "110";
}
protected:
string _name;//姓名
string _number;//电话
string _tit;
};
继承方法:
//基本信息
class info {
protected:
string _name;//姓名
string _number;//电话
};
//学生
class Student : public info {
public:
Student(){
info::_name = "张三";
info::_number = "123456789";
_tey = 18;
}
private:
int _tey;
};
//教师
class Techer :public info {
public:
Techer() {
info::_name = "李四";
info::_number = "8888888888888";
_tit = "110";
}
private:
string _tit;
};
继承的的定义
继承父类成员访问方式
//父
class Preson {
public:
void Print() {
cout << _name << endl;
}
protected:
string _name;
private:
int _age;
};
//子
class Student : public Preson
{
public:
Student() {
Preson::_name = "张三";
}
protected:
int _number;
};
int main() {
Student s1;
s1.Print();
return 0;
}
继承类模板
继承类模板是 C++ 中一个非常有用的特性,它允许你创建一个通用的类,该类可以从另一个模板类继承。这种能力对于构建灵活且可复用的代码非常重要。下面我将详细介绍继承类模板的概念和用法。
//继承类模板
#include<vector>
#include<list>
#include<deque>
#define ClASSTYPE std::vector
//#define ClASSTYPE std::list
//#define ClASSTYPE std::deque
template<class T>
class stack : public ClASSTYPE<T>
{
void push(const T& val) {
ClASSTYPE<T>::push_back(val);
}
void pop() {
ClASSTYPE<T>::pop_back();
}
const T& top()
{
return ClASSTYPE<T>::back();
}
bool empty()
{
return ClASSTYPE<T>::empty();
}
};
int main() {
stack<int> s1;
s1.push_back(1);
s1.push_back(2);
s1.push_back(3);
for (auto it : s1)
{
cout << it << endl;
}
return 0;
}
这段代码展示了如何使用类模板来创建一个通用的栈类,该栈类可以使用不同的容器类型(如 std::vector
, std::list
, 或 std::deque
)作为底层存储结构。让我们逐行解析这段代码:
父类和子类对象赋值兼容转换
继承中的作用域
⼦类的默认成员函数
实现⼀个不能被继承的类
//实现一个不能继承的类
class Person //final
{
public:
int _num;
private:
// Person() {}
};
class Student : public Person {
public:
void sun() {
cout << "Student" << endl;
}
};
int main() {
Student s1;
return 0;
}
继承与友元
class Person {
public:
//友元声明
friend void fun(const Person& x, const Student& y);
protected:
string _name;
};
//子类不能继承父类的友元
class Student : public Person{
public:
protected:
int _number;
};
void fun(const Person& x,const Student& y) {
cout << x._name << endl;
cout << y._number << endl;
}
这个代码会报错,因为友元不能继承
但是可以修改
//前置声明:由于函数向上找值的原因所以需要前置声明,不然就找不到Student
class Student;
class Person {
public:
//友元声明
friend void fun(const Person& x, const Student& y);
protected:
string _name;
};
//子类不能继承父类的友元
class Student : public Person{
public:
//友元声明
friend void fun(const Person& x, const Student& y);
protected:
int _number;
};
在两个类里都加上友元,就可以了
继承与静态成员
//继承和静态成员
class Person {
public:
static int num ;
protected:
string _name;
};
int Person::num = 10;
//子类不能继承父类的友元
class Student : public Person {
public:
protected:
int _number;
};
int main() {
Student s1;
Person s2;
cout << s1.num << endl;
cout << s2.num << endl;
s1.num = 100;
cout << s1.num << endl;
cout << s2.num << endl;
return 0;
}
本来static修饰的变量就存储在静态区的,按这样就很好理解为什么用的都是同一个
多继承及其菱形继承问题
继承模型
继承类型 | 描述 | 内存布局 | 问题 |
---|---|---|---|
单继承 | 一个子类只有一个直接父类。 | - 子类继承父类的成员。 | - 无特殊问题。 |
多继承 | 一个子类有两个或以上的直接父类。 | - 先继承的父类成员在前面。 - 后继承的父类成员在后面。 - 子类成员放在最后面。 | - 数据冗余和二义性问题较少,但仍需注意。 |
菱形继承 | 菱形继承是多继承的一种特殊情况。 | - 中间类(即被多个子类继承的类)的成员会被多次继承。 | - 数据冗余(中间类成员会被多次继承)。 - 二义性(通过不同路径继承的成员可能产生冲突)。 |
这种继承就是单继承,可以理解成你家里人那种代代相传的思想
class Person {
public:
string _name;
};
class Student : public Person {
public:
int _number;
};
class info : public Student {
public:
int year;
};
int main() {
info s1;
return 0;
}
class Person {
public:
string _name;
};
class Student {
public:
int _number;
};
class info : public Student
,public Person
{
public:
int year;
};
int main() {
info s1;
return 0;
}
这种继承是不建议写的
class Person {
public:
string _name;
};
class Student : public Person {
public:
int _number;
};
class info : public Person
{
public:
int year;
};
class Son : public Student, public info {
public:
string wang;
};
int main() {
Son s1;
s1._name = "李四"; //E0266 "Son::_name" 不明确
return 0;
}
这边就会直接报错
虚继承
IO库中的菱形虚拟继承
这边c++的输入输出就是用了菱形继承封装了一个类
这些我不做过多讲解,明白他是这个结构就行
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_ostream : virtual public std::basic_ios<CharT, Traits>
{};
template<class CharT, class Traits = std::char_traits<CharT>>
class basic_istream : virtual public std::basic_ios<CharT, Traits>
{};
继承和组合
下面这个表格解释的很清楚
继承类型 | 描述 | 复用类型 | 封装影响 | 依赖关系 | 耦合度 | 适用场景 |
---|---|---|---|---|---|---|
Public 继承 | 一个子类对象是一个父类对象。 | 白箱复用 | 父类的内部细节对子类可见,一定程度破坏了封装。 | 强 | 高 | 当子类和父类之间存在 is-a 关系时使用。 |
组合 | B 组合了 A,每个 B 对象中都有一个 A 对象。 | 黑箱复用 | 对象的内部细节是不可见的,只通过接口交互。 | 弱 | 低 | 当需要实现 has-a 关系时使用。 |
多继承 | 一个子类可以继承多个父类。 | 白箱复用 | 父类的内部细节对子类可见,可能导致数据冗余和二义性。 | 强 | 高 | 当需要从多个父类继承特性时使用。 |
菱形继承 | 多继承的一种特殊情况,会导致数据冗余和二义性。 | 白箱复用 | 父类的内部细节对子类可见,可能导致数据冗余和二义性。 | 强 | 高 | 避免设计出菱形继承,除非有明确的需求。 |
在什么情况使用组合,什么情况使用继承?
这里还有一种关系就是继承和组合都可以:
这段代码展示了如何使用模板来定义 vector
类和 stack
类,并展示了两种不同的实现方式:一种使用继承,另一种使用组合。下面是对这两种实现方式的分析以及为什么在这种情况下最好使用组合的解释。
使用继承的实现
template<class T>
class vector
{};
template<class T>
class stack : public vector<T>
{};
在这个实现中,stack
类继承自 vector<T>
类。这表明 stack
是 vector<T>
的一种特例,即 stack
是 vector<T>
的子类。这里存在 is-a 的关系,即 stack
是一种 vector<T>
。
使用组合的实现
template<class T>
class vector
{};
template<class T>
class stack
{
public:
vector<T> _v;
};