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关键字的资料请关注脚本之家其它相关文章!
