news 2026/10/10 12:09:41

类和对象(中下)彻底搞懂赋值/取地址运算符重载和日期类实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
类和对象(中下)彻底搞懂赋值/取地址运算符重载和日期类实战

这里紧跟着我上一期博客C++类和对象(中上)构造函数·析构函数·拷贝构造-CSDN博客去进行讲解。

一.赋值运算符重载

1.运算符重载(本质为函数)

注意:我们主要讲解的是赋值运算符重载这个默认成员函数,而运算符重载是其实现方法,要先进行学习。

• 在C语言中,运算符只能用来运算内置类型,因为编译器会直接将运算符转换成对应的指令。现在,C++中推出了运算符重载,其允许我们通过运算符重载的方式指定新的含义。C++规定,类类型对象使用运算符时,必须转换成对应的运算符重载(函数),若没有对应的运算符重载,则会编译报错。

• 运算符重载是有特殊名字的函数,其名字由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有返回类型、参数列表和函数体。

class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //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; }

这里涉及到两个问题:

①这里的返回类型是根据我的运算符的功能来的,比如这个等于运算符,其是要判断是否相等,所以我要返回一个bool值。名字就是operator+后面要定义的运算符,之后的参数列表和函数体就根据函数的功能来进行设计。

②我为了在类外也能访问到私有的成员函数,把它们都放成公有了,这里还有几种解决办法:1,Date类提供getxxx函数 2,友元函数 3,重载为成员函数。这个问题我们下面紧接着就会进行讲解。

• 运算符重载函数的参数个数要和该运算符作用的运算对象数量一样多。一元运算符要有一个参数,二元运算符要有两个参数,其中,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。

调用函数方法如下:

//正常调用,可读性高 d1 == d2; //当然也可以显式调用 operator==(d1,d2);

• 如果一个运算符重载函数是成员函数时,因为this指针的存在,其第一个运算对象(也就是二元运算符的左运算数)默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。下面演示一下重载为成员函数的写法:

class Date { public: bool operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } private: int _year; int _month; int _day; };

可以看到,参数只有一个了,因为前面有一个隐含的this指针,且其会指向调用这个函数的对象的地址。

• 运算符重载后,其优先级和结合性与对应的内置类型运算符保持一致。

• 不能通过语法中没有的符号来创造新的运算符:比如operator@。

•.*::sizeof?:.注意以上5个运算符不能重载。选择题常考,记忆下一就行。

• 重载运算符至少要有一个类类型的参数,不能通过运算符重载去改变内置类型对象的运算符含义,如:

int operator+(int x, int y)

• 一个类要重载哪些运算符,是要看哪些运算符重载后有意义,比如Date日期类重载operator-就有意义,但是重载operator*就没有意义。

• 重载++,--运算符时分为前置++和后置++,运算符重载的函数名都是operator++,无法很好区分。所以C++规定:后置++重载时,在参数列表里增加一个int形参,跟前置++构成函数重载,方便区分。

Date Date::operator++() { //... } Date Date::operator++(int) { //... }

注意:①这里使用了声明和定义分离,先在类内部写声明,在外部写函数,这个函数还是内部成员函数。②后置++中参数列表里的int只用作占位,用来和前置++形成函数重载和区分,无实际用途。

• 重载<<和>>时,要重载成全局函数,因为如果重载成成员函数,this指针默认抢占了第一个形参位置,而第一个形参位置是左侧运算对象,调用时就会变成: 对象<<cout,不符合使用习惯和可读性:

class Date { public: Date operator++(); Date operator++(int); Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void operator<<(ostream& _cout) { _cout << _year <<'/'<< _month <<'/'<< _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 10, 5); Date d2(2000, 1, 1); d1 << cout; return 0; }

而重载为全局函数把ostream/istream放到第一个形参就可以了,第二个形参位置为当前类类型的对象。此外要注意一点:输入输出要支持连续的输入输出也就是:

cin >> i >> n; cout << i << ' ' << n << endl;

而连续的输入输出的诀窍就是返回值,每单个输入输出后就会返回一个cout或cin,去进行下一次的输入或输出:

cin >> i >> n; //先cin>>i,->返回cin,再cin>>n; cout << i << ' ' << n << endl; //先cout<<i,返回cout,再去接下来的输出

所以我们在实现时也要自己加入返回值,这个实例在后续的日期类实现里完整实现。

2.赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的赋值,这里要注意和拷贝构造区分,拷贝构造用于一个已经存在的对象去拷贝初始化一个要创建的对象,而赋值运算符重载是两个对象都已存在后的赋值:

//拷贝构造 Date d3 = d1; //赋值运算符的重载 d1 = d2;

赋值运算符重载的特点:

1.赋值运算符重载是一个运算符重载,规定必须重载为成员函数(因其为默认的成员函数之一)。赋值运算符重载的参数建议写成const当前类类型的引用,否则传值传参会有拷贝。

2.有返回值,且建议写成当前类类型的引用,引用返回可以提高效率(跳过拷贝构造),有返回值的目的是为了支持连续赋值(和前文的<</>>一样)的场景。

3.没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认运算符重载行为跟拷贝构造函数类似。都是对内置类型完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用它自己的赋值重载函数。

4.像Date这样的类,成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。这里也和拷贝构造类似。

3.日期类的实现

让我们现在使用所学的全部知识去实现一个日期类,完成它的基本功能。这里也涉及到了const成员函数,接下来就会讲解。

//Date.h #include<iostream> using namespace std; #include<assert.h> class Date { friend ostream& operator<<(ostream& _cout, const Date& d); friend istream& operator>>(istream& _cin, Date& d); public: Date(int year = 1, int month = 1, int day = 1); void Print() const; bool CheckDate() const; //因其要频繁调用,所以不做声明和定义分离,使其成为inline函数 int GetMonthDay(int year, int month) const { assert(month >= 1 && month <= 12); int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month != 2) { return arr[month]; } if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { return 29; } return 28; } //比较大小,相等 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; //日期-日期 int operator-(const Date& d) const; //Date的自加和自减 Date& operator++(); Date operator++(int); Date& operator--(); Date operator--(int); private: int _year; int _month; int _day; }; //输入输出流的重载 ostream& operator<<(ostream& out, const Date& d); istream& operator>>(istream& in, Date& d); //Date.cpp #include"Date.h" Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day; assert(!CheckDate()); } void Date::Print() const { cout << _year << '/' << _month << '/' << _day << endl; } bool Date::CheckDate()const { if (_month > 12 || _month<1 || _day> GetMonthDay(_year, _month) || _day < 1) { return false; } else { return true; } } 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) { if (_day > d._day) { return true; } return false; } } return false; } 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 || *this == d; } bool Date::operator<(const Date& d) const { return d > *this; } bool Date::operator<=(const Date& d) const { return *this < d || *this == d; } bool Date::operator!=(const Date& d) const { return !(*this == d); } Date& Date::operator+=(int day) { if (day < 0) { day = -day; *this -= day; return *this; } _day += day; while (_day > GetMonthDay(_year, _month)) { _day = _day - GetMonthDay(_year, _month); _month += 1; if (_month > 12) { _month = 1; _year += 1; } } return *this; } Date Date::operator+(int day) const { Date tmp = *this; return tmp += day; } Date& Date::operator-=(int day) { if (day < 0) { day = -day; *this += day; return *this; } _day -= day; while (_day < 1) { _month -= 1; if (_month < 1) { _month = 12; _year -= 1; } _day = _day + GetMonthDay(_year, _month); } return *this; } Date Date::operator-(int day) const { Date tmp = *this; return tmp -= day; } int Date::operator-(const Date& d) const { Date max = *this; Date min = d; int flag = 1; if (max < min) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { min++; n++; } return flag * n; } Date& Date::operator++() { return *this += 1; } Date Date::operator++(int) { Date tmp = *this; *this += 1; return tmp; } Date& Date::operator--() { return *this -= 1; } Date Date::operator--(int) { Date tmp = *this; *this -= 1; return tmp; } ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } istream& operator>>(istream& in, Date& d) { cout << "请输入年月日" << endl; while (1) { in >> d._year >> d._month >> d._day; if (!d.CheckDate()) { cout << "输入信息不合法,请重新输入" << endl; } else { break; } } return in; }

二.取地址运算符的重载

1.const成员函数

• 规定把const修饰的成员函数称之为const成员函数,const修饰成员函数要放到成员函数参数列表的后面。

int GetMonthDay(int year, int month) const;

• 这里const实际修饰的是该成员函数的this指针,表明不能对类的任何成员进行修改,假设const修饰Date类的Print成员函数,那Print隐含的this指针发生如下变化:

Date* const this -> const Date* const this

没加const修饰函数之前,自带的const是修饰this本身的,意思是你this就一直指向调用它的那个对象。现在又加一个const,修饰的是*this,指的是不能对this指向的对象本身进行改变,不能去改变对象和其中的具体内容了。

• 加上const修饰函数还有一个优点:可以调用const修饰的对象了。

const Date d1; Date d2; //这里如果Print函数没有加上const修饰,会编译报错 d1.Print(); d2.Print();

其原因在于d1在取地址后是const Date*类型的,然后要把这个类型传给this指针,若this指针没被const修饰,则会权限放大,编译报错。

2.取地址运算符重载

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

因其为默认成员函数,这两个也要重载成成员函数。

class Date { public: Date* operator&() { return this; //return nullptr; } const Date* operator&()const//通过这个const修饰this指针完成函数重载 { return this; //return nullptr; } private: int _year; int _month; int _day; };

三.结语

类和对象中到这里就结束了,我们完成了对六个默认成员函数的基本学习,但是并不完全,如构造函数在类和对象下还有要补充的知识点,让我们努力前进吧!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 12:04:56

FFC排线优选生产厂家推荐:2026年东莞迅捷精密电子助力佛山家电行业线束适配方案

FFC排线优选生产厂家推荐&#xff1a;2026年东莞迅捷精密电子助力佛山家电行业线束适配方案FFC柔性排线是家电设备内部信号传输的关键连接部件在家电整机的内部结构中&#xff0c;控制主板与显示模块、传感模块、操作面板之间&#xff0c;需要依靠柔性扁平排线完成信号与数据的…

作者头像 李华
网站建设 2026/10/10 11:59:10

带钢表面缺陷检测数据集:XML标注转YOLO格式实战与避坑指南

简介&#xff1a;YOLO目标检测可直接使用的带钢表面缺陷数据集&#xff0c;面向计算机、电子信息工程、数学等专业学生&#xff0c;适用于课程设计、期末大作业与毕业设计。资源包含1800张带钢表面缺陷图像及对应XML标注文件&#xff0c;并附BMP格式图像副本和1个数据库文件&am…

作者头像 李华