参考Go关键字--type,原文除类型定义外,还介绍了type其它几种使用场合。本文只说类型定义。
type有如下几种用法:
1.定义结构体
2.定义接口
3.类型定义
4.类型别名
5.类型查询
一、类型定义--------节选自《Go语言圣经》第69页
为了说明类型声明,我们将不同温度单位分别定义为不同的类型:
// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv
import "fmt"
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转为对应的类型。Celsius(t)和Fahrenheit(t)是类型转换操作,它们并不是函数调用。类型转换不改变值本身,但是会使它们的语义发生变化。另一方面,CToF和FToC两个函数则是对不同温度单位下的温度进行换算,它们会返回不同的值。
对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(译注:如果T是指针类型,可能会需要用小括弧包装T,比如 (*int)(0) )。只有当两个类型的底层基础类型相同时,才允许这种转型操作,或者是两者都是指向相同底层结构的指针类型,这些转换只改变类型而不会影响值本身。如果x是可以赋值给T类型的值,那么x必然也可以被转为T类型,但是一般没有这个必要。
数值类型之间的转型也是允许的,并且在字符串和一些特定类型的slice之间也是可以转换的,在下一章我们会看到这样的例子。这类转换可能改变值的表现。例如,将一个浮点数转为整数将丢弃小数部分,将一个字符串转为 []byte 类型的slice将拷贝一个字符串数据的副本。在任何情况下,运行时不会发生转换失败的错误(译注: 错误只会发生在编译阶段)。
底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。这意味着,Celsius和Fahrenheit类型的算术运算行为和底层的float64类型是一样的,正如我们所期望的那样。
fmt.Printf("%g\n", BoilingC-FreezingC) // "100" °C
boilingF := CToF(BoilingC)
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都是零值。
二、类型定义让代码更加简洁
使用类型定义定义出来的类型与原类型不相同,所以不能使用新类型变量赋值给原类型变量,除非使用强制类型转换。下面来看一段示例代码,根据string类型,定义一种新的类型,新类型名称是name:
type name string
为什么要使用类型定义呢?类型定义可以在原类型的基础上创造出新的类型,有些场合下可以使代码更加简洁,如下边示例代码:
package main
import (
"fmt"
)
// 定义一个接收一个字符串类型参数的函数类型
type handle func(str string)
// exec函数,接收handle类型的参数
func exec(f handle) {
f("hello")
}
func main() {
// 定义一个函数类型变量,这个函数接收一个字符串类型的参数
var p = func(str string) {
fmt.Println("first", str)
}
exec(p)
// 匿名函数作为参数直接传递给exec函数
exec(func(str string) {
fmt.Println("second", str)
})
}
输出信息是:
first hello
second hello
上边的示例是类型定义的一种简单应用场合,如果不使用类型定义,那么想要实现上边示例中的功能,应该怎么书写这段代码呢?
// exec函数,接收handle类型的参数
func exec(f func(str string)) {
f("hello")
}
exec函数中的参数类型,需要替换成func(str string)了,咋一看去也不复杂,但是假如exec接收一个需要5个参数的函数变量呢?是不是感觉参数列表就会很长了。
func exec(f func(str string, str2 string, num int, money float64, flag bool)) {
f("hello")
}
从上边的代码可以发现,exec函数的参数列表可读性变差了。下边再来看看使用类型定义是怎么实现这个功能:
package main
import (
"fmt"
)
// 定义一个需要五个参数的函数类型
type handle func(str string, str2 string, num int, money float64, flag bool)
// exec函数,接收handle类型的参数
func exec(f handle) {
f("hello", "world", 10, 11.23, true)
}
func demo(str string, str2 string, num int, money float64, flag bool) {
fmt.Println(str, str2, num, money, flag)
}
func main() {
exec(demo)
}
三、详解 Go 语言中的 time.Duration 类型
在 Time 包中,定义有一个名为 Duration 的类型和一些辅助的常量:
type Duration int64
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)
然后是测试代码:
func Test() {
var waitFiveHundredMillisections int64 = 500
startingTime := time.Now().UTC()
time.Sleep(10 * time.Millisecond)
endingTime := time.Now().UTC()
var duration time.Duration = endingTime.Sub(startingTime)
var durationAsInt64 = int64(duration)
if durationAsInt64 >= waitFiveHundredMillisections {
fmt.Printf("Time Elapsed : Wait[%d] Duration[%d]\n",
waitFiveHundredMillisections, durationAsInt64)
} else {
fmt.Printf("Time DID NOT Elapsed : Wait[%d] Duration[%d]\n",
waitFiveHundredMillisections, durationAsInt64)
}
}
运行了这段测试代码,然后得到了下面的输出,从输出内容来看,我定义的 500 毫秒的时间已经用完了,但怎么可能。
Time Elapsed : Wait[500] Duration[10724798]
从上面的知识很容易看出问题, Duration 类型中时间的基本单位是 Nanosecond ,所以当我将一个表示 10 毫秒的 Duration 类型对象转换为 int64 类型时,我实际上得到的是 10,000,000。
改成这样测试一下
func Test() {
var waitFiveHundredMillisections time.Duration = 500 * time.Millisecond
startingTime := time.Now().UTC()
time.Sleep(600 * time.Millisecond)
endingTime := time.Now().UTC()
var duration time.Duration = endingTime.Sub(startingTime)
if duration >= waitFiveHundredMillisections {
fmt.Printf("Wait %v\nNative [%v]\nMilliseconds [%d]\nSeconds [%.3f]\n",
waitFiveHundredMillisections, duration, duration.Nanoseconds()/1e6, duration.Seconds())
}
}
实际上, Duration 类型拥有一些便捷的类型转换函数,它们能将 Duration 类型转化为 Go 语言的内建类型 int64 或 float64 ,像下面这样:
func Test() {
var duration_Seconds time.Duration = (1250 * 10) * time.Millisecond
var duration_Minute time.Duration = 2 * time.Minute
var float64_Seconds float64 = duration_Seconds.Seconds()
var float64_Minutes float64 = duration_Minute.Minutes()
fmt.Printf("Seconds [%.3f]\nMinutes [%.2f]\n", float64_Seconds, float64_Minutes)
}
我也迅速注意到了在时间转换函数中,并没有转换毫秒值的函数,使用 Seconds 和 Minutes 函数,我得到了如下输出:
Seconds [12.500]
Minutes [2.00]
但我需要转换毫秒值,为什么包里面单单没有提供毫秒值的转换呢?因为 Go 语言的设计者希望我有更多的选择,而不只是将毫秒值转换成某种单独的内建类型。下面的代码中,我将毫秒值转化为了 int64 类型和 float64 类型:
func Test() {
var duration_Milliseconds time.Duration = 500 * time.Millisecond
var castToInt64 int64 = duration_Milliseconds.Nanoseconds() / 1e6
var castToFloat64 float64 = duration_Milliseconds.Seconds() * 1e3
fmt.Printf("Duration [%v]\ncastToInt64 [%d]\ncastToFloat64 [%.0f]\n",
duration_Milliseconds, castToInt64, castToFloat64)
}
我将纳秒值除以 1e6 得到了 int64 类型表示的毫秒值,将秒值乘以 1e3 ,我得到了 float64 类型表示的毫秒值,上面代码的输出如下:
Duration [500ms]
castToInt64 [500]
castToFloat64 [500]
参考一下内置包time中的源码,很容易理解:
func (d Duration) Seconds() float64 {
sec := d / Second
nsec := d % Second
return float64(sec) + float64(nsec)/1e9
}
有疑问加站长微信联系(非本文作者)