Go type关键字(类型定义与类型别名的使用差异)用法实例探究
作者:wohu 程序员的自我进化
1. 类型别名定义
定义类型别名的写法为:
type TypeAlias = Type
类型别名规定:TypeAlias
只是 Type
的别名,本质上 TypeAlias
与 Type
是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
2. 类型定义
类型定义语法如下:
type newType Type
其中 newType
是一种新的类型, newType
本身依然具备 Type
类型的特性。
类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在包外部也可以使用。
一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型,这样即使它们底层类型相同也是不兼容的。
为了说明类型声明,我们将不同温度单位分别定义为不同的类型:
package main type Celsius float64 // 摄氏温度 type Fahrenheit float64 // 华氏温度 const ( AbsoluteZeroC Celsius = -273.15 // 绝对零度 FreezingC Celsius = 0 // 结冰点温度 BoilingC Celsius = 100 // 沸水温度 ) func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) } func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
我们在这个包声明了两种类型:Celsius
和 Fahrenheit
分别对应不同的温度单位。它们虽然有着相同的底层类型 float64
,但是它们是不同的数据类型,因此它们不可以被相互比较或混在一个表达式运算。
刻意区分类型,可以避免一些像无意中使用不同单位的温度混合计算导致的错误;因此需要一个类似 Celsius(t)
或 Fahrenheit(t)
形式的显式转型操作才能将 float64
转为对应的类型。
只有当两个类型的底层基础类型相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果 x
是可以赋值给 T
类型的值,那么 x
必然也可以被转为 T
类型,但是一般没有这个必要。
底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。这意味着, Celsius
和 Fahrenheit
类型的算术运算行为和底层的 float64
类型是一样的,正如我们所期望的那样。
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C</code><code>boilingF := CToF(BoilingC)</code><code>fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // "180" °F fmt.Printf("%g\n", boilingF-FreezingC) // compile error: type mismatch
比较运算符==
和<
也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直接进行比较:
var c Celsius var f Fahrenheit fmt.Println(c == 0) // "true" fmt.Println(f >= 0) // "true" fmt.Println(c == f) // compile error: type mismatch fmt.Println(c == Celsius(f)) // "true"!
注意最后那个语句。尽管看起来像函数调用,但是 Celsius(f)
是类型转换操作,它并不会改变值,仅仅是改变值的类型而已。测试为真的原因是因为 c
和 g
都是零值。
命名类型还可以为该类型的值定义新的行为。这些行为表示为一组关联到该类型的函数集合,我们称为类型的方法集。
下面的声明语句, Celsius
类型的参数 c
出现在了函数名的前面,表示声明的是 Celsius
类型的一个名叫 String
的方法,该方法返回该类型对象 c
带着 °C
温度单位的字符串:
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
许多类型都会定义一个 String
方法,因为当使用 fmt
包的打印方法时,将会优先使用该类型对应的 String
方法返回的结果打印。
c := FToC(212.0) fmt.Println(c.String()) // "100°C" fmt.Printf("%v\n", c) // "100°C"; no need to call String explicitly fmt.Printf("%s\n", c) // "100°C" fmt.Println(c) // "100°C" fmt.Printf("%g\n", c) // "100"; does not call String fmt.Println(float64(c)) // "100"; does not call String
3. 类型别名与类型定义差异
类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?下面通过一段代码来理解。
package main import ( "fmt" ) // 将NewInt定义为int类型 // 通过 type 关键字的定义,NewInt 会形成一种新的类型,NewInt 本身依然具备 int 类型的特性。 type NewInt int // 将int取一个别名叫IntAlias, 将 IntAlias 设置为 int 的一个别名,使 IntAlias 与 int 等效。 type IntAlias = int func main() { // 将a声明为NewInt类型 var a NewInt // 查看a的类型名 fmt.Printf("a type: %T\n", a) // a type: main.NewInt // 将 b 声明为IntAlias类型 var b IntAlias // 查看b的类型名 fmt.Printf("b type: %T\n", b) // b type: int }
结果显示 a 的类型是 main.NewInt
,表示 main 包下定义的 NewInt
类型,b 类型是 int
, IntAlias
类型只会在代码中存在,编译完成时,不会有 IntAlias
类型。
4. 非本地类型不能定义方法
能够随意地为各种类型起名字,是否意味着可以在自己包里为这些类型任意添加方法呢?参见下面的代码演示:
package main import ( "time" ) // 定义time.Duration的别名为MyDuration type MyDuration = time.Duration // 为 MyDuration 添加一个方法 func (m MyDuration) EasySet(a string) { } func main() { }
错误信息:
./hello.go:11:6: cannot define new methods on non-local type time.Duration
编译器提示:不能在一个非本地的类型 time.Duration
上定义新方法,非本地类型指的就是 time.Duration
不是在 main
包中定义的,而是在 time
包中定义的,与 main
包不在同一个包中,因此不能为不在一个包中的类型定义方法。修改方案为将第 8 行类型别名修改为类型定义,如下:
type MyDuration time.Duration
以上就是Go type关键字(类型定义与类型别名的使用差异)用法实例探究的详细内容,更多关于Go type关键字的资料请关注脚本之家其它相关文章!