大话C++语言:运算符重载
1 基本概念
C++运算符重载(operator overloading)是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。通过运算符重载,可以自定义运算符的行为,使其在作用于用户自定义的数据类型时具有特定的意义。
运算符重载只是一种语法上的方便,它并不是创建新的运算符,而是对已有的运算符进行重新定义。重载的运算符仍然保持原有的优先级和结合性,不能改变这些属性。
本质上,运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。运算符重载的语法是使用operator关键字,后跟要重载的运算符符号。重载的运算符函数可以作为类的成员函数或非成员函数进行定义。作为成员函数时,第一个参数隐式地是*this指针,表示当前对象。作为非成员函数时,需要显式地指定所有参数。
运算符重载的目的是为了满足自定义类型的运算需求,使得这些类型能够以更直观、易读和符合语法规则的方式进行运算。通过合理地重载运算符,可以提高代码的可读性和可维护性,同时增强代码的表达能力。然而,在使用运算符重载时需要注意避免过度使用,保持逻辑上的合理性和一致性,以避免引入歧义或性能问题。
2 可重载的运算符
几乎 C++ 中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是不能使用 C++ 中当前没有意义的运算符(例如用**求幂)不能改变运算符优先级,不能改变运算符的参数个数。这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意。 C++中可以重载的运算符类型包括:
运算符类型 | 例子 | 描述 |
算术运算符 | +, -, *, /, %, ++, -- | 用于执行算术运算的运算符。 |
关系运算符 | ==, !=, <, >, <=, >= | 用于比较两个值的关系的运算符。 |
逻辑运算符 | &&, ||,! | 用于逻辑运算 |
位运算符 | &、|、^、~、<<、>>。 | 用于位运算 |
赋值运算符 | =, +=, -=, *=, /=, %=, &=, |=、^=、<<=、>>= | 用于赋值运算 |
成员访问运算符 | ->, ->* | 用于访问类的成员或成员指针的运算符。 |
下标运算符 | [] | 用于访问数组或容器元素的运算符。 |
函数调用运算符 | () | 用于调用函数或方法的运算符。 |
类型转换运算符 | static_cast, dynamic_cast, reinterpret_cast, const_cast | 用于执行类型转换的运算符。这些不是直接重载的运算符,但可以在类 |
然而,并不是所有的运算符都可以被重载。例如,.(成员访问)、.*(成员指针访问)、::(域解析)、sizeof(长度)、?:(条件)等运算符是不能被重载的。
在重载运算符时,还需要注意以下几点:
- 重载的运算符应保持原有的语义和优先级,不能改变这些属性。
- 重载的运算符函数可以作为类的成员函数或非成员函数进行定义。作为成员函数时,第一个参数隐式地是*this指针;作为非成员函数时,需要显式地指定所有参数。
- 重载运算符的目的是为了增强代码的可读性和表达能力,而不是为了创建新的运算符。因此,在使用运算符重载时应谨慎,避免过度使用或滥用。
3 自增/自减(++/--)运算符重载
在C++中,可以通过重载运算符来定义自定义类型上的++和--行为。重载需要区分前置和后置的自增(++)和自减(--)运算符。其中,前置版本(如++c)先执行自增/自减操作,然后返回结果。后置版本(如c++)先返回当前值,然后再执行自增/自减操作。
3.1 前置自增/自减
前置版本(如++c)先执行自增/自减操作,然后返回结果。前置版本的语法格式如下:
class ClassName
{
// ... 类的其他成员 ...
public:
// 前置自增运算符重载
ClassName& operator++()
{
// 自增逻辑
return *this;
}
// 前置自减运算符重载
ClassName& operator--()
{
// 自减逻辑
return *this;
}
};
注意,前置版本返回的是引用对象。
3.2 后置自增/自减
后置版本(如c++)先返回当前值,然后再执行自增/自减操作。后置版本的语法格式如下:
class ClassName
{
// ... 类的其他成员 ...
public:
// 后置自增运算符重载
ClassName operator++(int)
{
ClassName temp = *this; // 保存当前对象的状态
// 自增逻辑
return temp; // 返回自增前的对象状态
}
// 后置自减运算符重载
ClassName operator--(int)
{
ClassName temp = *this; // 保存当前对象的状态
// 自减逻辑
return temp; // 返回自减前的对象状态
}
};
注意,后置版本返回的是对象副本。
3.3 综合案例
以下是一个综合案例,在本案例中,我们创建一个表示复数的类Complex,并为它重载前置和后置的自增自减运算符。
#include <iostream>
class Complex
{
public:
// 构造函数
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i)
{
}
// 获取实部
double GetReal() const
{
return real;
}
// 获取虚部
double GetImag() const
{
return imag;
}
// 设置实部
void SetReal(double r)
{
real = r;
}
// 设置虚部
void SetImag(double i)
{
imag = i;
}
// 输出复数
void Display() const
{
std::cout << "(" << real << ", " << imag << ")" << std::endl;
}
// 前置自增运算符重载
Complex& operator++()
{
++real; // 实部自增
++imag; // 虚部自增
return *this;
}
// 后置自增运算符重载
Complex operator++(int)
{
Complex temp = *this; // 保存当前对象状态
++real; // 实部自增
++imag; // 虚部自增
return temp; // 返回自增前的对象状态
}
// 前置自减运算符重载
Complex& operator--()
{
--real; // 实部自减
--imag; // 虚部自减
return *this;
}
// 后置自减运算符重载
Complex operator--(int)
{
Complex temp = *this; // 保存当前对象状态
--real; // 实部自减
--imag; // 虚部自减
return temp; // 返回自减前的对象状态
}
private:
double real; // 实部
double imag; // 虚部
};
int main()
{
Complex complexNum(1, 2); // 创建一个复数对象 c1,实部为1,虚部为2
// 使用前置自增运算符
++complexNum;
std::cout << "前置自增后,该该复数:";
complexNum.Display(); // 输出: (2, 3)
// 使用后置自增运算符
complexNum++;
std::cout << "后置自增后,该该复数:";
complexNum.Display(); // 输出: (3, 4)
// 使用前置自减运算符
--complexNum;
std::cout << "前置自减后,该该复数:";
complexNum.Display(); // 输出: (2, 3)
// 使用后置自减运算符
complexNum--;
std::cout << "后置自减后,该该复数:";
complexNum.Display(); // 输出: (1, 2)
return 0;
}
3.4 自增/自减注意事项
在C++中,重载自增(++)和自减(--)运算符时,需要注意以下事项:
- 返回值类型:
- 前置版本(如 ++operator)应该返回对象的引用(ClassName&),以便支持链式操作。
- 后置版本(如 operator++)应该返回对象的一个副本(ClassName),因为后置版本需要在修改对象之前返回其原始值。
- 参数:
- 后置版本通常带有一个未使用的整型参数(通常命名为dummy),这主要是为了与前置版本区分开。这个参数在函数调用时被忽略。
- 不改变运算符优先级:
- 重载运算符不会改变运算符的优先级。这意味着重载后的运算符应该遵循原有的优先级规则。
- 保持原有语义:
- 重载后的运算符应该尽量保持原有语义。例如,重载++运算符应该模拟原有加一的语义。
4 等于和不等于(==/!=)运算符重载
在C++中,==和!=运算符也可以被重载,以便为自定义类型提供相等性比较的逻辑,其语法格式如下:
class ClassName
{
// ... 类的其他成员 ...
public:
// 重载 == 运算符作为成员函数
bool operator==(const ClassName& other) const
{
// 实现比较逻辑并返回结果
}
// 重载 != 运算符作为成员函数(可选,但通常推荐)
bool operator!=(const ClassName& other) const
{
// 通常返回 !(this == other) 的结果
return !(*this == other);
}
// ... 类的其他成员 ...
};
注意,当重载这些运算符时,参数应该是常量引用(const ClassName&),这样可以避免不必要的对象复制,并且允许比较临时对象和常量对象。同时,这些函数通常也被声明为const,因为它们不修改调用它们的对象的状态。
#include <iostream>
class Complex
{
public:
// 构造函数
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i)
{
}
// 获取实部
double GetReal() const
{
return real;
}
// 获取虚部
double GetImag() const
{
return imag;
}
// 设置实部
void SetReal(double r)
{
real = r;
}
// 设置虚部
void SetImag(double i)
{
imag = i;
}
// 输出复数
void Display() const
{
std::cout << "(" << real << ", " << imag << ")" << std::endl;
}
// 重载 == 运算符
bool operator==(const Complex& other) const
{
return (real == other.real) && (imag == other.imag);
}
// 重载 != 运算符
bool operator!=(const Complex& other) const
{
return !(*this == other);
}
private:
double real; // 实部
double imag; // 虚部
};
int main()
{
Complex c1(1, 2); // 创建一个复数对象 c1,实部为1,虚部为2
Complex c2(1, 2); // 创建另一个复数对象 c2,与 c1 相等
Complex c3(3, 4); // 创建另一个复数对象 c3,与 c1 不相等
// 使用重载的 == 运算符
if (c1 == c2)
{
std::cout << "c1和c2相同" << std::endl;
} else {
std::cout << "c1和c2不相同" << std::endl;
}
// 使用重载的 != 运算符
if (c1 != c3)
{
std::cout << "c1和c3不相同" << std::endl;
}
else
{
std::cout << "c1和c3相同" << std::endl;
}
return 0;
}
注意,
- 返回值类型:==和!=运算符应该返回布尔值(bool),表示比较的结果(相等或不相等)。
- 对称性:==运算符应该满足对称性,即a == b和b == a应该产生相同的结果。!=运算符则是==的否定,即a != b当且仅当a == b为false时返回true。
- 传递性:如果a == b和b == c都为true,那么a == c也应该为true。同样地,如果a != b和b != c都为true,那么a != c也应该为true。
- 一致性:如果重载了==运算符,通常也应该重载!=运算符,以保持一致性。
5 函数调用符号()重载
在C++中,operator() 被称为函数调用运算符或调用操作符。当它被重载时,允许类的对象像函数那样被调用。这通常用于创建可调用对象,这些对象可以模拟函数的行为。重载 operator() 可以使对象看起来像是可调用的函数,并允许你以更直观和面向对象的方式封装某些操作。重载operator()的语法格式如下:
class ClassName
{
public:
// 重载 operator()
ReturnType operator()(ParameterList)
{
// 函数体,实现所需功能
// ReturnType 是你希望 operator() 返回的类型
// ParameterList 是 operator() 所接受的参数列表
}
};
其中,
- ClassName 是类名
- ReturnType 是 operator() 返回的类型
- ParameterList 是 operator() 所接受的参数列表
#include <iostream>
class Complex
{
private:
double real; // 实部
double imag; // 虚部
public:
// 构造函数
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i)
{
}
// 获取实部
double GetReal() const
{
return real;
}
// 获取虚部
double GetImag() const
{
return imag;
}
// 设置实部
void SetReal(double r)
{
real = r;
}
// 设置虚部
void SetImag(double i)
{
imag = i;
}
// 输出复数
void Display() const
{
std::cout << "(" << real << ", " << imag << ")" << std::endl;
}
// 重载 operator(),执行复数加法
Complex operator()(const Complex& other) const
{
return Complex(real + other.real, imag + other.imag);
}
};
int main()
{
// 创建两个 Complex 对象
Complex c1(1, 2); // (1, 2)
Complex c2(3, 4); // (3, 4)
// 使用重载的 operator() 进行复数加法
// 调用 c1 的 operator(),参数为 c2
Complex result = c1(c2);
// 输出结果
result.Display();
return 0;
}
注意,虽然 operator() 被重载了以模拟函数调用的外观,但它仍然是一个类的成员函数,并且遵循成员函数的调用规则。
---E N D---
喜欢的记得关注哦!
您的支持是我们前进的动力!