自动类型转换
让我们来学习自动类型转换和整型提升。
变量必须与它们参与的表达式类型兼容。从我们迄今为止看到的程序中可以明显看出,D 语言是一种静态类型语言,这意味着类型的兼容性在编译时就被验证。
到目前为止我们编写的所有表达式都具有兼容的类型,否则代码将被编译器拒绝。以下是一个类型不兼容的代码示例:
1import std.stdio; 2 3void main() { 4 char[] slice; 5 writeln(slice + 5); // ← 编译错误 6}
编译器拒绝该代码的原因是加法操作中存在 类型不兼容:char[] 和 int。
类型不兼容并不意味着类型不同;不同的类型实际上可以在表达式中安全使用。例如,一个 int 变量可以安全地用作 double 值:
1import std.stdio; 2 3void main() { 4 double sum = 1.25; 5 int increment = 3; 6 sum += increment; 7 8 write(sum); 9}
尽管 sum 和 increment 类型不同,但上述代码是合法的,因为用一个 int 值去增加一个 double 变量是允许的操作。
自动类型转换
自动类型转换也称为 隐式类型转换。
算术运算中的转换
虽然在上面的例子中 double 和 int 是兼容类型,但加法操作在微处理器级别仍需以特定类型进行计算。如你所知,64 位类型 double 比 32 位类型 int 更宽(或更大)。此外,任何能放入 int 的值也能放入 double。
当编译器遇到涉及类型不匹配的表达式时,会先将表达式的各部分转换为一个共同类型,然后再计算整个表达式。编译器执行的自动转换方向是避免数据丢失。例如,double 可以容纳 int 所有能表示的值,但反过来则不行。因此,+= 操作能够成立,是因为任何 int 值都可以安全地转换为 double。
自动转换生成的值始终是一个匿名(通常是临时)变量,原始值不会改变。例如,在 += 过程中的自动转换并不会改变 increment 的类型;它始终是 int。相反,会构造一个类型为 double 的临时值,其值等于 increment。这种后台发生的转换等价于以下代码:
1import std.stdio; 2 3void main() { 4 double sum = 1.25; 5 int increment = 3; 6 double an_anonymous_double_value = increment; 7 sum += an_anonymous_double_value; 8 9 write(sum); 10}
编译器将 int 值转换为一个临时的 double 值,并使用该值进行运算。
在此例中,临时变量仅在
+=操作期间存在。
函数调用中的转换
自动转换不仅限于算术运算。在其他情况下,类型也会被自动转换。只要转换有效,编译器就会利用类型转换来使值能在表达式中使用。例如,一个 byte 值 可以传递给需要 int 参数的函数:
1import std.stdio; 2 3void func(int number) { 4 write(number); 5 // ... 6} 7 8void main() { 9 byte smallValue = 7; 10 func(smallValue); 11 // 自动类型转换 12}
在上述代码中,首先会构造一个临时的 int 值,然后以该值调用函数。
整数提升
类型位于下表左侧的值在参与算术表达式时,永远不会以其实际类型参与。每种类型都会首先被提升为表中右侧对应的类型。
| 从 | 到 |
|---|---|
bool | int |
byte | int |
ubyte | int |
short | int |
ushort | int |
char | int |
wchar | int |
dchar | uint |
枚举值(enum)也会应用整数提升规则。
整数提升的原因既有历史因素(源自 C 语言的规则),也有微处理器的自然算术类型是 int 的事实。例如,尽管以下两个变量都是 ubyte,但加法操作在执行前,两个值都会分别被提升为 int:
1import std.stdio; 2 3void main() { 4 ubyte a = 1; 5 ubyte b = 2; 6 writeln(typeof(a + b).stringof); // 加法结果不是 ubyte 7}
请注意,变量 a 和 b 的类型并未改变;只是它们的值在加法操作期间被临时提升为 int。