运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
加号运算符重载
因为C++引入了自定义的类型,所以当两个自定义的类型需要做运算的时候,就需要运算符重载。
cpp
class Person
{
public:
int m_A;
int m_B;
}
Person P1;
P1.m_A = 10;
P1.m_B = 20;
Person P2;
P2.m_A = 20;
P2.m_B = 30;
Person P3 = P1 + P2 //如果不重载运算符,编译器不知道如何运算自定义的类型
下面是加号运算的重载 编译器统一命名的
1.通过成员函数进行运算符重载(在一个对象内部,所以一个使用this,一个使用(*传的时候使用P->,&传的时候使用P.)
2.通过全局函数进行运算符重载
对象相加返回对象 所以operator前面加void
cpp
// 1.通过成员函数进行运算符重载
Person P3 = P1.operator+(P2)
Person operator+ (Person &p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
// 使用的时候可以用 简化
Person P3 = P1+P2;
// 2.通过全局函数进行运算符重载
Person operator+ (Person &p1, Person &p2)
{
Person temp;
temp.m_A = p1->m_A + p2.m_A;
temp.m_B = p1->m_B + p2.m_B;
return temp;
}
Person p3 = operator+(p1,p2);
// 简化为
Person p3 = p1 + p2;
// 运算符重载的同时也支持函数重载
Person operator+ (Person &p1, int num)
{
Person temp;
temp.m_A = p1->m_A + num;
temp.m_B = p1->m_B + num;
return temp;
}
Person P3 = P2+10;
左移运算符重载
没有与这些操作数匹配的 运算符
只能利用全局函数重载左移运算符
cpp
int a = 10;
cout << a <<endl; //左移运算符直接输出整形
Person p;
p.m_A = 10;
p.m_B = 10;
cout<< p <<end; // 左移运算符无法直接输出自定义的类
// 通常不会使用成员函数重载左移运算符,因为 operator<<(cout) 简化完为 << cout,想要的是cout<< 成员函数运算符重载规则 【p 运算符 传的参数】
// 只能利用全局函数重载左移运算符 全局函数运算符重载规则 【传的参数1 运算符 传的参数2】
void operator<<(ostream &cout,Person &p)
{
cout << p.m_A << p.m_B <<endl;
}
cout<<p; // 重载完之后就可以直接输出p对象的属性
// 但是想继续换行,也就是 cout<<p<<endl; 会报错,因为上面定义的重载返回是void
// 链式编程思想需要每次返回的都是相同类型的 每次返回都是cout类型才行
//链式调用需要返回对象引用,所以是ostream&,而不是直接ostream
ostream& operator<<(ostream &cout,Person &p)
{
cout << p.m_A << p.m_B <<endl;
return cout;
}
// 可以链式调用了
cout<<p<<endl;
递增运算符重载
通过重载递增运算符,实现自己的整形数据
+= 重载
cpp
int a = 10;
cout<< ++a<<endl; // 11 前置递增运算符 先运算再表达式
cout<< a <<endl; // 11
int b = 10;
cout<< b++<<endl; // 10 后置递增运算符 先表达式再运算
cout<< b <<endl; // 11
实现自己的递增运算符,重载
cpp
#include <iostream>
using namespace std;
class MyInteger {
friend ostream& operator<<(ostream& cout, MyInteger myInt) ;
public:
MyInteger() {
m_Num = 0;
}
// 重载前置++运算符
MyInteger& operator++() { // 返回引用是为了链式调用
//仅返回值的话 链式调用不能对同一个对象进行操作,使得链式调用返回的值是对的,但是对象本身没有进行操作,单独输出对象的值,就是原来的值。
// 先进行++运算
m_Num++;
// 再自身做返回
return *this;
}
// 重载后置++运算符 int为了实现函数重载 是占位参数
MyInteger operator++(int) { // 后置递增返回值,因为temp是局部对象,当前函数执行结束后temp对象就会销毁,所以返回值不能是引用
// 后置递增与前置递增不同,先记录当时结果
MyInteger temp = *this; // 创建一个局部对象,让这个对象等于 this的解引用 就是自身的值
// 但是这里两种自增,多个访问
// 后进行递增运算
m_Num++;
// 最后将记录结果做返回
return temp;
}
private:
int m_Num;
};
// 重载 << 运算符
ostream& operator<<(ostream& cout, MyInteger myInt) {
cout << myInt.m_Num;
return cout;
}
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
test02();
return 0;
}
赋值运算符重载
c++编译器至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
赋值运算符 operator=, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
(默认浅拷贝)也就是copy后的对象属性中有指向同一个堆区地址,后面手动释放的时候会产生堆区内存重复释放的问题。程序崩了。
需要用深拷贝解决,需要在堆区重新开辟一块内存,放相同的数据。后续释放对象,就不会有冲突了。
cpp
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;
}
}
// 当开辟在堆区的数据需要释放时
~Person() {
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
}
};
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
cpp
// 重载赋值运算符 这里上面传int age 是Person的构造函数括号法初始化
// 这里operator是重载传入参数,代表运算符前后的类型关系 p=p 所以传person参数
Person &operator=(Person &p)
{
// 编译器提供的是浅拷贝如下:
// m_Age = p.m_Age;
// 我们要进行深拷贝,所以要开辟一块新的堆内存
m_Age = new int(*p.m_Age); // m_Age 是指针类型 所以要解引用取值 开辟heap内存值m_Age放进堆区
return *this; // this是指针 指向对象本身 *this解引用 相当于返回对象本身
// return this; 是返回指针 return *this是返回指向的内容
}
// 之后再进行释放就不会有堆区重复释放的问题了
完善的
cpp
Person &operator=(Person &p) // 这里返回值 返回自身 一是符合赋值 二是符合链式调用 返回相同对象的引用
{
// 先判断是否有属性在堆区,如果有就先释放干净,然后进行深拷贝
// 如果不先判断一下,直接就进行赋值 容易出现原内存无法释放的问题等等。
if(m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
// 再进行赋值
m_Age = new int(*p.m_Age); // 深拷贝
return *this;
}

关系运算符重载
cpp
#include <iostream>
using namespace std;
class Person {
public:
string m_Name;
int m_Age;
Person(string name, int age) {
this ->m_Age = age;
this ->m_Name = name;
};
bool operator==(Person &p) {
if (this -> m_Age == p.m_Age && this -> m_Name == p.m_Name) {
return true;
}
else return false;
}
bool operator!=(Person &p) {
if (this -> m_Age == p.m_Age && this -> m_Name == p.m_Name) return false;
else return true;
}
};
void test01() {
Person p1("Tom", 10);
Person p2("Tom", 10);
if (p1 == p2) {
cout << "p1 和 p2 一样" << endl;
}
else cout << "p1 和 p2 不一样" << endl;
if (p1 != p2) {
cout << "p1 和 p2 不一样" << endl;
}
else cout << "p1 和 p2 一样" << endl;
}
int main() {
test01();
return 0;
}
函数调用运算符重载
函数调用运算符()也可以重载。
由于重载后使用的方式非常像函数的调用,因此成为仿函数。
仿函数没有固定写法,非常灵活。
cpp
#include <iostream>
using namespace std;
class MyPrint {
public:
void operator()(string text) {
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 myAdd;
int ret = myAdd(10, 20);
cout << ret << endl;
// 匿名对象调用
cout << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
return 0;
}
仿函数非常灵活,没有固定的写法
匿名函数对象,当前行执行完,立即被释放。