本篇内容参考了b站up主黑马程序员的视频,这是链接https://www.bilibili.com/video/BV1et411b73Z?p=126
运算符重载类似于函数重载,回顾函数重载,我们会发现,发生函数重载使得同名函数具有不同的应用,运算符重载也是如此,针对不同的对象,运算符重载可以有不同的应用。但是,我们在做运算符重载时并不是扩大一个运算符的功能,而是扩大它应用的范围。
运算符重载常用于对类或者结构体的运算符操作,例如,一个类有几个int属性,然后我们实例化了两个这个类的对象,然后我们要对这两个类的进行常见的四则运算操作(即加减乘除),我们需要对这两个对象的所有属性都进行相应的运算操作,而如果,我们创建了不止两个对象,而是多个对象的时候,代码的长度无疑就会很长,而且让代码看起来很不灵活。
很容易想到的思路就是定义函数,这样可以使得代码长度大大缩减,但是直接定义函数就会和我们原来设想的几个对象直接用四则运算符操作相比起来没有那么直观。
所以c++就引入运算符重载的操作。
关键字 operator
operator运算符(相应的类型)
一共六类
计算运算符 | +,-,*, \ |
---|---|
位移运算符 | <<, >> |
自增自减 | ++, – |
赋值运算符 | = |
关系运算符 | ==, != |
函数调用运算符 | () |
对于类或者结构体,我们运算符的重载有两种途径:成员函数重载和全局函数重载
以下以‘+’重载为例,介绍这两种重载方式
如上图所示,这就是使用成员函数重载运算符的一个示例,重载的定义本身就是定义函数,所以写法基本就是成员函数的定义写法+运算符重载语法。
我们来试试
可以看到,结果如我们所猜测的一样
下面是全局函数重载
由于是全局函数,所以不像成员函数,对于 + ,需要两个对象,所以在定义时需要的形参会多一个。(成员函数可以直接访问该类对象对应的属性)
像上面的 + ,除了对两个类对象,也可以应用于一个类和一个int,如下图
像这样子定义运算符重载,就可以使得类对象的每一个属性都加上一个int值。
以上是 + 号的运算符重载,读者可以参照然后尝试四则运算符的另外三种进行重载
以下将介绍另外五种运算符重载
像 << , >> 这样的运算符有二进制位运算的作用,但是更多在c++中是以标准输出、输入来使用。
#include
using namespace std;
//左移运算符重载
class Person
{
friend ostream& operator<<(ostream& cout, Person p);
/*friend void test02();*/
public:
// 利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本为 p << cout
//不会利用成员函数来重载<<运算符, 因为无法实行cout在左侧
/*void operator<<(cout)
{
}*/
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
// 只能用全局函数来重载<<运算符
ostream& operator<<(ostream& cout, Person p) // 本质 operator<< (cout, p) 简化 cout << p
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
void test02()
{
/*Person p;
p.m_A = 10;
p.m_B = 10;*/
Person p(10, 10);
cout << p << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
#include
using namespace std;
// 重载递增运算符
//自定义整型
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
// 重载前置++ 返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++()
{
++m_Num;
return *this;
}
//重载后置++
//void operator++(int) int表示一个占位参数,可以用于区分前置和后置
MyInteger operator++(int)
{
// 先 返回结果
MyInteger temp = *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;
cout << (myint++)++ << endl;
cout << myint << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
由于我们使用了析构函数去释放在构造函数中开辟的堆内存,(详见下列代码)原本 的 = 的赋值作用带来的的是浅拷贝,即对象指针指向同一块内存,而每次对象结束时都会调用析构函数释放内存,这样会导致同一内存块被释放两次。导致程序最终会崩溃。所以需要对赋值操作符进行重载,并且在其使用深拷贝
#include
using namespace std;
// 赋值运算符重载
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
// 重载 赋值运算符
Person& operator=(Person& p)
{
// 编译器是提供浅拷贝
// m_Age = p.m_Age;
// 先判断是否有属性在堆区,如果有先释放,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
// 浅拷贝 ,导致对出内存(m_Age)重复释放
p2 = p1;
Person p3(30);
p1 = p2 = p3;
cout << "p1的年龄为: " << *p1.m_Age << endl;
cout << "p2的年龄为: " << *p2.m_Age << endl;
cout << "p3的年龄为: " << *p3.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
比较简单,基本上和之前的 + 重载一致,在此不赘述。
注意,要区分和函数重载
函数调用运算符 就是 ()
这种重载又称为仿函数,因为很像函数,其用法灵活,没有固定的写法,且在STL中用处颇多
此外,函数调用运算符重载还有一个引出,叫匿名函数调用(见代码)
#include
#include
using namespace std;
// 函数调用运算符重载——仿函数
class myPrint
{
public:
// 重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
class myAdd
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
void test01()
{
myPrint myprint;
myprint("hello world!");
// 匿名函数调用
myPrint()("hello world1");
}
void test02()
{
cout << myAdd()(10, 20) << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
运算符重载算是一个不错的特性,增加了代码的灵活性,让代码变得更美观和简洁,同时利于阅读,也使得原本的运算操作符的应用范围更广,但同时,读者也需要注意,不要滥用运算符重载,比如把 + 号的功能变成 - 号的功能,这会使得代码变得难以阅读。