目录
[5.1 运算符重载](#5.1 运算符重载)
[5.2 赋值运算符重载](#5.2 赋值运算符重载)
[5.3 日期类实现](#5.3 日期类实现)
[6.1 const成员函数](#6.1 const成员函数)
[6.2 取地址运算符重载](#6.2 取地址运算符重载)
一、类的默认成员函数
类的六个默认成员函数------不主动写,编译器自动生成的函数。
表格如下:
| 分类 | 函数 | 作用 | 需要自己写吗 |
| 初始化和清理 | 构造函数 | 对象创建时初始化 | 一般都自己写 |
| | 析构函数 | 对象销毁时清理资源 | 一般都自己写 |
| 拷贝复制 | 拷贝构造 | 用同类对象初始化新对象 | 涉及深拷贝时必须写 |
| | 赋值重载 | 把一个对象赋值给另一个已有对象 | 几乎不用写 |
| 取地址 | operator& | 普通对象取地址 | 几乎不用写 |
| const operator& | const 对象取地址 | 几乎不用写 |
|---|
C++又加了两个:移动构造、移动赋值(后续会讲)。
学习思路:
1、不写时编译器生成的版本做了什么?------是否满足需求。
2、不满足怎么自己写?------这是重点
二、构造函数
作用:替代以前手写的初始化函数,对象创建时自动调用,不用手动调用。
5个特点:
1、函数名 = 类名
cpp
class Date{
Date(){ } // 函数名 = 类名
};
2、无返回值(不用写 void)
不写返回值类型,也不写void
不是"没有返回值",是C++语法规定构造函数就不能写返回值类型
3、对象实例化时自动调用
cpp
Date d1; // 自动调用Date()
Dare d2(2026,11,7); // 自动调用Date(2026,11,7)
4、可以重载
cpp
class Date{
public:
Date(){ }; // 无参版本
Date(int year,int m,int d){ } // 带参版本
};
5、不主动写,编译器自动生成一个无参的;主动写,编译器就不生成了
一旦显示定义了任何构造函数,编译器就认为"你知道自己在干嘛",不再自动生成。
2个问题:
什么是默认构造函数?
不传实参就能够调用的构造函数,都叫默认构造函数,包括三种:
(1) 无参构造函数
(2) 全缺省构造函数
(3) 编译器自动生成的那个构造函数
这三个只能有一个存在,不能同时存在,为什么?
cpp
class Date {
public:
// 无参构造
Date() {
_year = 1;
_month = 1;
_day = 1;
}
// 全缺省构造
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1; // 编译器根本不知道调用哪个默认构造函数,因为两个都不需要传参
return 0;
}
编译器自动生成的构造函数,做了什么?
| 成员类型 | 编译器自动初始化吗 |
| 内置类型(int/char/double/ 指针等 | 不初始化,值是随机的 |
| 自定义类型(class/struct 等) | 自动调用它的默认构造函数初始化 |
|---|
如果自定义类型成员没有默认构造函数,编译报错------这时需要用初始化列表解决(下章讲解)
代码示例:
cpp
#include<iostream>
using namespace std;
class Date {
public:
// 无参构造
Date() {
_year = 1;
_month = 1;
_day = 1;
}
// 构造函数重载
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1; // 自动调用无参构造
Date d2(2026, 11, 7); // 自动调用带参构造
d1.Print();
d2.Print();
return 0;
}
编译器自动生成构造函数的例子:
cpp
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack {
public:
Stack(int n = 4) {
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a = nullptr) {
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
// ......Push/Pop/Top/Destory等
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
// 两个Stack实现队列
class MyQueue {
public:
// 你不写构造函数,编译器自动生成MyQueue的默认构造函数
// 它会自动调用两个Stack成员的构造函数,完成初始化
// 相当于:MyQueue() : pushst(4), popst(4) {}
private:
Stack pushst; // 编译器自动调 Stack(4) 初始化
Stack popst; // 编译器自动调 Stack(4) 初始化
};
int main() {
MyQueue mq; // 自动调MyQueue默认构造,里面自动调两个Stack的构造
return 0;
}
三、析构函数
对象销毁时自动调用,负责清理资源(释放malloc的空间等),代替之前手写的Destory。
注意一个关键区分:析构函数不是销毁对象本身。局部对象在栈上,函数结束栈帧一回收,对象空间就释放了,不用管,析构函数只是在销毁对象前,帮你清理对象里申请的资源------比如malloc的内存、打开的文件句柄等。
上文讲的 Date 类不需要析构函数------它没有malloc动态资源,成员都是栈上的int,对象销毁自动就没了,没东西需要清理。
6个特点:
1、析构函数名 = ~ + 类名
cpp
class Stack{
~Stack(){ } // 类名前加 ~
};
2、无参数,无返回值
不写返回值类型,也不写void;不能重载(因为没有参数,无法重载)
3、一个类只能有一个析构函数
不主动写,编译器自动生成一个默认的;主动写。编译器就不生成了。
4、对象生命周期结束时,系统自动调用
cpp
int main(){
Stack st; // 自动调构造函数
// ......
} // 函数结束,st自动销毁,自动调用析构函数
5、自己手写析构函数,自定义类型成员还是会自动调用析构函数
不管手不手写析构,只要自定义类型成员存在,它的析构函数一定会被调用;你自己手写的析构只负责你自己类里的资源,成员的析构编译器自动帮你调用。
6、多个对象的析构顺序
一个局部域里定义了多个对象,后定义的先析构(先进后出)
cpp
int main(){
Stack st1; // 先构造st1
Stack st2; // 再构造st2
// 函数结束:
// 先析构st2(后定义的先析构)
// 再析构st1
}
2个问题:
7、编译器自动生成的析构函数做了什么 ?
| 成员类型 | 是否自动处理 |
| 内置类型(int/char/ 指针等) | 不处理,什么都不做 |
| 自定义类型(class/struct 等) | 自动调用它的析构函数 |
|---|
8、什么时候必须自己写析构函数?
| 情况 | 是否需要自己写 |
| 类里没有申请资源(如 Date) | 不用写,编译器默认生成的就行 |
| 类里有自定义类型成员,但没有动态资源(如 MyQueue) | 不用写,默认生成的就行 |
| 类里有 malloc/new 等动态资源(如 Stack) | 必须自己写,否则内存泄漏 |
|---|
代码示例:
Stack类------构造+析构
cpp
#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;
}
// 析构函数:清理资源(释放malloc的空间)
~Stack() {
cout << "~Stack()" << endl; // 观察什么时候调
free(_a); // 释放动态申请的数组
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a; // 动态数组指针
size_t _capacity;
size_t _top;
};
// 两个Stack实现队列
class MyQueue {
public:
// 你不写析构函数,编译器自动生成MyQueue的析构函数
// 它会自动调用两个Stack成员的析构函数,释放Stack内部的资源
// 相当于:~MyQueue() { pushst.~Stack(); popst.~Stack(); }
private:
Stack pushst; // 编译器自动调pushst的析构
Stack popst; // 编译器自动调popst的析构
};
int main() {
Stack st; // 自动调Stack构造
MyQueue mq; // 自动调MyQueue默认构造,里面自动调两个Stack构造
// 函数结束:
// 先析构mq(后定义的先析构)
// → MyQueue析构自动调pushst析构、popst析构
// 再析构st(先定义的后析构)
// → 自动调~Stack()
return 0;
}
总结:析构函数就是类里类名前加~的无参无返回值函数,对象销毁时自动调用,替代你以前手写的 Destroy;有动态资源(malloc/new)的类必须自己写析构,否则内存泄漏。
四、拷贝构造函数
定义:用一个已存在的同类对象,初始化一个新对象时,自动调用的构造函数。
它是构造函数的一个重载------函数名还是类名,参数是同类对象的引用。
cpp
Date d1(2025,11,7);
Date d2(d1); // 用d1初始化d2,自动调拷贝构造
4个特点:
1、拷贝构造是构造函数的一个重载
函数名 = 类名;
第一个参数是自身类类型的引用
后面的参数可以有,但必须有缺省值
2、第一个参数必须是类类型对象的引用,不能传值
cpp
// 错误写法:传值方式,编译报错
Date(Date d){......}
为什么不能传值?会导致无穷递归
cpp
Date d1;
Date d2(d1); // 调用拷贝构造 Date(Date d)
// 传参d1给d,又要拷贝构造一次 Date(d1)
// 又传参......无限递归,栈溢出
所以必须用引用:
cpp
// 正确写法:const引用
Date(const Date& d){
......
}
3、自定义类型对象进行拷贝行为,必须调用拷贝构造
传值传参:Func1(Date d1)传 d1 进去,会调用拷贝构造生成临时对象
传值返回:Date Func2() 返回 tmp,会调用拷贝构造生成临时对象
4、不手动写,编译器会自动生成拷贝构造
编译器自动生成的拷贝构造做什么:
内置类型成员:值拷贝(浅拷贝),一个字节一个字节复制
自定义类型成员:自动调用它的拷贝构造
1个问题:
什么时候需要自己写拷贝构造?
| 类的情况 | 是否需要自己写 | 原因 |
| 成员全是内置类型,没有动态资源(如 Date) | 不用写 | 编译器自动生成的浅拷贝就够用 |
| 成员有 malloc/new 动态资源(如 Stack) | 必须自己写 | 浅拷贝只复制指针,两个对象指向同一块资源,析构两次崩溃 |
| 成员主要是自定义类型(如 MyQueue) | 不用写 | 自动生成的拷贝构造会自动调 Stack 的拷贝构造 |
|---|
小技巧:如果一个类自己写了析构函数释放资源,那它需要自己写拷贝构造(深拷贝)。
一个对比:
传值返回 vs 传引用返回
传值返回:会产生一个临时对象,调用拷贝构造(有拷贝开销)
传引用返回:返回的是原对象的别名,没有拷贝(减少开销)
注意:如果返回的是函数内的局部对象,函数结束就销毁了,传引用返回会变成野引用(类似野指针)
代码示例:
1、Date类------拷贝构造
cpp
#include<iostream>
using namespace std;
class Date {
public:
// 构造函数:全缺省
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数:第一个参数必须是const引用
// 编译报错:error C2652: "Date": 非法的复制构造函数:第一个参数不应是"Date"
// Date(Date d) { ... } // 错误!传值会无穷递归
Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
// 注意:这个不是拷贝构造,是普通构造(参数是指针)
Date(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(Date d) {
cout << &d << endl;
d.Print();
}
// 传值返回:会调用拷贝构造生成临时对象
// Date Func2() { ... }
// 传引用返回:返回局部对象的引用,是野引用!
Date& Func2() {
Date tmp(2024, 7, 5);
tmp.Print();
return tmp; // tmp是局部对象,函数结束就销毁了
// 返回的是野引用,外面用就是野指针
}
int main() {
Date d1(2024, 7, 5);
// 传值传参:自动调拷贝构造,把d1拷贝一份给d
Func1(d1);
cout << &d1 << endl; // 和Func1里的&d不一样,证明是两个对象
// 用指针构造:不是拷贝构造,是普通构造
Date d2(&d1);
d1.Print();
d2.Print();
// 拷贝构造:通过同类对象初始化新对象
Date d3(d1);
d3.Print();
// 也是拷贝构造:Date d4 = d1;
Date d4 = d1;
d4.Print();
// Func2返回局部对象的引用,是野引用,用了就崩
Date ret = Func2();
ret.Print();
return 0;
}
2、Stack类------深拷贝(必须自己写)
cpp
#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;
}
// 拷贝构造:深拷贝(必须自己写)
// 如果不写,编译器自动生成的浅拷贝会导致两个对象的_a指向同一块资源
// 析构时会free两次,程序崩溃
Stack(const Stack& st) {
// 重新malloc一块同样大小的空间
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a) {
perror("malloc申请空间失败!!!");
return;
}
// 把数据从st._a拷贝到新的_a
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;
};
// 两个Stack实现队列
class MyQueue {
// 不写拷贝构造,编译器自动生成
// 自动生成的拷贝构造会自动调用Stack的拷贝构造
// 只要Stack实现了深拷贝,MyQueue就没问题
private:
Stack pushst;
Stack popst;
};
int main() {
Stack st1;
st1.Push(1);
st1.Push(2);
// 拷贝构造st2 = st1
// 如果不写Stack的拷贝构造,编译器自动生成的浅拷贝会导致:
// st1._a 和 st2._a 指向同一块资源
// 析构时free两次,程序崩溃
Stack st2 = st1;
MyQueue mq;
// MyQueue自动生成的拷贝构造会自动调用Stack的拷贝构造
// 只要Stack实现了深拷贝,就没问题
MyQueue mq2 = mq;
return 0;
}
总结:拷贝构造就是用同类对象初始化新对象时自动调用的构造函数;第一个参数必须是 const 引用(传值会无穷递归);有动态资源(malloc/new)的类必须自己写深拷贝,否则两个对象指向同一块资源,析构两次崩溃。
五、赋值运算符重载
5.1 运算符重载
定义:让运算符(+、-、==、<等)也能用于自定义类型的对象,并且具有意义。
本质是一个特殊的函数,函数名称是 operator + 运算符
cpp
d1 == d2; // 编译器自动转换成 d1.operator==(d2);
4个特点:
1、函数名 = operator + 运算符
cpp
bool operator==(const Date& d1, const Date& d2){
......
}
2、参数个数 = 运算对象数量
一元运算符(如 ++):1个参数
二元运算符(如+、==):2个参数
注意:如果是成员函数,第一个参数默认传给this指针,所以参数比运算对象少一个
3、至少有一个类类型参数
不能重载内置类型的运算符,比如不能写 int operator+(int x, int y)
4、重载后优先级和结合性不变
a + b * c 还是先算乘后算加
不能重载的五个运算符
| 运算符 | 说明 |
| .* | 成员指针访问 |
| :: | 作用域解析 |
| sizeof | 取大小 |
| ?: | 三目运算符 |
| . | 成员访问 |
|---|
也不能创造新运算符,比如 operator@ 不行。
两个特殊点:
1、前置++ 和 后置++ 怎么区分?
函数名都叫 operator++,编译器用一个额外的 int 形参区分:
cpp
Date& operator++(); // 前置++:无额外参数
Date operator++(int); // 后置++:多一个int形参(不用传值,编译器自动传0)
2、为什么 << 和 >> 要重载为全局函数?
如果重载为成员函数,this指针抢占第一个参数位置:
cpp
// 成员函数:cout << d 会变成 cout.operator <(d),不对!
// 因为cout是ostream类,不是我们的Date类
所以必须重载为全局函数,把 ostream 放在第一个参数:
cpp
ostream& operator<<(ostream& out, const Date& d) {
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
代码示例:
1、错误示例:不能重载内置类型
cpp
#include<iostream>
using namespace std;
// 编译报错:"operator +"必须至少有一个类类型的形参
// int operator+(int x, int y) {
// return x - y;
// }
2、Date类------全局 operator==
cpp
#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(2024, 7, 5);
Date d2(2024, 7, 6);
// 运算符重载函数可以显式调用
operator==(d1, d2);
// 编译器自动转换成 operator==(d1, d2);
d1 == d2;
return 0;
}
Date类------成员函数operator== 和 前置/后置 ++
cpp
#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;
}
// 成员函数重载==
// 第一个参数是this(隐含),所以只有一个参数d
bool operator==(const Date& d) {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// 前置++:返回引用,先++再返回
Date& operator++() {
cout << "前置++" << endl;
// ... 年月日加1
return *this;
}
// 后置++:多一个int形参区分,返回临时对象
Date operator++(int) {
Date tmp = *this; // 先保存原来的值
cout << "后置++" << endl;
// ... 年月日加1
return tmp; // 返回原来的值
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2024, 7, 5);
Date d2(2024, 7, 6);
// 显式调用
d1.operator==(d2);
// 编译器自动转换成 d1.operator==(d2);
d1 == d2;
// 前置++:编译器转换成 d1.operator++();
++d1;
// 后置++:编译器转换成 d1.operator++(0);
d1++;
return 0;
}
5.2 赋值运算符重载
含义:赋值运算符重载是默认成员函数,用于两个已存在对象之间的拷贝赋值。
| | 特点 | 说明 |
| 1 | 必须重载为成员函数 | 参数建议写成 const **类类型& ,**避免传值拷贝 |
| 2 | 有返回值,建议返回 类类型& | 支持连续赋值 d1 = d2 = d3 |
| 3 | 不写编译器自动生成 | 内置类型值拷贝,自定义类型调它的赋值重载 |
| 4 | 有动态资源必须自己写 | 否则浅拷贝导致两个对象指向同一块资源 |
|---|
拷贝构造 vs 赋值重载
| | 拷贝构造 | 赋值重载 |
| 作用对象 | 用一个对象初始化新对象 | 把一个对象赋值给已存在对象 |
| 例子 | Date d2(d1); | d2 = d1; |
|---|
代码示例:
cpp
class Date {
public:
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
// 拷贝构造
Date(const Date& d) {
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
Date& operator=(const Date& d) {
// 检查自己给自己赋值的情况
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
// 返回*this,支持连续赋值 d1 = d2 = d3
return *this;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year, _month, _day;
};
int main() {
Date d1(2024, 7, 5);
Date d2(d1); // 拷贝构造
Date d3(2024, 7, 6);
d1 = d3; // 赋值重载(d1已经存在)
// 注意:Date d4 = d1; 这是拷贝构造,不是赋值重载!
// 因为d4是新创建的对象
Date d4 = d1;
return 0;
}
5.3 日期类实现
整体设计:
cpp
// Date.h
#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
class Date {
// 友元函数声明:operator<<和operator>>
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1);
void Print() const;
// 获取某月天数(静态成员函数,不依赖对象)
int GetMonthDay(int year, int month);
// 检查日期合法性
bool CheckDate();
// 比较运算符
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;
// 日期 + 天数
Date& operator+=(int day);
Date operator+(int day) const;
// 日期 - 天数
Date& operator-=(int day);
Date operator-(int day) const;
// 前置++ / 后置++
Date& operator++();
Date operator++(int);
// 前置-- / 后置--
Date& operator--();
Date operator--(int);
// 两个日期相减,返回天数差
int operator-(const Date& d) const;
private:
int _year, _month, _day;
};
关键函数实现
cpp
// Date.cpp
#include "Date.h"
// 获取某月天数
int Date::GetMonthDay(int year, int month) {
assert(month > 0 && month < 13);
static int monthDayArray[13] = {
-1, 31, 28, 31, 30, 31, 30, 31,
31, 30, 31, 30, 31
};
// 闰年2月29天
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
return 29;
}
return monthDayArray[month];
}
// 检查日期合法性
bool Date::CheckDate() {
if (_month < 1 || _month > 12
|| _day < 1 || _day > GetMonthDay(_year, _month)) {
return false;
}
return true;
}
// 构造函数
Date::Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
if (!CheckDate()) {
cout << "日期非法" << endl;
}
}
// 比较大小:d1 < d2
bool Date::operator<(const Date& d) const {
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) const {
return *this < d || *this == d;
}
bool Date::operator>(const Date& d) const {
return !(*this <= d);
}
bool Date::operator>=(const Date& d) const {
return !(*this < d);
}
bool Date::operator==(const Date& d) const {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator!=(const Date& d) const {
return !(*this == d);
}
// 日期 += 天数
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) const {
Date tmp = *this;
tmp += day;
return tmp;
}
// 前置++
Date& Date::operator++() {
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int) {
Date tmp = *this;
*this += 1;
return tmp; // 返回原来的值
}
// 两个日期相减,返回天数差
int Date::operator-(const Date& d) const {
Date max = *this;
Date min = d;
int flag = 1;
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;
if (!d.CheckDate()) {
cout << "日期非法" << endl;
}
return in;
}
测试代码:
cpp
// Test.cpp
#include "Date.h"
void TestDate1() {
Date d1(2024, 4, 14);
Date d2 = d1 + 30000; // 加30000天
d1.Print();
d2.Print();
}
void TestDate2() {
Date d1(2024, 4, 14);
Date d2 = ++d1; // 前置++
d1.Print();
d2.Print();
Date d3 = d1++; // 后置++
d1.Print();
d3.Print();
}
void TestDate3() {
Date d1(2024, 4, 14);
Date d2(2034, 4, 14);
int n = d1 - d2; // 两个日期相差多少天
cout << n << endl;
}
void TestDate4() {
Date d1(2024, 4, 14);
Date d2 = d1 + 30000;
cout << d1; // 自动调 operator<<(cout, d1)
cout << d2;
cin >> d1 >> d2;
cout << d1 << d2;
}
int main() {
TestDate1();
TestDate2();
TestDate3();
TestDate4();
return 0;
}
总结:赋值运算符重载是两个已存在对象之间的拷贝赋值,必须返回引用支持连续赋值;日期类实现把所有比较、加减、++、--、流插入都重载成运算符,用起来像内置类型一样方便。
六、取地址运算符重载
6.1 const成员函数
const 修饰的是什么?
const修饰的是成员函数隐含的this指针,不是函数本身。
cpp
void Print() const;
// 编译器看到的真实样子:
// void Print(const Date* const this) const;
原来的 this 是 Date* const this(指针本身不能改指向,但指向的内容能改)
加了 const 后变成 const Date* const this(指针不能改指向,指向的内容也不能改)
作用:
在 const 成员函数里,不能修改类的任何成员变量。
cpp
void Print() const {
_year = 2025; // ✗ 编译错误!const成员函数不能改成员
}
权限规则:
| 对象类型 | 能调普通成员函数 | 能调 const 成员函数 |
| 非 const 对象 | ✅ 能调 | ✅ 能调(权限缩小) |
| const 对象 | ❌ 不能调 | ✅ 只能调 const 成员函数 |
|---|
为什么要加const?
1、const 对象必须通过 const 成员函数访问,否则 const 对象什么都干不了。
2、只读函数加 const,明确告诉调用者:这个函数不会修改对象。
3、权限可以缩小不能放大:非 const 对象调 const 函数,相当于把 "能读能写" 缩小成 "只读",允许;反过来不行。
代码示例:
cpp
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
// const成员函数:this指针被const修饰,不能修改成员变量
// 编译器真实原型:void Print(const Date* const this) const
void Print() const {
cout << _year << "-" << _month << "-" << _day << endl;
// _year = 2025; // ✗ 编译错误!const成员函数不能改成员
}
private:
int _year;
int _month;
int _day;
};
int main() {
// 非const对象:能调普通函数,也能调const函数(权限缩小)
Date d1(2024, 7, 5);
d1.Print(); // ✓ 非const对象调const成员函数,允许
// const对象:只能调const成员函数
const Date d2(2024, 8, 5);
d2.Print(); // ✓ const对象只能调const成员函数
return 0;
}
总结:const 成员函数就是在函数后面加 const,本质是修饰 this 指针,让函数里不能改成员变量;const 对象只能调 const 成员函数,非 const 对象两种都能调。
6.2 取地址运算符重载
什么是取地址运算符重载?
就是重载 & 运算符,控制对类对象取地址时返回什么。
有两种形式:
**1、**普通版本:非 const 对象取地址
**2、**const 版本:const对象取地址
一般不用自己写,编译器自动生成的版本就够用了------就是正常返回对象的地址。
什么时候需要自己写?
特殊场景:不想让别人取到真实地址,就自己实现一份,返回一个假地址(比如nullptr)。
代码示例:
cpp
class Date {
public:
// 普通版本:非const对象取地址
Date* operator&() {
return this; // 返回对象真实地址
// return nullptr; // 特殊场景:不想暴露真实地址,返回空
}
// const版本:const对象取地址
const Date* operator&() const {
return this; // 返回const对象地址
// return nullptr; // 特殊场景:不想暴露真实地址
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
总结:取地址运算符重载就是重载 &,控制取地址时返回什么;一般不用自己写,编译器自动生成的就够用,只有特殊场景(不想暴露真实地址)才需要自己实现。
最后,感谢你的阅读,欢迎交流与指正!
下一篇我们再见🔜,Bye bye 👋