欢迎来到博主的专栏——C++编程
博主ID:代码小豪
文章目录
- 运算符重载
- 赋值重载函数
- 默认赋值重载函数
- 其他运算符重载函数
运算符重载
重载这个概念在c++中已经出现两次了,在前面的文章中,函数重载指的是可以用相同名字的函数实现不同的功能。而运算符重载则是可以用相同的运算符实现不同的功能。
运算符也能重载吗?在C语言也有可以复用的运算符,在不同的运用场景下有不同的作用,比如乘法操作符(*)和解引用操作符(*),取地址操作符(&)和位运算操作符(&)。
但这些和c++的运算符重载可不一样。在c++语言中,可以用operator加上运算符来表示函数,叫做运算符重载。运算符重载的声明格式如下:
返回类型 operator 运算符(参数列表……);
运算符是一种特殊形式的函数,即将运算符重载成一个与原运算符符号一致,但是重载运算符是一个函数,而原操作符是一个操作符,这个操作符就成为了一个可以调用的函数名。
其实大家在初学c++的时候就已经见过重载的运算符了。
cout << “hello world” << endl;
这个和左移操作符一样的就是重载操作符,其函数原型可以在c++参考手册中可以看到。
c++参考手册网址:cplusplus
ostream& operator<< (bool val);ostream& operator<< (short val);ostream& operator<< (unsigned short val);ostream& operator<< (int val);ostream& operator<< (unsigned int val);ostream& operator<< (long val);ostream& operator<< (unsigned long val);ostream& operator<< (float val);ostream& operator<< (double val);ostream& operator<< (long double val);ostream& operator<< (void* val);
ostream& operator<< (streambuf* sb );
ostream& operator<< (ostream& (*pf)(ostream&));ostream& operator<< (ios& (*pf)(ios&));ostream& operator<< (ios_base& (*pf)(ios_base&));
赋值重载函数
我们还是再拿date类型举例:
class Date
{
public:
Date(int year = 2024, int month = 4, int day = 16);//默认构造函数
Date(const Date& d);//拷贝构造函数
Date& operator =(const Date& D1);//赋值重载函数
private:
int _year;
int _month;
int _day;
};
Date::Date(int year, int month, int day)//默认构造函数
{
_year = year;
_month = month;
_day = day;
}
Date::Date(const Date& d)//拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
内置类型的赋值操作符是(=)。赋值重载函数可以让自定义类型的变量用(=)实现赋值操作。首先要让赋值操作符重载成函数。Date类的复制操作函数的声明如下:
Date& operator =(const Date& D1);//赋值重载函数
既然函数是要实现赋值操作,那么函数的定义内容就是要让Date类的变量进行拷贝。
Date& Date::operator=(const Date& d1)
{
if (this != &d1)//若是参数是对象本身,那么就不需要将成员一一拷贝,无意义且浪费时间
{
_year = d1._year;
_month = d1._month;
_day = d1._day;
}
return (*this);
}
参数类型使用const Date&,传递引用可以提高效率
返回类型使用const Date&,提高效率
检测是否为自己赋值,提高效率
返回*this可以让对象实现连续赋值
调用复制重载函数有两种方式,一种是和函数一样传递参数。
Date d1;
Date d2;
Date d3;
d1.operator=(d2);
d1.operator=(d2).operator=(d3);
也可以用运算符的形式调用函数,运算符与调用时的不同之处在于:对于普通函数,实参出现在函数的括号内,而操作符调用,实参出现在操作符两侧:
d1 = d2;
d1 = d2 = d3;
当运算符重载在类中时,函数显示的调用实参只有一个,但是实际上参与函数的实参有两个,一个是指向对象的this指针(this指针在上一篇文章中讲过),另外一个是被显示调用的实参。
在上例中,调用函数的对象是d1,因此this指针指向d1,将this解引用(*this)后得到的是d1,实参传递的是d2.
重载的赋值运算符是一个二元操作符,因此实际调用的参数必须是两个,比如将复制重载函数的声明修改成下例,那么编译器会报错。
Date& operator =(const Date& D1,Date&D2);
这是因为这个赋值重载函数是声明在类中的,类的this指针占据的一个参数,D1和D2作为实际上传的参数有两个,那么这个赋值重载函数的的参数共计三个。赋值操作符(=)如果是一个三元操作符,那么肯定是不符合逻辑的,因而不能超过操作符的操作数。
定义在全局(类外)的赋值重载函数的参数列表就可以声明两个形式参数。
Date& Date::operator =(Date& D1, Date& d2);//赋值重载函数
定义在类外的函数没有this指针指向对象,因此可以定义两个参数。
默认赋值重载函数
我们将Date类的赋值重载函数的声明和定义注释掉,运行以下代码:
Date d1;
Date d2;
Date d3;
d1 = d2;
d1 = d2 = d3;
通过调试,可以发现这段代码的编译过了,而且也实现的拷贝的功能
明明没有定义复制重载函数,怎么还能实现这些操作呢?这是因为若是用户没有实现赋值重载函数,编译器会生成一个默认的赋值重载函数,这个默认赋值重载函数是以值拷贝的形式实现的。和默认拷贝构造函数的作用类似。内置类型的成员变量直接赋值,自定义类型的成员变量则调用该类的赋值重载函数。
其他运算符重载函数
其他的运算符重载函数就没有赋值运算符重载那么重要了(毕竟赋值运算符有默认的函数生成,而其他的没有,除了取地址重载(&))。不能重载的运算符有(.*)(::)(sizeof)(.)(?:)。
具体要重载什么运算符,需要看这个类要实现什么功能,以Date类为例,我们可以实现判断的运算符(==,>,<等)。
bool operator==(const Date& D1)const;
bool operator>(const Date& D1)const;
bool operator!=(const Date& D1)const;
bool operator>=(const Date& D1)const;
bool operator<(const Date& D1)const;
在类中声明以上函数。
(==)需要判断两个对象是否相同,我们思考一下日期如何才算是相同?相同的日期需要具有以下特点:
年相等
月相等
日相等。
根据这个逻辑得到下面的函数定义:
bool Date::operator==(const Date& d1)const
{
return _year == d1._year &&
_month == d1._month &&
_day == d1._day;
}
运算符重载函数并不需要将所有的内容都实现出来,有时候可以复用其他的重载函数,前提是符合这个运算符的逻辑
例如不等(!=)运算符,其逻辑是与(==)相反的,因此可以复用==运算符重载函数实现。
bool Date::operator!=(const Date& D1)const
{
return !(*this == D1);
}
\(>)大于重载运算符的逻辑应该如下:
比较的日期的年份,大于则返回ture。
若年份相等,比较月份,大于则返回true
若月份相等,比较天数,大于则返回true
其余情况(包括日期相等)都返回false。
bool Date::operator>(const Date& D1)const
{
if (_year > D1._year)
{
return true;
}
else if (_year == D1._year)
{
if (_month > D1._month)
{
return true;
}
else if (_month == D1._month)
{
if (_day > D1._day)
{
return true;
}
}
}
return false;
}
接着实现>=和<,这些运算符的逻辑都是能和>和==运算符复用的。实现如下:
bool Date::operator>=(const Date& D1)const
{
return (*this) == D1 || (*this) > D1;
}
bool Date::operator<(const Date& D1)const
{
return !(*this >= D1);
}
为什么运算符重载函数需要符合运算符的逻辑呢?大家可以试一下,比如讲大于的逻辑写成小于,或者其他功能。这个函数是能通过编译的,但是这么做有什么好处呢?当然是什么都没有。我们可以涉想这么一个场景。你是某个公司的程序员,你从别人那里调用了一个库,这个库将某个类的加法运算符的逻辑写成了减法。并且导致了你的程序不停的出错花费了大量时间调试和修改,最后发现是同事的恶作剧,那么请问你什么感受呢?因此运算符重载函数的逻辑应该与运算符的本身逻辑一致。大大的增加了程序的可读性。这样才是c++推出运算符重载的初衷。