目录
一.介绍
在C++的世界里,运算符重载真的是一个强大的魔法!它让你有机会为你的自定义数据类型定制那些常见的运算符,比如加号“+”、减号“-”和等号“=”等。这样,当你在使用这些自定义类型时,感觉就像在使用内置类型一样轻松。
不过,这个魔法也不是万能的哦!有一些小规则需要遵守:
- 不是所有运算符都可以随便重载哦。有些“调皮”的运算符,比如throw或delete,因为它们有副作用或者行为复杂,是不能被重载的。
- 运算符重载需要是类的一个成员函数或者是全局函数。如果你是类的一个成员,那函数默认会把类的一个对象作为第一个参数。所以,运算符重载有两种常见的形式:一种是作为类的成员函数,另一种是作为普通的全局函数。
- 运算符重载不会改变运算符的优先级和结合性。也就是说,如果你重载了一个“+”,它还是会在“*”之前计算哦!
现在,你是不是觉得C++的运算符重载既强大又有点小复杂呢?但只要掌握了这些规则,你就能为你的代码增添一份独特的个性!
二.举例
1.“+”运算符重载(“-,*,/”类似)
前言:“+”号只会对那些已经内置的类型数据起作用,比如inr,double等类型数据。当尝试将两个类相加,编译器会报错,没有与这些操作数相匹配的操作符。所以要想实现类的相加,需要对“+”进行重载,改变它的功能。
创建Person类
class Person
{
public:
int m_A;
int m_B;
};
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
int main()
{
test01();
return 0;
}
成员函数重载
创建成员函数operator+(编译器默认函数名)进行“+”号重载,并用引用方式传入参数。
提示:
为何两个操作数,函数却只需要传入一个参数?
当你调用一个对象的方法时,该方法的第一个参数总是隐式地指向该对象自身。因此,对于成员函数来说,你只需要传递一个参数(即另一个Person
对象),因为this
指针已经隐式地指向了调用该方法的对象。
那么是谁调用这个函数呢?是p3吗?还是p1或p2?
其实下面这句代码是简化形式,为了更好理解,我们需要认识本质调用。
Person p3 = p1 + p2;
函数重载的本质调用
很明显,是p1调用的operator+ 函数。当 p1
对象调用 operator+
方法并传入 p2
对象作为参数时,this
指针指向 p1
对象。因此,this->m_A
和 this->m_B
分别表示 p1
对象的 m_A
和 m_B
成员变量。
//成员函数重载本质调用
Person p3 = p1.operator+ +(p2);
class Person
{
public:
//1.成员函数重载+号
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 m_A;
int m_B;
};
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class Person
{
public:
//1.成员函数重载+号
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 m_A;
int m_B;
};
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
//成员函数重载本质调用
/*Person p3 = p1.operator+ +(p2);*/
//简化形式
Person p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
int main()
{
test01();
return 0;
}
结果截图
全局函数重载
提示:
全局函数没有this
指针。它们没有与特定对象实例关联,因此必须显式地传递两个Person
对象作为参数,以便可以在函数内部访问和修改这两个对象的成员变量。
//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 operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A + num;
temp.m_B = p1.m_B + num;
return temp;
}
总结
1:对于内置的数据类型的表达式的运算符是不可能改变的
2:不要滥用运算符重载(相反,加号变减号)
2.左移运算符重载
创建Person类,成员函数Person用来给m_A,m_B赋值。
class Person
{
public:
Person(int a, int b)//方便初始化
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
用下面代码输出,我们有时觉得繁琐。那么可不可以用第二句代码进行输出呢?
cout<<"m_A = "<<m_A<<" m_B = "<<m_B<<endl;
cout<<p<<endl;
当然可以,我们需要对左移运算符<<重载。首先利用全局函数重载,如果采用成员函数重载,调用函数代码为p.operator<<(cout),很明显cout在右边,而按照使用惯性cout一般在左边,成员函数重载不符合使用惯性。
全局函数需要传入两个参数,一个是cout,一个是类,其中我们需要明确cout的类型。在Visual Studio 2022中选中cout,右击鼠标点击转到定义。在定义中,我们可以看到,cout是ostream(输出流)类型。
全局函数cout类型需要设为ostream &。之所以采用引用方式,是因为ostream对象只能有一个。
ostream & 是 C++ 中的一个类型,表示输出流引用。在 C++ 中,ostream 是一个库类,用于表示输出流,比如控制台输出或文件输出。&表示引用,这意味着我们正在引用一个已经存在的输出流对象,而不是创建一个新的对象。
当我们在重载流插入运算符 <<
时,返回类型通常为ostream & ,这样我们可以将这个函数与其他流操作符连续使用。
ostream& operator<<(ostream& out, Person p)//本质 operator<<(cout,p)
{//ostream 输出流 类型
out << "m_A = " << p.m_A << " m_B = " << p.m_B;
return out;
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
class Person
{
friend ostream& operator<<(ostream& out, Person p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
ostream& operator<<(ostream& out, Person p)//本质 operator<<(cout,p)
{//ostream 输出流 类型
out << "m_A = " << p.m_A << " m_B = " << p.m_B;
return out;
}
void test01()
{
Person p(10, 10);
cout << p << endl;
}
int main()
{
test01();
return 0;
}
//总结:重载左移运算符配合友元可以实现输出自定义数据类型
运行结果
3. 递增运算符重载(“--”类似)
自定义整形,重载左移运算符。此时test01中第二句代码还不能运行。
class MyInteger
{
public:
friend ostream& operator<<(ostream& out, MyInteger myint);
MyInteger()
{
m_Num = 0;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint)
{
out << "myint = " << myint.m_Num;
return out;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
cout << myint << endl;
}
int main()
{
test01();
return 0;
}
重载 ++(前置)运算符
前置自增运算符++
在C++中的标准行为是先增加操作数的值,然后返回增加后的值。为了实现这一行为,重载的函数需要返回一个对象的引用,这样就可以在表达式中连续使用该对象。
MyInteger& operator++()
{
//先进行++运算
m_Num++;
//再将自身返回
return *this;
}
如果前置自增运算符返回一个MyInteger
类型的值而不是引用,那么在下面示例中,表达式++a
将产生一个临时对象,而不是返回a
的引用。这将导致语法错误和不必要的拷贝操作。
MyInteger a(5);
MyInteger b = ++a; // a.operator++()返回a的引用,然后将其自增
重置 ++(后置)运算符
int是C++中后置++的标识符。this是指向当前对象的指针,*号解引用。这里不能使用返回引用的方式,而是要用值的方式返回。原因在于,temp是一个局部变量,函数运行完毕,temp会被销毁。如果采用引用方式返回,程序会去寻找一个已经不存在的地址,自然运行出现错误。
//重载后置++运算符
//void operator++(int) int代表占位参数,可以用来区分前置和后置递增
MyInteger operator++(int)
{
//先 记录当时结果
MyInteger temp = *this;
//后 递增
m_Num++;
//最后将记录结果返回
return temp;
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
using namespace std;
//递增运算符重载 ++
//自定义整形
class MyInteger
{
public:
friend ostream& operator<<(ostream& out, MyInteger myint);
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& out, MyInteger myint)
{
out << "myint = " << myint.m_Num;
return out;
}
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;
}
4.赋值运算符重载
使用堆内存允许程序更加灵活地管理内存。与栈内存不同,堆内存的分配和释放是在运行时决定的,这使得程序能够根据需要动态地分配和释放内存。
class Person
{
public:
Person(int age)
{
m_Age = new int(age);
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
int* m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
p2 = p1;//赋值操作
cout << "p1的年龄是: " << *p1.m_Age << endl;
cout << "p2的年龄是: " << *p2.m_Age << endl;
}
在赋值操作p2 = p1(m_Age = p.m_Age地址赋值);
后,p1
和p2
都指向同一块堆内存。这意味着修改一个对象的年龄也会修改另一个对象的年龄。在进行内存释放的时候,会导致对同一块内存反复释放,从而出现程序运行错误。
因为编译器是提供浅拷贝才导致这样的错误出现,所以我们重载赋值运算符时,要采用深拷贝的方式。
深拷贝重载赋值
//重载 赋值运算符
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;//目的是可以连等
}
完整代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string.h>
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);
Person p3(30);
p3 = p2 = p1;//赋值操作
cout << "p1的年龄是: " << *p1.m_Age << endl;
cout << "p2的年龄是: " << *p2.m_Age << endl;
cout << "p3的年龄是: " << *p3.m_Age << endl;
}
int main()
{
test01();
return 0;
}
5.关系运算符重载
相应类,函数的创建
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
string m_Name;
int m_Age;
};
void test01()
{
Person p1("Tom", 18);
Person p2("Tom", 18);
if (p1 == p2)
{
cout << "p1和p2是相等的" << endl;
}
else
{
cout << "p1和p2是不相等的" << endl;
}
}
void test02()
{
Person p3("Jake", 18);
Person p4("Lili", 18);
if (p3 != p4)
{
cout << "p3和p4是不相等的" << endl;
}
else
{
cout << "p3和p4是相等的" << endl;
}
}
int main()
{
test01();
test02();
return 0;
}
“==”重载
重载“==”运算符意味着为类定义一个特殊的成员函数,该函数允许两个对象使用“==”运算符进行比较。这个重载函数返回一个布尔值(bool
),表示两个对象是否相等。
bool operator==(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
“!=”重载
bool operator!=(Person& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
return true;
}
6.调用运算符重载
打印输出类重载
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void test01()
{
MyPrint myprint;
myprint("Hello World!");//由于使用起来非常类似于函数调用,因此成为仿函数
MyPrint02("Hello World!");
}
//仿函数非常灵活,没有固定的写法
void MyPrint02(string test)
{
cout << test << endl;
}
int main()
{
test01();
return 0;
}
加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100, 100);
cout << "ret = " << ret << endl;
//匿名函数对象
cout << MyAdd()(100, 100) << endl;
}
void MyPrint02(string test)
{
cout << test << endl;
}
int main()
{
test02();
return 0;
}
以上就是六种重载类型的举例。
看到这里,不妨点个攒,关注一下吧!
最后,谢谢你的观看