C++进一步认识类与对象
赋值操作符重载函数
1.运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
其函数名为: operator + 需要重载的运算符符号(参数列表)。
需要注意的是:
(1)不能通过连接其他符号来创建新的操作符,比如operator$.也就是说,operator只能重载已有的操作符,其他的符号不能通过该函数来创造。
(2)重载操作符必须有一个类类型或枚举类型的操作数。
(3)用于内置类型的操作符,其含义不能改变。比如,内置整型的==,不能改变其类型。
(4)运算符重载函数的参数个数为操作符的操作数个数,比如对于==操作符,其操作数由两个,那么重载该操作符的函数参数应为2个,即operator==(int x,int y);
(5)对于作为类成员的操作符重载函数,其参数看起来要比操作符的操作数个数少一个,这是因为函数隐含了一个形参this,并且this指针被限定为第一个形参。
(6)语法规定,有五个操作符不能被重载,即.*(成员中指针解引用)、::(作用域限定符)、?:(三目操作符)、.(成员(对象)选择)、sizeof(长度运算符)。这里需要注意的是.(解引用操作符)可以被重载,不能被重载的操作符为s1.*ptr中的.*操作符,即访问类中指针成员并解引用。
用我们熟悉的日期类来举例,比如我们要实现==这个操作符的重载函数:
//头文件Date.h中 class Date { public: //获取某年某月的天数 int GetDay(int year, int month) { assert(month > 0 && month < 13); int Day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && year % 4 == 0 && year % 100 != 0 || year % 400 == 0)//闰年二月 { return 29; } return Day[month]; } //全缺省的构造函数 Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; //判断初始化的日期是否合理 if (_month >= 13 || _day > GetDay(_year, _month) || _month <= 0 || _day <= 0 || _year < 0) { cout << _year << "/" << _month << "/" << _day << "->"; cout << "非法日期" << endl; } } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } //拷贝构造函数,d1(d2) Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //==运算符重载 bool operator==(const Date& d) const; private: int _year; int _month; int _day; }; //源文件Date.cpp中 //需要注意,左操作数为this指针指向的调用函数的对象, //即函数等价于bool operator==(Date* this, const Date d); bool Date::operator==(const Date& d) const { return this->_year == d._year && this->_month == d._month && this->_day == d._day; }
由于==操作符的结果为真或假,因此函数的返回值设为bool类型。
2.赋值运算符重载
我们知道赋值运算符为=,那么重载赋值运算符的函数应为:
//Date.cpp中 Date& Date::operator=(const Date& d) { if (this != &d)//排除两个操作数相同的情况 { _year = d._year; _month = d._month; _day = d._day; } return *this; }
需要注意的是:
(1)首先,为了减少形参拷贝导致的开销,我们用引用作为形参类型;其次,由于函数并不会修改原操作数,因此加上const可以保证代码的安全性。
(2)这里的*this即this所指向的对象出了这个赋值重载函数并不会销毁,因此可以用引用返回,返回类型为类名加引用。
(3)如果操作数为两个相同的对象,回导致赋值操作多余,因此需要检查是否出现自己给自己赋值的情况。
3.默认的赋值操作符重载函数
一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的浅拷贝(值拷贝)。
以上面的Date类为例,在我们不实现赋值操作符重载函数的情况下:
int main() { Date d1(2021,10,15); Date d2; //这里d2回调用编译器自己生成的operator=函数完成拷贝 //即d2.operator=(d1); d2 = d1; return 0; }
和拷贝构造函数一样,编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了,对于Date类这样的我们无需自己定义,但是对于Stack这样会向内存申请空间的类,不能直接调用编译器的默认函数,会对已经释放的空间重复释放。
4.赋值重载函数与拷贝构造函数的对比
赋值重载函数与拷贝构造函数的作用都是实现字节序的拷贝,那么二者之间有什么区别呢?
首先,拷贝构造函数是构造函数的一个函数重载形式,其函数名为类名,无返回值,而赋值重载函数为=这个操作符的重载,其函数名为operator=,返回值为类名,需要注意的是函数重载与操作符重载二者之间没有任何关联。
其次,一个对象在初始化时调用的时拷贝构造函数,而对象初始化完成后再调用即为赋值重载函数,比如:
int main() { Date d1(2021,10,15); //调用拷贝构造函数,相对于Date d2(d1); Date d2 = d1; Date d3(2021,10,15); Date d4; //调用赋值重载函数,即d4.operator=(d3); d4 = d3; return 0; }
最后,由于=可以连续使用,因此赋值重载函数可以连续调用,比如:
int main() { Date d1(2021,10,15); Date d2; Date d3; //连续调用,相当于,d3 = (d2 = d1); 而d2 = d1有一个返回值 //该返回值再作为操作数赋值给d3 //也就相当于d3.operator(d2.operator(d1)); d3 = d2 = d1; return 0; }
日期类的实现
学习完操作符重载,我们可以实现操作符==、+=、-=、>、<等等。
//赋值运算符重载,d2=d3 -> d2.operater=(d3) Date& operator=(const Date& d);//由于在类域中this所指向的对象并不会销毁,因此可以用引用返回 //日期+=天数 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); //==运算符重载 bool operator==(const Date& d) const; //!=运算符重载 inline bool operator!=(const Date& d) const; //>运算符重载 bool operator>(const Date& d); //<运算符重载 inline bool operator<(const Date& d); //>=运算符重载 inline bool operator>=(const Date& d); //<=运算符重载 inline bool operator<=(const Date& d); //日期 - 日期,返回天数 int operator-(const Date& d) const;
const成员
我们先来看看下面这个代码:
//>运算符重载 bool operator>(const Date& d); void Test6() { const Date d1(2021, 10, 16); Date d2 = d1 + 100; cout << (d1 > d2) << endl; }
实际运行过程中会发现d1 > d2这句代码编译不过去,这是因为隐含的this指针默认的类型为不加const修饰的指针类型,而d1的类型为const修饰的变量,因此传参给this后放大了修改的权限,导致出现错误。但是this指针的类型我们是无法修改的,那么要怎么解决这种情况呢?这就需要用到我们接下来要介绍的const修饰类的成员函数了。
1.const修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
比如说上述代码我们可以修改为:
//>运算符重载,其相当于bool operator>(const Date* this, const Date& d); bool operator>(const Date& d) const;
2.小结
1.成员函数加const,变成const成员函数,这样既可以让const对象调用,也可以让非const对象调用。
2.不是所有的成员函数都要加const,因为有的函数需要用this指针修改成员变量。
3.一个成员函数是否要加const应看其功能,若为修改型,比如operator+=();Push();等不需要加const;而对于只读型,Print();operator+();等就最好加上const。
综上,如果要修改成员就不加const,若不修改则最好加上const。
取地址及const取地址操作符重载函数
类的最后两个默认成员函数为操作符&的重载及其加上const修饰的函数。
class Date { public : Date* operator&() { return this ; } const Date* operator&()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 };
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!但在实际过程中应用不多。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程宝库的更多内容!
一. 再看构造函数我们之前已经了解了构造函数的基本内容,那么这里我们将深入认识构造函数。1.函数体内赋初值class Date{public:Date(int year, int month, int day ...