好的,我们来详细探讨 C++ 的操作符、运算符重载,并通过代码示例进行讲解。
1. C++ 操作符概述
C++ 提供了丰富的操作符(Operators),用于执行各种运算和操作。这些操作符可以大致分为以下几类:
- 算术运算符:
+
(加),-
(减),*
(乘),/
(除),%
(取模),++
(自增),--
(自减) - 关系运算符:
==
(等于),!=
(不等于),>
(大于),<
(小于),>=
(大于等于),<=
(小于等于) - 逻辑运算符:
&&
(逻辑与),||
(逻辑或),!
(逻辑非) - 位运算符:
&
(按位与),|
(按位或),^
(按位异或),~
(按位取反),<<
(左移),>>
(右移) - 赋值运算符:
=
,+=
,-=
,*=
,/=
,%=
,&=
,|=
,^=
,<<=
,>>=
- 成员访问运算符:
.
(成员选择),->
(指针成员选择) - 条件运算符:
?:
(三元运算符) - 逗号运算符:
,
- 下标运算符:
[]
- 函数调用运算符:
()
- 类型转换运算符:
static_cast
,dynamic_cast
,const_cast
,reinterpret_cast
- 内存管理运算符:
new
,delete
,new[]
,delete[]
- 作用域解析运算符:
::
- 指针相关运算符:
*
(解引用),&
(取地址)
2. 运算符重载 (Operator Overloading)
C++ 允许我们对大多数内置运算符进行重载,以便让它们能够操作用户自定义的数据类型(类或结构体)。运算符重载本质上是一种特殊的函数重载,它允许你为类的对象定义运算符的特定行为。
2.1 运算符重载的语法
运算符重载函数的语法如下:
return_type operator op (parameter_list) {
// 运算符的具体实现
}
return_type
: 运算符重载函数的返回类型。operator
: 关键字,表示这是一个运算符重载函数。op
: 要重载的运算符(例如+
,-
,*
,==
等)。parameter_list
: 运算符的参数列表,参数的个数和类型取决于运算符本身。
2.2 运算符重载的规则
- 只能重载已有的运算符,不能创建新的运算符。
- 不能改变运算符的优先级、结合性或操作数个数。
- 至少有一个操作数必须是用户自定义类型(类或结构体)。
- 不能重载的运算符包括:
.
(成员访问).*
(成员指针访问)::
(作用域解析)?:
(条件运算符)sizeof
typeid
- 运算符重载可以是成员函数,也可以是非成员函数(通常是友元函数)。
- 成员函数:当运算符左侧是类的对象时。
- 非成员函数(友元函数):当运算符左侧不是类的对象,或者需要访问类的私有成员时。
3. 运算符重载示例
3.1 重载加法运算符 (+)
假设我们有一个表示复数的类 Complex
:
#include <iostream>
class Complex {
public:
double real;
double imag;
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// 成员函数重载 + 运算符
Complex operator+(const Complex& other) const {
Complex result;
result.real = this->real + other.real;
result.imag = this->imag + other.imag;
return result;
}
void print() const {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
int main() {
Complex c1(2.0, 3.0);
Complex c2(1.5, -2.5);
Complex c3 = c1 + c2; // 使用重载的 + 运算符
c3.print(); // 输出: 3.5 + 0.5i
return 0;
}
解释:
Complex operator+(const Complex& other) const
: 这是成员函数形式的加法运算符重载。const Complex& other
: 传入另一个Complex
对象作为右操作数(引用传递以避免不必要的复制,const
表示不会修改传入对象)。const
: 最后的const
表示这个成员函数不会修改调用它的对象(即左操作数,this
指向的对象)。
result.real = this->real + other.real;
: 将两个复数的实部相加。this
指针指向调用该运算符的左侧对象(例如,在c1 + c2
中,this
指向c1
)。- 在
main
函数中,c1 + c2
会调用重载的+
运算符。
3.2 重载输出运算符 (<<)
为了方便地输出 Complex
对象,我们可以重载输出运算符 <<
。由于 <<
的左操作数是 std::ostream
对象,我们需要将 <<
重载为非成员函数(通常是友元函数):
#include <iostream>
class Complex {
public:
double real;
double imag;
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
Complex operator+(const Complex& other) const {
Complex result;
result.real = this->real + other.real;
result.imag = this->imag + other.imag;
return result;
}
// 声明为友元函数,以便访问私有成员
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
void print() const {
std::cout << real << " + " << imag << "i" << std::endl;
}
};
// 非成员函数(友元函数)重载 << 运算符
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real;
if (c.imag >= 0) {
os << " + " << c.imag << "i";
} else {
os << " - " << -c.imag << "i";
}
return os; // 返回 ostream 对象,以支持链式输出
}
int main() {
Complex c1(2.0, 3.0);
Complex c2(1.5, -2.5);
Complex c3 = c1+c2;
std::cout << "c1: " << c1 << std::endl; // 使用重载的 << 运算符
std::cout << "c2: " << c2 << std::endl;
std::cout << "c3: " << c3 << std::endl;
return 0;
}
解释:
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
: 在Complex
类中声明operator<<
为友元函数。friend
: 关键字,允许该函数访问Complex
类的私有成员。std::ostream& os
: 第一个参数是输出流对象(例如std::cout
)。const Complex& c
: 第二个参数是要输出的Complex
对象(引用传递)。
- 在
operator<<
函数体中,我们按照复数的格式将实部和虚部输出到os
流中。 return os;
: 返回ostream
对象,这是为了支持链式输出(例如std::cout << c1 << c2;
)。
3.3 重载前置和后置自增运算符 (++)
#include <iostream>
class Counter {
public:
int value;
Counter(int v = 0) : value(v) {}
// 前置自增 (++counter)
Counter& operator++() {
++value;
return *this; // 返回自增后的对象
}
// 后置自增 (counter++)
Counter operator++(int) {
Counter temp = *this; // 创建一个临时对象保存当前值
++value; // 自增
return temp; // 返回自增前的对象
}
};
int main() {
Counter c1(5);
Counter c2 = ++c1; // 前置自增
std::cout << "c1: " << c1.value << ", c2: " << c2.value << std::endl; // c1: 6, c2: 6
Counter c3 = c1++; // 后置自增
std::cout << "c1: " << c1.value << ", c3: " << c3.value << std::endl; // c1: 7, c3: 6
return 0;
}
解释:
- 前置自增 (
++counter
):Counter& operator++()
: 没有参数。++value;
: 先将value
自增。return *this;
: 返回自增后的对象(引用)。
- 后置自增 (
counter++
):Counter operator++(int)
: 有一个int
类型的哑元参数(dummy parameter),用于区分前置和后置自增。这个int
参数在函数体内并不使用。Counter temp = *this;
: 创建一个临时对象temp
来保存自增前的对象。++value;
: 将value
自增。return temp;
: 返回自增前的对象(临时对象)。
3.4 重载下标运算符 ([])
#include <iostream>
#include <vector>
class MyArray {
private:
std::vector<int> data;
public:
MyArray(int size) : data(size) {}
// 重载下标运算符 []
int& operator[](int index) {
if (index < 0 || index >= data.size()) {
std::cerr << "Error: Index out of bounds!" << std::endl;
// 实际应用中,这里应该抛出异常
exit(1); // 简单起见,这里直接退出程序
}
return data[index];
}
// 重载 const 版本的下标运算符 []
const int& operator[](int index) const {
if (index < 0 || index >= data.size()) {
std::cerr << "Error: Index out of bounds!" << std::endl;
exit(1);
}
return data[index];
}
int size() const {
return data.size();
}
};
int main() {
MyArray arr(5);
// 使用重载的 [] 运算符赋值
for (int i = 0; i < arr.size(); ++i) {
arr[i] = i * 2;
}
// 使用重载的 [] 运算符访问
for (int i = 0; i < arr.size(); ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
const MyArray constArr(3); // 创建一个const对象
//constArr[0] = 10; 不能给const对象赋值,编译错误
std::cout << constArr[0] << std::endl; //可以读取const对象的成员
return 0;
}
解释:
int& operator[](int index)
: 重载非常量版本的[]
运算符。int&
: 返回元素的引用,这样可以通过[]
运算符修改元素的值。- 进行了边界检查,如果索引越界,输出错误信息并退出程序(更健壮的做法是抛出异常)。
const int& operator[](int index) const
重载了const
版本的[]
运算符。- 返回值是
const int&
,表示不能通过这个运算符来修改数组元素。 - 函数声明最后的
const
表示这个成员函数不会修改MyArray
对象的状态。 - 在
main
函数中创建const
对象,并验证不能赋值,但是可以读取。
- 返回值是
4. 总结
运算符重载是 C++ 中一项强大的功能,它允许你为自定义类型赋予更直观、更自然的语法。通过合理地重载运算符,可以使代码更易读、更易维护,并提高代码的可重用性。但是,运算符重载也应该谨慎使用,避免滥用导致代码难以理解。