文章目录
一. 运算符重载的意义
- 对于内置的数据类型,运算符是可以进行直接运算使用的.
- 自定义的类型如果需要用到运算符的这些操作,就要自己定一个运算符的重载函数.
二. 加号运算符(+)的重载
① 函数原型(成员函数)
Obj operator+(const Obj&p){};
② 函数原型(全局函数)
Obj operator+(Obj1 o1,Obj2 o2){};
③ 成员函数的例子
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
mMoney = 0;
}
Person(int money) :mMoney(money)
{
}
// 加号运算符的重载,其实本质上这里有两个参数 operator+(this,Person p)
Person operator+(Person p)
{
cout << "成员函数加号操作符重载调用!" << endl;
Person ret;
ret.mMoney = this->mMoney + p.mMoney;
return ret;
}
public:
int mMoney;
};
int main()
{
Person p1(100);
Person p2(200);
// 这里其实调用的是p1.operator+(p2) 成员函数的操作符重载的显示参数少了一个,
// this变成了一个隐藏的参数
Person p3 = p1 + p2; // 成员函数操作符重载的调用本质 p3 = p1.operator+(p2);
cout << "金钱总和: " << p3.mMoney << endl;
Person p4 = p1.operator+(p2);
cout << "金钱总和: " << p4.mMoney << endl;
system("pause");
return 0;
}
④ 全局函数调用的例子
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
mMoney = 0;
}
Person(int money) :mMoney(money)
{
}
public:
int mMoney;
};
Person operator+(const Person& p1, const Person& p2)
{
// 全局函数的重载加号运算符
Person temp;
temp.mMoney = p1.mMoney + p2.mMoney;
return temp;
}
int main()
{
Person p1(100);
Person p2(100);
Person p3 = p1 + p2;
cout << "金钱总和: " << p3.mMoney << endl;
// 全局函数的本质调用
Person p4 = operator+(p1, p2);
cout << "金钱总和: " << p4.mMoney << endl;
system("pause");
return 0;
}
⑤ 成员函数重载和全局函数重载可不可以同时存在
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
mMoney = 0;
}
Person(int money) :mMoney(money)
{
}
Person operator+(const Person& p)
{
Person temp;
temp.mMoney = this->mMoney + p.mMoney;
return temp;
}
public:
int mMoney;
};
Person operator+(const Person& p1, const Person& p2)
{
// 全局函数的重载加号运算符
cout << "全局的加号操作符重载被调用!" << endl;
Person temp;
temp.mMoney = p1.mMoney + p2.mMoney;
return temp;
}
int main()
{
Person p1(100);
Person p2(100);
//Person p3 = p1 + p2; // 会报错,多个操作符+函数被匹配上,编译器不知道调用哪一个
// cout << "金钱总和: " << p3.mMoney << endl;
// 全局函数的本质调用
// 但是可以这么来使用
Person p3 = p1.operator+(p2);
cout << "金钱总和: " << p3.mMoney << endl;
Person p4 = operator+(p1, p2); // 这里不会报错,因为它是显示的调用了operator+的全局重载函数
cout << "金钱总和: " << p4.mMoney << endl;
system("pause");
return 0;
}
结论:
可以同时存在,但是不能使用
+
号运算符了,也就失去了重载操作符的意义.所以一般使用过程中,成员的操作符重载和全局的操作符重载一般保留一个即可
三. 左移运算符重载(<<)
① 成员函数重载左移运算符
如果采用成员函数重载左移运算符,因为opertor<<操作符重载的时候,第一个参数永远都是this.
而cout在使用的额时候的习惯是cout <<
,cout在左侧,所以成员函数重载的cout
运算符在使用的时候
不符合使用习惯,因为cout在右侧.
函数原型
ostream& operator<<(ostream &cout){} // 本质上 opertor<<(obj,cout)
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <ostream>
using namespace std;
class Person
{
public:
Person(string name, int age):mName(name),mAge(age)
{
}
ostream &operator<<(ostream &cout)
{
cout << "姓名: " << mName << ",年龄: " << mAge << endl;
return cout;
}
public:
string mName;
int mAge;
};
int main()
{
Person p("张三", 18);
p << cout; // 使用的时候必须这么去使用,不符合平时的使用习惯
//p << cout << p << cout; // 不能连续的使用cout了,因为上一个表达式的结果在左侧,也就是cout在左侧,如果要连续使用
p << (p << cout); // 必须要这么去使用,很不习惯,并且很繁琐
// cout << p; // 这里会报错,匹配不上
system("pause");
return 0;
}
结果:
② 全局函数重载运算符
因为成员函数重载运算符的时候限定了第一个参数只能是this,所以有些操作符第一个参数必须是非this对象的时候,就必须用到全局函数的重载.
原型:
ostream& operator<<(ostream& cout ,Obj o) {}
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
Person(string name, int age) :mName(name),mAge(age)
{
}
friend ostream &operator<<(ostream &cout, Person p); // 声明友元函数
private:
string mName;
int mAge;
};
ostream &operator<<(ostream &cout, Person p)
{
cout << "姓名: " << p.mName << ", 年龄: " << p.mAge;
return cout;
}
int main()
{
Person p("Fioman", 28);
Person p2("张三", 18);
cout << p << endl;
cout << p <<" " << p2 << endl;
system("pause");
return 0;
}
四. 递增递减运算符重载
- 前递增,先递增,再计算表达式
- 后递增,先计算表达式,再递增自己
- 前递增运算符重载的时候,返回的是一个引用,为什么要返回一个引用,是因为如果不返回一个引用,不能实现类似
++(++a)
这种连续的操作.- 后递增运算符重载的时候,怎么和前递增区分,加一个
int
占位符参数,并且后递增返回的是一个对象,不是一个引用,为什么不返回一个引用,因为后递增返回的是一个局部对象,如果返回一个局部对象的引用,后续就是非法操作了.既然是返回的是一个局部对象,如何保证是对同一个对象操作的呢?因为这个局部对象每次都会调用一次temp=*this
的赋值,保证了每次操作的都是当前的对象.
下面创建一个自定义的Integer类来重载一下上面四种运算符
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <iostream>
using namespace std;
class MyInteger
{
public:
MyInteger()
{
mData = 0;
}
// 递增运算符,前递增,返回引用的原因是,可以实现连续的操作,保证连续的操作是同一个对象.
MyInteger &operator++()
{
++mData;
return *this;
}
// 后递增,因为是先返回,然后再递增,所以要创建一个临时的变量保存之前的值,
// 为了和前自增区分开来,需要使用一个int来占位
MyInteger operator++(int)
{
MyInteger temp = *this;
mData++;
return temp; // 不返回引用的原因是temp是一个临时变量,
//不能返回一个临时变量的引用,后面是非法的操作
}
// 前递减
MyInteger &operator--()
{
--mData;
return *this;
}
// 后递减
MyInteger &operator--(int)
{
MyInteger temp = *this;
mData--;
return temp;
}
// 输出运算符的重载
friend ostream &operator<<(ostream &cout, MyInteger i);
private:
int mData;
};
ostream &operator<<(ostream &cout, MyInteger i)
{
cout << i.mData;
return cout;
}
int main()
{
MyInteger a;
cout << a++ << endl; // 0
cout << ++(++a) << endl; // 3
cout << a++ << endl; // 3
cout << a-- << endl; // 4
cout << --(--a) << endl;// 1
system("pause");
return 0;
}
五. 赋值运算符的重载
一个类在创建的时候,编译器会自动创建一个赋值运算符函数
① 赋值运算符的原型
Obj & operator=(const Obj o){};
② 赋值运算符的例子
和拷贝构造类似,默认的赋值运算符,也是将数据成员进行一一赋值,就是浅拷贝.所以如果成员属性有指针的情况,尽量要重载一下赋值运算符,使用深拷贝代替默认的浅拷贝带来的问题
默认的赋值运算符可能出现的问题
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
mAge = new int(age);
}
~Person()
{
// 堆区的变量要手动的释放掉
if (mAge != NULL)
{
delete mAge;
mAge = NULL;
}
}
public:
int *mAge;
};
void test_01(void)
{
Person p1(18);
Person p2(20);
p2 = p1; // 堆区的内存重复释放 因为p2 和 p1指向了同一块内存空间.
cout << "p1的年龄为: " << *p1.mAge << endl;
cout << "p2的年龄为: " << *p2.mAge << endl;
}
int main()
{
test_01();
system("pause");
return 0;
}
结果:会报错,报堆区内存重复释放,内存非法操作.
实现深拷贝来重载赋值操作符的重载
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
mAge = new int(age);
}
Person& operator=(const Person& p)
{
if (mAge != NULL) // 如果被赋值的那个对象已经存在了,要先释放掉它之前的内存,不然就会造成内存泄漏
{
delete mAge;
mAge = NULL;
}
mAge = new int(*p.mAge); // 从新分配一块内存
return *this;
}
~Person()
{
if (mAge != NULL)
{
delete mAge;
mAge = NULL;
}
}
public:
int *mAge;
};
int main()
{
Person p1(10);
Person p2(20);
p2 = p1;
cout << "p2.age = " << *p2.mAge << endl;
system("pause");
return 0;
}
六. 关系运算符的重载(== !=)
- 关系运算符的重载 == 和 != 应该都是成对出现的
- 关系运算符一般都是返回一个bool值
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string name,int age):mName(name),mAge(age)
{
}
bool operator==(const Person p)
{
if (this->mName == p.mName && this->mAge == p.mAge)
{
return true;
}
return false;
}
bool operator!=(const Person p)
{
if (this->mName == p.mName && this->mAge == p.mAge)
{
return false;
}
return true;
}
public:
string mName;
int mAge;
};
int main()
{
Person p1("Fioman", 18);
Person p2("Fioman", 18);
Person p3("Fioman", 20);
cout << "p1 == p2: " << (p1 == p2 ? "相等":"不相等") << endl;
// 这里加括号的是因为左移运算符的优先级比==号的优先级高
cout << "p2 == p3: " << (p2 != p3 ? "不相等":"相等") << endl;
system("pause");
return 0;
}
七. 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定的写法,非常的灵活,可以根据自己的需求,灵活定义
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <string>
using namespace std;
class MyPrint
{
public:
void operator()(string s)
{
cout << s << endl;
}
};
void print_func(string s)
{
cout << s << endl;
}
class MyAdd
{
public:
// 小括号的重载非常的灵活,可以根据需要定义参数和返回值
int operator()(int a, int b)
{
return a + b;
}
};
int main()
{
MyPrint mp;
mp("你好"); // 很像在调用一个函数,所以也叫仿函数
print_func("你好"); // 普通的函数调用
MyAdd add;
add(3, 4);
// 可以使用匿名对象来直接使用
cout << MyAdd()(10, 20) << endl;
system("pause");
return 0;
}