參考Go關(guān)鍵字--type闰靴,原文除類型定義外角溃,還介紹了type其它幾種使用場合杠愧。本文只說類型定義利朵。
type有如下幾種用法:
1.定義結(jié)構(gòu)體
2.定義接口
3.類型定義
4.類型別名
5.類型查詢
一律想、類型定義--------節(jié)選自《Go語言圣經(jīng)》第69頁
為了說明類型聲明,我們將不同溫度單位分別定義為不同的類型:
// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv
import "fmt"
type Celsius float64 // 攝氏溫度
type Fahrenheit float64 // 華氏溫度
const (
AbsoluteZeroC Celsius = -273.15 // 絕對(duì)零度
FreezingC Celsius = 0 // 結(jié)冰點(diǎn)溫度
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) }
我們?cè)谶@個(gè)包聲明了兩種類型:Celsius和Fahrenheit分別對(duì)應(yīng)不同的溫度單位绍弟。它們雖然有著相同的底層類型float64技即,但是它們是不同的數(shù)據(jù)類型,因此它們不可以被相互比較或混在一個(gè)表達(dá)式運(yùn)算樟遣《穑刻意區(qū)分類型,可以避免一些像無意中使用不同單位的溫度混合計(jì)算導(dǎo)致錯(cuò)誤豹悬;因此需要一個(gè)類似Celsius(t)或Fahrenheit(t)形式的顯式轉(zhuǎn)型操作才能將float64轉(zhuǎn)為對(duì)應(yīng)的類型葵陵。Celsius(t)和Fahrenheit(t)是類型轉(zhuǎn)換操作,它們并不是函數(shù)調(diào)用瞻佛。類型轉(zhuǎn)換不改變值本身脱篙,但是會(huì)使它們的語義發(fā)生變化。另一方面伤柄,CToF和FToC兩個(gè)函數(shù)則是對(duì)不同溫度單位下的溫度進(jìn)行換算绊困,它們會(huì)返回不同的值。
對(duì)于每一個(gè)類型T适刀,都有一個(gè)對(duì)應(yīng)的類型轉(zhuǎn)換操作T(x)秤朗,用于將x轉(zhuǎn)為T類型(譯注:如果T是指針類型,可能會(huì)需要用小括弧包裝T蔗彤,比如 (*int)(0) )川梅。只有當(dāng)兩個(gè)類型的底層基礎(chǔ)類型相同時(shí),才允許這種轉(zhuǎn)型操作然遏,或者是兩者都是指向相同底層結(jié)構(gòu)的指針類型贫途,這些轉(zhuǎn)換只改變類型而不會(huì)影響值本身。如果x是可以賦值給T類型的值待侵,那么x必然也可以被轉(zhuǎn)為T類型丢早,但是一般沒有這個(gè)必要。
數(shù)值類型之間的轉(zhuǎn)型也是允許的,并且在字符串和一些特定類型的slice之間也是可以轉(zhuǎn)換的怨酝,在下一章我們會(huì)看到這樣的例子傀缩。這類轉(zhuǎn)換可能改變值的表現(xiàn)。例如农猬,將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)為整數(shù)將丟棄小數(shù)部分赡艰,將一個(gè)字符串轉(zhuǎn)為 []byte 類型的slice將拷貝一個(gè)字符串?dāng)?shù)據(jù)的副本。在任何情況下斤葱,運(yùn)行時(shí)不會(huì)發(fā)生轉(zhuǎn)換失敗的錯(cuò)誤(譯注: 錯(cuò)誤只會(huì)發(fā)生在編譯階段)慷垮。
底層數(shù)據(jù)類型決定了內(nèi)部結(jié)構(gòu)和表達(dá)方式,也決定是否可以像底層類型一樣對(duì)內(nèi)置運(yùn)算符的支持揍堕。這意味著料身,Celsius和Fahrenheit類型的算術(shù)運(yùn)算行為和底層的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
比較運(yùn)算符 == 和 < 也可以用來比較一個(gè)命名類型的變量和另一個(gè)有相同類型的變量芹血,或有著相同底層類型的未命名類型的值之間做比較。但是如果兩個(gè)值有著不同的類型楞慈,則不能直接進(jìn)行比較:
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"!
注意最后那個(gè)語句幔烛。盡管看起來想函數(shù)調(diào)用,但是Celsius(f)是類型轉(zhuǎn)換操作抖部,它并不會(huì)改變值说贝,僅僅是改變值的類型而已。測試為真的原因是因?yàn)閏和g都是零值慎颗。
二乡恕、類型定義讓代碼更加簡潔
使用類型定義定義出來的類型與原類型不相同,所以不能使用新類型變量賦值給原類型變量俯萎,除非使用強(qiáng)制類型轉(zhuǎn)換傲宜。下面來看一段示例代碼,根據(jù)string類型夫啊,定義一種新的類型函卒,新類型名稱是name:
type name string
為什么要使用類型定義呢?類型定義可以在原類型的基礎(chǔ)上創(chuàng)造出新的類型撇眯,有些場合下可以使代碼更加簡潔报嵌,如下邊示例代碼:
package main
import (
"fmt"
)
// 定義一個(gè)接收一個(gè)字符串類型參數(shù)的函數(shù)類型
type handle func(str string)
// exec函數(shù),接收handle類型的參數(shù)
func exec(f handle) {
f("hello")
}
func main() {
// 定義一個(gè)函數(shù)類型變量熊榛,這個(gè)函數(shù)接收一個(gè)字符串類型的參數(shù)
var p = func(str string) {
fmt.Println("first", str)
}
exec(p)
// 匿名函數(shù)作為參數(shù)直接傳遞給exec函數(shù)
exec(func(str string) {
fmt.Println("second", str)
})
}
輸出信息是:
first hello
second hello
上邊的示例是類型定義的一種簡單應(yīng)用場合,如果不使用類型定義玄坦,那么想要實(shí)現(xiàn)上邊示例中的功能绘沉,應(yīng)該怎么書寫這段代碼呢?
// exec函數(shù)车伞,接收handle類型的參數(shù)
func exec(f func(str string)) {
f("hello")
}
exec函數(shù)中的參數(shù)類型,需要替換成func(str string)了另玖,咋一看去也不復(fù)雜,但是假如exec接收一個(gè)需要5個(gè)參數(shù)的函數(shù)變量呢日矫?是不是感覺參數(shù)列表就會(huì)很長了赂弓。
func exec(f func(str string, str2 string, num int, money float64, flag bool)) {
f("hello")
}
從上邊的代碼可以發(fā)現(xiàn),exec函數(shù)的參數(shù)列表可讀性變差了盈魁。下邊再來看看使用類型定義是怎么實(shí)現(xiàn)這個(gè)功能:
package main
import (
"fmt"
)
// 定義一個(gè)需要五個(gè)參數(shù)的函數(shù)類型
type handle func(str string, str2 string, num int, money float64, flag bool)
// exec函數(shù),接收handle類型的參數(shù)
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 包中,定義有一個(gè)名為 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)
}
}
運(yùn)行了這段測試代碼珊膜,然后得到了下面的輸出,從輸出內(nèi)容來看车柠,我定義的 500 毫秒的時(shí)間已經(jīng)用完了,但怎么可能竹祷。
Time Elapsed : Wait[500] Duration[10724798]
從上面的知識(shí)很容易看出問題谈跛, Duration 類型中時(shí)間的基本單位是 Nanosecond ,所以當(dāng)我將一個(gè)表示 10 毫秒的 Duration 類型對(duì)象轉(zhuǎn)換為 int64 類型時(shí)塑陵,我實(shí)際上得到的是 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())
}
}
實(shí)際上, Duration 類型擁有一些便捷的類型轉(zhuǎn)換函數(shù)令花,它們能將 Duration 類型轉(zhuǎn)化為 Go 語言的內(nèi)建類型 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)
}
我也迅速注意到了在時(shí)間轉(zhuǎn)換函數(shù)中,并沒有轉(zhuǎn)換毫秒值的函數(shù)兼都,使用 Seconds 和 Minutes 函數(shù)嫂沉,我得到了如下輸出:
Seconds [12.500]
Minutes [2.00]
但我需要轉(zhuǎn)換毫秒值,為什么包里面單單沒有提供毫秒值的轉(zhuǎn)換呢俯抖?因?yàn)?Go 語言的設(shè)計(jì)者希望我有更多的選擇输瓜,而不只是將毫秒值轉(zhuǎn)換成某種單獨(dú)的內(nèi)建類型。下面的代碼中,我將毫秒值轉(zhuǎn)化為了 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]
參考一下內(nèi)置包time中的源碼北戏,很容易理解:
func (d Duration) Seconds() float64 {
sec := d / Second
nsec := d % Second
return float64(sec) + float64(nsec)/1e9
}
四负芋、類型定義和類型別名,不要弄混了
類型別名與類型定義不同之處在于嗜愈,使用類型別名需要在別名和原類型之間加上賦值符號(hào)(=)旧蛾;使用類型別名定義的類型與原類型等價(jià),而使用類型定義出來的類型是一種新的類型蠕嫁。
在Golang 1.9 新特性-類型別名(Type Aliases):
1.設(shè)計(jì)初衷
類型別名的設(shè)計(jì)初衷是為了解決代碼重構(gòu)時(shí)锨天,類型在包(package)之間轉(zhuǎn)移時(shí)產(chǎn)生的問題(參考 Codebase Refactoring (with help from Go) )。
考慮如下情景:
項(xiàng)目中有一個(gè)叫
p1
的包剃毒,其中包含一個(gè)結(jié)構(gòu)體T1
病袄。隨著項(xiàng)目的進(jìn)展T1
變得越來越龐大。我們希望通過代碼重構(gòu)將T1
抽取并放入到獨(dú)立的包p2
赘阀,同時(shí)不希望影響現(xiàn)有的其他代碼益缠。這種情況下以往的go語言的功能不能很好的滿足此類需求。類型別名的引入可以為此類需求提供良好的支持基公。
首先我們可以將T1相關(guān)的代碼抽取到包p2中:
package p2
type T1 struct {
...
}
func (*T1) SomeFunc() {
...
}
之后在p1中放入T1的別名
package p1
import "p2"
type T1 = p2.T1
通過這種操作我們可以在不影響現(xiàn)有其他代碼的前提下將類型抽取到新的包當(dāng)中》牛現(xiàn)有代碼依舊可以通過導(dǎo)入p1來使用T1。而不需要立馬切換到p2轰豆,可以進(jìn)行逐步的遷移胰伍。此類重構(gòu)發(fā)生不僅僅存在于上述場景還可能存在于以下場景:
- 優(yōu)化命名:如早期Go版本中的io.ByteBuffer修改為bytes.Buffer。
- 減小依賴大忻胱伞:如io.EOF曾經(jīng)放在os.EOF喇辽,為了使用EOF必需導(dǎo)入整個(gè)os包菩咨。
- 解決循環(huán)依賴問題抽米。
參考Go語言 | Go 1.9 新特性 Type Alias詳解
type alias這個(gè)特性的主要目的是用于已經(jīng)定義的類型糙置,在package之間的移動(dòng)時(shí)的兼容。比如我們有一個(gè)導(dǎo)出的類型flysnow.org/lib/T1标捺,現(xiàn)在要遷移到另外一個(gè)package中, 比如flysnow.org/lib2/T1中。沒有type alias的時(shí)候我們這么做嗤疯,就會(huì)導(dǎo)致其他第三方引用舊的package路徑的代碼闺兢,都要統(tǒng)一修改,不然無法使用脚囊。
有了type alias就不一樣了桐磁,類型T1的實(shí)現(xiàn)我們可以遷移到lib2下,同時(shí)我們?cè)谠瓉淼膌ib下定義一個(gè)lib2下T1的別名淮逊,這樣第三方的引用就可以不用修改,也可以正常使用郎任,只需要兼容一段時(shí)間,再徹底的去掉舊的package里的類型兼容分井,這樣就可以漸進(jìn)式的重構(gòu)我們的代碼霉猛,而不是一刀切。
//package:flysnow.org/lib
type T1=lib2.T1