当前位置: 首页 > news >正文

【C++继承】深入浅出C++继承机制

在这里插入图片描述

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C++。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:C++ 炼 魂 场:从 青 铜 到 王 者 的 进 阶 之 路
✨代 码 趣 语:友 元 不 继 承 像 “爸 爸 的 朋 友”:爸 爸 的 好 朋 友(基 类 友 元),不 一 定 是 儿 子 的 好 朋 友;儿 子 想 让 他 进 门,得 自 己 再 递 张 邀 请 卡(子 类 重 新 声 明 友 元)。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在这里插入图片描述

文 章 目 录

  • 一、继 承
    • (1)定 义
    • (2)格 式
    • (3)继 承 关 系 和 访 问 限 定 符
    • (4)继 承 基 类 成 员 访 问 方 式 的 变 化
    • (5)基 类 和 派 生 类 对 象 赋 值 转 换
    • (6)继 承 中 的 作 用 域
    • (7)派 生 类 的 默 认 成 员 函 数
    • (8)友 元
    • (9)继 承 与 静 态 成 员
  • 二、多 继 承 及 菱 形 继 承
    • (1)单 继 承
    • (2)多 继 承
    • (3)菱 形 继 承
      • 1、菱 形 继 承 的 问 题
      • 2、虚 拟 继 承
      • 3、virtual 的 原 理
  • 三、总 结

        继 承 是 C++ 面 向 对 象 编 程 实 现 代 码 复 用 的 核 心,也 是 构 建 类 层 次 结 构 的 基 础。初 学 者 常 被 访 问 权 限、对 象 转 换、作 用 域 隐 藏、多 继 承 问 题 等 困 扰,本 文 从 单 继 承 基 础 入 手,逐 步 拆 解 核 心 知 识 点,结 合 代 码 与 内 存 分 析,帮 你 理 清 逻 辑、避 开 陷 阱。


一、继 承

(1)定 义

         继 承 机 制 是 面 向 对 象 程 序 设 计 使 代 码 可 以 复 用 的 最 重 要 的 手 段,它 允 许 程 序 员 在 保 持 原 有 类 特 性 的 基 础 上 进 行 扩 展,增 加 功 能,这 样 产 生 新 的 类,称 派 生 类。继 承 呈 现 了 面 向 对 象 程 序 设 计 的 层 次 结 构,体 现 了 由 简 单 到 复 杂 的 认 知 过 程。以 前 我 们 接 触 的 复 用 都 是 函 数 复 用,继 承 是 类 设 计 层 次 的 复 用。


(2)格 式

         Person 是 父 类,也 称 作 基 类。Student 是 子 类,也 称 作 派 生 类。
在这里插入图片描述


(3)继 承 关 系 和 访 问 限 定 符

在这里插入图片描述


(4)继 承 基 类 成 员 访 问 方 式 的 变 化

在这里插入图片描述

  1. 基 类 private 成 员 在 派 生 类 中 无 论 以 什 么 方 式 继 承 都 是 不 可 见 的。这 里 的 不 可 见 是 指 基 类 的 私 有 成 员 还 是 被 继 承 到 了 派 生 类 对 象 中,但 是 语 法 上 限 制 派 生 类 对 象 不 管 在 类 里 面 还 是 类 外 面 都 不 能 去 访 问 它。
  2. 基 类 private 成 员 在 派 生 类 中 是 不 能 被 访 问,如 果 基 类 成 员 不 想 在 类 外 直 接 被 访 问,但 需 要 在 派 生 类 中 能 访 问,就 定 义 为 protected。可 以 看 出 保 护 成 员 限 定 符 是 因 继 承 才 出 现 的
  3. 基 类 的 私 有 成 员 在 子 类 都 是 不 可 见。基 类 的 其 他 成 员 在 子 类 的 访 问 方 式 == Min(成 员 在 基 类 的 访 问 限 定 符,继 承 方 式),public > protected > private。
  4. 使 用 关 键 字 class 时 默 认 的 继 承 方 式 是 private,使 用 struct 时 默 认 的 继 承 方 式 是 public,不 过 最 好 显 示 的 写 出 继 承 方 式。
  5. 在 实 际 运 用 中 一 般 使 用 都 是 public 继 承,几 乎 很 少 使 用 protetced/private 继 承,也 不 提 倡 使 用 protetced/private 继 承,因 为 protetced/private 继 承 下 来 的 成 员 都 只 能 在 派 生 类 的 类 里 面 使 用,实 际 中 扩 展 维 护 性 不 强。
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; //姓名int _age = 18;			//年龄
};
class Student : public Person
{
protected:int _stuid; //学号
};
class Teacher : public Person
{
protected:int _jobid; //工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

在这里插入图片描述


(5)基 类 和 派 生 类 对 象 赋 值 转 换

  1. 派 生 类 对 象 可 以 赋 值 给 基 类 的 对 象 / 基 类 的 指 针 / 基 类 的 引 用。这 里 有 个 操 作 叫 切 片 或 者 切 割。把 派 生 类 中 父 类 那 部 分 切 来 赋 值 过 去。
  2. 基 类 对 象 不 能 赋 值 给 派 生 类 对 象。
  3. 基 类 的 指 针 或 者 引 用 可 以 通 过 强 制 类 型 转 换 赋 值 给 派 生 类 的 指 针 或 者 引 用。但 是 必 须 是 基 类 的 指 针 是 指 向 派 生 类 对 象 时 才 是 安 全 的。这 里 基 类 如 果 是 多 态 类 型,可 以 使 用 RTTI 的 dynamic_cast 来 进 行 识 别 后 进 行 安 全 转 换。
    在这里插入图片描述
#include<iostream>
using namespace std;
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; //姓名int _age = 18;			//年龄
};
class Student : public Person
{
protected:int _stuid; //学号
};
class Teacher : public Person
{
protected:int _jobid; //工号
};
int main()
{Person p;Student s;Teacher t;p = t;//子类可以给父类//t = p;//父类不可以给子类t = (Student)p;return 0;
}

在这里插入图片描述
         子 类 可 以 给 父 类,即 向 上 转 换。子 类 对 象 是 特 殊 的 父 类 对 象,父 类 不 可 以 给 子 类。
在这里插入图片描述
子 类 对 象 可 以 赋 值 给 父 类 对 象 /指 针/引 用

Student sobj;
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;

         这 里 是 切 割,没 有 发 生 类 型 转 换,因 为 如 果 发 生 类 型 转 换,会 产 生 临 时 变 量,临 时 变 量 前 面 需 要 添 加 const,否 则 代 码 会 报 错。


基 类 的 指 针 可 以 通 过 强 制 类 型 转 换 赋 值 给 派 生 类 的 指 针

Student sobj;
pp = &sobj;
Student* ps1 = (Student*)pp; 
ps1->_No = 10;

这 种 情 况 转 换 时 虽 然 可 以,但 是 会 存 在 越 界 访 问 的 问 题

Student sobj;
Person pobj = sobj;
pp = &pobj;
Student* ps2 = (Student*)pp; 
ps2->_No = 10;

         这 里 将 基 类 对 象 指 针 强 制 转 换 为 派 生 类 指 针,然 后 访 问 派 生 类 特 有 成 员 _No,这 会 导 致 未 定 义 行 为,可 能 造 成 内 存 越 界 和 程 序 崩 溃。


(6)继 承 中 的 作 用 域

  1. 在 继 承 体 系 中 基 类 和 派 生 类 都 有 独 立 的 作 用 域。
  2. 子 类 和 父 类 中 有 同 名 成 员,子 类 成 员 将 屏 蔽 父 类 对 同 名 成 员 的 直 接 访 问,这 种 情 况 叫 隐 藏,也 叫 重 定 义。(在 子 类 成 员 函 数 中,可 以 使 用 基 类::基 类 成 员 显 示 访 问)
  3. 需 要 注 意 的 是 如 果 是 成 员 函 数 的 隐 藏,只 需 要 函 数 名 相 同 就 构 成 隐 藏。
  4. 注 意 在 实 际 中 在 继 承 体 系 里 面 最 好 不 要 定 义 同 名 的 成 员。父 类 可 以 和 子 类 定 义 同 名 的 成 员,因 为 不 在 同 一 个 作 用 域 中。查 找 变 量 时,先 在 局 部 域 中 查 找,然 后 去 全 局 域 查 找。可 以 指 定 作 用 域 查 找,如 果 找 不 到,就 会 报 错。
#include<iostream>
using namespace std;
class Person
{
protected:string _name = "小李子"; // 姓名int _num = 111; // 身份证号
};
class Student : public Person
{
public:void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;//就近原则,先查找子类的作用域}
protected:int _num = 999; // 学号
};
int main()
{Student s1;s1.Print();return 0;
}

在这里插入图片描述


#include<iostream>
using namespace std;
class Person
{
public:void func(){cout << "Person" << endl;}
protected:string _name = "小李子"; // 姓名int _num = 111;         // 身份证号
};
class Student : public Person
{
public:void func(int i){cout << "Student : public Person" << endl;}void Print(){cout << " 姓名:" << _name << endl;cout << " 身份证号:" << Person::_num << endl;cout << " 学号:" << _num << endl;//就近原则,先查找子类的作用域}
protected:int _num = 999; // 学号
};
int main()
{Student s1;s1.func(1);return 0;
}

在这里插入图片描述
         子 类 和 父 类 的 两 个 func 构 成 隐 藏 / 重 定 义,父 子 类 域 中,成 员 函 数 名 相 同 就 构 成 函 数 重 载,函 数 重 载 的 前 提 是 在 同 一 个 作 用 域 中。


(7)派 生 类 的 默 认 成 员 函 数

         6 个 默 认 成 员 函 数,“默 认” 的 意 思 就 是 指 我 们 不 写,编 译 器 会 帮 我 们 自 动 生 成 一 个,那 么 在 派 生 类 中,这 几 个 成 员 函 数 是 如 何 生 成 的 呢?

  1. 派 生 类 的 构 造 函 数 必 须 调 用 基 类 的 构 造 函 数 初 始 化 基 类 的 那 一 部 分 成 员。如 果 基 类 没 有 默 认 的 构 造 函 数,则 必 须 在 派 生 类 构 造 函 数 的 初 始 化 列 表 阶 段 显 示 调 用。
  2. 派 生 类 的 拷 贝 构 造 函 数 必 须 调 用 基 类 的 拷 贝 构 造 完 成 基 类 的 拷 贝 初 始 化。
  3. 派 生 类 的 operator= 必 须 要 调 用 基 类 的 operator= 完 成 基 类 的 复 制。
  4. 派 生 类 的 析 构 函 数 会 在 被 调 用 完 成 后 自 动 调 用 基 类 的 析 构 函 数 清 理 基 类 成 员。因 为 这 样 才 能 保 证 派 生 类 对 象 先 清 理 派 生 类 成 员 再 清 理 基 类 成 员 的 顺 序。
  5. 派 生 类 对 象 初 始 化 先 调 用 基 类 构 造 再 调 派 生 类 构 造。
  6. 派 生 类 对 象 析 构 清 理 先 调 用 派 生 类 析 构 再 调 基 类 的 析 构。
  7. 因 为 后 续 一 些 场 景 析 构 函 数 需 要 构 成 重 写,重 写 的 条 件 之 一 是 函 数 名 相 同(这 个 我 们 后 面 会 讲 解)。那 么 编 译 器 会 对 析 构 函 数 名 进 行 特 殊 处 理,处 理 成 destrutor(), 所 以 父 类 析 构 函 数 不 加 virtual 的 情 况 下,子 类 析 构 函 数 和 父 类 析 构 函 数 构 成 隐 藏 关 系。
    在这里插入图片描述
    在这里插入图片描述
#include<iostream>
using namespace std;
class Person
{
public:Person(){cout << "Person" << endl;}void func(){cout << "Person" << endl;}~Person(){cout << "~Person" << endl;}
protected:string _name;
};
class student : public Person
{
public:student(const char* name = "张三",int id = 0):_id(0),Person(name)//,_name(name){ }
protected:int _id;
};
int main()
{student s;return 0;
}

在这里插入图片描述

  1. C++ 规 定,派 生 类 不 能 初 始 化 父 类 的 成 员 变 量,父 类 的 成 员 变 量 可 以 通 过 父 类 的 构 造 函 数 或 者 通 过 派 生 类 的 匿 名 对 象 来 初 始 化。
  2. 派 生 类 会 调 用 父 类 的 构 造 函 数 初 始 化 成 员,初 始 化 时 先 初 始 化 父 类 的 成 员,然 后 初 始 化 子 类 的 成 员。

在这里插入图片描述
         先 子 后 父 的 原 因 是 子 类 当 中 可 能 有 父 类 成 员。子 可 以 用 父,父 不 能 用 子。

#include<iostream>
using namespace std;
class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;delete _pstr;}
protected:string _name; // 姓名string* _pstr = new string("111111");
};
class student : public Person
{
public:student(const char* name = "张三",int id = 0):Person(name),_id(0)//,_name(name){ }student(const student& s):_id(s._id), Person(s){}student& operator=(const student& s){if (this != &s){Person::operator=(s);_id = s._id;}return *this;}~student(){//由于后面多态的原因,析构函数的函数名被特殊处理//统一处理成destructorcout << *_pstr << endl;//Person::~Person();//显示调用父类析构,无法保证先子后父//所以子类析构函数完成后,自动调用父类析构,这样就保证了先子后父delete _ptr;}
protected:int _id;int* _ptr = new int;
};
int main()
{student s1;student s2(s1);student s3("李四", 1);s1 = s3;return 0;
}

在这里插入图片描述
         显 示 调 用 父 类 析 构,无 法 保 证 先 子 后 父。所 以 子 类 析 构 函 数 完 成 后,自 动 调 用 父 类 析 构,这 样 就 保 证 了 先 子 后 父。可 以 不 显 示 调 用 父 类 析 构,编 译 器 会 自 动 调 用。


(8)友 元

#include<iostream>
using namespace std;
class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
int main()
{Person p;Student s;Display(p, s);return 0;
}

在这里插入图片描述
         友 元 函 数 不 能 继 承。
解 决 方 法
         在 student 类 中 添 加 友 元 函 数 即 可。

class Student : public Person
{friend void Display(const Person& p, const Student& s);
protected:int _stuNum; // 学号
};

(9)继 承 与 静 态 成 员

         基 类 定 义 了 static 静 态 成 员,则 整 个 继 承 体 系 里 面 只 有 一 个 这 样 的 成 员。无 论 派 生 出 多 少 个 子 类,都 只 有 一 个 static 成 员 实 例。

#include<iostream>
using namespace std;
class Person
{
public:Person() { ++_count; }
//protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
int main()
{Person p;Student s;cout << &p._name << endl;cout << &s._name << endl;cout << &p._count << endl;cout << &s._count << endl;cout << &Person::_count << endl;cout << &Student::_count << endl;return 0;
}

在这里插入图片描述
         静 态 成 员 继 承 的 是 使 用 权,静 态 成 员 属 于 父 类 和 派 生 类,派 生 类 中 不 会 单 独 拷 贝 一 份。


int main()
{Person p;Student s1;Student s2;cout << p._count << endl;return 0;
}

在这里插入图片描述
         子 类 的 构 造 必 须 调 用 父 类 的 构 造 函 数 来 完 成,所 以 这 里 的 结 果 为 3。


二、多 继 承 及 菱 形 继 承

         一 个 类 同 时 具 有 多 个 类 的 特 征。

(1)单 继 承

         一 个 子 类 只 有 一 个 直 接 父 类 时 称 这 个 继 承 关 系 为 单 继 承。
在这里插入图片描述

(2)多 继 承

         一 个 子 类 有 两 个 或 以 上 直 接 父 类 时 称 这 个 继 承 关 系 为 多 继 承。
在这里插入图片描述

(3)菱 形 继 承

         菱 形 继 承 是 多 继 承 的 一 种 特 殊 情 况。
在这里插入图片描述

1、菱 形 继 承 的 问 题

         数 据 的 冗 余 性,从 下 面 的 对 象 成 员 模 型 构 造,可 以 看 出 菱 形 继 承 有 数 据 冗 余 和 二 义 性 的 问 题。在 Assistant 的 对 象 中 Person 成 员 会 有 两 份。
在这里插入图片描述

#include<iostream>
using namespace std;
class Person
{
public:string _name; // 姓名int _age;
};
class Student : public Person
{
protected:int _num; //学号
};
class Teacher : public Person
{
protected:int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:string _majorCourse; // 主修课程
};
int main()
{Assistant as;as.Teacher::_age = 18;as.Student::_age = 30;return 0;
}

         需 要 显 示 指 定 访 问 哪 个 父 类 的 成 员 可 以 解 决 二 义 性 问 题,但 是 数 据 冗 余 问 题 无 法 解 决。这 样 会 有 二 义 性 无 法 明 确 知 道 访 问 的 是 哪 一 个。


2、虚 拟 继 承

         修 改 菱 形 继 承 的 问 题。虚 拟 继 承 可 以 解 决 菱 形 继 承 的 二 义 性 和 数 据 冗 余 的 问 题。如 上 面 的 继 承 关 系,在Student 和 Teacher 的 继 承 Person 时 使 用 虚 拟 继 承,即 添 加 关 键 字 virtual 即 可 解 决 问 题。需 要 注 意 的 是,虚 拟 继 承 不 要 在 其 他 地 方 去 使 用。

class Student : virtual public Person
{
protected:int _num; //学号
};
class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};

3、virtual 的 原 理

菱 形 继 承

#include<iostream>
using namespace std;
class A
{
public:int _a;
};
// class B : public A
class B :  public A
{
public:int _b;
};
// class C : public A
class C :  public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}

在这里插入图片描述
菱 形 虚 拟 继 承

#include<iostream>
using namespace std;
class A
{
public:int _a;
};
// class B : public A
class B : virtual public A
{
public:int _b;
};
// class C : public A
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;d._a = 0;return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


三、总 结

         本 文 覆 盖 C++ 继 承 关 键 内 容:单 继 承 的 访 问 权 限、对 象 转 换、作 用 域 隐 藏、默 认 成 员 函 数;多 继 承 场 景;菱 形 继 承 的 二 义 性 / 数 据 冗 余 及 虚 拟 继 承 解 决 方 案。学 习 核 心 是 理 解 “类 层 次 与 内 存 布 局”,建 议 结 合 实 战 调 试,夯 实 基 础 以 应 对 后 续 多 态 学 习。

http://www.dtcms.com/a/469799.html

相关文章:

  • Mysql杂志(三十一)——Join连接算法与子查询、排序优化
  • HashMap - 底层原理
  • Python第二次作业
  • Vspy使用教程
  • 通用网站模板网站备案要幕布照
  • 网站三要素关键词 描述怎么做青海项目信息网官网
  • JavaScript学习笔记(二十八):JavaScript性能优化全攻略
  • mooc自动互评脚本笔记---2025年10月11日
  • 什么是语言模型
  • 免费网站正能量不用下载网站程序是什么?
  • 海外住宅IP的分类方式
  • wpf之ToggleButton控件
  • 【传奇开心果系列】基于Flet框架实现的文件选择文件保存和目录选择的样例自定义模板特色和实现原理深度解析
  • 做网站什么数据库用的多低价建站在哪里买
  • 从零搭建 React Native 到项目运行全记录(0.73.6 稳定版)
  • HTML DOM 对象
  • 红日靶场(四)——个人笔记
  • b树,b+树,红黑树
  • win7 iis网站无法显示上海网站设计公司有哪些
  • 马来西亚代表团到访愿景娱乐 共探TikTok直播电商增长新路径
  • 唯识主义:哲学爱智慧本质的当代回归
  • 网站开发公司照片网站建设开发协议
  • 小网站模板网页传奇推荐
  • 支持400+格式!FileOptimizer文件一键压缩
  • 【Unity笔记】Unity Lighting Settings 全解析:一文读懂烘焙光照的每个参数(VR项目Lighting优化)
  • 全链路智能运维中的业务负载预测与弹性伸缩机制
  • 健康管理实训室建设方案:标准化构建与质量保障
  • 甘肃建设局网站wordpress获取文章来源
  • 超简洁网站网络软文营销
  • XXE 注入漏洞全解析:从原理到实战