3.6 类型转换
隐式类型转换造成的问题远大于它带来的好处
Ruby | Javascript | Go | Lua | |
---|---|---|---|---|
隐式类型转换 | 不存在 (但的确有些自动转换) |
存在 | 不存在 | 存在 |
条件/逻辑表达式值类型 | 任意类型 | 任意类型 | 布尔值 | 任意类型 |
如何转换为bool | !!var 自行判断 x == 0 |
!!x 自行判断 x == 0 |
自行判断 x != 0 |
not not var 自行判断 x == 0 |
需要注意, Ruby中!!x
结果为false的情况只有x等于nil或者false, 而javascript中有6种情况(null, undefined, "", 0, NaN, false
), 而Go语言中只有准确的bool类型才能使用!
操作, 其他类型数据无法通过!
进行bool转换; Lua 类似Ruby, false和nil在条件判断中会被认为是逻辑假.
1. Ruby
显式的类型转换
Ruby 2.4 中以to_
开头的方法名有30多个, 大都在多个类中有不同的实现.
- 转换为字符串
#to_s
: 任何ruby对象都有to_s
方法, 因为该方法定义在Kernel#to_s
, 很多类中都复写了该方法, 在Ruby 2.4中有98个实现, 包括 Integer, Array, Float, Hash等等 - 转换为整数
#to_i
: 在Ruby 2.4中有18个类实现了此方法, 包括 String, Time, Float, ARGFF等 - 转换为浮点数
#to_f
- 转换为浮点数
#to_a
- 转为为Hash
#to_h
- 转换为Symbol:
String#to_sym
Kernel还定义了四个和类名相同的转换函数: Kernel#Array
Kernel#Float
Kernel#Integer
Kernel#String
, 不过我觉得这样做(和类名相同)会让人感到迷惑, 至少我曾经遇到过.
自动类型转换
严格的讲, Ruby作为强类型语言, 偏向于不容忍隐式类型转换, 因此你会发现以下错误:
"fox" + 3 #TypeError: no implicit conversion of Fixnum into String
不过Ruby的理念是让使用者happy, 在很多地方使用了自动调用了转换方法, 看起来很像隐式类型转换, 这些自动转换使得用户不必考虑这些繁琐细节, 通常来说这些自动转换都是安全的, 不过知悉这些转换的存在, 将有助于我们写出更健壮的代码.
- 字符串内插会自动调用
to_s
方法:"now is #{Time.now}"
String#+
会自动调用右值的to_str
class A def to_str self.object_id.to_s end end s = 'test' a = A.new puts s + a #test2156356600
类似的自动调用转换方法还有
to_int
to_ary
to_hash
不过在内建类中, 实现并不广泛, 使用也不多String, Array, Hash 的实例方法相等运算
==
也类似一种week类型转换, 如果左右值是同一类型则进行常规比较, 否则, 会检测右值是否定义了to_str
to_ary
to_hash
, 如果存在, 则使用方法调用后的结果再进行比较.Numeric之间使用
==
比较, 会尝试进行简单类型转换, 如1 == 1.0
为true
2. Javascript
显式类型转换
转换布尔值
显示布尔转换的函数是
Boolean()
, 实际编码中也经常出现使用双重否定求布尔:!!someValue
逻辑判断中会自动进行Boolean转换, 以下6个值会转换为false, 其他值是true
false, "", 0, NaN, null, undefined
转换数字
Number(anyValue)
parseInt(string, radix)
parseFloat(string)
转换为字符串
Object.prototype.toString()
从继承链 关系看, 除了null, undefined以外的所有值, 都有toString
方法String()
在不确定需要转换的值是否是null或者undefined时, 可以使用String方法, String的转换规则是:- 如果有
toString
则调用toString
- 如果是null, 则返回
null
- 如果是undefined, 则返回
undefined
- 如果有
实际编码中常常出现使用
+
空字符串进行字符串转换:0 + ''
隐式类型转换
+
操作, 如果其中有一个操作数是string, 那么另一个将自动被调用toString()
方法转换为字符串, 然后进行字符串相加
6 + '6' //'66'
+
操作, 如果没有string操作数, 但其中有Object, number, undefined或者null, 将对其调用String()
方法转换为字符串, 然后按照上面的流程进行字符串相加
null + '6' //'null6'
相等比较==
的操作数如果不是相同类型, 将会被自动转换为相同类型再进行比较, 转换规则比较复杂, 建议尽量避免使用
4 == '4' //true
null == undefined //true
最佳实践:
- 不要依赖隐式类型转换
- 不要使用
==
,!=
, 一直使用===
!==
3. Go
不存在隐式转换
要求显式类型转换: a := 10; b := byte(a)
(除位移外)二元操作表达式必须确保类型一致: c := a + int(b)
加上不支持操作符重载, 所以语句总能表达明确的含义
转换为布尔值
在弱类型语言中, 经常会使用数字, 字符串等去逻辑判断, 因为在这些语言中通常会支持任一值自动转换布尔值, 不过在Go中没有自动转换, 甚至也不支持显示布尔转换:
i1 := 1
i2 := 0
fmt.Printf("%t %t\n", bool(i1), bool(i2)) //报错
只能自行使用判断语句如n > 0
type 定义的类型是全新类型
对于type
关键字创建的类型是全新类型, 即使底层数据结构相同, 仍然不能混用:
type INT int
var k int = 5
var j INT = k
「类似」隐式的类型转换场景
以下场景看起来像隐式的类型转景, 但其实不是:
- 常量: 无显示声明的常量, 作为操作数时, 可以自动转换
- 别名类型(byte和uint8, rune和int32)
- 未命名的类型: 具有相同声明的未命名类型视为同一类型, 参见(Go语言学笔记)page 35
- 对象实现了目标接口的所有方法, 那么该对象可以赋值给此接口类型, 这是面向接口编程的基础
未命名类型有赋值转换规则
- 具有相同声明的2种未命名类型
- 一个是未命名类型, 另一个是和前者基础类型相同的命名类型
- 数据类型相同, 将双向通道赋给单向通道, 且其中一个是未命名类型
- 把nil赋值给slice, map, channel, pointer, function, interface
4. Lua
显式类型转换
貌似只找到用双重否定: not not 4 => true
转换布尔值
转换数字
内置函数tonumber (e [, base])
, 如果无法转换, 返回nil
转换为字符串
内置函数 tostring (v)
隐式类型转换
因为Lua中数字相加和字符串连接操作符是分开的, +
的期望操作数明确是数字, ..
的期望操作数明确字符串, 所以Lua对这两类操作可以「理直气壮」的进行隐式转换:
算术运算中, 所有字符串操作数, 会自动转换为数字
'1' + '2' => 3
2 - '1' => 1字符串连接运算
..
也会把操作符中的数字自动转换为string10 .. 20 => 30