一、什么是运算符重载,解决了什么问题?
运算符重载指的是对已有的运算符,如:+ - * / 等重新进行定义,以适应不同的数据类型进行相应的运算。
为什么要重载运算符,对于操作系统基本的数据类型,如 int, float等,编译器知道如何进行运算。但是对于我们自定义的数据类型,比如我想实现两个结构体或者类的相加。编译器就不知道如何对其进行运算了。这个时候我们就需要重载相应的运算符。
二、运算符重载格式
operator 运算符(args…)
例如,operator+() 重载+运算符
operator*()重载*运算符
三、运算符重载示例
3.1 “+” 加法运算符重载
因为每个人都可以实现该成员函数,且函数名各不相同,你也可以叫AddStudent。为了统一名称,编译器提供了operator+。
#include <iostream>
using namespace std;
class Student{
public:
// 3.1 类的成员函数实现+重载
Student Add(Student& s) // 我们自己实现的Add
{
Student tmp;
tmp.m_A = this->m_A + s.m_A;
tmp.m_B = this->m_B + s.m_B;
return tmp;
}
Student operator+(Student& s) // operator+ 是编译器提供的统一的+运算符重载的名称
{
Student tmp;
tmp.m_A = this->m_A + s.m_A;
tmp.m_B = this->m_B + s.m_B;
return tmp;
}
public:
int m_A;
int m_B;
};
// 3.2 全局函数实现+重载
Student operator+(Student &s1, Student &s2)
{
Student tmp;
tmp.m_A = s1.m_A + s2.m_A;
tmp.m_B = s1.m_B + s2.m_B;
return tmp;
}
int main(int argc, char* argv[])
{
Student s1, s2;
s1.m_A = 10;
s1.m_B = 10;
s2.m_A = 20;
s2.m_B = 20;
std::cout << " =========== Add ========"<< std::endl;
Student s3 = s1.Add(s2);
std::cout << "s3.A = "<< s3.m_A << " B = "<< s3.m_B << std::endl;
std::cout << " =========== operator+ ========" << std::endl;
// Student s4 = s1.operator+(s2); // 可简写为:Student s4 = s1 + s2;
Student s4 = s1 + s2;
std::cout << "s4.A = "<< s4.m_A << " B = "<< s4.m_B << std::endl;
std::cout << " =========== operator+(Student &s1, Student &s2) ========" << std::endl;
// Student s6 = operator+(s1, s2);
Student s6 = s1 + s2; // 可简写为:Student s6 = s1 + s2;
std::cout << "s6.A = "<< s6.m_A << " B = "<< s6.m_B << std::endl;
return 0;
}
小结:
对于基本数据类型表达式的运算符无法重载。
不要滥用运算符重载,比如故意将运算符+重载为减法的功能。
3.2 “<<” 左移运算符重载
#include <iostream>
using namespace std;
class Student{
public:
// 成员函数实现左移运算符
// void operator<<(Student& s){} // 不可避免调用形式 s1.operator<<(s) 简化版本 s1 << s
// void operator<<(cout){} // 不可避免调用形式 s1.operator<<(cout) 简化版本 s1 << cout,不满足cout在左侧,所以左移运算符只能使用全局函数重载
public:
int m_A;
int m_B;
};
// 改造前,全局函数实现左移运算符重载
void operator<<(ostream& cout, Student& s)
{
cout << "s.a = " << s.m_A << " s.b = " << s.m_B;
}
// 改造后,全局函数实现左移运算符重载
ostream& operator<<(ostream& cout, Student& s)
{
cout << "s.a = " << s.m_A << " s.b = " << s.m_B;
return cout;
}
int main(int argc, char* argv[])
{
Student s1, s2;
s1.m_A = 10;
s1.m_B = 10;
s2.m_A = 20;
s2.m_B = 20;
std::cout << "[s1] "<< s1; // 改造前
std::cout << "[s1] "<< s1 << " [s2] "<< s2 << std::endl; // 改造后,支持
return 0;
}
运行结果:
改造前:
改造后:
注意,此时 std::cout << “p” << s1 ; 我们是没有加换行的,如果加了换行代码(std::cout << “p” << s1 << std::endl;)会报错。为什么呢?因为我们用来实现的左移运算符重载的全局函数的返回值是void。如果要继续输出,我们的返回值就必须还是cout的类型。这就是链式编程思想。
3.3 “++” 自增运算符重载
#include <iostream>
using namespace std;
class Myinteger{
public:
friend ostream& operator<<(ostream& cout, Myinteger m); // 声明该函数为本类的友元函数,可以访问本类的私有成员
Myinteger(){m_Num = 0;}
// 前置++运算符重载,返回值必须是引用
// 比如:(++(++a)) 是对a加两次,如果返回值不是引用,那么(++(++Myinteger)) 就不是同一个对象
Myinteger& operator++(){
m_Num++;
return *this;
}
// 后置++运算符重载 因为同一作用域下,函数返回值不同不能作为函数重载的依据,需要加int占位。
// 后置++ 是先返回值,再运算
Myinteger operator++(int){
Myinteger tmp = *this;
m_Num++;
return tmp; // 注意,这里的tmp是临时变量,离开作用域会销毁,如果返回值是引用,继续使用该临时对象就是非法操作
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, Myinteger m)
{
cout << m.m_Num;
return cout;
}
int main(int argc, char* argv[])
{
std::cout << "========= int =========" << std::endl;
int a = 0;
std::cout << "++a = " << ++a << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "a++ = " << a++ << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "========= Myinteger =========" << std::endl;
Myinteger m;
std::cout << "++m = " << ++m << std::endl;
std::cout << "m = " << m << std::endl;
std::cout << "m++ = " << m++ << std::endl;
std::cout << "m = " << m << std::endl;
return 0;
}
运行结果:
3.4 “=” 赋值运算符重载
class Person{
public:
Person(int age)
{
m_Age = new int(age); // 在堆上维护一块空间
}
~Person(){ // 在析构函数中释放堆上的空间
if (m_Age){
delete m_Age;
m_Age = nullptr;
}
}
Person& operator=(Person& p){
//先判断堆上是否存在旧数据,如果有清除干净
if (m_Age){
delete m_Age;
m_Age = nullptr;
}
// 执行深拷贝
m_Age = new int(*p.m_Age);
// 返回对象本身,链式编程思想
return *this;
}
public:
int *m_Age;
};
int main(int argc, char* argv[])
{
Person p1(10);
std::cout << "p1.age: " << *p1.m_Age << std::endl;
Person p2(20);
Person p3(30);
std::cout << "赋值前 p2.age: " << *p2.m_Age << " p3.age: "<< *p3.m_Age<< std::endl;
p3 = p2 = p1; // 链式编程
std::cout << "赋值后 p2.age: " << *p2.m_Age << " p3.age: "<< *p3.m_Age<< std::endl;
return 0;
}
运行结果:
编译器默认提供的赋值是简单的值拷贝,浅拷贝缺陷,同一块内存释放两次,造成程序崩溃!
3.5 关系运算符重载(< > == <= )
#include <iostream>
using namespace std;
class Person{
public:
Person(int val){
m_a = val;
}
bool operator<=(Person& p)
{
return m_a <= p.m_a ? true : false;
}
bool operator<(Person& p)
{
return m_a < p.m_a ? true : false;
}
bool operator>(Person& p)
{
return m_a > p.m_a ? true : false;
}
bool operator==(Person& p)
{
return m_a == p.m_a ? true : false;
}
private:
int m_a;
};
int main(int argc, char* argv[])
{
Person p1(20);
Person p2(30);
Person p3(30);
if (p1 < p2){
std::cout << "p1 < p2" << std::endl;
}
if (p2 > p1){
std::cout << "p2 > p1" << std::endl;
}
if (p3 == p2){
std::cout << "p3 == p2" << std::endl;
}
if (p3 <= p2){
std::cout << "p3 <= p2" << std::endl;
}
return 0;
}
运行结果:
3.6 “()”函数调用运算符重载
- 函数调用运算符“()”也可以重载
- 重载后的使用方式跟函数调用类似,因此称为仿函数
- 仿函数非常灵活,没有固定的写法
class MyPrint{
public:
void operator()(std::string str){
std::cout << str << std::endl;
}
};
void MyPrint_Func(std::string str){
std::cout << str << std::endl;
}
class MyAdd{
public:
int operator()(int a, int b){
return a+b;
}
};
int MyAdd_Func(int a, int b){
return a+b;
}
int main(int argc, char* argv[])
{
MyPrint print;
print("hello word!"); // 仿函数,顾名思义跟函数调用类似,都是 返回值 函数名+(形参);
MyPrint_Func("hello word!"); // 函数调用
int a = 10, b = 20;
MyAdd add;
std::cout << "add res: " << add(a, b) << std::endl; // 仿函数,顾名思义跟函数调用类似,都是 返回值 函数名+(形参);
std::cout << "MyAdd_Func res: " << MyAdd_Func(a, b) << std::endl; // 函数调用
//匿名对象,顾名思义对象没有显示的名字,且这行代码结束,对象被回收
std::cout << "匿名对象: " << MyAdd()(20, 30) << std::endl;
return 0;
}
运行结果: