4.5 运算符重载
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义类型(如Person p1
和 Person p2
)相加的运算
对于内置的数据类型,编译器知道如何进行运算,如下:
int a = 10;
int b = 10;
int c = a+b;
但是对于以下运算,编译器不知道如何对自定义数据类型进行相加:
class Person
{
public:
int m_A;
int m_B;
};
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
// 想创建第三个人,让第三个人的m_A和m_B属性等于p1和p2的m_A和m_B的和
Person p3 = p1 + p2; // 编译器此时不知道要完成我们程序员想要实现的这种操作,会报错:
// 没有与这些操作数匹配的"+"运算符,操作数类型为:Person + Person
}
(先抛开加号运算符重载的知识)我们可以自己定义一个成员函数(即需要通过p1
、p2
这些对象来调用的函数)来实现我们想要的相加功能:
// 在Person类内定义一个成员函数,实现我们想要的相加功能
Person PersonAddPerson(Person &p)
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp; // 调用拷贝构造函数,创建一个拷贝体作为返回值
}
这样我们可以实现相加功能:
Person p3 = p1.PersonAddPerson(p2);
但是每个程序员不一定都起名PersonAddPerson()
,这样函数名就会因人而异,于是编译器就说:“不如我来起个名,大家都统一用我的,并且我能承诺提供一个简易的"+"就让你们能调用这个函数!这个通用的名称就叫operator+
,也就是说我们上面定义的成员函数统一改成:
Person operator+(Person &p)
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp;
}
这个时候如果我们要来调用这个函数,正常的调用方式就是这样的:
Person p3 = p1.operator+(p2); // 本质的调用方式
考虑到编译器的承诺,我们可以简化上面的调用方式为:
Person p3 = p1 + p2; // 简化的调用方式
也就是说当我们用了编译器提供的函数名:operator+
之后,我们就可以实现上述的简化的函数调用方式,此时我们再来实现我们开始时想要实现的运算:
class Person
{
public:
int m_A;
int m_B;
Person operator+(Person& p) // 成员函数实现运算符重载
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp;
}
};
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
/*****************************************开始时我们想要实现的运算*****************************************/
// 想创建第三个人,让第三个人的m_A和m_B属性等于p1和p2的m_A和m_B的和
Person p3 = p1 + p2; // 编译器此时就不会报错了
/******************************************************************************************************/
cout << p3.m_A << p3.m_B << endl; // 2020,预期的输出✔
//Person p4 = p1.operator+(p2); // 这行可以直接使用,它是第28行代码的本质调用函数,该行代码的简化调用就是
// 第28行代码
}
此时编译器就不会报错了!以上即成员函数实现"+"运算符重载
那么我们还可以通过全局函数来实现运算符重载:
// 在Person类外定义应该全局函数,实现我们想要的相加功能
Person operator+(Person &p1, Person &p2)
{
Person tmp;
tmp.m_A = p1.m_A + p2.m_A;
tmp.m_B = p1.m_B + p2.m_B;
return tmp;
}
这个时候如果我们要来调用这个函数,正常的调用方式就是这样的:
Person p3 = operator+(p1, p2); // 本质的调用方式
我们可以同样简化上面的调用方式为:
Person p3 = p1 + p2; // 简化的调用方式
此时我们也同样可以完成我们开始时想要实现的运算
class Person
{
public:
int m_A;
int m_B;
};
Person operator+(Person &p1, Person &p2) // 全局函数实现运算符重载
{
Person tmp;
tmp.m_A = p1.m_A + p2.m_A;
tmp.m_B = p1.m_B + p2.m_B;
return tmp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//Person p3 = operator+(p1, p2);
/*****************************************开始时我们想要实现的运算*****************************************/
// 想创建第三个人,让第三个人的m_A和m_B属性等于p1和p2的m_A和m_B的和
Person p3 = p1 + p2; // 编译器此时就不会报错了
/******************************************************************************************************/
cout << p3.m_A << p3.m_B << endl; // 2020,预期的输出✔
//Person p3 = operator+(p1, p2); // 这行可以直接使用,它是第29行代码的本质调用函数,该行代码的简化调用就是
// 第29行代码
}
以上即完成了全局函数实现"+"运算符重载!
4.5.1.1 成员函数和全局函数的调用小区别
我们还可以注意成员函数和全局函数的调用区别:
- 成员函数:
// 在Person类内定义一个成员函数,实现我们想要的相加功能
Person PersonAddPerson(Person &p)
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp; // 调用拷贝构造函数,创建一个拷贝体作为返回值
}
- 全局函数:
// 在Person类外定义应该全局函数,实现我们想要的相加功能
Person operator+(Person &p1, Person &p2)
{
Person tmp;
tmp.m_A = p1.m_A + p2.m_A;
tmp.m_B = p1.m_B + p2.m_B;
return tmp;
}
成员函数调用时是这样的:
p1.operator+(p2);
即通过一个对象p1
调用函数,然后传入另一个对象p2
作为实参
而全局函数调用是这样的:
operator+(p1, p2);
即对象p1
和对象p2
都作为函数实参
4.5.1.2 运算符重载也可以发生函数重载
函数名相同,传入的参数类型不同实现重载。
目的:复用operator+
这个函数名
例如我们如果要实现以下代码
Person p3 = p1 + 20; // 此时编译器会报错:
// 没有与这些操作数匹配的"+"运算符,操作数类型为:Person + int
- 此时我们若要通过成员函数重载来实现运算符重载,则Person类定义如下:
class Person
{
public:
int m_A;
int m_B;
Person operator+(Person& p) // 运算符重载
{
Person tmp;
tmp.m_A = this->m_A + p.m_A;
tmp.m_B = this->m_B + p.m_B;
return tmp;
}
Person operator+(int num) // 成员函数重载实现运算符重载
{
Person tmp;
tmp.m_A = this->m_A + num;
tmp.m_B = this->m_B + num;
return tmp;
}
};
然后在test02()
中即可完成Person + int
的操作且不报错:
void test02()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p4 = p1 + 20;
cout << p4.m_A << p4.m_B << endl; // 3030,预期的输出✔
}
- 全局函数重载实现运算符重载
class Person
{
public:
int m_A;
int m_B;
};
Person operator+(Person &p1, Person &p2)// 运算符重载
{
Person tmp;
tmp.m_A = p1.m_A + p2.m_A;
tmp.m_B = p1.m_B + p2.m_B;
return tmp;
}
Person operator+(Person& p1, int num) // 全局函数重载实现运算符重载
{
Person tmp;
tmp.m_A = p1.m_A + num;
tmp.m_B = p1.m_B + num;
return tmp;
}
同样的可以在test02()
中完成Person + int
的操作且不报错。
**注意:**全局函数重载实现运算符重载时,operator+()
中的形参类型不可调换,如下:
Person operator+(int num, Person& p1)
{
Person tmp;
tmp.m_A = p1.m_A + num;
tmp.m_B = p1.m_B + num;
return tmp;
}
调换后当我在test02()
中调用以下代码时就会报错:
Person p4 = p1 + 20; // 没有与这些操作数匹配的 "+" 运算符:Person + int
此时只有调换参数顺序才能正常执行test02()
,因为此时编译器只认int + Person
的’‘+’'操作:
Person p4 = 20 + p1; // 调换参数顺序,int + Person
另外还要注意:
- 对于内置的数据类型(如int,double这些)的表达式的运算是不可能改变的,改变的只能是我们自定义的数据类型
- 不要滥用运算符重载(即不要在写
operator+()
函数时,函数内实现的却是"-"(减)操作,这种情况编译器可以编译和运行,但不符合我们的规范!!!这叫滥用)
以上内容为2022年11月23日,我的C++加号运算符重载学习日志,内容总结整理来自这位老师的视频:39 类和对象-C++运算符重载-加号运算符重载