> 技术文档 > 绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!


绝 了!这 6 个 C++ 默 认 函 数 堪 称 \"类 的 灵 魂 工 程 师\",不 懂 它 们 等 于 白 学 面 向 对 象!

  • 类 的 6 个 默 认 成 员 函 数
    • 空 类
    • 默 认 成 员 函 数
    • 构 造 函 数
      • 定 义
      • 特 性
      • 特 征
      • 没 有 定 义 构 造 函 数
      • 内 置 类 型 构 造 函 数 的 调 用 与 初 始 化
    • 析 构 函 数
      • 特 征
    • 拷 贝 构 造 函 数
      • 特 征
  • 运 算 符 重 载
    • 定 义
    • 函 数 名 字
    • 函 数 原 型
    • 规 则
    • 定 义 方 式
  • 赋 值 运 算 符 重 载
    • 赋 值 运 算 符 重 载 与 拷 贝 构 造 之 间 的 区 别
    • 规 则
    • 前 置 ++ 和 后 置 ++
    • 前 置 - - 和 后 置 - -
  • 总 结

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:C++ 炼 魂 场:从 青 铜 到 王 者 的 进 阶 之 路
✨代 码 趣 语:在 C++ 里,指 针 是 距 离 硬 件 最 近 的 魔 法,当 然,也 可 能 是 距 离 bug 最 近 的 魔 法。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在这里插入图片描述

         在 C++ 中,类 的 6 个 默 认 成 员 函 数 是 编 译 器 自 动 生 成 的 核 心 操 作,支 撑 着 对 象 的 创 建、初 始 化、销 毁、拷 贝 等 基 础 行 为。从 构 造 函 数 初 始 化 对 象,到 析 构 函 数 清 理 资 源,再 到 拷 贝 构 造、赋 值 重 载 等 操 作,这 些 函 数 虽 常 被 隐 式 调 用,却 决 定 了 类 的 基 本 交 互 逻 辑。而 运 算 符 重 载 则 让 自 定 义 类 型 能 像 内 置 类 型 一 样 使 用 运 算 符,简 化 了 代 码 表 达。本 文 将 拆 解 这 些 机 制 的 关 键 细 节。


类 的 6 个 默 认 成 员 函 数

空 类

         类 中 没 有 成 员 函 数 和 成 员 变 量。

默 认 成 员 函 数

         在 C++ 里,当 你 定 义 一 个 类 时,即 便 你 没 有 明 确 编 写 某 些 成 员 函 数,编 译 器 也 会 自 动 为 这 个 类 生 成 6 个 默 认 的 成 员 函 数。用 户 没 有 显 式 实 现,编 译 器 会 生 成 的 成 员 函 数 称 为 默 认 成 员 函 数。

构 造 函 数

定 义

         构 造 函 数 是 一 个 特 殊 的 成 员 函 数,名 字 与 类 名 相 同,创 建 类 类 型 对 象 时 由 编 译 器 自 动 调 用,以 保 证 每 个 数 据 成 员 都 有 一 个 合 适 的 初 始 值,并 且 在 对 象 整 个 生 命 周 期 内 只 调 用 一 次。

特 性

         构 造 函 数 是 特 殊 的 成 员 函 数,需 要 注 意 的 是,构 造 函 数 虽 然 名 称 叫 构 造,但 是 构 造 函 数 的 主 要 任 务 并 不 是 开 空 间 创 建 对 象,而 是 初 始 化 对 象。

特 征

  1. 函 数 名 与 类 名 相 同。
  2. 无 返 回 值。
  3. 对 象 实 例 化 时 编 译 器 自 动 调 用 对 应 的 构 造 函 数。构 造 函 数 权 限 是 公 有 的 可 以 自 动 调 用,如 果 改 成 私 有 的 无 法 自 动 调 用。
  4. 构 造 函 数 可 以 重 载。
#include <iostream>using namespace std;typedef int DataType;class Stack{public://构造函数等价于InitStack(int capacity = 4){cout << \"Stack(int capacity = 4)\" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = capacity;_top = 0;}void Init(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = 4;_top = 0;}void Push(int x){_a[_top++] = x;}void Destory(){free(_a);_a = nullptr;_top = _capacity;}int Top(){return _a[_top - 1];}private:int* _a;int _top;int _capacity;};int main(){Stack s;//构造函数等价于Init//s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);s.Destory();return 0;}

         上 面 的 代 码 可 以 成 功 运 行,相 比 于 之 前 的 代 码,构 造 函 数 Stack 通 过 构 造 函 数 整 合 初 始 化 逻 辑,实 例 化 对 象 时 编 译 器 自 动 调 用 构 造 函 数 完 成 初 始 化,无 需 手 动 调 用 Init,简 化 流 程 且 让 对 象 初 始 化 更 自 然 高 效。

没 有 定 义 构 造 函 数

         如 果 类 中 没 有 显 式 定 义 构 造 函 数,则 C++ 编 译 器 会 自 动 生 成 一 个 无 参 的 默 认 构 造 函 数,一 旦 用 户 显 式 定 义 编 译 器 将 不 再 生 成。C++ 标 准 没 有 规 定 要 初 始 化 为 0,但 有 的 编 译 器 规 定 初 始 化 为 0。
默 认 构 造 函 数 是 指 编 译 器 生 成 的、全 缺 省 的、无 参 的。
         C++ 主 要 有 内 置 类 型 / 基 本 类 型、语 言 本 身 定 义 的 基 础 类 型 int/char/double/ 指 针 等 等 和 自 定 义、用 struct / class 等 等 定 义 的 类 型。
         如 果 不 写 构 造 函 数,编 译 器 默 认 生 成 构 造 函 数,内 置 类 型 可 能 不 做 处 理,自 定 义 类 型 会 去 调 用 它 的 默 认 构 造 函 数。

#include <iostream>using namespace std;typedef int DataType;class Stack{public://构造函数支持重载Stack(DataType* a, int n){cout << \"Stack(DataType* a, int n)\" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}memcpy(_a, a, sizeof(DataType) * n);_capacity = n;_top = 0;}//构造函数等价于InitStack(int capacity = 4){cout << \"Stack(int capacity = 4)\" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = capacity;_top = 0;}void Init(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = 4;_top = 0;}void Push(int x){_a[_top++] = x;}//析构函数在对象生命周期结束后自动调用~Stack(){cout << \"~Stack()\" << endl;if (_a){free(_a);_a = nullptr;_top = 0;_capacity = 0;}}void Destory(){free(_a);_a = nullptr;_top = _capacity;}int Top(){return _a[_top - 1];}private:int* _a;int _top;int _capacity;};class Date{public:void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}private://内置类型int _year;int _month;int _day;//自定义类型Stack _st;};int main(){Date d1;Date d2;d1.Print();return 0;}

有 自 定 义 类 型
         内 置 类 型 会 被 初 始 化 为 0,编 译 器 会 自 动 调 用 自 定 义 类 型 的 默 认 构 造 函 数。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
没 有 自 定 义 类 型
内 置 类 型 不 会 被 初 始 化 为 0。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
总 结

  1. 一 般 情 况 下,有 内 置 类 型 成 员,就 需 要 自 己 写 构 造 函 数,不 能 用 编 译 器 生 成 的。
  2. 全 部 都 是 自 定 义 类 型,可 以 考 虑 让 编 译 器 自 己 生 成。
  3. 永 远 显 式 初 始 化 内 置 类 型 成 员。
class Date {public: void Print() { cout << _year << \"-\" << _month << \"-\" << _day << endl; }private://这里不是初始化,因为这里只是声明//这里是缺省值,给编译器生成的默认构造函数用 int _year = 1; int _month = 1; int _day = 1; Stack _st;};

内 置 类 型 构 造 函 数 的 调 用 与 初 始 化

无 参 构 造 函 数

#include <iostream>using namespace std;class Date{public://无参构造函数Date(){cout << \"Date()\" << endl;_year = 1;_month = 2;_day = 1;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}private://内置类型//这里不是初始化,因为这里只是声明//这里是缺省值,给编译器生成的默认构造函数用//无参构造函数这里可以添加缺省值int _year = 1;int _month = 1;int _day = 1;};int main(){Date d1;d1.Print();//对象.函数()return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         无 参 构 造 函 数 可 以 在 内 置 类 型 的 声 明 后 面 添 加 缺 省 值,缺 省 值 可 以 给 编 译 器 默 认 生 成 的 构 造 函 数 使 用,在 main 函 数 里 面,d1 是 对 象,和 普 通 函 数 不 一 样 的 是,这 里 不 能 加 括 号,如 果 添 加 括 号 会 报 错。如 果 声 明 和 无 参 构 造 函 数 后 面 同 时 添 加 值,则 使 用 无 参 构 造 函 数 的 值。

有 参 构 造 函 数

#include <iostream>using namespace std;class Date{public://有参构造函数Date(int year, int month, int day){cout << \"Date(int year, int month, int day)\" << endl;_year = year;_month = month;_day = day;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}private://有参构造函数这里不能添加缺省值int _year;int _month;int _day;};int main(){Date d2(2023, 1, 2);d2.Print();return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         与 无 参 构 造 函 数 不 一 样 的 是 有 参 构 造 函 数 不 可 以 在 内 置 类 型 的 声 明 后 面 添 加 缺 省 值,在 main 函 数 里 面,d2 是 对 象,对 象 后 面 添 加 括 号,对 内 置 函 数 进 行 初 始 化。

#include <iostream>using namespace std;class Date{public:void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}private://内置类型//这里不是初始化,因为这里只是声明//这里是缺省值,给编译器生成的默认构造函数用int _year = 1;int _month = 1;int _day = 1;};int main(){Date d1;d1.Print();return 0;}

         如 果 不 写 构 造 函 数,C++ 编 译 器 默 认 生 成 无 参 构 造 函 数,即 不 能 在 对 象 后 面 添 加 括 号 进 行 传 参,否 则 会 报 错。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         如 果 在 内 置 类 型 的 声 明 后 面 不 写 缺 省 值,则 内 置 类 型 是 随 机 值。
有 缺 省 值
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
没 有 缺 省 值
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
注 意

  1. 如 果 一 旦 显 式 定 义 任 何 构 造 函 数,编 译 器 将 不 再 生 成 构 造 函 数。
  2. 无 参 构 造 函 数 和 全 缺 省 构 造 函 数 只 能 存 在 一 个。因 为 缺 省 函 数 可 以 不 写 参 数 与 无 参 构 造 函 数 调 用 不 明 确。
  3. 一 般 情 况 下,构 造 函 数 都 需 要 我 们 自 己 写。内 置 类 型 的 成 员 都 有 缺 省 值 且 初 始 化 符 合 要 求 和 全 是 自 定 义 类 型 的 构 造 且 这 些 类 型 都 定 义 默 认 值 这 两 种 情 况 不 需 要 写 构 造 函 数,其 余 情 况 都 要 写 构 造 函 数。

析 构 函 数

         与 构 造 函 数 功 能 相 反,析 构 函 数 不 是 完 成 对 对 象 本 身 的 销 毁,局 部 对 象 销 毁 工 作 是 由 编 译 器 完 成 的。而 对 象 在 销 毁 时 会 自 动 调 用 析 构 函 数,完 成 对 象 中 资 源 的 清 理 工 作。


特 征

  1. 析 构 函 数 名 是 在 类 名 前 加 上 字 符 ~。
  2. 无 参 数 无 返 回 值 类 型。
  3. 一 个 类 只 能 有 一 个 析 构 函 数。若 未 显 式 定 义,系 统 会 自 动 生 成 默 认 的 析 构 函 数。注 意:析 构 函 数 不 能 重 载
  4. 对 象 生 命 周 期 结 束 时,C++ 编 译 系 统 系 统 自 动 调 用 析 构 函 数。
  5. 析 构 函 数 没 有 参 数,所 以 它 不 支 持 重 载。
  6. 内 置 类 型 不 做 处 理,自 定 义 类 型 会 去 调 用 它 的 析 构 函 数。
  7. 一 般 情 况 下,有 动 态 申 请 资 源,就 需 要 显 示 写 析 构 函 数 释 放 资 源。
  8. 没 有 动 态 申 请 资 源,不 需 要 写 析 构。
  9. 需 要 释 放 资 源 的 成 员 都 是 自 定 义 类 型,不 需 要 写 析 构。
#include <iostream>using namespace std;typedef int DataType;class Stack{public://构造函数等价于InitStack(int capacity = 4){cout << \"Stack(int capacity = 4)\" << endl;_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = capacity;_top = 0;}void Init(){_a = (DataType*)malloc(sizeof(DataType) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_capacity = 4;_top = 0;}void Push(int x){_a[_top++] = x;}//析构函数等价于Destory~Stack(){cout << \"~Stack()\" << endl;if (_a){free(_a);_a = nullptr;_top = 0;_capacity = 0;}}void Destory(){free(_a);_a = nullptr;_top = _capacity;}int Top(){return _a[_top - 1];}private:int* _a;int _top;int _capacity;};int main(){Stack s;//构造函数等价于Init//s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);//s.Destory();return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         有 了 构 造 函 数 和 析 构 函 数,就 不 害 怕 没 写 初 始 化 和 清 理 函 数 了,也 简 化 了 代 码。


拷 贝 构 造 函 数

         只 有 单 个 形 参,该 形 参 是 对 本 类 类 型 对 象 的 引 用 (一 般 常 用 const 修 饰),在 用 已 存 在 的 类 类 型 对 象 创 建 新 对 象 时 由 编 译 器 自 动 调 用。拷 贝 构 造 使 用 1 个 对 象 去 初 始 化 另 一 个 对 象,是 已 经 存 在 的 两 个 对 象 之 间 的 拷 贝。

特 征

  1. 拷 贝 构 造 函 数 是 构 造 函 数 的 一 个 重 载 形 式。

  2. 拷 贝 构 造 函 数 的 参 数 只 有 一 个 且 必 须 是 类 类 型 对 象 的 引 用 或 指 针,使 用 传 值 方 式 编 译 器 直 接 报 错,因 为 会 引 发 无 穷 递 归 调 用。
    引 用
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    指 针
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    注 意
             如 果 使 用 指 针 传 参 时 不 写 取 地 址 符,则 函 数 传 参 不 会 进 入 拷 贝 构 造 函 数,代 码 可 能 会 报 错。

  3. 自 定 义 类 型 必 须 要 用 拷 贝 构 造 去 完 成,内 置 类 型 直 接 拷 贝,不 需 要 拷 贝 构 造 函 数。

  4. 任 何 类 型 的 指 针 都 是 内 置 类 型。

  5. 一 般 建 议 在 引 用 前 加 上 const,防 止 代 码 出 现 错 误。
    错 误 代 码
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    正 确 代 码
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
    如 果 加 上 const 则 代 码 会 报 错,有 利 于 检 查 代 码
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

#include<iostream>using namespace std;class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//拷贝构造函数Date(Date& d)//类对象的引用{_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};void Func(Date d){}void Func(int d){}int main(){Date d1(2023, 4, 25);//Date d2(d1);//规定了自定义类型必须要用拷贝构造去完成Func(d1);//内置类型直接拷贝Func(10);return 0;}

         C++ 规 定,自 定 义 类 型 必 须 使 用 拷 贝 构 造 函 数 才 能 完 成,内 置 类 型 可 以 直 接 拷 贝。如 果 传 参 传 的 是 构 造 函 数,则 函 数 传 参 后 先 进 入 拷 贝 构 造 函 数,然 后 进 入 函 数 体。
         例 如 在 上 面 的 代 码 中,Func(d1) 传 参 之 后 先 进 入 拷 贝 构 造 函 数,再 进 入 Func 函 数。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         Date d2(d1) 在 这 句 代 码,d1 就 是 d 的 别 名,this 指 针 指 向 的 是 d2。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         在 上 面 代 码 中,_year、_month 和 _day 是 私 有 的,只 能 在 类 里 面 访 问,在 类 外 面 不 能 访 问,拷 贝 构 造 函 数 中,等 号 左 右 两 边 的 变 量 不 是 声 明 中 的 _year、_month 和 _day,等 号 左 边 的 _year、_month 和 _day 是 this 指 针 的,等 号 右 边 的 _year、_month 和 _day 是 d 的。可 以 将 拷 贝 构 造 函 数 改 写 成 这 样。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
6. 若 未 显 式 定 义,编 译 器 会 生 成 默 认 的 拷 贝 构 造 函 数。默 认 的 拷 贝 构 造 函 数 对 象 按 内 存 存 储 按 字 节 序 完 成 拷 贝,这 种 拷 贝 叫 做 浅 拷 贝,或 者 值 拷 贝。
没 有 开 辟 空 间

#include<iostream>using namespace std;class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};int main(){Date d1(2023, 4, 25);Date d2(d1);return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         由 结 果 可 知,如 果 没 有 开 辟 空 间,默 认 的 拷 贝 构 造 函 数 可 以 使 用,不 需 要 写 拷 贝 构 造 函 数。
开 辟 空 间

#include<iostream>using namespace std;class Stack{public:Stack(){cout << \"Stack()\" << endl;_a = (int*)malloc(sizeof(int) * 4);if (nullptr == _a){perror(\"malloc fail\");return;}_top = 0;_capacity = 4;}Stack(int capacity){cout << \"Stack(int capacity)\" << endl;_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror(\"malloc fail\");return;}_top = 0;_capacity = capacity;}~Stack(){cout << \"~Stack()\" << endl;free(_a);_a = nullptr;_capacity = _top = 0;}private:int* _a;int _top;int _capacity;};int main(){Stack st1;Stack st2(st1);return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         上 面 的 程 序 会 崩 溃,原 因 是 拷 贝 构 造 函 数 在 拷 贝 空 间 时,会 将 st1 和 st2 指 向 同 一 块 空 间,析 构 函 数 会 先 释 放 st2,此 时 空 间 销 毁,再 去 释 放 st1 的 空 间 程 序 会 崩 溃。如 果 1 个 修 改,因 为 指 向 同 一 个 空 间 另 一 个 也 会 被 修 改。

         释 放 空 间 的 顺 序 为 先 释 放 st2 再 释 放 st1,拷 贝 构 造 函 数 是 将 st1 拷 贝 给 st2,先 有 st1 再 有 st2,符 合 先 进 后 出 原 则。
深 拷 贝

Stack(const Stack& st){_a = (int*)malloc(sizeof(int) * st._capacity);if (nullptr == _a){perror(\"malloc fail\");return;}memcpy(_a, st._a, sizeof(int) * st._top);_top = st._top;_capacity = st._capacity;}

运 算 符 重 载

定 义

         运 算 符 重 载 是 具 有 特 殊 函 数 名 的 函 数,也 具 有 其 返 回 值 类 型,函 数 名 字 以 及 参 数 列 表,其 返 回 值 类 型 与 参 数 列 表 与 普 通 的 函 数 类 似。是 否 要 重 载 运 算 符,要 明 确 这 个 运 算 符 对 这 个 类 是 否 有 意 义,以 日 期 函 数 为 例,日 期 + 日 期 没 有 意 义,日 期 - 日 期 有 意 义。

函 数 名 字

         关 键 字 operator 后 面 接 需 要 重 载 的 运 算 符 符 号。

函 数 原 型

         返 回 值 类 型 operator 操 作 符 (参 数 列 表)

返回类型 operator运算符(参数列表) { // 函数体}

规 则

1. 不 能 通 过 连 接 其 他 符 号 来 创 建 新 的 操 作 符:比 如 operator@
2. 重 载 操 作 符 必 须 有 一 个 类 类 型 参 数,因 为 要 对 自 定 义 类 型 进 行 控 制,不 能 全 部 的 参 数 都 是 内 置 类 型,例 如:bool operator<(const int& x, const int& y)
3. 用 于 内 置 类 型 的 运 算 符,其 含 义 不 能 改 变,例 如:内 置 的 整 型 +,不 能 改 变 其 含 义
4. 作 为 类 成 员 函 数 重 载 时,其 形 参 看 起 来 比 操 作 数 数 目 少 1,因 为 成 员 函 数 的 第 一 个 参 数 为 隐 藏 的 this。
5. .*、::、sizeof、 ?:、. 注 意 以 上 5 个 运 算 符 不 能 重 载。这 个 经 常 在 选 择 题 中 出 现。
6. 在 全 局 函 数 中,操 作 符 是 几 个 操 作 数,重 载 函 数 就 有 几 个 参 数。在 成 员 函 数 中,重 载 函 数 少 一 个 参 数。

定 义 方 式

         运 算 符 重 载 函 数 的 定 义 方 式 有 两 种,既 可 以 作 为 类 的 成 员 函 数,也 能 作 为 全 局 函 数。
         这 里 以 日 期 函 数 为 例 进 行 解 释 运 算 符 重 载。
全 局 函 数

#include<iostream>using namespace std;class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;};bool operator<(const Date& x1, const Date& x2){if (x1._year < x2._year){return true;}else if (x1._year == x2._year && x1._month < x2._month){return true;}else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day){return true;}return false;}int main(){Date d1(2023, 4, 25);Date d2(2023, 5, 25);cout << (d1 < d2) << endl;cout << (operator<(d1, d2)) << endl;return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         内 置 类 型 可 以 直 接 比 较,自 定 义 类 型 不 能 直 接 比 较,<< 是 流 插 入 运 算 符,它 的 优 先 级 高 于 比 较 运 算 符,所 以 流 插 入 运 算 符 和 比 较 运 算 符 同 时 存 在 时 应 给 比 较 运 算 符 添 加 括 号。

         在 上 面 的 代 码 中,d1 < d2; 和 operator<(d1, d2); 是 完 全 等 价 的,因 为 运 算 符 重 载 本 质 上 是 函 数 调 用,d1 < d2 是 隐 式 调 用,operator<(d1, d2); 是 显 示 函 数 调 用,编 译 器 会 将 d1 < d2 解 释 为 对 operator< 函 数 的 调 用,就 像 调 用 普 通 函 数 一 样。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

         如 果 要 在 全 局 函 数 使 用 类 中 的 变 量,权 限 必 须 是 public,即 权 限 是 公 有 的,如 果 权 限 是 私 有 的,则 不 能 在 类 外 面 访 问 变 量。

成 员 函 数

#include<iostream>using namespace std;class Date{public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}//作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的thisbool operator<(const Date& x){if (_year < x._year)//这里等价于if (this->_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;}private:int _year;int _month;int _day;};int main(){Date d1(2018, 9, 26);Date d2(2018, 10, 27);//日期-日期有意义//日期+日期没意义//是否要重载运算符,这个运算符对这个类是否有意义//全局函数//d1 < d2;//转换成operator<(d1, d2);//成员函数d1 < d2;//转换成d1.operator<(d2);d1.operator<(d2);if (d1 < d2){}return 0;}

         相 比 于 全 局 函 数,如 果 是 成 员 函 数,函 数 调 用 应 该 写 成 d1.operator<(d2); 和 全 局 函 数 类 似,d1.operator<(d2); 和 d1 < d2 是 等 价 的,如 下 图,在 汇 编 语 言 中 这 两 段 代 码 的 地 址一 样,编 译 器 会 将 d1 < d2 转 换 成 d1.operator<(d2); 这 句 代 码。
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

赋 值 运 算 符 重 载

赋 值 运 算 符 重 载 与 拷 贝 构 造 之 间 的 区 别

         赋 值 运 算 符 是 一 种 拷 贝,拷 贝 构 造 是 使 用 1 个 对 象 去 初 始 化 另 一 个 对 象,赋 值 运 算 符 重 载 是 已 经 存 在 的 两 个 对 象 之 间 的 拷 贝。

规 则

  1. 参 数 类 型:const T&,传 递 引 用 可 以 提 高 传 参 效 率。
  2. 返 回 值 类 型:T&,返 回 引 用 可 以 提 高 返 回 的 效 率,有 返 回 值 目 的 是 为 了 支 持 连 续 赋 值。
  3. 检 测 是 否 自 己 给 自 己 赋 值。
  4. 返 回 *this:要 复 合 连 续 赋 值 的 含 义。
#include<iostream>using namespace std;class Date{public://构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}//作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的thisbool operator<(const Date& x){if (this->_year < x._year)//这里等价于if (this->_year < x._year){return true;}else if (this->_year == x._year && this->_month < x._month){return true;}else if (this->_year == x._year && this->_month == x._month && this->_day < x._day){return true;}return false;}void operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;}private:int _year;int _month;int _day;};int main(){Date d1(2018, 9, 26);Date d2(2018, 10, 27);d1 = d2;d1.operator=(d2);return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         这 里 是 浅 拷 贝,也 就 是 值 拷 贝 没 有 开 辟 空 间。和 运 算 符 重 载 类 似 d1 = d2; 等 价 于 d1.operator=(d2);
绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         如 果 要 进 行 连 续 赋 值 则 需 要 返 回 this 指 针,返 回 不 能 是 void。在 连 续 赋 值 中,运 算 是 从 右 向 左 执 行 的,即 先 执 行 k = 0,返 回 k 的 值,以 此 类 推,j = k 返 回 j 的 值,最 后 i = j 返 回 i 的 值。

int i, j, k;i = j = k = 0;

         如 果 是 连 续 赋 值,d2 = d3 返 回 的 是 d2,在 这 里 this 是 d2 的 地 址,即 返 回 this 指 针。this 指 针 不 能 在 形 参 和 实 参 的 位 置 显 示,但 可 以 在 函 数 内 部 使 用。类 似 的 d1 = d2 返 回 d1,this 此 时 是 d1 的 地 址。

Date d1(2018, 9, 26);Date d2(2018, 10, 27);Date d3(2018, 11, 28);d1 = d2 = d3;
Date operator=(const Date& d){this->_year = d._year;this->_month = d._month;this->_day = d._day;return *this;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         在 这 里 如 果 添 加 拷 贝 构 造 函 数,在 连 续 赋 值 中,会 调 用 两 次 拷 贝 构 造 函 数,可 以 使 用 引 用 返 回 提 高 代 码 效 率。在 这 里 this 指 针 是 全 局 变 量,出 了 函 数 作 用 域 不 会 被 销 毁。
拷 贝 构 造 函 数

Date(const Date& d){cout << \"Date(const Date& d)\" << endl;_year = d._year;_month = d._month;_day = d._day;}

原 来 的

Date operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

改 进 后

Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
         函 数 返 回 时 会 生 成 临 时 副 本 返 回,连 续 赋 值 会 生 成 临 时 副 本,调 用 两 次 拷 贝 构 造 函 数,而 使 用 引 用 返 回 不 会 调 用 拷 贝 构 造 函 数,而 是 直 接 返 回 对 象 本 身 的 别 名。

  1. 赋 值 运 算 符 重 载 不 可 以 使 用 全 局 变 量,它 是 默 认 成 员 函 数,如 果 写 会 与 默 认 生 成 的 冲 突。类 似 的,拷 贝 构 造 函 数、构 造 函 数、析 构 函 数 都 不 能 使 用 全 局 变 量,即 不 能 写 在 类 外 面,但 是 可 以 声 明 和 定 义 分 离,运 算 符 重 载 函 数 不 是 默 认 成 员 函 数,但 是 建 议 写 在 类 里 面,因 为 受 访 问 限 定 符 的 限 制。

  2. 用 户 没 有 显 式 实 现 时,编 译 器 会 生 成 一 个 默 认 赋 值 运 算 符 重 载,以 值 的 方 式 逐 字 节 拷 贝。默 认 生 成 的 赋 值 运 算 符 重 载 函 数 支 持 连 续 赋 值。
    注 意
             内 置 类 型 成 员 变 量 是 直 接 赋 值 的,也 就 是 值 拷 贝 或 者 浅 拷 贝,而 自 定 义 类 型 成 员 变 量 需 要 调 用 对 应 类 的 赋 值 运 算 符 重 载 完 成 赋 值。
             如 果 开 辟 空 间 需 要 写 赋 值 运 算 符 重 载 函 数,不 开 辟 空 间 不 需 要 写。
    没 有 写 赋 值 运 算 符 重 载 函 数
    绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!

前 置 ++ 和 后 置 ++

         前 置 ++ 是 先 ++ 再 使 用,返 回 的 是 +1 之 后 的 结 果,可 以 直 接 使 用 this 指 针 加 1。
         后 置 ++ 是 先 使 用 再 ++,返 回 的 是 +1 之 前 的 结 果,因 此 要 使 用 临 时 变 量,再 +1。
         为 了 让 前 置 ++ 和 后 置 ++ 两 个 函 数 构 成 重 载,在 后 置 ++ 的 参 数 位 置 添 加 整 型 int 用 来 占 位,来 使 两 个 函 数 构 成 函 数 重 载。

#include <iostream>using namespace std;class Date{public://构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}//前置++Date& operator++(){*this += 1;return *this;}//后置++//int是为了占位,不是为了接受具体的值,方便区分,为了跟前置++构成重载Date operator++(int){Date tmp = *this;*this += 1;return tmp;}private:int _year;int _month;int _day;};void TestDate(){//前置++Date d3(2025, 7, 13);++d3;d3.Print();//后置++Date d4(2025, 7, 13);d4++;d4.Print();}int main(){TestDate();return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
注 意
         在 前 置 ++ 中 使 用 传 引 用 返 回,在 后 置 ++ 中 不 能 使 用 传 引 用 返 回,是 因 为 tmp 是 局 部 变 量,局 部 变 量 在 函 数 出 了 作 用 域 后 会 销 毁,无 法 使 用 传 引 用 返 回。

前 置 - - 和 后 置 - -

         前 置 - - 是 先 - - 再 使 用,返 回 的 是 -1 之 后 的 结 果,可 以 直 接 使 用 this 指 针 减 1。
         后 置 - - 是 先 使 用 再 - -,返 回 的 是 -1 之 前 的 结 果,因 此 要 使 用 临 时 变 量,再 -1。
         和 ++ 类 似,为 了 让 前 置 - - 和 后 置 - - 两 个 函 数 构 成 重 载,在 后 置 - - 的 参 数 位 置 添 加 整 型 int 用 来 占 位,来 使 两 个 函 数 构 成 函 数 重 载。

#include <iostream>using namespace std;class Date{public://构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << \"-\" << _month << \"-\" << _day << endl;}//前置--Date& operator--(){*this -= 1;return *this;}//后置--//int是为了占位,不是为了接受具体的值,方便区分,为了跟前置--构成重载Date operator--(int){Date tmp = *this;*this -= 1;return tmp;}private:int _year;int _month;int _day;};void TestDate(){//前置--Date d3(2025, 7, 13);--d3;d3.Print();//后置--Date d4(2025, 7, 13);d4--;d4.Print();}int main(){TestDate();return 0;}

绝 了!这 6 个 C++ 默 认 函 数 堪 称 “类 的 灵 魂 工 程 师“,不 懂 它 们 等 于 白 学 面 向 对 象!
在这里插入图片描述


总 结

         类 的 默 认 成 员 函 数 与 运 算 符 重 载,是 C++ 管 理 对 象 生 命 周 期 和 简 化 操 作 的 核 心 工 具。它 们 规 范 了 对 象 从 创 建 到 销 毁、从 拷 贝 到 运 算 的 全 过 程,尤 其 在 资 源 处 理 时,深 拷 贝、析 构 函 数 等 的 合 理 实 现 直 接 影 响 程 序 稳 定 性。理 解 这 些 机 制,能 帮 助 我 们 写 出 更 规 范、高 效 的 面 向 对 象 代 码,充 分 发 挥 C++ 的 封 装 与 抽 象 优 势。