跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 类和对象(中):默认成员函数与运算符重载

综述由AI生成C++ 类和对象(中)主要讲解默认成员函数,包括构造函数、析构函数、拷贝构造函数及赋值运算符重载。文章阐述了编译器自动生成机制与手动实现的区别,重点分析了浅拷贝与深拷贝在资源管理中的重要性,并通过日期类 Date 的完整实现演示了运算符重载(比较、算术、流操作)及 const 成员函数的用法。内容涵盖对象生命周期管理、内存安全及代码规范。

狂少发布于 2026/2/4更新于 2026/5/303K 浏览
C++ 类和对象(中):默认成员函数与运算符重载

一、类的默认成员函数

编译器会自动生成的成员函数称为默认成员函数。一个类,不写的情况下编译器会默认生成以下 6 个默认成员函数。另外在 C++11 中,增加了两个默认成员函数,移动构造和移动赋值。默认成员函数从两方面学习:

  1. 我们不写时,编译器默认生成的函数行为是啥?满足我们的需求吗?
  2. 编译器默认生成的函数不满足我们的需求,那如何自己实现?

二、构造函数

构造函数主要任务是对象实例化时初始化对象。就像每次写栈或队列时需要初始化 Stack Init()、Queue Init(),用了构造函数就不需要写这一步。

构造函数的特点:

  • 函数名与类名相同:类 class Stack,类中的函数 Stack() 无返回值,也无 void
  • 对象实例化时系统会自动调用对应的构造函数
  • 构造函数可以重载
  • 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  • 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意默认构造函数不止是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造
  • 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决

1、构造函数的基本运用

class Date {
public:
    // 1. 无参构造函数
    Date() { _year = 1; _month = 1; _day = 1; }
    
    // 2. 带参构造函数
    Date(int year, int month, int day) { _year = year; _month = month; _day = day; }
    
    // 3. 全缺省构造函数
    // Date(int year = 1, int month = 1, int day = 1) {
    //     _year = year;
    //     _month = month;
    //     _day = day;
    // }
    
    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();
    
    // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则编译器无法
    // 区分这里是函数声明还是实例化对象
    /* Date d3(2007, 6, 18); d3.Print(); */
    return 0;
}

2、队列中的特殊运用

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;
};

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

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

三、析构函数

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁 (C++ 规定对象在销毁时会自动析构函数,完成对对象资源的清理释放工作)。功能类似于 Destroy,而像 Date 没有 Destroy,就是没有资源需要释放,所以 Date 不需要析构函数。

析构函数的特点:

  • 析构函数名是在类名前加上字符 ~:~Stack()
  • 无参数无返回值
  • 一个类只能有一个析构函数
  • 若未显式定义,系统会自动生成默认的析构函数
  • 对象生命周期结束时,系统会自动调用析构函数
  • 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定类型成员会调用他的析构函数
  • 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用它的析构,也就是说自定义类型成员无论什么情况都会调用析构函数
  • 如何类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如 Date。如果默认生成的析构就可以用,也就不需要显示写析构,如 MyQueue。但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如 Stack

1、析构函数运用

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;
};

// 两个 Stack 实现队列
class MyQueue {
public:
    // 编译器默认生成 MyQueue 的构造函数调用了 Stack 的构造,完成了两个成员的初始化
    // 编译器默认生成 MyQueue 的析构函数调用了 Stack 的析构,释放的 Stack 内部的资源
private:
    // 自定义类型,会调用自己的析构函数
    Stack pushst;
    Stack popst;
};

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

四、拷贝构造函数

一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

拷贝构造函数的特点:

  • 拷贝构造函数是构造函数的一个重载
  • 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器会直接报错,因为会引发无穷递归调用
  • 拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值
  • 自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成
  • 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造
    1. 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造
    2. 像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝
    3. 像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显示实现 MyQueue 的拷贝构造
  • 这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要
  • 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝。但是如果返回对象是函数的局部对象,函数结束就销毁了,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

有误解代码

1、无穷递归调用 (引用的运用)
(1) 无穷递归调用 (无引用)
#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; }
    
    // 这里必须是引用,如果直接调用会导致无穷递归调用
    // d2(d1)
    Date(const Date d) { _year = d._year; _month = d._month; _day = d._day; }
    
private:
    int _year;
    int _month;
    int _day;
};

void Func1(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;
}
(2) 修改后的无穷递归调用 (有引用)
#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; }
    
    // 这里必须是引用,如果直接调用会导致无穷递归调用
    // d2(d1),加 const 是为了保护 d 不被修改
    Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; }
    
private:
    int _year;
    int _month;
    int _day;
};

// void Func1(Date d) // 这里把 Date d--->Date& d 是为了减少创建空间,调用自己提高效率
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;

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; }
    
    // Date(const Date& d) {
    //     _year = d._year;
    //     _month = d._month;
    //     _day = d._day;
    // }
private:
    int _year;
    int _month;
    int _day;
};

void Func1(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;
}
2、空间释放 (浅拷贝与深拷贝的运用)
(1) 同一个空间被释放两次 (浅拷贝)
#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);
    
    // Stack 不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝
    // 会导致 st1 和 st2 里面的_a 指针指向同一块资源,析构时会析构两次,程序崩溃
    Stack st2(st1);
    
    return 0;
}
(2) 修改正确后的代码 (深拷贝)
#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;
    }
    
    // st2(st1)
    Stack(const Stack& st) { 
        cout << "Stack(const Stack& st)" << endl;
        // 需要对_a 指向资源创建同样大的资源再拷贝值
        _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;
    }
    
    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);
    
    // Stack 不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝
    // 会导致 st1 和 st2 里面的_a 指针指向同一块资源,析构时会析构两次,程序崩溃
    Stack st2(st1);
    
    return 0;
}
3、拷贝调用函数的运用

假如一个自定义函数有 10000 个数据,如果使用拷贝调用话,效率就特别低下。这时候就可以把 Stack st 改写成 const Stack& st,st 就是 st1 的另一个名称,这是传的就是 st1 本身。

void func(const Stack& st) {}

int main() {
    Stack st1; st1.Push(1); st1.Push(2);
    func(st1);
}
4、引用返回的运用
// 用引用可以减少拷贝的消耗
Stack& func2() {
    // 出来作用域 st 会销毁,防止放回空引用,用 static 改为静态
    static Stack st;
    return st;
}

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

五、赋值运算符重载

1、运算符重载

当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。运算符重载是具有特殊名字的函数,他的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数 (常见的有 ++、–),二元运算符有两个参数 (常见的有 +、-),二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。例如:bool operator==(const Date& d1,const Date& d2)和 operator==(d1, d2); 如果重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,例如:Date& operator=(const Date& d) 运算符重载后,其优先级和结合性与对应的内置类型保持一致 不能通过连接语法中没有的符号来创建新的操作符。例如:operator@ .* :: sizeof ?: . 以上运算符不能重载 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。例如:int operator+(int x, int y) 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,跟前置 ++ 构成函数重载,方便区分。Date& operator++(int) 与 Date& operator++() 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this 指针 默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象 <<cout,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第一个形参位置就可以了,第二个形参位置当类类型对象。

(1) 重载未全局的面临对象访问私有成员变量的问题

有几种方法可以解决:

  1. 成员公开
  2. Date 提供 get 函数
  3. 友元函数
  4. 重载未成员函数
1.1 成员公开
#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; }
    
    // 这里把内置类型设置为公开,但是工作上不会这么写
    // private:
    int _year;
    int _month;
    int _day;
};

// 重载未全局的面临对象访问私有成员变量的问题
bool operator==(const Date& d1, const Date& d2) {
    return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

int main() {
    Date d1(2025, 11, 26); Date d2(2025, 11, 27);
    // 运算符重载函数可以显示调用 operator==(d1, d2);
    // 编译器会转变为 operator==(d1, d2); d1 == d2;
    return 0;
}
1.2 友元函数
#include <iostream>
using namespace std;

class Date {
    // 友元函数
    friend bool operator==(const Date& d1, const Date& d2);
public:
    // ...
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1(2025, 11, 26); Date d2(2025, 11, 27);
    // 运算符重载函数可以显示调用 d1.operator==(d2);
    // 编译器会转变为 d1.operator==(d2); d1 == d2;
    return 0;
}
1.3 重载成员函数
#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; }
    
    // 重载未全局的面临对象访问私有成员变量的问题
    bool operator==(const Date& d) {
        return _year == d._year && _month == d._month && _day == d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1(2025, 11, 26); Date d2(2025, 11, 27);
    // 运算符重载函数可以显示调用 d1.operator==(d2);
    // 编译器会转变为 d1.operator==(d2); d1 == d2;
    return 0;
}

(2) 赋值运算符重载

  • 赋值运算符重载用于两个已经存在的对象直接拷贝赋值
  • 拷贝构造用于一个对象拷贝初始化给另一个要创建的对象
int main() {
    Date d1(2025, 11, 26); Date d2(2025, 11, 27);
    // 赋值重载拷贝 d2 = d1;
    // 拷贝构造 Date d3(d2); Date d4 = d3;
    return 0;
}

赋值运算符重载的特点:

  • 赋值运算符重载是一个运算符重载,规定必须重载为成员函数
  • 赋值运算重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝
  • 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景
  • 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝,对自定义类型成员变量会调用他的赋值重载函数
  • 总结:如果显示了析构释放资源,就需要实现拷贝构造和赋值重载。没有显示析构就不需要实现拷贝构造和赋值重载。
#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; }
    
    // 传引用返回减少拷贝
    // d1 = d2
    Date& operator=(const Date& d) {
        _year = d._year; _month = d._month; _day = d._day;
        // d1=d2 表达式的返回对象应该为 d1,也就是*this
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    Date d1(2025, 11, 26);
    // 拷贝构造 Date d2 = d1;
    Date d3(2025, 11, 27);
    // 拷贝运算符重载 d1 = d3;
    // 拷贝构造 Date d4 = d3;
    return 0;
}
(3) 连续赋值
Date& operator=(const Date& d) {
    _year = d._year; _month = d._month; _day = d._day;
    // d1=d2 表达式的返回对象应该为 d1,也就是*this
    return *this;
}
int main() {
    Date d1(2025, 11, 26);
    // 拷贝构造 Date d2 = d1;
    Date d3(2025, 11, 27);
    // 拷贝运算符重载 d1 = d3;
    // 拷贝构造 Date d4 = d3;
    // 连续赋值 d1 = d2 = d3;
    return 0;
}

六、构造函数与运算符重载总结

#include <iostream>
using namespace std;

class Date {
public:
    // 构造函数
    Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; }
    void Print() { cout << _year << "/" << _month << "/" << _day << endl; }
    
    // 拷贝构造函数
    Date(const Date& d) { _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;
    }
private:
    int _year;
    int _month;
    int _day;
};

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

void Test01() {
    // 构造函数
    Date d1(2020, 11, 28); d1.Print(); Func1(d1);
    // 拷贝构造函数
    Date d2 = d1; d2.Print();
    // 构造函数
    Date d3(2007, 6, 18);
    // 赋值运算符重载
    d1 = d3; d1.Print();
    // 赋值运算符重载
    d2 = d1 = d3;
}

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

七、日期实现

(1) 整个代码

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

class Date {
    // 友元函数 (可将流放入 this 位)
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);
public:
    // 构造函数 (可省略创建 Init)
    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) 提高效率
    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();
    
    // 用运算符重载来比较两日期
    // 在类中,this 指针指向第一类,第二类在 () 中写入
    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);
    
    // 前置 ++() 中为空
    // 后置 ++ 需要 () 中加上 int
    Date operator++(int);
    Date& operator++();
    Date operator--(int);
    Date& operator--();
    
    // 两日期相减,返回的是整形
    int operator-(const Date& d);
    
    // this 接收 d、io 流只能在第二位
    // ostream& operator<<(ostream& out);
private:
    // 内置类型没有申请空间,不需要析构函数
    int _year;
    int _month;
    int _day;
};

// 用全局,可将 this 做为 io 流,d 在第二位
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;
    } else {
        return true;
    }
}

Date& Date::operator+=(int day) {
    // 实现日期改变
    if(day < 0) {
        return *this -= (-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 += (-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;
}

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

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

// d2 < d1
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;
}

// d2 <= d1
bool Date::operator<=(const Date& d) {
    return *this < d || *this == d;
}

// d2 > d1
bool Date::operator>(const Date& d) {
    return !(*this <= d);
}

// d2 >= d1
bool Date::operator>=(const Date& d) {
    return !(*this < d);
}

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

// d2 != d1
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;
}

// d1 - d2
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& Date::operator<<(ostream& out)
// {
//     out << _year << "年" << _month << "月" << _day << "日" << endl;
//     return out;
// }
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;
}
/* test.cpp */
#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();
}

void Test03() {
    Date d1(2024, 7, 12); d1 += -100; d1.Print(); d1 -= -100; d1.Print();
}

void Test04() {
    Date d1(2034, 10, 1); Date d2(2024, 6, 31);
    cout << d1 - d2 << endl;
}

void Test05() {
    Date d1(2025, 11, 27); Date d2(2025, 11, 28);
    // 倒反天罡 (在成员函数中,this 指针是 d1,而 out 却在第二个中)
    // d1 << cout;
    // d1.operator<<(cout);
    // 返回值接收流,可以输出多个
    cout << d1 << d2;
}

void Test06() {
    Date d1; Date d2;
    cin >> d1 >> d2;
    cout << d1 << d2;
    cout << d1 - d2 << endl;
}

int main() {
    Test06();
    return 0;
}

(2) 超级拆解日期实现

1.1 构造函数
// 构造函数 (可省略创建 Init)
Date(int year = 1990, int month = 1, int day = 1) {
    _year = year; _month = month; _day = day;
    // 当日期不正确时,报错并打印出非法日期
    if(!CheckDate()) {
        cout << "非法日期";
        Print();
    }
}

void Test01() {
    Date d1(2025, 11, 28); d1.Print();
}
1.2 析构函数

内置类型没有申请空间,不需要自己写析构函数,编译器会自己生成

private:
    // 内置类型没有申请空间,不需要析构函数
    int _year;
    int _month;
    int _day;
1.3 二元运算符重载
class Date {
    // 运用引用时,减少拷贝,提高效率
    Date& operator+=(int day);
    Date operator+(int day);
    Date& operator-=(int day);
    Date operator-(int day);
};

/* Date.cpp */
Date& Date::operator+=(int day) {
    // 实现日期改变
    if(day < 0) {
        return *this -= (-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 += (-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;
}
1.4 二元运算符重载 (比较)
class Date {
    // 用运算符重载来比较两日期
    // 在类中,this 指针指向第一类,第二类在 () 中写入
    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);
};

// d2 < d1
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;
}

// d2 <= d1
bool Date::operator<=(const Date& d) {
    return *this < d || *this == d;
}

// d2 > d1
bool Date::operator>(const Date& d) {
    return !(*this <= d);
}

// d2 >= d1
bool Date::operator>=(const Date& d) {
    return !(*this < d);
}

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

// d2 != d1
bool Date::operator!=(const Date& d) {
    return !(*this == d);
}
1.5 一元运算符重载
class Date {
    // 前置 ++() 中为空
    // 后置 ++ 需要 () 中加上 int
    Date operator++(int);
    Date& operator++();
    Date operator--(int);
    Date& operator--();
};

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;
}
1.6 日期相减
class Date {
    // 两日期相减,返回的是整形
    int operator-(const Date& d);
};

// d1 - d2
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;
}
1.7 流的运用
class Date {
    // 友元函数 (可将流放入 this 位)
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);
};

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;
}

八、取地址运算符重载

1、const 成员函数

将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数放到成员函数参数列表的后面 const 实际修饰成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰 Date 类的 Print 成员函数,Print 隐含的 this 指针由 Date* const this 变为 const Date* const this

class Date {
    // 这里的 Print 也要进行权限放大
    void Print() const {
        cout << _year << "/" << _month << "/" << _day << endl;
    }
};

void Test01() {
    // 这里用 const 将 d1 的权限放大,不能对 d1 进行打印
    const Date d1(2025, 11, 30); d1.Print();
    // const 的 Print 照样可以打印不加 const 的 d2
    Date d2(2025, 12, 1); d2.Print();
}

2、取地址运算符重载

取地址运算符重载分为普通地址运算符和 const 取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们使用,不需要去显示实现。除非有些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址

#include <iostream>
using namespace std;

class Date {
public:
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
    
    // Date const this;
    Date* operator&() {
        // 可以是自己定义的值,但是要修改成定义类型
        return this;
        // return nullptr;
        // return (Date*)2256FF00X30;
    }
    
    // const Date const this;
    const Date* operator&() const {
        // 可以是自己定义的值,但是要修改成定义类型
        return this;
        // return nullptr;
        // return (Date*)2256FF00X46;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main() {
    return 0;
}

目录

  1. 一、类的默认成员函数
  2. 二、构造函数
  3. 1、构造函数的基本运用
  4. 2、队列中的特殊运用
  5. 三、析构函数
  6. 1、析构函数运用
  7. 四、拷贝构造函数
  8. 有误解代码
  9. 1、无穷递归调用 (引用的运用)
  10. (1) 无穷递归调用 (无引用)
  11. (2) 修改后的无穷递归调用 (有引用)
  12. 2、空间释放 (浅拷贝与深拷贝的运用)
  13. (1) 同一个空间被释放两次 (浅拷贝)
  14. (2) 修改正确后的代码 (深拷贝)
  15. 3、拷贝调用函数的运用
  16. 4、引用返回的运用
  17. 五、赋值运算符重载
  18. 1、运算符重载
  19. (1) 重载未全局的面临对象访问私有成员变量的问题
  20. 1.1 成员公开
  21. 1.2 友元函数
  22. 1.3 重载成员函数
  23. (2) 赋值运算符重载
  24. (3) 连续赋值
  25. 六、构造函数与运算符重载总结
  26. 七、日期实现
  27. (1) 整个代码
  28. (2) 超级拆解日期实现
  29. 1.1 构造函数
  30. 1.2 析构函数
  31. 1.3 二元运算符重载
  32. 1.4 二元运算符重载 (比较)
  33. 1.5 一元运算符重载
  34. 1.6 日期相减
  35. 1.7 流的运用
  36. 八、取地址运算符重载
  37. 1、const 成员函数
  38. 2、取地址运算符重载
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 语音交互实战:基于 WebRTC 与 AI 接口构建实时语音对话系统
  • Python 环境配置及 pip 安装指南
  • Hadoop YARN SLS 运行中常见问题及解决方案
  • llama.cpp 多环境部署指南:CPU 到 CUDA/Metal 高效推理
  • 算法实战:位运算与字符唯一性判断
  • AI 公司滥用用户协议:将用户视为数据提款机的现象分析
  • 机器人轨迹规划详解:从概念到常用方法
  • 基于 RAG 技术的商品智能搜索高效解决方案
  • Spec Kit:GitHub 规范驱动开发工具的 Go 语言实战
  • 融合选择性卷积与残差结构的 SKResNet 架构详解
  • 5 本经典 Python 书籍推荐,构建扎实编程基础
  • Z-Image-Turbo 新手入门:从 0 开始使用 AI 绘画
  • OpenClaw 对接飞书机器人:消息无响应与 Gateway 断开排查
  • 零基础入门网络安全:2023 年专业学习路线指南
  • 移动端 Python 开发环境推荐:QPython 与 Aid Learning 深度评测
  • 算法实战:位运算解两数之和、唯一数字及缺失数字
  • 大模型 LLM 微调经验与总结
  • C++ 多态机制详解
  • Ubuntu 20.04 和 22.04 安装 Python 3 实战指南
  • OpenHarmony 下 Flutter 跨域难题:flutter_cors 实战与适配方案

相关免费在线工具

  • 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

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online