一、什么是运算符重载,解决了什么问题?
运算符重载指的是对已有的运算符,如:+ - * / 等重新进行定义,以适应不同的数据类型进行相应的运算。
为什么要重载运算符,对于操作系统基本的数据类型,如 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; }
运行结果: