目录
〇、运算符重载的意义
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
就拿"+"运算符为例,在设计类是完整的前提下,下面的语句是会报错的,原因在于编译器没有人的智慧,不会活用"+"运算符,遇到1+1知道是2,遇到3.52+2.1知道是5.62,但是++遇到两个对象相加,并不知道具体是将类中的成员变量相加还是将字符串进行拼接...++
++如果想要对象之间的运算,就需要对相关的运算符进行重载,用类的成员函数或者全局函数去实现。++
cpp
int main() {
A a(1, 2), b(3, 4);
A c = a + b;
return 0;
}
一、加号运算符重载
实现两个自定义数据类型相加的运算。
加法运算符用于把两个相同类的对象相加,返回最终的该类对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。
【全局函数实现+运算符重载】
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A(){
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
};
A operator+(const A& a1, const A& a2) {
A t;
t.a = a1.a + a2.a;
t.b = a1.b + a2.b;
return t;
}
int main() {
A a(1, 2), b(3, 4);
A c = a + b;
cout << c.a << " " << c.b; // 输出4 6
return 0;
}
输出结果表示对象c中c.a = a.a+b.a,c.b=a.b+b.b,故为4 6。
【类成员函数实现+运算符重载】
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A(){
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
A operator+(const A& a0) {
A t;
t.a = this->a + a0.a;
t.b = this->b + a0.b;
return t;
}
};
int main() {
A a(1, 2), b(3, 4);
A c = a + b;
cout << c.a << " " << c.b; // 输出4 6
return 0;
}
这里和前方法的区别在于:类内this指针的存在给重载函数减少了一个参数。
二、左移运算符重载
能够输出自定义数据类型。
在常见的"cout<<"中,一般只支持C++内置类型(int、double、string等),但如果想输出对象型,依旧需要进行运算符重载,告诉编译器应该怎么操作。
注:首先这里要知道一件事!
输出流cout的类型是ostream,输入流cin的类型是istream。
【全局函数实现<<运算符重载】
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A(){
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
};
ostream& operator<<(ostream& out,A& a) {
out << a.a << " " << a.b << endl;
return out;
}
int main() {
A a(1, 2);
cout << a; // 输出1 2
return 0;
}
其中重载函数的第一个参数是用来接收cout的,第二个参数是接收要输出的类对象。
【类成员函数实现<<运算符重载】
本质是无法实现的。
首先,成员重载函数的第一个默认参数是this指针,这个参数就对应着左移运算符的左操作数。而cout<<a的本质是,<<的左操作数是cout(ostream类型对象),右操作数是a(A类型对象)。如果强行将<<重载为A的成员函数,那么this指针就会绑定到<<的左操作数,<<的右操作数就只能是ostream对象,这样即不符合常理,也无法使用。
三、+=运算符重载
【成员函数实现+=运算符重载】
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A(){
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
A operator+=(A& other) {
this->a += other.a;
this->b += other.b;
return *this;
}
};
ostream& operator<<(ostream& out,A& a) {
out << a.a << " " << a.b << endl;
return out;
}
int main() {
A a(1, 2), b(5, 7);
a += b;
cout << a; // 输出6 9
return 0;
}
这里面要注意的是,重载函数中返回值为*this,注意指针指向谁,*指针就是谁;重载函数中第一个参数由于是+=运算符中变化的那个,所以不能用const修饰。
【全局函数实现+=运算符重载】
全局函数实现+=运算符重载的过程中会存在参数问题,不能使用万能引用,不能值传递,所以只能使用左值引用,如果想要接收右值,也可以再重载一个右值引用。
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
//A operator+=(A& other) {
// this->a += other.a;
// this->b += other.b;
// return *this;
//}
};
ostream& operator<<(ostream& out, A& a) {
out << a.a << " " << a.b << endl;
return out;
}
A operator+=(A& a1, const A& a2) {
a1.a += a2.a;
a1.b += a2.b;
return a1;
}
A operator+=(A&& a1, const A& a2) {
a1.a += a2.a;
a1.b += a2.b;
return a1;
}
int main() {
A a(1, 2), b(5, 7);
a += b;
cout << a; // 输出6 9
return 0;
}
四、自增运算符重载
通过重载递增运算符,实现自己的整型数据。(只有整型才有自增)
自增包括先自增(++a)和后自增(a++),这样实现重载就有两种方式了。
【全局函数实现++运算符重载】
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 100;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
};
ostream& operator<<(ostream& out, const A& a) {
out << a.a << " " << a.b << endl;
return out;
}
//全局函数实现自增运算符
//先自增
A& operator++(A& a1) {
++a1.a;
++a1.b;
return a1;
}
//后自增
A operator++(A& a1, int) {
A temp = a1;
++a1.a;
++a1.b;
return temp;
}
int main() {
A a(1, 2), b(5, 7);
a++;
++b;
cout << a; // 输出2 3
cout << b; // 输出6 8
return 0;
}
Q1:这里发现后自增的运算符多了一个参数int,那这个参数是干什么用的呢?
- 如果没有int参数,那么前自增和后自增的重载函数的参数完全相同,编译器是无法区分的。
- 该参数未命名,只保留类型是因为该参数无实质用处,不需要在函数体中使用它。
Q2:后自增运算中为什么要在重载函数体中先声明对象?
是因为后自增的本质是直接返回原来的值再加1,故如果不事先声明新的对象用来保存自增前的状态而是直接加1的话,就不符合后自增的真正用处了。
【成员函数实现++运算符重载】
由于成员函数的第一个默认参数是this指针,所以这里就不需要传参了。
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 0;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
A& operator++() {
++this->a;
++this->b;
return *this;
}
A& operator++(int) {
A other = *this;
++this->a;
++this->b;
return other;
}
};
ostream& operator<<(ostream& out, const A& a) {
out << a.a << " " << a.b << endl;
return out;
}
int main() {
A a(1, 2), b(5, 7);
a++;
++b;
cout << a; // 输出2 3
cout << b; // 输出6 8
return 0;
}
五、赋值运算符重载
这里要知道,如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题,比如这里面的m_Age参数,创建对象时会在堆区开辟内存。
cpp
class Person {
public:
// 无参构造
Person() : m_Age(new int(0)) {}
// 有参构造
Person(int age) : m_Age(new int(age)) {}
// 拷贝构造函数(深拷贝)
Person(const Person& p) {
m_Age = new int(*p.m_Age);
}
// 赋值运算符重载(优化后)
Person& operator=(const Person& p) {
// 1. 自赋值检查
if (this == &p) {
return *this;
}
// 2. 释放当前对象的旧资源
delete m_Age;
m_Age = nullptr;
// 3. 深拷贝
m_Age = new int(*p.m_Age);
// 4. 返回当前对象(支持链式赋值)
return *this;
}
// 析构函数(释放动态内存)
~Person() {
delete m_Age;
m_Age = nullptr;
}
private:
int* m_Age;
};
六、关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作。
1. 判等
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 0;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
bool operator==(A& a) {
if (this->a == a.a) {
return true;
}
else {
return false;
}
}
};
ostream& operator<<(ostream& out, const A& a) {
out << a.a << " " << a.b << endl;
return out;
}
int main() {
A a(1, 2), b(5, 7), c(1, 7);
cout << (a == b) << " " << (a == c);//输出0 1
return 0;
}
该程序中需要注意的是,输出对象a和b判等的结果时应该注意各个运算符的优先级,"<<"的优先级高于"==",所以如果不加括号的话,会让编译器误认为是(cout<<a)==b,就会导致报错。
2. 判不等
其中,判断大于、小于、大于等于、小于等于的逻辑是一样的,所以这里只展示"!="的程序:
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 0;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
bool operator!=(A& a) {
if (this->a != a.a) {
return true;
}
else {
return false;
}
}
};
ostream& operator<<(ostream& out, const A& a) {
out << a.a << " " << a.b << endl;
return out;
}
int main() {
A a(1, 2), b(5, 7), c(1, 7);
cout << (a != b) << " " << (a != c);//输出1 0
return 0;
}
七、函数调用运算符
operator()是 C++ 中一种特殊的运算符重载,它的核心作用是:让类的对象可以像普通函数一样,通过对象名(参数)的形式被调用。 这种重载了operator()的类的对象,也被称为仿函数(Functor)看起来像函数,但本质是对象。
cpp
#include<iostream>
using namespace std;
class A {
public:
int a;
int b;
A() {
a = 0;
b = 0;
}
A(int a, int b) {
this->a = a;
this->b = b;
}
};
ostream& operator<<(ostream& out, const A& a) {
out << a.a << " " << a.b << endl;
return out;
}
class MyAdd {
public:
int operator()(int a, int b) {
return a + b;
}
};
int main() {
MyAdd()(100, 200);
MyAdd add;
cout << add(100, 200);//300
return 0;
}