12.3 类型转换
1. 对象的类型转换可以用构造函数和转换函数来指定。这些转换称为用户自定义的转换,用作隐式类型转换、初始化和显式类型转换。
2. 只有在无二义性的情况下,用户自定义的转换才有效。转换遵循权限控制规则。权限检查在二义性解析之后发生。
3. 【 13.3 讨论了函数中如何使用转换。】
4. 对于一个简单的值,最多只发生一次用户自定义的类型转换。【例:
class X {
// ...
public:
operator int();
};
class Y {
// ...
public:
operator X();
};
Y a;
int b = a; // 错误,用户自定义的转换只能发生一次
int c = X(a); // OK: a.operator X().operator int()
】
5. 用户自定义的转换只在无二义性的情况下才可以隐式使用 。派生类的类型转换函数不会隐藏掉基类的类型转换函数,除非它们转换成相同的类型。函数重载解析自动选择最匹配的函数来执行转换。【例:
class X {
public:
// ...
operator int();
};
class Y : public X {
public:
// ...
operator char();
};
void f(Y& a)
{
if (a) { // 非法: X::operator int() 还是 Y::operator char() ? 【按:构造函数无名,所以不遵循名字查找规则】
// ...
}
}
】
12.3.1 通过构造函数来转换类型
1. 声明时没有 explicit 描述符、且调用时可以只传一个参数的构造函数指定了一个从第一个参数类型转换为该类的类型的转换函数。这种构造函数称为类型转换构造函数。【例:
class X {
// ...
public:
X(int);
X(const char*, int =0);
};
void f(X arg)
{
X a = 1; // a <-- X(1)
X b = "Jessie"; // b = X("Jessie",0)
a = 2; // a = X(2)
f(3); // f(X(3))
}
】
2. 用 explicit 构造函数创建对象的方式与非 explicit 构造函数类似,但是必须使用直接初始化语法或显式使用类型转换。默认构造函数可能是个 explicit 构造函数,这时它是用来进行默认初始化或赋值初始化。【例:
class Z {
public:
explicit Z();
explicit Z(int);
// ...
};
Z a; // OK: 执行默认初始化
Z a1 = 1; // error: 没有对应的隐式转换函数 【按:从这里可以看出两种初始化方式的区别! 】
Z a3 = Z(1); // OK: 使用了直接初始化的语法
Z a2(1); // OK: 使用了直接初始化的语法
Z* p = new Z(1); // OK: 使用了直接初始化的语法
Z a4 = (Z)1; // OK: 使用了显式类型转换语法
Z a5 = static_cast<Z>(1); // OK: 使用了显式类型转换语法
】
3. 非 explicit 的拷贝构造函数是类型转换构造函数。隐式声明的拷贝构造函数不是 explicit 的,因此可以在隐式类型转换时调用。
12.3.2 转换函数
1. X 的成员有如下形式的名字:
conversion-function-id:
operator conversion-type-id
conversion-type-id:
type-specifier-seq conversion-declarator opt
conversion-declarator:
ptr-operator conversion-declarator opt
那它就指定了一个从 X 到 conversion-type-id 类型的类型转换,这种成员函数叫做类型转换成员函数。 type-specifier-seq 不能为类、枚举、或 typedef-name 。对于这种函数,既不能指定参数类型,又不能指定返回值;它的类型是:“无参数、返回 conversion-type-id 的函数”类型。类型转换函数永远不能用来转成相同的类型、不能转成基类、不能转成 void ,不论是否加了 cv 修饰符 【按:const 、volatile 】
2. 【例:
class X {
// ...
public:
operator int();
};
void f(X a)
{
int i = int(a);
i = (int)a;
i = a;
}
在上述 3 中情况中, a 被 X::operator int() 转成 int 类型
】
3. 用户自定义的转换函数可以在赋值和初始化中自由使用。【例:
void g(X a, X b)
{
int i = (a) ? 1+a : 0;
int j = (a&&b) ? a+b : i;
if (a) { // ...
}
}
】
4. conversion-type-id 不能是函数或数组类型。 conversion-function-id 中的 conversion-type-id 是尽可能长的 conversion-declarator s 串。【注:这样就防止了声明符 * 与后面的表达式之间的二义性 【例:
&ac.operator int*i; // 语法错误,被处理为: &(ac.operator int*) i ,而不是 &(ac.operator int) * i
* 在这里了被当做指针声明符,而不是乘号
】
】
转换函数被自动继承给派生类。
转换函数可以是虚函数。