类与对象(中)笔记整理

一、类的 6 个默认成员函数概述

首先明确类的 6 个默认成员函数(编译器在用户未显式定义时会自动生成):

  1. 构造函数:初始化对象
  2. 析构函数:清理对象资源
  3. 拷贝构造函数:用已有对象初始化新对象
  4. 赋值运算符重载:已有对象间的赋值
  5. 取地址运算符重载:获取对象地址
  6. const 取地址运算符重载:获取 const 对象地址

默认成员函数的核心特点:仅当用户未显式定义对应函数时,编译器才会生成;若用户显式定义,编译器则不再生成。

二、构造函数

2.1 概念

构造函数是类的特殊成员函数,核心作用是初始化对象的成员变量(而非开辟对象存储空间),在对象创建时由编译器自动调用。

2.2 特性

  1. 函数名与类名完全一致(如 Date 类的构造函数名必为 Date)。
  2. 无返回值(无需声明 void 或其他类型,直接定义函数体)。
  3. 支持函数重载(可定义多个参数列表不同的构造函数)。
  4. 若用户未显式定义,编译器生成无参默认构造函数(仅初始化自定义类型成员,内置类型成员不初始化)。

2.3 构造函数的常见实现形式(以 Date 类为例)

(1)无参构造函数
复制代码
// 无参构造:创建对象时无需传参,仅初始化成员变量(此处未赋值,内置类型成员为随机值)
class Date {
public:
    Date() {
        // 若未给_year/_month/_day赋值,其值为随机(内置类型特性)
        // 可手动初始化:_year = 0; _month = 1; _day = 1;
    }
private:
    int _year;   // 内置类型
    int _month;
    int _day;
};

// 调用方式:Date d; (无需传参,触发无参构造)

讲解:无参构造属于 "默认构造函数"(无需参数即可调用),但仅定义无参构造时,若需指定日期初始化(如 2025,10,11),需额外定义带参构造。

(2)带参构造函数
复制代码
// 带参构造:创建对象时可指定年/月/日,针对性初始化成员变量
class Date {
public:
    // 参数列表:接收外部传入的年、月、日
    Date(int year, int month, int day) {
        _year = year;   // 用传入参数初始化成员变量
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用方式:Date d(2025, 10, 11); (必须传3个int参数,触发带参构造)

讲解 :带参构造解决了无参构造无法自定义初始化的问题,但需注意:若仅定义带参构造,未定义无参构造,则无法调用Date d;(编译器不再生成默认无参构造)。

(3)全缺省构造函数(推荐)
复制代码
// 全缺省构造:所有参数均设置默认值,兼容"无参"到"全参"所有初始化场景
class Date {
public:
    // 参数默认值:year默认0,month默认1,day默认1(默认日期可自定义)
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用方式(灵活适配):
Date d1;                  // 无参:使用所有默认值 → 0年1月1日
Date d2(2025);            // 传1参:year=2025,month=1,day=1
Date d3(2025, 10);        // 传2参:year=2025,month=10,day=1
Date d4(2025, 10, 11);    // 传3参:year=2025,month=10,day=11

讲解 :全缺省构造是 "默认构造函数" 的最优形式 ------ 仅需一个函数,覆盖所有初始化场景,避免多重重载的冗余代码。补充注意 :默认构造函数只能有一个(若同时定义无参构造和全缺省构造,编译器会报错 "默认构造函数不明确")。

2.4 编译器生成的默认构造函数特性

若用户未显式定义任何构造函数,编译器生成的无参默认构造函数,对成员变量的处理规则如下:

成员变量类型 处理方式 原因
内置类型(int、double 等) 不主动初始化(值为随机) 1. 兼容 C 语言行为(C 中局部变量默认不初始化);2. 性能优化(避免不必要的初始化开销)
自定义类型(如 Time 类、Stack 类) 调用该自定义类型的默认构造函数 保证自定义类型成员的合法性(避免其处于无效状态,如未初始化的指针)

示例验证

复制代码
// 自定义类型Time(有自己的默认构造)
class Time {
public:
    Time() {
        _hour = 0;   // 显式初始化内置类型成员
        _minute = 0;
        _second = 0;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

// Date类(用户未定义构造函数,编译器生成默认构造)
class Date {
private:
    int _year;   // 内置类型:默认构造不初始化 → 随机值
    Time _t;     // 自定义类型:默认构造调用Time的默认构造 → _t._hour=0
};

// 测试:
Date d;
// d._year:随机值;d._t._hour:0(因Time的默认构造初始化)

三、析构函数

3.1 概念

析构函数是类的特殊成员函数,核心作用是清理对象生命周期内占用的资源(如动态内存、文件句柄等),在对象生命周期结束时(如局部对象出作用域、动态对象被 delete)由编译器自动调用。

3.2 特性

  1. 函数名:类名前加~(如 Date 类的析构函数名为~Date)。
  2. 无参数、无返回值(无法重载,一个类仅一个析构函数)。
  3. 若用户未显式定义,编译器生成默认析构函数(仅清理自定义类型成员)。

3.3 析构函数的实现(以 Stack 类为例)

Stack 类需动态开辟内存(_a指向堆空间),必须显式定义析构函数释放资源,否则会导致内存泄漏:

复制代码
class Stack {
public:
    // 构造函数:动态开辟内存
    Stack(int capacity = 4) {
        _a = (int*)malloc(sizeof(int) * capacity);  // 堆内存分配
        if (_a == nullptr) {
            // 处理内存分配失败(补充用户可能疏漏的异常场景)
            perror("malloc fail");
            exit(-1);
        }
        _size = 0;
        _capacity = capacity;
    }

    // 析构函数:释放动态开辟的内存(核心清理逻辑)
    ~Stack() {
        if (_a != nullptr) {  // 避免野指针释放
            free(_a);         // 释放堆内存
            _a = nullptr;     // 置空指针,防止后续误操作
            _size = 0;
            _capacity = 0;
        }
    }
private:
    int* _a;     // 指向堆内存的指针(需手动释放)
    int _size;   // 内置类型(无需特殊清理)
    int _capacity;
};

// 调用场景:
void test() {
    Stack st(10);  // 创建对象:调用构造函数,开辟10个int的堆空间
}  // st出作用域:调用析构函数,释放_a指向的堆内存(无内存泄漏)

3.4 编译器生成的默认析构函数特性

与默认构造函数类似,编译器生成的默认析构函数,对成员变量的处理规则:

  • 内置类型成员:不做任何清理(因内置类型无 "资源" 可言,如 int 无需释放)。
  • 自定义类型成员:调用该自定义类型的析构函数(清理其占用的资源)。

补充注意 :若类中包含动态资源(如指针_a),必须显式定义析构函数 ------ 否则编译器默认析构函数仅释放自定义类型成员,动态资源会泄漏。

四、拷贝构造函数

4.1 概念

拷贝构造函数是类的特殊成员函数,核心作用是用已存在的对象(实参)初始化新创建的对象 ,本质是 "对象的复制初始化"(如Date d2(d1);Date d3 = d1;)。

4.2 特性

  1. 函数名与类名一致(同构造函数)。
  2. 参数列表:必须是 "const 类类型 &"(const 修饰防止修改实参,引用传递避免无限递归)。
  3. 若用户未显式定义,编译器生成默认拷贝构造函数(浅拷贝:按字节复制成员变量)。

4.3 拷贝构造函数的参数传递陷阱(值传递 vs 引用传递)

(1)错误形式:值传递参数(触发无限递归)
复制代码
// 错误示例:拷贝构造函数参数为值传递(Date d)
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 错误:参数为值传递
    Date(Date d) {  // 调用时需将实参d1复制给形参d → 触发拷贝构造
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用:Date d2(d1); → 触发无限递归

递归原因分析

  1. 执行Date d2(d1);时,需调用拷贝构造函数,实参为 d1,形参为 d(值传递)。
  2. 值传递的本质是 "用实参初始化形参",即Date d = d1; → 再次触发拷贝构造函数。
  3. 新的拷贝构造调用又需值传递参数,再次触发拷贝构造...... 无限循环,编译报错。
(2)正确形式:const 引用传递参数
复制代码
// 正确示例:参数为const Date&(引用传递,无拷贝)
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 正确:const引用传递
    Date(const Date& d) {  // d是d1的别名,无需复制,不触发新的拷贝构造
        _year = d._year;   // 用d(即d1)的成员初始化新对象d2
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用:Date d2(d1); → 正常执行,无递归

const 修饰的必要性

  • 防止在拷贝构造函数内部误修改实参 d(如d._year = 2026;)。
  • 支持用 const 对象初始化新对象(如const Date d1(2025,10,11); Date d2(d1);,非 const 引用无法接收 const 实参)。

4.4 常见错误的拷贝构造实现(用户示例分析)

用户提供的错误代码:

复制代码
// 错误的拷贝构造函数
class Date {
public:
    Date(const Date& d) {
        d._day = _day;  // 两处错误
        // ... 其他成员赋值
    }
private:
    int _year;
    int _month;
    int _day;
};

错误分析

  1. 违反 const 约束:dconst Date&(常引用),代表其指向的对象不可修改,而d._day = _day;试图修改 d 的成员 → 编译报错。
  2. 赋值逻辑颠倒:拷贝构造的目的是 "用实参 d 的成员初始化新对象(this 指向的对象)",正确逻辑应为_day = d._day;(新对象成员 = 实参成员),而非反向赋值。

4.5 编译器生成的默认拷贝构造函数(浅拷贝)

若用户未显式定义拷贝构造函数,编译器生成的默认拷贝构造函数采用浅拷贝(按字节复制) ------ 将实参对象的每个成员变量值,直接复制到新对象的对应成员。

(1)浅拷贝的适用场景

当类的成员变量均为内置类型(无动态资源)时,浅拷贝可正常工作,无需显式定义拷贝构造。例如 Date 类:

复制代码
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
    // 未显式定义拷贝构造,编译器生成默认浅拷贝
private:
    int _year;  // 内置类型,浅拷贝无问题
    int _month;
    int _day;
};

// 测试:
Date d1(2025,10,11);
Date d2(d1);  // 浅拷贝:d2._year = d1._year,d2._month = d1._month,d2._day = d1._day → 正常
(2)浅拷贝的致命问题(含动态资源的类)

当类包含动态资源(如指针、堆内存)时,浅拷贝会导致 "同一块资源被重复释放",程序崩溃。以 Stack 类为例:

复制代码
// Stack类(未显式定义拷贝构造,用编译器默认浅拷贝)
class Stack {
public:
    Stack(int capacity = 4) {
        _a = (int*)malloc(sizeof(int)*capacity);
        _size = 0;
        _capacity = capacity;
    }
    ~Stack() {  // 显式定义析构函数,释放堆内存
        if (_a) {
            free(_a);
            _a = nullptr;
        }
    }
private:
    int* _a;     // 动态资源(堆内存指针)
    int _size;
    int _capacity;
};

// 测试:浅拷贝导致崩溃
void test() {
    Stack st1(10);  // st1._a指向堆内存(地址0x1234)
    Stack st2(st1); // 浅拷贝:st2._a = st1._a → 0x1234(同一块内存)
    
    // 生命周期结束,调用析构函数:
    // 1. st2先析构:free(st2._a) → 释放0x1234
    // 2. st1再析构:free(st1._a) → 再次释放0x1234(非法操作,程序崩溃)
}

解决方案:显式定义 "深拷贝" 的拷贝构造函数 ------ 为新对象分配独立的动态资源,再复制实参的资源内容,而非直接复制指针地址:

复制代码
// Stack类的深拷贝构造(补充用户疏漏的深拷贝实现)
class Stack {
public:
    Stack(int capacity = 4) {
        _a = (int*)malloc(sizeof(int)*capacity);
        if (!_a) { perror("malloc fail"); exit(-1); }
        _size = 0;
        _capacity = capacity;
    }

    // 深拷贝构造
    Stack(const Stack& st) {
        // 1. 为新对象分配独立的堆内存
        _a = (int*)malloc(sizeof(int)*st._capacity);
        if (!_a) { perror("malloc fail"); exit(-1); }
        
        // 2. 复制实参的资源内容(而非指针地址)
        memcpy(_a, st._a, sizeof(int)*st._size);  // 内存拷贝函数
        _size = st._size;
        _capacity = st._capacity;
    }

    ~Stack() {
        if (_a) { free(_a); _a = nullptr; }
    }
private:
    int* _a;
    int _size;
    int _capacity;
};

// 测试:深拷贝无崩溃
Stack st1(10);
Stack st2(st1);  // st2._a指向新堆内存(如0x5678),与st1._a(0x1234)独立
// 析构时分别释放各自的内存,无重复释放问题

4.6 拷贝构造函数的调用场景

  1. 用已存在对象初始化新对象(如Date d2(d1);Date d3 = d1;)。

  2. 函数参数为类类型(值传递):

    复制代码
    void PrintDate(Date d) {  // 值传递:调用Date的拷贝构造,用实参初始化形参d
        // ...
    }
    Date d1(2025,10,11);
    PrintDate(d1);  // 触发拷贝构造
  3. 函数返回值为类类型(值传递,C++11 后可能被优化,但需了解原始逻辑):

    复制代码
    Date GetDate() {
        Date d(2025,10,11);
        return d;  // 值传递返回:调用拷贝构造,用d初始化临时对象
    }
    Date d2 = GetDate();  // 触发拷贝构造(部分编译器优化后可能省略)

五、赋值运算符重载

5.1 运算符重载基础

5.1.1 概念

运算符重载是 C++ 为自定义类型提供的特性,核心目的是让自定义类型(如 Date、Stack)能像内置类型(int、double)一样使用标准运算符 (如+===),提升代码可读性。

本质:将运算符转换为对应的函数调用(如d1 == d2等价于operator==(d1, d2))。

5.1.2 运算符重载的规则
  1. 函数格式:返回值类型 operator运算符(参数列表) { 运算符逻辑 }例:重载==判断日期相等,函数名为operator==

  2. 核心限制:

    • 不能创造新运算符(如operator@operator#,需为 C++ 原生运算符)。
    • 至少一个操作数为 "类类型" 或 "枚举类型"(禁止重载纯内置类型的运算符,如int operator+(int a, int b)------ 编译器报错)。
    • 不破坏内置类型的运算符语义(如重载int operator+(int a, int b)时,逻辑不能是 "a - b",需符合运算符原意)。
    • 成员函数重载时隐含this指针:成员函数的参数个数 = 运算符操作数个数 - 1(this指向左操作数)。
  3. 5 个无法重载的运算符(笔试高频考点):

    运算符 名称 无法重载的原因
    . 类成员访问符 语法基础,重载会破坏对象成员访问逻辑
    .* 类成员指针访问符 .类似,涉及成员指针的核心语法
    :: 作用域解析符 用于区分全局 / 类 / 命名空间的成员,无重载必要
    sizeof 计算大小运算符 操作数是类型或对象,结果为编译期常量,无需重载
    ?: 三目条件运算符 运算符优先级和结合性复杂,重载会导致歧义

5.2 赋值运算符重载(operator=)

5.2.1 概念

赋值运算符重载是特殊的运算符重载,核心作用是将已存在的对象(右操作数)的值,赋值给另一个已存在的对象(左操作数)(区别于拷贝构造:拷贝构造是 "用已有对象初始化新对象")。

5.2.2 赋值运算符重载的实现(以 Date 类为例)
(1)成员函数实现(推荐,编译器默认生成的也是成员函数)
复制代码
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 赋值运算符重载(成员函数)
    Date& operator=(const Date& d) {  // 返回值为引用,参数为const引用
        // 1. 自赋值检查(避免自己赋值给自己,如d1 = d1)
        if (this != &d) {  // this是左操作数指针,&d是右操作数地址
            _year = d._year;   // 赋值成员变量
            _month = d._month;
            _day = d._day;
        }
        return *this;  // 返回左操作数(支持连续赋值)
    }
private:
    int _year;
    int _month;
    int _day;
};
(2)关键细节解析
  1. 参数为何是const Date&

    • const:防止修改右操作数(如d1 = d2时,不允许在重载函数中修改d2)。
    • 引用:避免传值导致的拷贝构造(减少性能开销,尤其是对象较大时)。
  2. 返回值为何是Date&(引用),而非Date(值传递)?

    返回类型 特点 问题
    Date&(引用) 返回左操作数的别名(*this),无拷贝构造 无问题,支持连续赋值
    Date(值传递) 返回时需复制*this生成临时对象,触发拷贝构造 1. 性能开销;2. 无法支持连续赋值(临时对象是右值,无法作为左操作数)

    连续赋值支持示例d1 = d2 = d3; → 等价于d1.operator=(d2.operator=(d3));

    • 先执行d2.operator=(d3),返回d2(引用);
    • 再执行d1.operator=(d2),完成赋值。
  3. 为何需要 "自赋值检查"(if (this != &d))?

    • 若没有检查,当执行d1 = d1时,会重复执行赋值逻辑(虽对 Date 类无影响,但对含动态资源的类会致命)。例如 Stack 类的赋值重载:

      复制代码
      Stack& Stack::operator=(const Stack& st) {
          // 无自赋值检查:d1 = d1时,先free(_a),再malloc → 原资源已释放,复制内容出错
          if (this != &st) {  // 必须检查
              free(_a);  // 释放左操作数原有资源
              _a = (int*)malloc(sizeof(int)*st._capacity);
              memcpy(_a, st._a, sizeof(int)*st._size);
              _size = st._size;
              _capacity = st._capacity;
          }
          return *this;
      }
5.2.3 编译器生成的默认赋值运算符重载

若用户未显式定义赋值运算符重载,编译器生成的默认版本采用浅拷贝(与默认拷贝构造一致):

  • 适用场景:类无动态资源(如 Date 类),浅拷贝可正常赋值。
  • 问题场景:类含动态资源(如 Stack 类),浅拷贝导致 "重复释放资源" 或 "内存泄漏"(同拷贝构造的浅拷贝问题),需显式定义深拷贝的赋值运算符重载。

5.3 其他运算符重载示例(以 == 为例)

(1)全局函数实现(非成员函数)
复制代码
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }
    // 需将成员变量设为public,或提供getter函数(否则全局函数无法访问)
    int _year;
    int _month;
    int _day;
};

// 全局函数重载==:判断两个Date对象是否相等
bool operator==(const Date& d1, const Date& d2) {
    return d1._year == d2._year 
        && d1._month == d2._month 
        && d1._day == d2._day;
}

// 调用方式:
Date d1(2025,10,11), d2(2025,10,12);
cout << (d1 == d2) << endl;  // 等价于operator==(d1, d2) → 输出0(false)
(2)成员函数实现
复制代码
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 成员函数重载==:隐含this指针(左操作数)
    bool operator==(const Date& d) {  // 参数d是右操作数
        // this指向左操作数(如d1 == d2时,this = &d1)
        return this->_year == d._year 
            && _month == d._month  // 可省略this->,编译器自动补充
            && _day == d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用方式:
Date d1(2025,10,11), d2(2025,10,11);
cout << (d1 == d2) << endl;  // 等价于d1.operator==(d2) → 输出1(true)

六、const 成员

6.1 const 修饰类的成员函数(const 成员函数)

6.1.1 概念

const 成员函数是指用const修饰的类成员函数,核心作用是限制该函数不能修改类的成员变量,保证函数的 "只读" 属性。

6.1.2 语法与原理
  1. 语法格式:返回值类型 函数名(参数列表) const { 函数体 }例:void Print() const;(const 写在参数列表后、函数体前)。

  2. 原理:类的成员函数隐含this指针(类型为类类型* const,如 Date 类成员函数的thisDate* const)。当函数被const修饰时,this指针的类型变为const 类类型* const(如const Date* const)------ 第一个const限制 "不能通过this修改对象成员",第二个const限制 "this指针本身不能修改指向"。

6.1.3 const 成员函数的调用规则
对象类型 可调用的成员函数类型 原因
普通对象(非 const) const 成员函数、非 const 成员函数 普通对象允许被修改,也允许只读访问
const 对象 仅 const 成员函数 const 对象禁止被修改,非 const 成员函数可能修改成员,故禁止调用

示例验证(用户错误代码修正)

复制代码
// 错误示例:const对象调用非const成员函数
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 非const成员函数(可能修改成员变量)
    void Print() {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

void f(const Date& d) {  // d是const引用(const对象)
    d.Print();  // 编译报错:const对象不能调用非const成员函数
}

// 正确示例:将Print改为const成员函数
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 正确:const成员函数(只读,不修改成员)
    void Print() const {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

void f(const Date& d) {
    d.Print();  // 编译通过:const对象可调用const成员函数
}
6.1.4 const 成员函数的内部调用规则

const 成员函数内部仅能调用其他 const 成员函数,不能调用非 const 成员函数 ------ 因非 const 成员函数可能修改成员变量,违反 const 成员函数的 "只读" 约定。

复制代码
class Date {
public:
    void f1() const {  // const成员函数
        f2();  // 正确:f2是const成员函数
        // f3();  // 错误:f3是非const成员函数
    }

    void f2() const {  // const成员函数
        // ...
    }

    void f3() {  // 非const成员函数
        // ...
    }
private:
    int _year;
    int _month;
    int _day;
};

6.2 const 修饰类的成员变量

除了修饰成员函数,const 还可修饰类的成员变量(const 成员变量),特性如下:

  1. 必须在构造函数的初始化列表中初始化(不能在构造函数体中赋值,因 const 变量初始化后不能修改)。
  2. 一旦初始化,终身不可修改。

示例

复制代码
class Circle {
public:
    // 初始化列表:初始化const成员变量_radius
    Circle(double radius) : _radius(radius) {
        // _radius = radius;  // 错误:const变量不能赋值,只能初始化
    }
private:
    const double _radius;  // const成员变量(圆的半径,初始化后不可改)
    double _area;
};

七、取地址及 const 取地址运算符重载

7.1 概念

取地址运算符重载(operator&)和 const 取地址运算符重载(operator&() const)是类的默认成员函数,核心作用是返回对象的地址 ,默认行为是返回this指针(普通对象返回类类型*,const 对象返回const 类类型*)。

7.2 特性

  1. 若用户未显式定义,编译器自动生成默认版本(返回this)。
  2. 通常无需显式定义(默认行为满足需求),仅在特殊场景下重写(如隐藏对象真实地址,返回假地址)。

7.3 实现示例

复制代码
class Date {
public:
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
    }

    // 1. 普通取地址运算符重载(返回普通对象地址)
    Date* operator&() {
        return this;  // 默认行为,可省略不写
    }

    // 2. const取地址运算符重载(返回const对象地址)
    const Date* operator&() const {
        return this;  // 默认行为,可省略不写
    }
private:
    int _year;
    int _month;
    int _day;
};

// 调用:
Date d1(2025,10,11);
const Date d2(2025,10,12);

Date* p1 = &d1;          // 调用Date* operator&() → 返回&d1
const Date* p2 = &d2;    // 调用const Date* operator&() const → 返回&d2

八、日期类(Date)完整实现整合

基于上述知识点,整合 Date 类的核心功能(构造、拷贝构造、赋值重载、== 重载、Print 函数),形成完整示例:

复制代码
#include <iostream>
using namespace std;

class Date {
public:
    // 1. 全缺省构造函数(默认构造)
    Date(int year = 0, int month = 1, int day = 1) {
        _year = year;
        _month = month;
        _day = day;
        // 补充:可添加日期合法性检查(用户疏漏的健壮性逻辑)
        if (!IsValidDate()) {
            cout << "日期非法!" << endl;
            // 可选择初始化默认日期或终止程序
            _year = 0;
            _month = 1;
            _day = 1;
        }
    }

    // 2. 拷贝构造函数(深拷贝,Date类无动态资源,浅拷贝即可)
    Date(const Date& d) {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    // 3. 赋值运算符重载
    Date& operator=(const Date& d) {
        if (this != &d) {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }
        return *this;
    }

    // 4. 析构函数(Date类无动态资源,可省略,编译器生成默认版本)
    ~Date() {}

    // 5. ==运算符重载(判断日期相等)
    bool operator==(const Date& d) const {
        return _year == d._year
            && _month == d._month
            && _day == d._day;
    }

    // 6. const成员函数(打印日期,只读)
    void Print() const {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

private:
    int _year;
    int _month;
    int _day;

    // 补充:日期合法性检查(私有成员函数,仅内部调用)
    bool IsValidDate() const {
        // 月份范围:1-12
        if (_month < 1 || _month > 12) return false;
        // 每月天数(简化版,未处理闰年2月)
        int daysInMonth[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
        // 天数范围:1-当月最大天数
        if (_day < 1 || _day > daysInMonth[_month]) return false;
        return true;
    }
};

// 测试函数
int main() {
    Date d1(2025, 10, 11);  // 全缺省构造(传3参)
    Date d2(d1);             // 拷贝构造
    Date d3 = d2;            // 拷贝构造(等价于Date d3(d2))
    Date d4;                 // 全缺省构造(无参,默认0-1-1)
    d4 = d1;                 // 赋值运算符重载

    d1.Print();  // 输出2025-10-11
    d2.Print();  // 输出2025-10-11
    d3.Print();  // 输出2025-10-11
    d4.Print();  // 输出2025-10-11

    cout << (d1 == d2) << endl;  // 输出1(true)
    cout << (d1 == d4) << endl;  // 输出1(true)

    Date d5(2025, 13, 1);  // 非法日期,打印"日期非法!",初始化0-1-1
    d5.Print();            // 输出0-1-1

    return 0;
}

补充说明

  • 新增IsValidDate函数实现日期合法性检查(用户疏漏的健壮性逻辑),避免创建非法日期(如 2025 年 13 月 1 日)。
  • 析构函数因 Date 类无动态资源,可省略,编译器生成的默认版本足够使用。
相关推荐
Luffe船长3 小时前
前端vue2+js+springboot实现excle导入优化
前端·javascript·spring boot
周杰伦_Jay3 小时前
【Spring Boot从入门到精通】原理、实战与最佳实践
java·spring boot·后端
呼哧呼哧.3 小时前
SpringBoot 的入门开发
java·spring boot·后端
新子y3 小时前
【小白笔记】KNN 核心预测函数 _predict_one 的过程
笔记
橘子是码猴子4 小时前
LangExtract:基于LLM的信息抽取框架 学习笔记
笔记·学习
观望过往4 小时前
【Java数据结构】队列详解与经典 OJ 题目实战
java·数据结构
仲夏幻境4 小时前
js利用ajax同步调用如何
开发语言·javascript·ajax
天地人-神君4 小时前
将.idea取消git托管
java·git·intellij-idea
鹿鹿鹿鹿isNotDefined4 小时前
Pixelium Design:Vue3 的像素风 UI 组件库
前端·javascript·vue.js