运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
一、加号运算符重载
目的:实现两个自定义数据类型相加的运算
-
通过成员函数重载+号
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.operator+(p2);
简化为 : Person p3 = p1 + p2;
-
通过全局函数重载+号
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;
二、左移运算符重载
作用:可以输出自定义的数据类型
-
全局函数重载左移运算符
(只能能利用全局函数重载左移运算符)
void operator<<(ostream &cout , Person &p)
{ 本质 operator<<(cout , p) 简化 cout << p
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
}
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p;
}
上例中,cout属于ostream类型(输出流),由于对象只能有一个,故采用引用的方式传递
ostream & operator<<(ostream &cout , Person &p)
{ //本质 operator<<(cout , p) 简化 cout << p << endl
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout; //★
}
void test01()
{
Person p;
p.m_A = 10;
p.m_B = 10;
cout << p <<endl;
}
cout << p <<endl 这个语句属于于链式编程思想,只有返回值是cout才能够继续在后面追加输入,故我们需要将全局函数返回值为cout的类型,即ostream。
相当与调用函数 operator(cout,p),如果没有返回值 operator( ,endl)第二个调用函数少一个对象cout
-
为什么无法用成员函数重载左移运算符(
<<
)
无法实现cout在左侧
假设我们有一个
Person
类,我们想要重载<<
运算符来打印出人的名字和年龄。
尝试使用成员函数来重载<<
运算符:
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
// 尝试使用成员函数重载 <<
void operator<<(ostream& os) {
os << "Name: " << name << ", Age: " << age;
}
};
int main() {
Person p;
p.name = "Alice";
p.age = 30;
// 这里会报错,因为成员函数无法这样调用
cout << p;
return 0;
}
在上面的代码中,我们尝试通过成员函数重载
<<
运算符。但是,当我们尝试调用cout << p;
时,编译器会报错。这是因为成员函数需要通过对象来调用,而cout << p;
这样的调用方式实际上是在寻找一个全局函数,而不是成员函数。
三、递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
准备知识
前置加
int a = 10;
cout << ++a <<endl; // 11
cout << a <<endl; // 11
后置加
int b = 10;
cout << b++ <<endl; // 10
cout << b <<endl; // 11
目标
MyInteger myint;
(相当于int a ;)
cout << myint <<endl; // 0
cout << ++myint << endl; // 1
cout << myint++ <<endl; // 1
cout << myint <<endl; // 2
实现
class MyInteger { friend ostream& operator<<(ostream& out, MyInteger myint); public: MyInteger() { m_Num = 0; } // 前置++ MyInteger& operator++() { // 先++ m_Num++; // 再返回 return *this; } // 后置++ MyInteger operator++(int) { // 先返回 MyInteger temp = *this; // 记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++ m_Num++; return temp; } private: int m_Num; }; ostream& operator<<(ostream& out, MyInteger myint) { out << 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; }
为什么前置用了引用而后置没有用?
前置递增运算符使用引用是为了支持链式调用,而后置递增运算符返回副本是为了保留递增操作之前的对象状态。
四、赋值运算符重载
C++编译器至少给一个类添加4个函数
1. 默认构造函数(无参,函数体为空)
2. 默认析构函数(无参,函数体为空)
3. 默认拷贝构造函数,对属性进行值拷贝
4. 赋值运算符 operator=,对属性进行值拷贝
前三点在之前的帖子已经细讲过,此处我们着重看第四点
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题(析构函数重复释放)
例
由于编译器默认的赋值运算符“=”是浅拷贝,若我们类中有属性指向堆区,在赋值操作时会出现深浅拷贝的问题,此时我们就需要重新创造一种赋值方式,即赋值运算符重载
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;
}
五、关系运算符重载
作用:重载关系运算符,可让两个自定义类型对象进行对比操作
class Person
{
public:
/ 构造函数,初始化姓名和年龄
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
/ 重载等于运算符
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
return false;
}
/ 重载不等于运算符
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
private:
string m_Name; // 姓名
int m_Age; // 年龄
};
此处比较运算符被定义为成员函数,全局函数(也称为非成员函数)通常用于重载那些不能作为成员函数实现的运算符
六、函数调用运算符重载
- 函数调用运算符"()"也可以重载 [重载小括号]
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活 (可以在小括号里传string也可以传int)
例
class Adder {
public:
/ 重载函数调用运算符
int operator()(int a, int b) {
return a + b; / 返回两个数的和
}
};
int main() {
Adder add; / 创建Adder对象
int result = add(3, 4); / 使用重载的()运算符调用对象,执行加法
cout << "Result: " << result << endl; / 输出结果
return 0;
}
函数调用运算符重载在后续STL用的比较多,现在仅供了解
运算符重载总结
何时使用全局函数重载运算符以及何时使用成员函数重载运算符
运算符类型 | 全局函数重载 | 成员函数重载 | 备注 |
---|---|---|---|
一元运算符 | 是 | 是 | 如operator++ (前置和后置)和operator! |
二元运算符 | 是 | 否 | 如operator+ , operator- ,当操作数之一不是类类型时使用全局函数 |
输出运算符 | 是 | 否 | operator<< 用于输出流 |
输入运算符 | 是 | 否 | operator>> 用于输入流 |
下标运算符 | 是 | 否 | operator[] 用于访问数组或容器元素 |
函数调用运算符 | 是 | 否 | operator() 用于类实例的函数调用 |
赋值运算符 | 否 | 是 | operator= 通常作为成员函数实现 |
比较运算符 | 否 | 是 | 如operator== , operator!= ,通常作为成员函数实现,除非需要比较类类型与非类类型 |
类型转换运算符 | 否 | 是 | 如operator int ,用于类实例到基本数据类型的转换 |
代码案例
一、加号运算符重载
// 成员函数重载(推荐在操作数类型固定时使用)
Person operator+(const Person& p) const { // 添加const修饰
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.operator+(p2)
// 全局函数重载(支持隐式类型转换)
Person operator+(const Person& p1, const Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
// 注意:若访问私有成员需声明为友元
二、左移运算符重载
// 全局函数重载(必须返回ostream&以支持链式调用)
ostream& operator<<(ostream& cout, const Person& p) { // 参数添加const
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout; // 返回引用以实现链式调用
}
// 若访问私有成员需在类内声明:
friend ostream& operator<<(ostream& cout, const Person& p);
// 错误示例:成员函数重载会导致调用方向相反
void operator<<(ostream& os) { ... } // 实际调用方式为:p << cout
三、递增运算符重载
// 前置++(返回引用)
MyInteger& operator++() {
m_Num++;
return *this;
}
// 后置++(int占位符区分,返回const防止连续后置++)
const MyInteger operator++(int) { // 添加const修饰
MyInteger temp = *this;
m_Num++;
return temp; // 返回旧值副本
}
四、赋值运算符重载
Person& operator=(const Person& p) { // 参数应为const
// 1. 防止自赋值
if (this == &p) return *this;
// 2. 释放原有资源
if (m_Age != nullptr) { // 建议使用nullptr
delete m_Age;
m_Age = nullptr;
}
// 3. 深拷贝
m_Age = new int(*p.m_Age);
return *this;
}
// 注意:需同时实现拷贝构造函数
Person(const Person& p) : m_Age(new int(*p.m_Age)) {}
五、关系运算符重载
bool operator==(const Person& p) const { // 添加const
return (m_Name == p.m_Name) && (m_Age == p.m_Age);
}
bool operator!=(const Person& p) const {
return !(*this == p); // 复用==运算符
}
六、函数调用运算符重载
class Adder {
public:
// 可携带状态的仿函数
int operator()(int a, int b) const { // const版本
return a + b + offset;
}
private:
int offset = 5; // 示例成员变量
};
// 应用场景:STL算法中的自定义行为
sort(vec.begin(), vec.end(), MyComparator());
活动发起人@小虚竹 想对你说:
这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!我们一起发掘写作的魅力,书写出属于我们的故事。我们诚挚邀请你参加为期14天的创作挑战赛!
提醒:在发布作品前,请将不需要的内容删除。