跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 类和对象进阶:构造、析构与运算符重载

深入讲解 C++ 类中的默认成员函数机制。涵盖构造函数初始化规则、析构函数资源清理原则、拷贝构造函数的深浅拷贝区别及递归陷阱、赋值运算符重载与连续赋值技巧。重点剖析运算符重载的实现细节,包括比较、IO 流、前后置自增自减等场景,并通过完整的日期类实现案例,演示如何将理论应用于实际工程代码中。内容涉及 const 成员函数权限控制及取地址运算符的特殊应用。

292440837发布于 2026/3/23更新于 2026/5/44 浏览
C++ 类和对象进阶:构造、析构与运算符重载

类的默认成员函数

编译器会自动生成一些成员函数,称为默认成员函数。在 C++11 之前,一个类如果不显式定义,编译器会默认生成以下 6 个函数;C++11 之后增加了移动构造函数和移动赋值函数。

我们主要关注两点:

  1. 不写时,编译器生成的行为是否符合需求?
  2. 如果不符合,如何自己实现?

通常编译器生成的默认版本并不完全满足业务逻辑,特别是涉及资源管理时,我们需要手动干预。

构造函数

构造函数的核心任务是对象实例化时的初始化。就像以前写栈或队列需要单独调用 Stack Init(),有了构造函数就不需要这一步了。

关键特性:

  • 函数名与类名相同,无返回值(连 void 都不能写)。
  • 对象实例化时系统自动调用。
  • 支持重载。
  • 若未显式定义,编译器生成一个无参的默认构造函数。一旦用户定义了任意构造函数,编译器将不再自动生成无参版本。
  • 注意: 无参构造函数、全缺省构造函数以及编译器默认生成的构造函数,统称为'默认构造函数'。它们有且只有一个存在,不能同时存在。虽然无参和全缺省构成重载,但调用时若无实参会有歧义。

对于内置类型成员变量,编译器默认生成的构造函数不会初始化,值是不确定的。对于自定义类型成员变量,则会调用其默认构造函数。如果该成员没有默认构造函数,编译会报错,此时必须使用初始化列表。

基本用法

class Date {
public:
    // 无参构造函数
    Date() { _year = 1; _month = 1; _day = 1; }
    
    // 带参构造函数
    Date(int year, int month, int day) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 全缺省构造函数
    // Date(int year = 1, int month = 1, int day = 1) { ... }

    void Print() {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1;              // 调用默认构造函数
    d1.Print();           // 输出:1/1/1

    Date d2(2025, 11, 25); // 调用带参构造函数
    d2.Print();

    return 0;
}

特殊场景:容器中的对象

当类中包含其他自定义类型成员时,构造顺序很重要。编译器会先调用成员的构造函数,再执行当前类的构造函数体。

typedef int STDataType;

class Stack {
public:
    // 默认构造函数,若改成 int n 则不是默认构造函数
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc fail");
            return;
        }
        _capacity = n;
        _top = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

class MyQueue {
public:
    // 编译器默认生成 MyQueue 的构造函数,调用了 Stack 的构造
    // 完成了两个成员的初始化
private:
    Stack pushst;
    Stack popst;
};

int main() {
    MyQueue mq;
    return 0;
}

析构函数

析构函数与构造函数功能相反,用于对象生命周期结束时清理资源。C++ 规定对象销毁时会自动调用析构函数。

关键特性:

  • 函数名是类名前加 ~,如 ~Stack()。
  • 无参数、无返回值。
  • 一个类只能有一个析构函数。
  • 若未显式定义,系统生成默认析构函数。
  • 对象生命周期结束(如离开作用域)时自动调用。

对于内置类型成员,默认析构不做处理。自定义类型成员会调用其析构函数。重点: 如果类中申请了动态资源(如 malloc),必须显式编写析构函数释放内存,否则会造成泄漏。

运用示例

typedef int STDataType;

class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc fail");
            return;
        }
        _capacity = n;
        _top = 0;
    }

    ~Stack() {
        free(_a);
        _a = nullptr;  // 修正原代码笔误
        _capacity = _top = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

class MyQueue {
private:
    // 自定义类型成员,析构时会递归调用 Stack 的析构
    Stack pushst;
    Stack popst;
};

int main() {
    MyQueue mq;
    return 0;
}

拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且额外参数都有默认值,则称为拷贝构造函数。它是构造函数的重载形式。

核心规则:

  • 第一个参数必须是类类型对象的引用(传值会导致无穷递归)。
  • 若未显式定义,编译器生成默认的拷贝构造函数。
  • 默认拷贝对内置类型是浅拷贝(字节级复制),对自定义类型成员调用其拷贝构造。

何时需要自己实现?

  1. 像 Date 这样全是内置类型且无资源,默认即可。
  2. 像 Stack 这样有指针指向堆资源,默认浅拷贝会导致析构时重复释放,需实现深拷贝。
  3. 像 MyQueue 内部是自定义类型,默认会调用成员的拷贝构造,通常无需重写。

经验法则: 如果类显式实现了析构并释放资源,那么通常需要显式实现拷贝构造和赋值运算符。

常见误区:无穷递归

#include<iostream>
using namespace std;

class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
    }
    
    // 错误写法:传值会导致递归调用自身
    // Date(const Date d) { ... }

    // 正确写法:必须加引用 const
    Date(const Date& d) {
        _year = d._year; _month = d._month; _day = d._day;
    }

    void Print() {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

void Func1(const Date& d) {
    cout << &d << endl;
    d.Print();
}

int main() {
    Date d1(2025, 11, 16);
    d1.Print();
    Func1(d1);
    Date d2(d1); // 调用拷贝构造
    d2.Print();
    return 0;
}

浅拷贝与深拷贝

问题:同一空间被释放两次
#include<iostream>
using namespace std;
typedef int STDataType;

class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 失败");
            return;
        }
        _capacity = n;
        _top = 0;
    }

    void Push(STDataType x) {
        if (_top == _capacity) {
            int newCapacity = _capacity * 2;
            STDataType* tmp = (STDataType*)realloc(_a, newCapacity * sizeof(STDataType));
            if (tmp == NULL) {
                perror("realloc fail");
                return;
            }
            _a = tmp;
            _capacity = newCapacity;
        }
        _a[_top++] = x;
    }

    ~Stack() {
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

int main() {
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    
    // 未实现拷贝构造,默认浅拷贝
    // st1 和 st2 的 _a 指向同一块内存,析构时 crash
    Stack st2(st1);
    return 0;
}
解决:深拷贝
// 在 Stack 类中添加拷贝构造
Stack(const Stack& st) {
    cout << "Stack(const Stack& st)" << endl;
    _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
    if (nullptr == _a) {
        perror("malloc 失败!!!");
        return;
    }
    memcpy(_a, st._a, sizeof(STDataType) * st._top);
    _top = st._top;
    _capacity = st._capacity;
}

引用返回与效率优化

传值返回会产生临时对象调用拷贝构造,效率低。可以使用引用返回减少拷贝,但要注意对象生命周期。

// 减少拷贝消耗
Stack& func2() {
    static Stack st; // 静态存储期,避免返回局部对象野引用
    return st;
}

int main() {
    Stack ret = func2();
    return 0;
}

赋值运算符重载

赋值运算符用于两个已存在对象之间的赋值,区别于拷贝构造(初始化新对象)。

特点:

  • 必须重载为成员函数。
  • 参数建议为 const 当前类类型引用。
  • 返回值建议为 当前类类型引用,以支持连续赋值(如 a = b = c)。
  • 默认行为同默认拷贝构造(浅拷贝)。

规则总结: 如果显示了析构释放资源,就需要实现拷贝构造和赋值重载。

#include<iostream>
using namespace std;

class Date {
public:
    Date(int year = 1, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
    }

    // 赋值运算符重载
    Date& operator=(const Date& d) {
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this; // 支持连续赋值
    }

    void Print() {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1(2025, 11, 26);
    Date d2(2025, 11, 27);
    
    d1 = d2;      // 调用赋值运算符
    Date d3 = d2; // 调用拷贝构造
    
    return 0;
}

运算符重载

C++ 允许通过运算符重载为类类型指定新的含义。重载后的优先级和结合性与内置类型一致。

比较运算符

比较运算符通常重载为成员函数,this 指针指向左侧对象。

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

IO 流重载

<< 和 >> 重载为全局函数,因为左侧运算对象通常是 ostream/istream,无法作为成员函数的 this。

friend ostream& operator<<(ostream& out, const Date& d) {
    out << d._year << "/" << d._month << "/" << d._day;
    return out;
}

friend istream& operator>>(istream& in, Date& d) {
    in >> d._year >> d._month >> d._day;
    return in;
}

前置与后置 ++/--

后置重载需要一个 int 形参来区分。

Date operator++(int) { // 后置
    Date tmp = *this;
    *this += 1;
    return tmp;
}

Date& operator++() { // 前置
    *this += 1;
    return *this;
}

日期类完整实现

下面是一个综合了上述知识点的 Date 类实现,包含日期校验、加减运算及比较。

头文件 (Date.h)

#pragma once
#include <assert.h>
#include <iostream>
using namespace std;

class Date {
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);

public:
    Date(int year = 1990, int month = 1, int day = 1) {
        _year = year; _month = month; _day = day;
        if (!CheckDate()) {
            cout << "非法日期";
            Print();
        }
    }

    void Print() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

    inline int GetMonthDay(int year, int month) {
        assert(month > 0 && month < 13);
        static int MontDayArray[13] = {-1, 31, 28, 31, 30, 31, 30, 31, 30, 31, 30, 31, 30};
        if ((month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))) {
            return 29;
        }
        return MontDayArray[month];
    }

    bool CheckDate();

    bool operator<(const Date& d);
    bool operator<=(const Date& d);
    bool operator>(const Date& d);
    bool operator>=(const Date& d);
    bool operator==(const Date& d);
    bool operator!=(const Date& d);

    Date& operator+=(int day);
    Date operator+(int day);
    Date& operator-=(int day);
    Date operator-(int day);

    Date operator++(int);
    Date& operator++();
    Date operator--(int);
    Date& operator--();

    int operator-(const Date& d);

private:
    int _year;
    int _month;
    int _day;
};

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

实现文件 (Date.cpp)

#define _CRT_SECURE_NO_WARNINGS
#include "Date.h"

bool Date::CheckDate() {
    if (_month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month)) {
        return false;
    }
    return true;
}

Date& Date::operator+=(int day) {
    if (day < 0) return this->operator-=(-day);
    _day += day;
    while (_day > GetMonthDay(_year, _month)) {
        _day -= GetMonthDay(_year, _month);
        _month++;
        if (_month == 13) {
            _year++;
            _month = 1;
        }
    }
    return *this;
}

Date Date::operator+(int day) {
    Date tmp = *this;
    tmp += day;
    return tmp;
}

Date& Date::operator-=(int day) {
    if (day < 0) return this->operator+=(-day);
    _day -= day;
    while (_day <= 0) {
        _month--;
        if (_month == 0) {
            _year--;
            _month = 12;
        }
        _day += GetMonthDay(_year, _month);
    }
    return *this;
}

Date Date::operator-(int day) {
    Date tmp = *this;
    tmp -= day;
    return tmp;
}

bool Date::operator<(const Date& d) {
    if (_year < d._year) return true;
    else if (_year == d._year) {
        if (_month < d._month) return true;
        else if (_month == d._month) return _day < d._day;
    }
    return false;
}

bool Date::operator<=(const Date& d) { return *this < d || *this == d; }
bool Date::operator>(const Date& d) { return !(*this <= d); }
bool Date::operator>=(const Date& d) { return !(*this < d); }
bool Date::operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; }
bool Date::operator!=(const Date& d) { return !(*this == d); }

Date Date::operator++(int) {
    Date tmp = *this;
    *this += 1;
    return tmp;
}

Date& Date::operator++() {
    *this += 1;
    return *this;
}

Date Date::operator--(int) {
    Date tmp = *this;
    *this -= 1;
    return tmp;
}

Date& Date::operator--() {
    *this -= 1;
    return *this;
}

int Date::operator-(const Date& d) {
    int flag = 1;
    Date max = *this;
    Date min = d;
    if (*this < d) {
        max = d;
        min = *this;
        flag = -1;
    }
    int n = 0;
    while (min != max) {
        ++min;
        ++n;
    }
    return n * flag;
}

ostream& operator<<(ostream& out, const Date& d) {
    out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
    return out;
}

istream& operator>>(istream& in, Date& d) {
    cout << "请输入年月日:";
    in >> d._year >> d._month >> d._day;
    return in;
}

测试用例

#define _CRT_SECURE_NO_WARNINGS
#include "Date.h"

void Test01() {
    Date d1(2025, 11, 28);
    d1.Print();
    Date d2 = d1 - 100;
    d2.Print();
}

void Test02() {
    Date d1(2024, 7, 13);
    Date ret1 = d1++;
    ret1.Print();
    d1.Print();
    Date d2(2024, 7, 13);
    Date ret2 = ++d2;
    ret2.Print();
    d2.Print();
}

int main() {
    Test01();
    Test02();
    return 0;
}

取地址运算符重载与 const 成员函数

const 成员函数

在成员函数参数列表后加 const,修饰隐含的 this 指针,表明函数内不修改成员变量。这允许在 const 对象上调用该函数。

class Date {
public:
    void Print() const {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

void Test() {
    const Date d1(2025, 11, 30);
    d1.Print(); // 合法
}

取地址运算符重载

一般不需要显式实现,编译器生成的足够用。但在特殊场景(如单例模式或禁止取址)下可自定义。

class Date {
public:
    Date* operator&() {
        return this;
    }
    const Date* operator&() const {
        return this;
    }
};

目录

  1. 类的默认成员函数
  2. 构造函数
  3. 基本用法
  4. 特殊场景:容器中的对象
  5. 析构函数
  6. 运用示例
  7. 拷贝构造函数
  8. 常见误区:无穷递归
  9. 浅拷贝与深拷贝
  10. 问题:同一空间被释放两次
  11. 解决:深拷贝
  12. 引用返回与效率优化
  13. 赋值运算符重载
  14. 运算符重载
  15. 比较运算符
  16. IO 流重载
  17. 前置与后置 ++/--
  18. 日期类完整实现
  19. 头文件 (Date.h)
  20. 实现文件 (Date.cpp)
  21. 测试用例
  22. 取地址运算符重载与 const 成员函数
  23. const 成员函数
  24. 取地址运算符重载
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • AI 大模型高质量代码数据集构建:代理抓取与 API 工具实战
  • 基于 Rokid 眼镜的 AI 天气与旅游规划应用实战
  • Kimi K2.5 开源权重多模态旗舰大模型详解
  • Vibe Coding:AI 时代的新编程范式
  • 基于 GLM-4.6V-Flash-WEB 的电力设施周边活动监测方案
  • AI 农业创业:基于 ViT 的轻量化病虫害检测系统
  • Neo4j Desktop 2.0 安装及自定义路径配置指南
  • C++可变参数队列与压栈顺序:模板语法及汇编调用约定
  • AIGC 个性化与定制化内容生成:技术原理与实践
  • MySQL 与 Navicat Windows 安装配置实战指南
  • Spring Cloud 微服务核心组件详解
  • 双指针算法核心概念与 C++ 经典例题解析
  • OpenClaw 龙虾机器人本地部署与配置教程
  • HarmonyOS 开发核心知识点汇总
  • 强化学习:PPO 算法的 Python 实现与解析
  • 飞算 JavaAI 插件安装与 Spring Boot 项目生成实战
  • 文心一言开源模型部署与多维性能测评实战
  • LlamaFactory 多模态大模型微调实战指南
  • Spatial Joy 2025 全球 AR&AI 赛事:资源、玩法与避坑指南
  • 闲置小米 9 打造复古掌机及天马 G 前端配置指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online