运算符重载
运算符重载
概念:对已有的运算符进行重新定义,可以对不同的数据结构进行不同处理
运算符重载的参数位置
一、运算符重载的两种方式
1. 成员函数重载
- 特点:运算符重载函数是类的成员函数。
- 参数位置:
- 左操作数是当前对象(
this
指针指向的对象)。 - 右操作数是函数的参数。
- 左操作数是当前对象(
- 适用运算符:
+
、-
、*
、/
、[]
、()
等。
2. 全局函数重载
- 特点:运算符重载函数是全局函数(通常声明为类的友元函数)。
- 参数位置:
- 左操作数是第一个参数。
- 右操作数是第二个参数。
- 适用运算符:
<<
、>>
、+
、-
等。
二、参数位置的具体规则
1. 二元运算符(如 +
、-
、\*
、/
)
-
成员函数重载:
-
左操作数是当前对象(
this
),右操作数是参数。 -
示例:
class MyClass { public: MyClass operator+(const MyClass& other) const { MyClass result; // 实现加法逻辑 return result; } }; MyClass a, b, c; c = a + b; // a 是左操作数(this),b 是右操作数(参数)
-
-
全局函数重载:
-
左操作数是第一个参数,右操作数是第二个参数。
-
示例:
class MyClass { public: int value; friend MyClass operator+(const MyClass& lhs, const MyClass& rhs); }; MyClass operator+(const MyClass& lhs, const MyClass& rhs) { MyClass result; result.value = lhs.value + rhs.value; return result; } MyClass a, b, c; c = a + b; // a 是第一个参数,b 是第二个参数
-
2. 一元运算符(如 ++
、--
、!
)
-
成员函数重载:
-
操作数是当前对象(
this
),无额外参数。 -
示例:
class MyClass { public: MyClass& operator++() { // 前置++ // 实现自增逻辑 return *this; } }; MyClass a; ++a; // a 是操作数(this)
-
-
全局函数重载:
-
操作数是唯一参数。
-
示例:
class MyClass { public: int value; friend MyClass operator++(MyClass& obj); // 前置++ }; MyClass operator++(MyClass& obj) { obj.value++; return obj; } MyClass a; ++a; // a 是参数
-
3. 流插入运算符(<<
)和流提取运算符(>>
)
-
必须使用全局函数重载:
-
左操作数是流对象(如
cout
),右操作数是要输出的对象。 -
示例:
class MyClass { public: int value; friend ostream& operator<<(ostream& os, const MyClass& obj); }; ostream& operator<<(ostream& os, const MyClass& obj) { os << obj.value; return os; } MyClass a; cout << a; // cout 是第一个参数,a 是第二个参数
-
4. 下标运算符([]
)
-
必须使用成员函数重载:
-
左操作数是当前对象(
this
),右操作数是下标值。 -
示例:
class MyClass { public: int data[10]; int& operator[](int index) { return data[index]; } }; MyClass a; a[3] = 42; // a 是左操作数(this),3 是右操作数(参数)
-
5. 函数调用运算符(()
)
-
必须使用成员函数重载:
-
左操作数是当前对象(
this
),右操作数是参数列表。 -
示例:
class MyClass { public: int operator()(int x, int y) { return x + y; } }; MyClass a; int result = a(3, 4); // a 是左操作数(this),(3, 4) 是参数列表
-
三、参数位置的总结
运算符类型 | 成员函数重载 | 全局函数重载 |
---|---|---|
二元运算符 | 左操作数:this ,右操作数:参数 |
左操作数:第一个参数,右操作数:第二个参数 |
一元运算符 | 操作数:this ,无额外参数 |
操作数:唯一参数 |
流运算符 | 不支持 | 左操作数:流对象,右操作数:输出对象 |
下标运算符 | 左操作数:this ,右操作数:下标 |
不支持 |
函数调用运算符 | 左操作数:this ,右操作数:参数列表 |
不支持 |
四、注意事项
-
链式调用:
-
对于二元运算符(如
+
、<<
),返回类型应为引用或值,以支持链式调用。 -
示例:
cout << a << b; // 链式调用
-
-
友元函数:
-
如果全局函数需要访问类的私有成员,必须声明为友元函数。
-
示例:
class MyClass { friend ostream& operator<<(ostream& os, const MyClass& obj); };
-
-
运算符优先级:
- 重载运算符的优先级与内置运算符一致,但需注意括号的使用以避免歧义。
五、总结
- 成员函数重载:左操作数是当前对象(
this
),右操作数是参数。 - 全局函数重载:左操作数是第一个参数,右操作数是第二个参数。
- 特殊运算符:
- 流运算符(
<<
、>>
)必须使用全局函数重载。 - 下标运算符(
[]
)和函数调用运算符(()
)必须使用成员函数重载。
- 流运算符(
加号减号重载(+、-)
语法:
类型 operator+/-(参数){
对+的运算重新定义
}
类的成员函数可以实现运算符重载,全局函数也可以
/*
+
*/
//复数类
class Complex {
friend Complex operator+(Complex& a, Complex& b);
friend Complex operator-(Complex& a, Complex& b);
public:
Complex(): real(0),image(0){
}
Complex(int real, int image) {
this->real = real;
this->image = image;
}
Complex add(Complex& other) {
Complex ret;
ret.real = this->real + other.real;
ret.image = this->image + other.image;
return ret;
}
/*可在成员函数
Complex operator+(Complex& other) {
Complex ret;
ret.real = this->real + other.real;
ret.image = this->image + other.image;
return ret;
}
*/
void Print() {
cout << real << '+' << image << "i" << endl;
}
private:
int real;
int image;
};
//可在全局函数 +
Complex operator+(Complex& a,Complex& b) {
Complex ret;
ret.real = a.real + b.real;
ret.image = a.image + b.image;
return ret;
}
// -
Complex operator-(Complex& a, Complex& b) {
Complex ret;
ret.real = a.real - b.real;
ret.image = a.image - b.image;
return ret;
}
int main() {
Complex a(10, 20);
Complex b(5, 8);
Complex c = a.add(b);
Complex d = a + b;
Complex f = a - b;
c.Print();
d.Print();
f.Print();
return 0;
}
左移运算符重载(<<)
为什么要重载<<
运算符:
默认情况下,cout
无法直接输出自定义类对象,通过重载 <<
,可以定义对象的输出格式
重载 <<
的两种方式
1. 全局函数重载(推荐)
- 适用场景:需要访问类的私有成员时,需声明为类的友元函数。
class MyClass {
// 声明友元函数
friend ostream& operator<<(ostream& os, const MyClass& obj);
};
// 实现全局函数
ostream& operator<<(ostream& os, const MyClass& obj) {
// 输出 obj 的成员
return os;
}
2. 成员函数重载(不推荐)
- 问题:成员函数重载时,左操作数必须是类的对象(而
<<
的左操作数应为ostream
)。我们希望cout << c 而不是 c << cout
- 语法(仅作了解,实际几乎不用):
class MyClass {
public:
ostream& operator<<(ostream& os) { // 错误!实际应为 os << *this
os << this->data;
return os;
}
};
// 错误用法:
MyClass obj;
obj << cout; // 不符合习惯,实际不可行
关键细节
1. 参数顺序与返回值
- 参数顺序:必须为
(ostream& os, const T& obj)
,因为<<
的左操作数是ostream
。 - 返回值:返回
ostream&
以支持链式调用(如cout << a << b
)。
2. const
修饰
- 对象参数:使用
const T&
避免拷贝,同时禁止修改对象。 - 流参数:流对象 (
os
) 不能是const
,因为输出操作会修改流状态。
3. 友元函数必要性
如果类的成员是私有的,必须声明 operator<<
为友元函数,否则无法访问私有成员。
总结
- 核心目的:让自定义类型支持
cout << obj
的直观输出。 - 关键步骤:
- 声明全局函数
operator<<
为类的友元(若需访问私有成员)。 - 函数参数顺序为
(ostream&, const T&)
。 - 返回
ostream&
以支持链式调用。
- 声明全局函数
- 适用场景:调试输出、日志记录、数据可视化等。
递增运算符重载(++)
在C++
中,递增运算符(++)
分为前置(++obj)
和后置(obj++)
两种形式,它们的重载方式不同。
1. 前置递增运算符重载
- 语法:
ClassName& operator++()
- 特点:
- 直接修改对象本身,返回修改后的对象的引用。
- 允许连续调用(如
++++obj
)。
class Counter {
private:
int count;
public:
Counter() : count(0) {}
// 前置递增运算符重载
Counter& operator++() {
++count;
return *this; // 返回当前对象的引用
}
int getCount() const { return count; }
};
// 使用示例
Counter c;
++c; // count 变为 1
++++c; // 合法,count 变为 3
2. 后置递增运算符重载
- 语法:
ClassName operator++(int)
int
是哑元参数,仅用于区分前置和后置。
- 特点:
- 先保存原对象的值,再修改对象,最后返回修改前的临时副本。
- 返回值是值类型(而非引用),禁止连续调用(如
c++++
非法)。
class Counter {
private:
int count;
public:
Counter() : count(0) {}
// 后置递增运算符重载
Counter operator++(int) {
Counter temp = *this; // 保存当前对象的值
++count; // 修改当前对象
return temp; // 返回修改前的副本(临时对象)
}
int getCount() const { return count; }
};
// 使用示例
Counter c;
c++; // 返回0,但 c.count 变为1
// c++++; // 非法!返回的临时对象无法修改
3.完整代码示例
class Complex {
friend ostream& operator<<(ostream& cout, Complex c);
private:
int real;
int image;
public:
Complex():real(0),image(0){}
Complex(int real, int image) {
this->real = real;
this->image = image;
}
Complex& operator++() {
this->real += 1;
return *this;
}
Complex operator++(int) {
Complex c = *this;
this->real += 1;
return c;
}
};
ostream& operator<<(ostream& cout,Complex c) {
cout << c.real << '+' << c.image << "i" ;
return cout;
}
int main() {
Complex c(10, 12);
cout << c << endl;
cout << ++c << endl;
cout << c++ << endl;
cout << c << endl;
return 0;
}
4. 关键注意事项
- 返回值类型:
- 前置返回引用(
&
),避免拷贝。 - 后置返回值(非引用),因为返回的是临时对象。
- 前置返回引用(
- 哑元参数
int
:- 仅用于区分前置和后置,无实际用途。
- 效率:
- 前置递增通常更高效(无临时对象),推荐优先使用。
赋值运算符重载(=)
默认赋值运算符会把所有的成员变量赋值过去,包括指针。
class Hero {
public:
int* m_Data;
Hero():m_Data(NULL){}
Hero(int data) {
m_Data = new int;
*m_Data = data;
}
~Hero() {
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
}
};
Hero h1(8);
Hero h2(5);
h1 = h2; //此时使用默认赋值运算符,把h2的m_Data指针也赋值给h1的m_Data,h1和h2的m_Data此时指向同一个地址
这样在析构时会出现double free
问题,即析构h1时释放了h1的m_Data
,在析构h2时释放h2的m_Data
,但h1和h2的m_Data指向的是同一个地址,在h1析构时已经释放过,h2又释放一次,导致报错。
所以对于自定义类需要重构赋值运算符
class Hero {
public:
int* m_Data;
Hero():m_Data(NULL){}
Hero(int data) {
m_Data = new int;
*m_Data = data;
}
~Hero() {
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
}
Hero& operator=(Hero& h) {
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
m_Data = new int;
*m_Data = *h.m_Data;
return *this;
}
};
int main() {
Hero h1(8);
Hero h2(5);
Hero h3(4);
h3 = h1 = h2;
return 0;
}
关系运算符重载(>、<、==、...)
默认的关系运算符不能对自定义类进行比较,需要重载关系运算符。
因为只是用于比较,不需要修改,所以使用常函数
和常量引用
来保证数据不被修改。
class Point {
public:
Point(int x,int y):m_x(x),m_y(y){}
bool operator==(const Point& p) const {//常函数和常量引用作为参数
return this->m_x == p.m_x && this->m_y == p.m_y;
}
bool operator<(const Point& p) const {
int d = this->m_x * this->m_x + this->m_y * this->m_y;
int f = p.m_x * p.m_x + p.m_y * p.m_y;
return d < f;
}
bool operator>(const Point& p) const {
if (*this == p) {
return false;
}
if (*this < p) {
return false;
}
return true;
}
private:
int m_x;
int m_y;
};
int main() {
Point a(3, 4);
Point b(1, 2);
if (a == b) {
cout << "a与b相等" << endl;
}
else if (a < b) {
cout << "a比b更接近原点" << endl;
}
else if (a > b) {
cout << "b比a更接近原点" << endl;
}
return 0;
}
函数调用运算符重载(())
重载函数调用运算符,实现仿函数,可以存储状态,用于其他目的
class AddFiction {
private:
int m_acc;//状态
public:
AddFiction():m_acc(0){}
int operator()(int a,int b) {
m_acc++;//状态改变
return a + b;
}
};
int Add(int a, int b) {
return a + b;
}
int main() {
AddFiction add;
add(4, 5); //仿函数
Add(4, 5); //函数
cout<<add(5,6)<<endl;
Add(4, 5);
Add(4, 5); //多次调用函数,参数不变,结果相同
add(5,6)
add(5,6) //多次调用仿函数,状态在改变
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端