文章目录
- 前情回顾
- 运算符重载
- 概念
- 为什么会出现运算符重载
- 运算符重载中函数名格式
- 加减运算符重载
- 作用
- 实现
- 左移运算符重载
- 作用
- 左移运算符是什么?
- 实现
- 递增递减运算符
- 作用
- 实现
- 前置
- 后置
- 赋值运算符重载
- 关系运算符重载
- 作用
- 实现
- 函数调用运算符重载
- 第二种重载掌握!突破
- 本章知识点(图片形式)
🎉welcome🎉
✒️博主介绍:一名大一的智能制造专业学生,在学习C/C++的路上会越走越远,后面不定期更新有关C/C++语法,数据结构,算法,Linux,ue5使用,制作游戏的心得,和大家一起共同成长。
✈️C++专栏:C++爬塔日记
😘博客制作不易,👍点赞+⭐收藏+➕关注
前情回顾
第四层中,我遇到了一个很新奇的力量,叫做友元,可以把一些人变成好朋友的神奇力量,除此之外,还学习到了,类内声明函数,类外进行定义的操作,同时,也踏入了第五层…
- 🚄上章地址:第四层:友元与函数成员别样定义
运算符重载
黑黝黝的洞口,一个人影在缓缓出现,“我就知道你肯定可以掌握友元的,这次需要你掌握另一种重载的力量——运算符重载,祝你好运…”。“新的重载吗…”。
概念
- 对已有的运算符进行重新定义,赋予其另一种功能,以适应各种不同的运算类型
为什么会出现运算符重载
- 对于内置的数据类型,编译器知道如何运算,但是对于自定义类型,是不知道如何去运算的,这个时候就需要运算符重载,本质上,是写一个函数来告诉编译器。
运算符重载中函数名格式
- 对于运算符重载中,上面提到,本质上是写一个函数,不同的人对于函数的命名方式是不一样的,这样编译器也不好识辨,这个函数是不是在实现运算符重载,所以编译器提供了一个固定的格式:
- operator+(这里这个加号可以替换成别的符号,当你要对什么符号进行运算符重载时就用什么符号)
加减运算符重载
作用
- 实现两个自定义数据类型的相加减运算
实现
上面提到对于内置数据类型编译器可以知道如何去运算,而自定义类型是不知道的,那现在有一个类,它有三个对象,其中一个对象等于其他两个对象加起来,要怎么实现,可以先验证直接用+能不能进行:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
int _a;
int _b;
};
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3;
a3 = a1 + a2;
}
int main()
{
test1();
return 0;
}
报错了,没有与这些操作数匹配的“+”运算符,这个错误说明了编译器是不知道对象内部怎么进行加减的,那设计一个函数,函数名字用上面提到的operate+:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
A operator+(A &a1)
{
A tmp;
tmp._a = this->_a + a1._a;
tmp._b = this->_b + a1._b;
return tmp;
}
int _a;
int _b;
};
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3;
a3 = a1 + a2;
cout << a3._a<<" "<< a3._b;
}
int main()
{
test1();
return 0;
}
其实这中写法的本质还是调用函数,即:
- a3=a1+a2 == a3=a1.operator+(a2)
同时也可以把这个改成类外的全局函数,这个时候传参就需要传两个,内部改一下:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator+(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a + a2._a;
tmp._b = a1._b + a2._b;
return tmp;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3;
a3 = a1 + a2;
cout << a3._a<<" "<< a3._b;
}
int main()
{
test1();
return 0;
}
也是可以正常跑起来的。
减号同理,与加号实现原理一致,只需要把+换成-。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3;
a3 = a1 - a2;
cout << a3._a<<" "<< a3._b;
}
int main()
{
test1();
return 0;
}
左移运算符重载
作用
- 可以输出自定义的数据类型
左移运算符是什么?
- 左移运算为程序员调用cout的时候,会在后面加“<<”,这个就是左移运算符
实现
同样,可以直接用cout来进行只用类就把类内属性都打印出来吗:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3(0, 0);
a3 = a1 - a2;
cout << a3 << endl ;
}
int main()
{
test1();
return 0;
}
明显是不可以的,那要怎么样进行改造呢?如果是成员函数呢?传参和返回类型是什么?返回类型暂定为void,那参数呢?cout吗?那传参cout简化就变成了:
- 对象<<cout
放的顺序不一样,所以成员函数无法实现,那就用全局函数,用之前,需要探究一个事情,cout是什么类型?转到定义看一下:
cout的类型就是ostream,那类型解决了,还有什么注意事项吗?有,并且很重要:
- 对于全局来说,只有一个cout,所以cout是需要进行引用的,对象也是需要进行引用才可以。
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
void operator<<(ostream& cout, A& a3)
{
cout << a3._a << " " << a3._b;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3(0, 0);
a3 = a1 - a2;
cout << a3 << endl;
}
int main()
{
test1();
return 0;
}
为什么这里还是有报错?对于cout这种能一直在后面追加编程,称作链式编程,在前面的this指针中,提到过一种用途,这里也要用到,因为返回的是void,所以对于endl来说,前面的就不是cout,所以就不能进行输出,那这里就需要返回一个cout,cout的类型,同时记得加引用:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
ostream& operator<<(ostream& cout, A& a3)
{
cout << a3._a << " " << a3._b;
return cout;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3(0, 0);
a3 = a1 - a2;
cout << a3 << endl;
}
int main()
{
test1();
return 0;
}
递增递减运算符
作用
- 通过重载递增递减预算符,实现自己类内的整型数据
实现
前置
前置的递增递减是前++(- -),在使用,所以可以先在实现的函数内部先进行++(- -),在返回引用,这里为什么要返回引用呢?如果不返回引用,那就是对局部这个变量进行了++(- -),多次使用++(- -),本身只会生成一次,因为不是引用,会产生拷贝,把数据拷贝到新的空间上:
- 使用引用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
A& operator++()
{
this->_a++;
this->_b++;
return *this;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
ostream& operator<<(ostream& cout, A& a3)
{
cout << a3._a << " " << a3._b;
return cout;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3(0, 0);
a3 = a1 - a2;
cout << ++(++a3) << endl;
}
int main()
{
test1();
return 0;
}
- 不使用引用
#include<iostream>
using namespace std;
class A
{
public:
A()
{
}
A(int a, int b)
{
_a = a;
_b = b;
}
A operator++()
{
this->_a++;
this->_b++;
return *this;
}
//A operator+(A &a1)
//{
// A tmp;
// tmp._a = this->_a + a1._a;
// tmp._b = this->_b + a1._b;
// return tmp;
//}
int _a;
int _b;
};
A operator-(A& a1, A& a2)
{
A tmp;
tmp._a = a1._a - a2._a;
tmp._b = a1._b - a2._b;
return tmp;
}
ostream& operator<<(ostream& cout, A& a3)
{
cout << a3._a << " " << a3._b;
return cout;
}
void test1()
{
A a1(10, 10);
A a2(10, 10);
A a3(0, 0);
a3 = a1 - a2;
cout << ++(++a3) << endl;
}
int main()
{
test1();
return 0;
}
直接是报错的。
前置递减实现也相同,将++换成–即可。
后置
当定义后置++的时候,发现,与前置++使用的是一样的名字,发现了重定义,那这个时候怎么办?可以在参数中写一个int,用于区分,这个int属于占位参数,用于区分前置递增和后置递增,编译器是只认int的,对于后置递增,应该记录初始结果,如果在递增,返回初始结果,这个时候应该返回值,而不是引用,因为返回的是一个局部变量,如果返回引用,就会非法访问:
- 不是用引用
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
{
_a = a;
}
//后置++
A operator++(int)
{
int a = this->_a++;
return a;
}
int _a;
};
void test1()
{
A a1(10);
cout << a1._a++ << endl;
cout << a1._a << endl;
}
int main()
{
test1();
return 0;
}
- 是引用
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
{
_a = a;
}
//后置++
A& operator++(int)
{
int a = this->_a++;
return a;
}
int _a;
};
void test1()
{
A a1(10);
cout << a1._a++ << endl;
cout << a1._a << endl;
}
int main()
{
test1();
return 0;
}
赋值运算符重载
- 其实在C++中,编译器一般会给一个类默认提供四个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝函数,对属性进行浅拷贝
- 赋值运算符,对属性进行浅拷贝
注意!:
- 当这个时候属性内的值有属性指向堆区时,赋值操作符也会产生深拷贝和浅拷贝问题
编译器提供的是浅拷贝,这个时候在析构函数内进行delete释放内存时,会出现内存释放两次,所以这个时候operator=修改成深拷贝即可,注意,在赋值操作符之前,应该先判断赋值左侧值内堆区是否有数据,有数据要先释放干净,在进行深拷贝,记得返回值要返回自身:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
p = new int;
}
~A()
{
delete p;
p = NULL;
}
void operator=(A& a)
{
if (p != NULL)
{
p = NULL;
}
p = new int(*a.p);
}
int* p;
};
void test1()
{
A a;
*a.p = 10;
A b;
b = a;
cout << *b.p << endl;
}
int main()
{
test1();
return 0;
}
关系运算符重载
作用
- 可以人两个自定义类型进行比较操作
实现
只用对比每一组属性是否相等,相等返回真,不相等返回假:
#include<iostream>
using namespace std;
class A
{
public:
A(int a, char c)
{
_a = a;
_c = c;
}
bool operator==(A& a)
{
if (_a == a._a && _c == a._c)
{
return 1;
}
return 0;
}
int _a;
char _c;
};
void test1()
{
A a(10, 'c');
A b(10, 'c');
if (a == b)
{
cout << "a=b" << endl;
}
else
{
cout << "a!=b" << endl;
}
}
int main()
{
test1();
return 0;
}
函数调用运算符重载
- 函数调用操作符()也可以重载
- 由于重载后调用的方式和函数本身的调用相似,所以也被称为仿函数
- 仿函数没有固定写法,函数体内可以写任何。
事例:
#include<iostream>
using namespace std;
class A
{
public:
void operator()()
{
cout << "仿函数调用" << endl;
}
};
void test1()
{
A a;
a();
}
int main()
{
test1();
return 0;
}
第二种重载掌握!突破
面前的石碑碎去,露出后面黑黝黝的洞口…
本章知识点(图片形式)
😘预知后事如何,关注新专栏,和我一起征服C++这座巨塔
🚀专栏:C++爬塔日记
🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉