运算符重载: 类型转换运算符
2024年1月4日 2024年1月4日
用户定义的类型转换 user-defined conversions
:
- 类的转换构造函数
- 重载类型转换运算符
转换构造函数和类型转换运算符共同定义了类类型转换 class-type conversions
必须定义为成员函数, 将本类型转换为目的类型
类型转换运算符
conversion operator
形如
1operator T() const;
将本类型转换为类型T, T不能为void
返回目的类型的值
和函数返回类型一样, T也不能是数组或函数, 可以是函数指针, 数组指针和引用
不能声明返回类型, 形参列表必须为空 : 源类型为自身, 目的类型在函数名中给出
类型转换运算符通常不应该改变待转换对象的内容, 一般被定义为const
示例
1class SmallInt 2{ 3public: 4 SmallInt(int i = 0) : val(i) 5 { 6 if (i < 0 || i > 255) 7 throw out_of_range("Bad SmallInt value"); 8 } 9 operator int() const { return val; } 10private: 11 size_t val; 12}; 13 14 15SmallInt si; 16si = 4; // 使用转换构造函数将4转换SmallInt对象, 赋值给si 17si + 3; // si被转换成int
编译器一次只能执行一个用户定义的类型转换
编译器一次只能执行一个用户定义的类型转换, 但隐式的类类型转换可以在内置转换之前或之后, 与其一起使用
1SmallInt si = 3.14; // 只使用内置类型转换, 将double转换为int 2si + 3.14; // si转换成int(用户定义类型转换),int转换成double(内置类型转换)
SmallInt的转换构造函数接受所有算术类型
1SmallInt(int i = 0);
实际编写代码时, 较少为类定义类型转换运算符
一个常见的类型转换运算符需求: 目的类型为bool
显式的类型转换运算符
explicit conversion operator
-
可通过static_cast使用
1class SmallInt 2{ 3public: 4 SmallInt(int i = 0) : val(i) 5 { 6 if (i < 0 || i > 255) 7 throw out_of_range("Bad SmallInt value"); 8 } 9 explicit operator int() const { return val; } 10private: 11 size_t val; 12}; 13 14SmallInt si = 3; // 转换构造函数, 非显式 15si + 3; // 错误: 类型转换运算符要求显式 16static_cast<int>(si) + 3; // 正确
-
如果表达式(变量)被用作条件,编译器会将显式的类型转换自动应用于它
condition if、while和do while的条件部分 for的条件表达式 逻辑非 !
,逻辑或,逻辑与的运算对象&&
条件运算符 ? :
将目的类型为bool的类型转换运算符声明为显式
-
移位操作
如果cin向bool的类型转换非隐式, 编译器会将cin转换为bool, 并提升为int, 然后左移42位1int i = 42; 2cin << i;
-
IO类型提供向bool的显式类型转换
- 输入运算符返回左操作数, 在while的条件部分发生向bool的显式类型转换
- 如果cin的条件状态为good, 函数返回true; false
1while (cin >> value) 2{ 3 // 处理 4}
- 输入运算符返回左操作数, 在while的条件部分发生向bool的显式类型转换
避免有二义性的类型转换
确保类类型和目的类型之间只存在唯一一种转换方式
第一种情形: 多重转换路径
一个类定义了转换构造函数, 而另一个类重载了类型转换运算符
- 类A: 定义了B到A转换构造函数
1struct B; 2struct A 3{ 4 A = default; 5 A(const B &); 6};
- 类B: 定义了B到A的类型转换
1struct A; 2struct B 3{ 4 operator A() const; 5};
- 发生二义性
1A f(const A &); // 函数声明 2B b; 3A a = f(b); // 错误:此时B转换为A类型时,有两种路径 4 5A a1 = f(b.opeartor A()); // 正确: 指定转换途径, 调用结果返回一个A类型的对象 6A a2 = f(A(b)); // 正确: 指定转换途径
不要为类定义相同的类型转换
第二种情形: 多个向内置类型转换的类型转换函数 + 内置类型转换搭桥
- 类A
1struct A 2{ 3 A(int i = 0); 4 A(double); 5 operator int() const; 6 operator double() const; 7};
- 发生二义性
1void f2(long double); 2A a; 3f2(a); // 错误,可以是:A -> int -> long double ; A -> double -> long double 4 5long lg; 6A a2(lg); // 错误,可以是:long -> int ; long -> double 7 8short s = 42; 9A a3(s); // 匹配 A(int),因为short -> int 优于 short -> double
不要在类中定义两个及以上转换源或转换目标是算术类型的转换
第三种情形: 重载函数与转换构造函数
如果两个或多个类型转换都提供了同一种可行匹配, 这些类型转换一样好
1struct C 2{ 3 C(int); 4}; 5 6struct D 7{ 8 D(int); 9}; 10void manip(const C&); 11void manip(const D&); 12 13manip(10); // 错误,可以是: int -> C ; int -> D 14 15manip(C(10)); // 正确
如果在调用重载函数时, 需要使用构造函数或强制类型转换来改变实参的类型, 则这通常意味着程序的设计存在不足
第四种情形: 重载函数与用户定义的类型转换
调用重载函数时, 如果两个或多个用户定义的类型转换都提供了可行匹配, 我们认为这些类型转换一样好; 即使同时存在内置类型转换, 会对其进行忽略
1struct C 2{ 3 C(int); 4}; 5struct E 6{ 7 E(double); 8}; 9void manip2(const C&); 10void manip2(const E&); 11 12manip2(10); // 错误,可以是:先内置类型转换 int -> double,再 double -> E ; int -> C
即使int到C能精准匹配, 另一个需要int到double到E, 多一次内置类型转换, 编译器仍将该调用标示为错误