C++运算符重载与类型转换全解析
立即解锁
发布时间: 2025-08-19 01:36:31 阅读量: 2 订阅数: 6 


C++面向对象编程入门与实践
# C++ 运算符重载与类型转换全解析
## 1. 转换时机与方式
在进行类型转换时,我们常常会面临选择:是使用目标类的单参数构造函数,还是源类的转换运算符呢?通常情况下,这两种方式都可行,但有时情况会限制我们的选择。
### 1.1 选择原则
- **使用单参数构造函数的情况**:当你使用一个购买的类库,且无法访问其源代码时,如果将该类库中的对象作为转换的源对象,你只能访问目标类,此时就需要使用目标类的单参数构造函数。
- **使用转换运算符的情况**:若类库对象是转换的目标对象,那么你必须使用源类的转换运算符。
## 2. UML 类图
UML(统一建模语言)类图为我们提供了一种全新的视角来审视面向对象程序,它能帮助我们更好地理解程序中类与类之间的关系。
### 2.1 类的表示
在 UML 类图中,类用矩形表示。例如,在 TIMES1 程序中有两个类:time12 和 time24,它们在 UML 类图中就是两个矩形,如下所示:
```mermaid
classDiagram
class time12
class time24
```
每个类矩形被水平线分割成多个部分,类名位于顶部部分。此外,还可以包含成员数据(在 UML 中称为属性)和成员函数(称为操作)的部分。
### 2.2 类之间的关联
类与类之间可能存在各种关系,在 TIMES1 程序中,类之间的关系是关联。关联用连接两个类矩形的线表示。
关联的概念源于现实世界中实体之间的明显关系,例如司机与汽车、书籍与图书馆、赛马与赛马场。如果这些实体在程序中被表示为类,那么它们之间就存在关联。
在 TIMES2.CPP 程序中,time12 类与 time24 类相关联,因为我们可以将一个类的对象转换为另一个类的对象。
类关联实际上意味着类的对象之间存在某种关系。通常,如果一个类的对象调用另一个类对象的成员函数(操作),或者一个类的属性是另一个类的对象,那么这两个类就存在关联。
在 TIMES1 程序中,time12 类的对象 t12 调用了 time24 类对象 t24 中的转换例程 operator time12(),这一调用在 main() 函数中的语句如下:
```cpp
time12 t12 = t24; // convert time24 to time12
```
这种调用在 UML 类图中用两个类之间的关联线表示。
### 2.3 导航性
我们可以在关联线上添加开放箭头来表示关联的方向或导航性。由于 time12 调用 time24,箭头从 time12 指向 time24,这被称为单向关联。如果两个类相互调用对方的操作,那么关联线的两端都会有箭头,这被称为双向关联。导航性箭头在 UML 中是可选的。
## 3. 运算符重载与转换的陷阱
运算符重载和类型转换为我们提供了创建一种全新语言的机会,但同时也可能带来一些问题。当 a、b 和 c 是用户定义类的对象,并且 + 运算符被重载时,语句 `a = b + c;` 的含义可能与 a、b 和 c 是基本数据类型变量时完全不同。
### 3.1 使用相似的含义
使用重载运算符执行的操作应尽可能与对基本数据类型执行的操作相似。例如,虽然可以将 + 符号重载为执行减法操作,但这显然会使代码更难理解。
重载运算符时,需要确保对某个类的对象执行特定操作是有意义的。例如,在本章中,我们展示了如何为 English Distance 类重载 + 运算符,两个距离相加显然是有意义的;我们也为 String 类重载了 + 运算符,将两个字符串相加表示将一个字符串放在另一个字符串后面形成第三个字符串,这也有直观的解释。但对于许多类来说,谈论“添加”它们的对象可能并不合理,例如,你可能不想将包含个人数据的 employee 类的两个对象相加。
### 3.2 使用相似的语法
使用重载运算符的方式应与使用基本类型的方式相同。例如,如果 alpha 和 beta 是基本类型,语句 `alpha += beta;` 会将 alpha 设置为 alpha 和 beta 的和。任何重载版本的该运算符都应执行类似的操作,可能与 `alpha = alpha + beta;`(其中 + 被重载)的操作相同。
如果重载了一个算术运算符,为了保持一致性,可能需要重载所有算术运算符,以避免混淆。同时,运算符的一些语法特性是不能改变的,例如,不能将二元运算符重载为一元运算符,反之亦然。
### 3.3 克制使用
要记住,如果你重载了 + 运算符,不熟悉你代码的人需要进行大量研究才能理解语句 `a = b + c;` 的真正含义。如果重载运算符的数量过多,并且运算符的使用方式不直观,那么使用它们的初衷就会丧失,代码阅读会变得更加困难。因此,应谨慎使用重载运算符,仅在使用方式明显时使用。当有疑问时,使用函数而不是重载运算符,因为函数名可以明确表达其用途。例如,如果你编写一个函数来获取字符串的左侧部分,最好将其命名为 getleft(),而不是尝试重载某个运算符(如 &&)来完成相同的任务。
### 3.4 避免歧义
避免使用单参数构造函数和转换运算符来执行相同的转换(例如,将 time24 转换为 time12)。因为编译器不知道该使用哪种转换方式,会发出错误信号。所以,应避免以多种方式进行相同的转换。
### 3.5 并非所有运算符都能重载
以下运算符不能被重载:成员访问或点运算符 (.)、作用域解析运算符 (::)、条件运算符 (?:)。此外,尚未介绍的指向成员的运算符 (->) 也不能被重载。同时,不能创建新的运算符(如 *&)并尝试重载它们,只能重载现有的运算符。
## 4. 关键字 explicit 和 mutable
### 4.1 explicit 关键字:防止隐式转换
在某些情况下,你可能希望允许特定的转换,但不希望发生其他不必要的转换。对于转换运算符,防止转换很简单,只需不定义该运算符即可。但对于构造函数,情况就不同了。
标准 C++ 引入了 explicit 关键字来解决这个问题。该关键字放在单参数构造函数的声明之前。以下是一个示例程序:
```cpp
// explicit.cpp
#include <iostream>
using namespace std;
class Distance {
private:
const float MTF; // meters to feet
int feet;
float inches;
public:
// no-args constructor
Distance() : feet(0), inches(0.0), MTF(3.280833F) {}
// EXPLICIT one-arg constructor
explicit Distance(float meters) : MTF(3.280833F) {
float fltfeet = MTF * meters;
feet = int(fltfeet);
inches = 12 * (fltfeet - feet);
}
void showdist() {
cout << feet << "\'-" << inches << '\"';
}
};
int main() {
void fancyDist(Distance); // declaration
Distance dist1(2.35F); // uses 1-arg constructor to convert meters to Distance
// Distance dist1 = 2.35F; // ERROR if ctor is explicit
cout << "\ndist1 = ";
dist1.showdist();
float mtrs = 3.0F;
cout << "\ndist1 ";
// fancyDist(mtrs); // ERROR if ctor is explicit
return 0;
}
void fancyDist(Distance d) {
cout << "(in
```
0
0
复制全文
相关推荐









