C++ 中,运算符重载是一种特殊的函数,它允许程序员为自定义的数据类型(如类和结构体)重新定义运算符的行为,使得这些运算符能够像处理内置数据类型一样处理自定义类型的数据。下面将从多个方面详细讲解 C++ 里的运算符重载。
基本语法
运算符重载的本质是定义一个函数,其一般形式如下:
返回类型 operator运算符(参数列表) {// 函数体
}
其中,operator
是关键字,后面紧跟要重载的运算符,参数列表根据运算符的不同而有所变化。
运算符重载的类型
1. 成员函数重载
成员函数重载的运算符函数定义在类的内部,它隐含了一个 this
指针,指向调用该运算符的对象。
示例:
#include <iostream>class Complex {
private:double real;double imag;
public:Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}// 重载 + 运算符Complex operator+(const Complex& other) const {return Complex(real + other.real, imag + other.imag);}void display() const {std::cout << real << " + " << imag << "i" << std::endl;}
};int main() {Complex c1(1, 2);Complex c2(3, 4);Complex c3 = c1 + c2;c3.display();return 0;
}
在上述代码中,operator+
是一个成员函数,它接受一个 const Complex&
类型的参数,返回一个新的 Complex
对象,表示两个复数相加的结果。
2. 非成员函数重载
非成员函数重载的运算符函数定义在类的外部,通常用于需要访问类的私有成员的情况,此时需要将该函数声明为类的友元函数。
示例:
#include <iostream>class Complex {
private:double real;double imag;
public:Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}// 声明友元函数friend Complex operator+(const Complex& c1, const Complex& c2);void display() const {std::cout << real << " + " << imag << "i" << std::endl;}
};// 非成员函数重载 + 运算符
Complex operator+(const Complex& c1, const Complex& c2) {return Complex(c1.real + c2.real, c1.imag + c2.imag);
}int main() {Complex c1(1, 2);Complex c2(3, 4);Complex c3 = c1 + c2;c3.display();return 0;
}
在这个例子中,operator+
是一个非成员函数,它接受两个 const Complex&
类型的参数,返回一个新的 Complex
对象。通过将其声明为 Complex
类的友元函数,它可以访问 Complex
类的私有成员。
可重载和不可重载的运算符
- 可重载的运算符:大多数运算符都可以重载,包括算术运算符(
+
、-
、*
、/
等)、关系运算符(==
、!=
、<
、>
等)、逻辑运算符(&&
、||
、!
)、位运算符(&
、|
、^
等)、赋值运算符(=
、+=
、-=
等)、自增自减运算符(++
、--
)等。 - 不可重载的运算符:一些运算符是不能重载的,如
.
(成员访问运算符)、.*
(成员指针访问运算符)、::
(作用域解析运算符)、?:
(条件运算符)和sizeof
运算符。
注意事项
- 不能改变运算符的优先级和结合性:运算符重载只是改变了运算符对自定义类型的操作行为,不能改变运算符原有的优先级和结合性。
- 不能创建新的运算符:只能对已有的运算符进行重载,不能创造新的运算符。
- 保持运算符的原有语义:在重载运算符时,应尽量保持运算符原有的语义,避免造成混淆。例如,重载
+
运算符应该实现加法的功能。
以下再举几个例子来解释说明运算符重载相关注意事项
重载操作符至少有一个类类型参数,不能改变内置类型对象含义。
“不能写
int operator+(int x, int y)
这种试图改变内置int
类型加法含义的重载函数”
1. C++ 的设计原则
C++ 语言设计时,为了保证语言的稳定性和可预测性,规定内置数据类型的运算符行为是固定的,不能被修改。例如,对于两个 int
类型的变量 a
和 b
,a + b
就是执行标准的整数加法运算,这是所有 C++ 程序员都默认和熟悉的行为。如果允许重载改变其含义,那么代码的可读性和一致性就会被破坏,可能会导致很多难以调试和理解的问题。
2. 示例说明
假设允许写 int operator+(int x, int y)
这样的函数来重载 int
类型的 +
运算符,比如:
int operator+(int x, int y) {return x * y; // 这里试图让 + 运算符执行乘法的功能
}
然后在代码中使用:
int main() {int num1 = 3;int num2 = 4;int result = num1 + num2; // 按照正常理解应该是 3 + 4 = 7,但因为重载改变了行为,实际执行的是 3 * 4 = 12return 0;
}
这样就会让原本熟悉 int
类型加法的程序员产生困惑,并且可能在各种依赖于标准加法运算的代码中引入错误,因为大家都默认 int
类型的 +
就是执行加法。
3. 正确的重载应用场景
运算符重载的正确使用场景是针对自定义类型。比如定义一个 Fraction
类表示分数:
class Fraction {
private:int numerator; // 分子int denominator; // 分母
public:Fraction(int num, int den) : numerator(num), denominator(den) {}// 重载 + 运算符,用于分数的加法Fraction operator+(const Fraction& other) {int newNumerator = numerator * other.denominator + other.numerator * denominator;int newDenominator = denominator * other.denominator;return Fraction(newNumerator, newDenominator);}
};
在这个例子中,重载 +
运算符使得 Fraction
类型的对象可以像内置类型一样进行加法操作,而不会影响 int
等内置类型的 +
运算符的正常行为。
重载函数参数个数
一元运算符有一个参数,二元运算符有两个参数。成员函数形式的运算符重载,第一个运算对象由
this
指针隐式传递,参数比运算对象少一个。
class Counter {
private:int count;
public:Counter(int c = 0) : count(c) {}// 成员函数形式重载 ++ 一元运算符Counter operator++() {count++;return *this;}
};
上述代码中,Counter
类以成员函数重载前置 ++
运算符,它只有一个隐含的 this
指针,无需额外参数。
以重载前置自增运算符 ++
为例,假设有一个 Counter
类表示计数器:
class Counter {
private:int value;
public:Counter(int v = 0) : value(v) {}// 重载前置自增运算符,一元运算符,有一个参数(隐含的this指针指向调用对象)Counter operator++() {value++;return *this;}void display() {std::cout << "Value: " << value << std::endl;}
};
在上述代码中,operator++
是重载的前置自增运算符函数。当使用 ++counter
(counter
是 Counter
类的对象)这样的表达式时,operator++
函数被调用,this
指针指向 counter
对象,函数对 value
进行自增操作。从参数角度看,这个函数虽然没有显式的参数,但通过 this
指针隐式地接收了一个运算对象,也就是调用该运算符的对象本身。
二元运算符
二元运算符作用于两个运算对象。在运算符重载函数中,就需要有两个参数来分别接收这两个运算对象。比如重载 +
运算符用于两个自定义类对象相加。
假设有一个 Complex
类表示复数:
class Complex {
private:double real;double imag;
public:Complex(double r = 0, double i = 0) : real(r), imag(i) {}// 非成员函数重载 + 运算符,二元运算符,有两个参数friend Complex operator+(const Complex& c1, const Complex& c2);
};Complex operator+(const Complex& c1, const Complex& c2) {return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
这里的 operator+
函数是重载的 +
运算符函数,它有两个参数 c1
和 c2
,分别代表 +
运算符左右两边的 Complex
类对象。当执行 Complex c3 = c1 + c2;
这样的表达式时,c1
传递给第一个参数,c2
传递给第二个参数,函数执行复数相加的操作并返回结果。
成员函数形式的运算符重载
如果运算符重载函数是成员函数,那么第一个运算对象会默认通过 this
指针隐式传递,所以参数个数比运算对象的数量少一个。
还是以 Complex
类的 +
运算符重载为例,用成员函数形式来实现:
class Complex {
private:double real;double imag;
public:Complex(double r = 0, double i = 0) : real(r), imag(i) {}// 成员函数重载 + 运算符,二元运算符,但参数比运算对象少一个Complex operator+(const Complex& other) {return Complex(real + other.real, imag + other.imag);}
};
在这个成员函数形式的 operator+
中,只有一个参数 other
。当执行 Complex c3 = c1 + c2;
时,c1
是调用该成员函数的对象,通过 this
指针隐式传递,c2
传递给 other
参数。函数内部通过 this
指针访问调用对象(c1
)的成员变量 real
和 imag
,并与 other
(c2
)的成员变量进行相加运算。