C++:类和对象:运算符重载
前言:
运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
1:加号运算符重载
对于内置的数据类型, 编译器知道如何运算,可以很直观的得到结果
int a = 10;
int b = 10;
int c = a+b;
但是现在我们有个 Person类,类中有两个成员变量m_A和 m_B,现在我们有两个 Person对象(P1和P2) ,如果我们直接通过加号运算符+ 将两个对象变量相加创建出一个新对象,这样的行为肯定是不行的。那么我们应该想个办法:通过自己写个成员函数,实现两个对象的成员变量相加并返回新对象。
class Person {
public:
int m_A;
int m_B;
Person PersonAndPerson(Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
但是这个方法也有缺点,如果多个人都这样实现函数,那么可能每个人定义的函数名不一样,这样会造成混乱,所以编译器干脆给我们提供一个函数名:oprator+
1.1:成员函数重载+号运算符
class Person {
public:
Person() {}
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
int m_A;
int m_B;
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;
}
};
int main() {
Person P1(10, 10);
Person P2(20, 20);
// 本质是:Person p3 = p1.operator+(p2),重载加号运算符
Person P3 = P1 + P2;
}
从运行结果可知:可以直接通过 + 号 完成两个对象的成员变量相加。
1.2 全局函数重载+号运算符
我们也可以通过 全局函数 重载+号运算符
运行结果可知:通过 + 符号可以直接完成两个对象的成员相加
1.3 运算符重载的函数重载
现在我们想让 Person 变量和 int类型变量相加,即将 int类型的变量值加在 Person的两个成员变量上,那么我们可以对运算符重载使用函数重载。
#include<iostream>
using namespace std;
class Person {
public:
Person() {}
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
int m_A;
int m_B;
};
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 operator+(const Person& p2, int value) {
Person temp;
temp.m_A = p2.m_A + value;
temp.m_B = p2.m_B + value;
return temp;
}
int main() {
Person P1(30, 30);
// 本质是:Person p3 = operator+(p1,p2),重载加号运算符
Person P3 = P1 + 10;
cout << "p3 m_A = " << P3.m_A << " ,p3 m_B = " << P3.m_B << endl;
}
运行结果可知:调用的是下面这个重载运算符
Person operator+(const Person& p2, int value)
需要注意的是
1:对于内置的数据类型表达式的运算符是不可以改变的
2:请勿滥用运算符重载
2:左移运算符重载
全局函数来重载左移运算符,大致框架如下:
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
Person(int a, int b) {
this->m_A = a;
this->m_B = b;
}
public:
int m_A;
int m_B;
};
// 全局函数实现左移重载
// 实现 cout << p
ostream& operator<<(ostream& out,Person& p) {
out << "m_A :" << p.m_A << " b:" << p.m_B;
return out;
}
int main() {
Person p1(10, 20);
cout << p1<< endl;
return 0;
}
运行结果可知:可以正确重载 << 运算符 并输出了 对象 p1的成员变量。
3:递增运算符重载
3.1 重载前置
#include<iostream>
using namespace std;
class MyInter {
friend ostream& operator<<(ostream& cout, MyInter myint);
public:
MyInter(){
m_Num= 0;
}
MyInter& operator++() {
m_Num++; // 先进行++运算
return *this;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInter myint) {
cout << myint.m_Num;
return cout;
}
int main() {
MyInter myint;
cout << myint << " 自增一次:";
cout << ++ myint << endl;
}
运行结果可知:MyInter 变量 myint 自增一次得到了正确的值。
3.1 重载后置
#include<iostream>
using namespace std;
class MyInter {
friend ostream& operator<<(ostream& cout, MyInter myint);
public:
MyInter(){
m_Num= 0;
}
MyInter& operator++() {
m_Num++; // 先进行++运算
return *this;
}
MyInter operator++(int a) {
// 先记录 当前值
MyInter temp = *this;
// 后递增
m_Num++;
// 最后将结果返回
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInter myint) {
cout << myint.m_Num;
return cout;
}
int main() {
MyInter myint;
cout << myint++ << endl;
cout << myint;
}
4:赋值运算符
C++ 编译器会至少给一个类添加4个函数
1:默认构造函数(无参,函数体为空)
2:默认析构函数(无参,函数体为空)
3:默认拷贝函数,对属性进行值拷贝
4:赋值运算符 operator= , 对属性进行值拷贝
赋值运算符重载时(重载=),要进行 深拷贝,而不是直接将值进行复制。
#include<iostream>
using namespace std;
class Person {
public:
int* m_Age;
Person(int age) {
// 将年龄数据开辟到堆区
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
void operator=(Person& p) {
if (m_Age != NULL)
{
// 先释放掉自己开辟的内存
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
}
};
int main() {
Person p1(10);
Person p2(20);
p2 = p1;
cout << "p1的年龄为: " << *p1.m_Age << endl;
cout << "p2的年龄为: " << *p2.m_Age << endl;
return 0;
}
运行结果可知:是深拷贝。
这里有个注意点:如果是出现这种连等情况(右边的那个数赋值给左边),那么就需要 对重载函数 进行修改 ,让其返回当前对象 (return *this)
#include<iostream>
using namespace std;
class Person {
public:
int* m_Age;
Person(int age) {
// 将年龄数据开辟到堆区
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
Person& operator=(Person& p) {
if (m_Age != NULL)
{
// 先释放掉自己开辟的内存
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
};
int main() {
Person p1(10);
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;
return 0;
}
5: 关系运算符重载
关系运算符包含 == 和 != ,如果现在我们想对比两个自定义的数据类型,则需要重载关系运算符。
案例:假如现在我们有个Person类,包含一个 string类型成员变量name 和 int 类型成员变量age,如果两个 Person对象的 name 和age想对就打印相等,否则打印不相等。
#include<iostream>
#include<string>
using namespace std;
class Person {
public:
string name;
int age;
bool operator==(Person p) {
if (this->age == p.age && this->name == p.name)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person p) {
if (this->age != p.age || this->name != p.name)
{
return true;
}
else
return false;
}
Person(int age, string name) {
this->age = age;
this->name = name;
}
};
int main() {
Person p1(10, "tom");
Person p2(20, "tom");
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
if (p1 != p2)
{
cout << "p1和p2是不相等的" << endl;
}
return 0;
}
运行结果:也是符号我们预期的
6:函数调用运算符重载
1:函数调用运算符() 也可以 重载
2:由于重载后使用的方式非常像函数的调用,因此称为仿函数
3:仿函数没有固定写法,比较 灵活
案例:我们现在创建一个 MyPrint类,通过重载函数调用运算符完成字符串打印输出
#include<iostream>
#include<string>
using namespace std;
class MyPrint {
public:
void operator()(string str) {
cout << str << endl;
}
};
int main() {
MyPrint myFunc;
myFunc("hello function operator override");
}
运行结果表明:可以看出类似函数一样完成了字符串的打印输出。
案例:假设现在有一个MyAdd类,通过重载函数调用运算符完成两个整数相加。
#include<iostream>
#include<string>
using namespace std;
class MyAdd {
public:
int operator()(int a,int b) {
return a + b;
}
};
int main() {
MyAdd myFunc;
cout << myFunc(10, 20) << endl;
// 通过匿名对象调用
cout << MyAdd()(20,30) << endl;
}
匿名函数调用:即先通过 MyAdd()创建一个匿名对象,这个匿名对象在当前指向结束后会被释放,然后为这个匿名对象调用了重载的()运算符函数。