在C++ 中,我们可以使用运算符(operator)来对数据进行各种操作,例如加法、减法、赋值、比较等。运算符是一种特殊的函数,它可以接受一个或多个操作数(operand),并返回一个结果(result)。例如,a + b
就是一个运算符表达式,其中+
是运算符,a
和b
是操作数,a + b
的值就是结果。
C++ 提供了许多内置运算符(built-in operator),它们可以对内置类型(built-in type)的数据进行操作,例如int
、double
、char
等。例如,我们可以使用+
运算符来对两个整数进行加法运算,或者使用==
运算符来判断两个字符是否相等。但是,如果我们想要对自定义类型(user-defined type)的数据进行操作,例如类的对象,那么内置运算符就可能无法满足我们的需求。例如,如果我们定义了一个Complex
类来表示复数,那么我们就不能直接使用+
运算符来对两个复数进行加法运算,因为内置运算符不知道如何处理复数的实部和虚部。这时,我们就需要使用运算符重载(operator overloading)来定义自定义类型的运算符。
运算符重载是一种让已有的运算符对新的数据类型起作用的方法,它可以让我们使用相同的符号来表示不同的操作,从而提高代码的可读性和一致性。运算符重载的本质就是定义一个重载函数(overloaded function),它的函数名就是运算符的符号,它的参数就是运算符的操作数,它的返回值就是运算符的结果。例如,如果我们想要重载+
运算符来实现复数的加法运算,我们就可以定义一个重载函数,它的函数名是operator+
,它的参数是两个Complex
类型的对象,它的返回值是一个Complex
类型的对象,它的函数体是将两个复数的实部和虚部分别相加,然后返回一个新的复数对象。这样,我们就可以使用+
运算符来对两个复数进行加法运算,就像对两个整数进行加法运算一样。例如,我们可以写出如下的代码:
// 定义Complex 类
class Complex {
public:
// 构造函数,用于初始化复数对象
Complex(double real, double imag) {
this->real = real; // this 是一个指向当前对象的指针,用于区分成员变量和参数
this->imag = imag;
}
// 重载+ 运算符,用于实现复数的加法运算
Complex operator+(const Complex& c) { // const 表示参数是一个常量引用,不会被修改,也可以提高效率
// 返回一个新的复数对象,其实部和虚部分别为两个复数的实部和虚部之和
return Complex(this->real + c.real, this->imag + c.imag);
}
// 定义一个友元函数,用于输出复数对象
friend std::ostream& operator<<(std::ostream& out, const Complex& c); // std::ostream 是一个标准输出流类,用于输出数据
private:
// 成员变量,用于存储复数的实部和虚部
double real;
double imag;
};
// 定义友元函数,用于输出复数对象
std::ostream& operator<<(std::ostream& out, const Complex& c) {
// 输出复数的实部和虚部,用i 表示虚数单位
out << c.real << " + " << c.imag << "i";
// 返回输出流对象,以便进行连续输出
return out;
}
// 主函数,用于测试
int main() {
// 创建两个复数对象
Complex c1(1.0, 2.0);
Complex c2(3.0, 4.0);
// 使用+ 运算符对两个复数进行加法运算,并输出结果
std::cout << c1 + c2 << std::endl; // std::cout 是一个标准输出流对象,用于输出数据,std::endl 是一个换行符
// 输出结果为:4 + 6i
return 0;
}
从上面的代码可以看出,运算符重载可以让我们使用自然和直观的方式来对自定义类型的数据进行操作,而不需要使用特殊的函数名或者语法。这样,我们就可以提高代码的可读性和一致性,也可以增加代码的复用性和扩展性。
运算符重载的规则和注意事项
运算符重载是一种非常强大和灵活的特性,但也有一些规则和注意事项需要遵守和注意,否则可能会导致一些错误或者混淆。下面我们就来介绍一些运算符重载的规则和注意事项:
- 运算符重载不改变运算符的优先级和结合性。例如,
+
运算符的优先级高于=
运算符,且具有左结合性,即a = b + c
等价于a = (b + c)
,而不是(a = b) + c
。这一点在重载运算符时不能改变,否则会导致语法错误或者逻辑错误。 - 运算符重载不改变运算符的操作数的个数。例如,
+
运算符是一个二元运算符,它需要两个操作数,而!
运算符是一个一元运算符,它只需要一个操作数。这一点在重载运算符时不能改变,否则会导致编译错误或者运行错误。 - 运算符重载不改变运算符的操作数的类型。例如,
+
运算符可以对两个整数或者两个浮点数进行加法运算,但不能对一个整数和一个字符串进行加法运算。这一点在重载运算符时不能改变,否则会导致类型错误或者转换错误。 - 运算符重载应该保持运算符的原有的含义和用法习惯。例如,
+
运算符通常表示两个操作数的相加,而不是相减或者相乘。这一点在重载运算符时应该遵守,否则会导致代码的可读性和一致性降低,也可能会引起误解或者混淆。
- 运算符重载应该保持运算符的原有的特性和性质。例如,
+
运算符具有交换律和结合律,即a + b = b + a
和(a + b) + c = a + (b + c)
。这一点在重载运算符时应该尽量保持,否则会导致代码的逻辑和效率降低,也可能会引起错误或者歧义。 - 运算符重载应该保持运算符的原有的返回值类型。例如,
+
运算符通常返回一个与操作数相同类型的结果,而不是一个不同类型的结果。这一点在重载运算符时应该遵守,否则会导致类型错误或者转换错误。 - 运算符重载应该避免改变运算符的副作用。例如,
++
运算符除了返回操作数加一的结果外,还会改变操作数本身的值,这就是一个副作用。这一点在重载运算符时应该避免,否则会导致代码的可读性和一致性降低,也可能会引起错误或者混淆。 - 运算符重载应该避免重载一些具有特殊意义的运算符。例如,
.
运算符用于访问对象的成员,::
运算符用于访问命名空间或者类的成员,?:
运算符用于实现条件表达式,sizeof
运算符用于获取数据的大小,typeid
运算符用于获取数据的类型等。这些运算符都有特殊的语法和用法,如果重载它们,可能会导致编译错误或者运行错误,也可能会引起误解或者混淆。 - 运算符重载应该避免重载一些不可重载的运算符。例如,
.
运算符、::
运算符、?:
运算符、sizeof
运算符、typeid
运算符等都是不可重载的运算符,因为它们是C++ 语言的一部分,不能被修改或者替换。如果尝试重载它们,会导致编译错误或者运行错误。
运算符重载的方法和形式
运算符重载的方法和形式有两种,一种是类内重载,一种是类外重载。类内重载是指在类的内部定义重载函数,作为类的成员函数,类外重载是指在类的外部定义重载函数,作为类的友元函数或者普通函数。下面我们就来介绍一下这两种方法和形式的区别和用法:
- 类内重载的特点是,重载函数的第一个参数隐含为当前对象的引用,即
this
指针,因此只需要显式指定第二个参数,如果是一元运算符,则不需要指定任何参数。类内重载的优点是,可以直接访问类的私有成员,也可以实现运算符的链式调用,例如a += b += c
。类内重载的缺点是,不能改变运算符的左操作数的类型,也不能实现对称的运算符重载,例如a + b
和b + a
。类内重载的形式如下:
// 类内重载的形式
class A {
public:
// 重载二元运算符
A operator+(const A& a) {
// 返回一个新的对象,其值为当前对象和参数对象的运算结果
return A(this->value + a.value);
}
// 重载一元运算符
A operator-() {
// 返回一个新的对象,其值为当前对象的相反数
return A(-this->value);
}
private:
// 私有成员变量,用于存储对象的值
int value;
};
- 类外重载的特点是,重载函数的第一个参数和第二个参数都需要显式指定,如果是一元运算符,则只需要指定第一个参数。类外重载的优点是,可以改变运算符的左操作数的类型,也可以实现对称的运算符重载,例如
a + b
和b + a
。类外重载的缺点是,不能直接访问类的私有成员,除非将重载函数声明为类的友元函数,但这样会破坏类的封装性,也不能实现运算符的链式调用,例如a += b += c
。类外重载的形式如下:
// 类外重载的形式
class A {
public:
// 构造函数,用于初始化对象的值
A(int value) {
this->value = value;
}
// 声明友元函数,用于输出对象的值
friend std::ostream& operator<<(std::ostream& out, const A& a);
private:
// 私有成员变量,用于存储对象的值
int value;
};
// 定义友元函数,用于输出对象的值
std::ostream& operator<<(std::ostream& out, const A& a) {
// 输出对象的值
out << a.value;
// 返回输出流对象,以便进行连续输出
return out;
}
// 定义普通函数,用于重载二元运算符
A operator+(const A& a, const A& b) {
// 返回一个新的对象,其值为两个参数对象的运算结果
return A(a.value + b.value);
}
// 定义普通函数,用于重载一元运算符
A operator-(const A& a) {
// 返回一个新的对象,其值为参数对象的相反数
return A(-a.value);
}