类与对象(中)笔记整理
一、类的 6 个默认成员函数概述
首先明确类的 6 个默认成员函数(编译器在用户未显式定义时会自动生成):
- 构造函数:初始化对象
- 析构函数:清理对象资源
- 拷贝构造函数:用已有对象初始化新对象
- 赋值运算符重载:已有对象间的赋值
- 取地址运算符重载:获取对象地址
- const 取地址运算符重载:获取 const 对象地址
默认成员函数的核心特点:仅当用户未显式定义对应函数时,编译器才会生成;若用户显式定义,编译器则不再生成。
二、构造函数
2.1 概念
构造函数是类的特殊成员函数,核心作用是初始化对象的成员变量(而非开辟对象存储空间),在对象创建时由编译器自动调用。
2.2 特性
- 函数名与类名完全一致(如 Date 类的构造函数名必为 Date)。
- 无返回值(无需声明 void 或其他类型,直接定义函数体)。
- 支持函数重载(可定义多个参数列表不同的构造函数)。
- 若用户未显式定义,编译器生成无参默认构造函数(仅初始化自定义类型成员,内置类型成员不初始化)。
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 特性
- 函数名:类名前加
~
(如 Date 类的析构函数名为~Date
)。 - 无参数、无返回值(无法重载,一个类仅一个析构函数)。
- 若用户未显式定义,编译器生成默认析构函数(仅清理自定义类型成员)。
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 特性
- 函数名与类名一致(同构造函数)。
- 参数列表:必须是 “const 类类型 &”(const 修饰防止修改实参,引用传递避免无限递归)。
- 若用户未显式定义,编译器生成默认拷贝构造函数(浅拷贝:按字节复制成员变量)。
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); → 触发无限递归
递归原因分析:
- 执行
Date d2(d1);
时,需调用拷贝构造函数,实参为 d1,形参为 d(值传递)。 - 值传递的本质是 “用实参初始化形参”,即
Date d = d1;
→ 再次触发拷贝构造函数。 - 新的拷贝构造调用又需值传递参数,再次触发拷贝构造…… 无限循环,编译报错。
(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;
};
错误分析:
- 违反 const 约束:
d
是const Date&
(常引用),代表其指向的对象不可修改,而d._day = _day;
试图修改 d 的成员 → 编译报错。 - 赋值逻辑颠倒:拷贝构造的目的是 “用实参 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 拷贝构造函数的调用场景
- 用已存在对象初始化新对象(如
Date d2(d1);
、Date d3 = d1;
)。 - 函数参数为类类型(值传递):
void PrintDate(Date d) { // 值传递:调用Date的拷贝构造,用实参初始化形参d// ... } Date d1(2025,10,11); PrintDate(d1); // 触发拷贝构造
- 函数返回值为类类型(值传递,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 运算符重载的规则
函数格式:
返回值类型 operator运算符(参数列表) { 运算符逻辑 }
例:重载==
判断日期相等,函数名为operator==
。核心限制:
- 不能创造新运算符(如
operator@
、operator#
,需为 C++ 原生运算符)。 - 至少一个操作数为 “类类型” 或 “枚举类型”(禁止重载纯内置类型的运算符,如
int operator+(int a, int b)
—— 编译器报错)。 - 不破坏内置类型的运算符语义(如重载
int operator+(int a, int b)
时,逻辑不能是 “a - b”,需符合运算符原意)。 - 成员函数重载时隐含
this
指针:成员函数的参数个数 = 运算符操作数个数 - 1(this
指向左操作数)。
- 不能创造新运算符(如
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)关键细节解析
参数为何是
const Date&
?const
:防止修改右操作数(如d1 = d2
时,不允许在重载函数中修改d2
)。引用
:避免传值导致的拷贝构造(减少性能开销,尤其是对象较大时)。
返回值为何是
Date&
(引用),而非Date
(值传递)?返回类型 特点 问题 Date&
(引用)返回左操作数的别名( *this
),无拷贝构造无问题,支持连续赋值 Date
(值传递)返回时需复制 *this
生成临时对象,触发拷贝构造1. 性能开销;2. 无法支持连续赋值(临时对象是右值,无法作为左操作数) 连续赋值支持示例:
d1 = d2 = d3;
→ 等价于d1.operator=(d2.operator=(d3));
- 先执行
d2.operator=(d3)
,返回d2
(引用); - 再执行
d1.operator=(d2)
,完成赋值。
- 先执行
为何需要 “自赋值检查”(
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 语法与原理
语法格式:
返回值类型 函数名(参数列表) const { 函数体 }
例:void Print() const;
(const 写在参数列表后、函数体前)。原理:类的成员函数隐含
this
指针(类型为类类型* const
,如 Date 类成员函数的this
是Date* 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 成员变量),特性如下:
- 必须在构造函数的初始化列表中初始化(不能在构造函数体中赋值,因 const 变量初始化后不能修改)。
- 一旦初始化,终身不可修改。
示例:
class Circle {
public:// 初始化列表:初始化const成员变量_radiusCircle(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 特性
- 若用户未显式定义,编译器自动生成默认版本(返回
this
)。 - 通常无需显式定义(默认行为满足需求),仅在特殊场景下重写(如隐藏对象真实地址,返回假地址)。
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-12if (_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-11d2.Print(); // 输出2025-10-11d3.Print(); // 输出2025-10-11d4.Print(); // 输出2025-10-11cout << (d1 == d2) << endl; // 输出1(true)cout << (d1 == d4) << endl; // 输出1(true)Date d5(2025, 13, 1); // 非法日期,打印“日期非法!”,初始化0-1-1d5.Print(); // 输出0-1-1return 0;
}
补充说明:
- 新增
IsValidDate
函数实现日期合法性检查(用户疏漏的健壮性逻辑),避免创建非法日期(如 2025 年 13 月 1 日)。 - 析构函数因 Date 类无动态资源,可省略,编译器生成的默认版本足够使用。