
🫧个人主页:小年糕是糕手
💫个人专栏:《C++》《C++同步练习》《数据结构》《C语言》
🎨你不能左右天气,但你可以改变心情;你不能改变过去,但你可以决定未来!

目录
一、运算符重载
重载>>和<<时,需要重载为全局函数,因为如果重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream / istream放到第一个形参位置就可以了,第二个形参位置当类类型对 象。
我们下面依旧以上一篇博客基础上实现的日期类举例:
1.1、Date.h
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
class Date
{
//友元函数声明
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();
//给我一个年份和月份我们要获取这个月的天数
//高频调用的小函数最好使用内联,类里面的函数本身就内联/
int 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月天数
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return 29;
}
else
{
return monthDayArray[month];
}
}
//检查日期是否合法
bool CheckDate()
{
if (_month < 1 || _month>12)
return false;
if (_day<1 || _day>GetMonthDay(_year, _month))
return false;
return true;
}
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);
// d1 += 天数
Date& operator+=(int day);
Date operator+(int day);
// d1 -= 天数
Date& operator-=(int day);
Date operator-(int day);
//// d1 - d2
int operator-(const Date& d);
//前置++
// ++d1 -> d1.operator++()
Date& operator++();
//后置++
// d1++ -> d1.operator++(0)
// 为了区分,构成重载,给后置++,强⾏增加了⼀个int形参
// 这⾥不需要写形参名,因为接收值是多少不重要,也不需要⽤
// 这个参数仅仅是为了跟前置++构成重载区分
Date operator++(int);
//前置--
Date& operator--();
//后置--
Date operator--(int);
//我们写成成员函数的原因是想访问私有变量
////日期类传给了this指针,cout传给了out
//void operator<<(ostream& out)
//{
// //多个函数调用(从左至右),返回值是cout的别名然后再作为左操作数再次调用
// out << _year << "/" << _month << "/" << _day << '\n';
//}
private:
int _year;
int _month;
int _day;
};
//我们发现成员函数不符合我们的需求,我们需要写成全局的
//日期类传给了this指针,cout传给了out
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
1.2、Date.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
Date::Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
if (!(CheckDate()))
{
cout << "非法日期:>" << *this;
}
}
void Date::Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//日期比较大小
//<
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);
}
//日期 + 天数
//d1 += 100
//+=改变自己返回自己,传引用返回
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day = _day - GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
//不能改变自己
//d1 + 100
Date Date:: operator+(int day)
{
//拷贝构造
Date tmp(*this);
////我们去改变拷贝构造不去改变自己
//tmp._day += day;
//while (tmp._day > GetMonthDay(tmp._year, tmp._month))
//{
// tmp._day = tmp._day - GetMonthDay(tmp._year, tmp._month);
// ++tmp._month;
// if (tmp._month == 13)
// {
// ++tmp._year;
// tmp._month = 1;
// }
//}
tmp += day;
return tmp;
}
// d1 -= 天数
Date& Date::operator-=(int day)
{
if (day < 0)
{
// 处理负天数(等价于 += 绝对值)
return *this += -day;
}
_day -= day;
// 当日期≤0时,向前借月/年
while (_day <= 0) //循环中写得是继续循环的条件
{
--_month; // 月份减1
if (_month == 0)
{
// 月份减到0,切换到上一年的12月
--_year;
_month = 12;
}
// 日期 += 当前月份的天数(向前借月,用当月天数补)
_day += GetMonthDay(_year, _month);
}
return *this; // 返回自身引用,支持链式操作(如 d1 -= 5 -= 3)
}
//d1 - 天数
Date Date::operator-(int day)
{
Date tmp = *this; // 拷贝原对象(不修改自身)
tmp -= day; // 调用 -= 完成计算(复用逻辑,避免冗余)
return tmp; // 返回新对象
//这里返回的是一个局部对象,不能返回他的引用
}
//前置++
//++d1 -> d1.operator++( );
//调用完成后d1还在,所以用引用返回
Date& Date::operator++()
{
*this += 1;
return *this;
}
//后置++
//d1++ -> d1.operator++(0);
//返回的是一个局部对象不能用传引用返回
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;
}
//日期 - 日期
// d1 - d2
int Date::operator-(const Date& d)
{
//假设俩个日期第一个大,第二个小
Date max = *this;
Date min = d;
int flag = 1;
if (*this < d)
{
min = *this;
max = d;
flag = -1;
}
int day = 0;
while (min != max)
{
++min;
++day;
}
return day * flag;
}
//流的插入和提取
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << '\n';
return out; //必须返回 ostream 对象
}
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "请依次输入年月日:>";
in >> d._year >> d._month >> d._day;
if (d.CheckDate())
{
break;
}
else
{
cout << "输入的日期非法,请重新输入" << endl;
}
}
return in;
}
1.3、test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
//重载运算符
int main()
{
//流插入
int i = 1;
double d = 1.1;
//他会自动识别类型,其实是一个函数重载
//cout是一个ostream类型的对象,ostream是库里面的一个类
cout << i << endl;//cout.operator<<(i);
cout << d << endl;//cout.operator<<(d);
//我们对于日期类的打印是不可以直接打印的需要重载运算符<<
Date d1(2025, 12, 1);
//cout << d1;
//d1传给了this,cout传给了out
//d1.operator<<(cout);
////这样虽然可以跑,但是不符合可读性,运算符重载就是为了增强文章的可读性
//d1 << cout;
Date d2(2025, 12, 2);
cout << d1 << d2;
//流提取
cin >> d1;
cout << d1;
}
二、取地址运算符重载
2.1、const成员函数
将 const 修饰的成员函数称之为 const 成员函数,const 修饰成员函数放到成员函数参数列表的后面。
const 实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰 Date 类的 Print 成员函数,Print 隐含的 this 指针由
Date* const this变为const Date* const this
cpp
#pragma once
#define _CRT_SECURE_NO_WARNINGS 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(const Date* const this) const
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩
Date d1(2024, 7, 5);
d1.Print();
const Date d2(2024, 8, 5);
d2.Print();
return 0;
}
2.2、取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显示实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。
cpp
class Date
{
public:
Date* operator&()
{
return this;
// return nullptr;
}
const Date* operator&()const
{
return this;
// return nullptr;
}
private:
int _year; // 年
int _month; // ⽉
int _day; // ⽇
};
三、再探构造函数
3.1、构造函数plus
之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 "成员变量" 后面跟一个放在括号中的初始值或表达式。
每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
引用成员变量,const 成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
C++11 支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
尽量使用初始化列表初始化,**因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。**如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++ 并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
初始化列表总结:无论是否显示写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化;

cpp
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour)//没有默认构造
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year = 1, int month = 1, int day = 1)
//初始化列表,冒号开始,逗号分割,一个括号放初始化的值/表达式
:_year(year)
, _month(month)
, _day(day)
//初始化列表是他们定义的地方
, _t(12)
, _ref(x)
, _n(1)
{
// error C2512: "Time": 没有合适的默认构造函数可⽤
// error C2530 : "Date::_ref" : 必须初始化引⽤
// error C2789 : "Date::_n" : 必须初始化常量限定类型的对象
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//声明
int _year;
int _month;
int _day;
//必须在定义时初始化
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
};
int main()
{
int i = 0;
//对象整体定义
Date d1(i);
d1.Print();
return 0;
}
总结:
1)一般情况下,建议尽量使用初始化列表显示初始化
2)如果没在初始化列表初始化的值,尽量给缺省值
cpp
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date()
:_month(2)
{
cout << "Date()" << endl;
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的
// 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化
int _year = 1;
int _month = 1;
int _day;
//不在初始化列表初始化,也可以在这初始化
Time _t = 1;
const int _n = 1;
int* _ptr = (int*)malloc(12);
};
int main()
{
Date d1;
d1.Print();
return 0;
}
3.2、习题
下面程序的运行结果是什么()
cpp#include<iostream> using namespace std; class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2 = 2; int _a1 = 2; }; int main() { A aa(1); aa.Print(); return 0; }答案:1和随机值
分析:
我们要知道初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关,我们首先定义了一个A类的变量aa并且给他初始化为1,即传1回去,int a就是1而我们我们类中是先声明_a2的,初始化列表中
_a2(_a1)是尝试用未初始化的_a1来初始化_a2 ,而此时_a1并没有初始化所以是个随机值,所以_a2也是随机值,接下来我们定义了_a1,并且将a的值用来初始化列表的初始化操作给他,故他的值也为1。
