环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1
文章目录
地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)类和对象----对象初始化和清理
「地表最强」C++核心编程(六)类和对象----对象模型和this指针
「地表最强」C++核心编程(七)类和对象----友元
「地表最强」C++核心编程(八)类和对象----运算符重载
「地表最强」C++核心编程(九)类和对象----继承
「地表最强」C++核心编程(十)类和对象----多态
「地表最强」C++核心编程(十一)文件操作
对于内置的数据类型,编译器知道如何进行运算,但是不知道自定义数据类型如何运算。为了应对这种情况,可以对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型,这就是运算符重载。但要注意,对于内置的数据类型的表达式的的运算符是不可能改变的。
一、加号运算符重载
实现加号+的重载可以有两种方式:成员函数重载和全局函数重载。
1.1 成员函数实现+重载
class Person {
public:
int m_A;
int m_B;
public:
Person() {};
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person &p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式
Person p3 = p2 + p1; //右边本质是p2.operaor+(p1); 也就是p2 + p1相当于p2调用+函数,p1是参数
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;//30 30
}
1.2 全局函数实现+重载
//全局函数实现 + 号运算符重载
Person operator+(const Person &p1, const Person &p2) {
Person temp(0, 0);
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//全局函数方式
Person p3 = p1 + p2;//本质是 p3 = operator+(p1,p2);
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;//30 30
}
1.3 运算符重载可以进行函数重载
//运算符重载 可以发生函数重载
Person operator+(const Person &p2, int val) {
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
//函数重载
Person p4 = p1 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;//20 20
}
二、左移运算符重载
重载左移运算符可以实现输出自定义数据类型,一般配合友元使用。
需要注意的是,左移运算符的重载一般不用成员函数实现而只用全局函数实现。
class Person {
friend ostream& operator<<(ostream& out, Person& p);
private:
int m_A;
int m_B;
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现不了:若参数为Person& p,则调用的时候是p.operator<<(p),这不符合逻辑
// 若参数为cout,则调用的时候是p.operator<<(cout),简化为p << cout;我们需要cout << p;显然这不是我们想要的效果
// void operator<<(Person& p){//因此不用成员函数来实现重载<<
// }
};
//全局函数实现左移重载
//ostream对象只能有一个,因此传引用
ostream& operator<<(ostream& out, Person& p) {//返回值不是void
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //若返回类型为void,此处报错,因为只有ostream对象才能继续使用这个重载
}
三、递增运算符重载
为了重载++,先来自定义一个整型类:
//自定义整型
class MyInteger {
private:
int m_Num;
public:
MyInteger() {
m_Num = 0;
}
};
3.1 前置递增重载
//重载前置++
MyInteger& operator++() {//返回值是引用保证了一直对一个对象递增,否则多次++时,只有第一次是目标对象的操作,后续的每次++都是对新的复制品的操作
//先++
m_Num++;
//再返回
return *this;
}
3.2 后置递增重载
//重载后置++:返回值不能是引用,因为返回局部变量的引用是非法的
MyInteger operator++(int) {//int代表占位参数,可以用于区分前置和后置递增,只能用int
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;//返回之前记录过的值
}
3.3 重载<<来输出
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
private:
int m_Num;
public:
MyInteger() {
m_Num = 0;
}
//重载前置++
MyInteger& operator++() {//返回值是引用保证了一直对一个对象递增,否则多次++时,只有第一次是目标对象的操作,后续的每次++都是对新的复制品的操作
//先++
m_Num++;
//再返回
return *this;
}
//重载后置++:返回值不能是引用,因为返回局部变量的引用是非法的
MyInteger operator++(int) {//int代表占位参数,可以用于区分前置和后置递增,只能用int
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;//返回之前记录过的值
}
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;//1
cout << myInt << endl;//1
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;//0
cout << myInt << endl;//1
}
四、赋值运算符重载
c++编译器至少给一个类添加4个函数:
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
4.赋值运算符 operator=, 对属性进行值拷贝,是浅拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题。
关于深浅拷贝,可以参考「地表最强」C++核心编程(五)类和对象----对象初始化和清理第五点。
class Person {
public:
//年龄的指针
int *m_Age;
Person(int age) {
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person &p) {//注意返回值类型
//如果自身已经有数据,先释放掉
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
~Person() {
if (m_Age != NULL) {
delete m_Age;//释放堆区数据
m_Age = NULL;
}
}
};
void test01() {
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作,返回值是Person&才可以这样操作,若返回值为void则不能连等
cout << "p1的年龄为:" << *p1.m_Age << endl;//18
cout << "p2的年龄为:" << *p2.m_Age << endl;//18
cout << "p3的年龄为:" << *p3.m_Age << endl;//18
}
五、关系运算符重载
重载关系运算符,可以让两个自定义类型对象进行对比操作。
class Person {
public:
string m_Name;
int m_Age;
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person &p) {//重载==
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return true;
} else {
return false;
}
}
bool operator!=(Person &p) {//重载!=
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return false;
} else {
return true;
}
}
};
void test01() {
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b) {
cout << "a和b相等" << endl;
} else {
cout << "a和b不相等" << endl;
}
if (a != b) {
cout << "a和b不相等" << endl;
} else {
cout << "a和b相等" << endl;
}
}
六、函数调用运算符重载
函数调用运算符 () 也可以重载,由于重载后使用的方式非常像函数的调用,因此称为仿函数。仿函数没有固定写法,非常灵活。
class MyPrint {
public:
void operator()(string text) {//重载什么operator后边就写什么
cout << text << endl;
}
};
void test01() {
//重载的()操作符 也称为仿函数
MyPrint myFunc;
myFunc("hello world");//对象使用重载后的(),不是函数调用
}
class MyAdd {
public:
int operator()(int v1, int v2) {
return v1 + v2;
}
};
void test02() {
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
//匿名函数对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;//MyAdd()是匿名对象,调用了重载的()
}