Golang關(guān)鍵字--type 類型定義

參考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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘫辩,一起剝皮案震驚了整個(gè)濱河市伐厌,隨后出現(xiàn)的幾起案子挣轨,更是在濱河造成了極大的恐慌轩猩,老刑警劉巖荡澎,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衔瓮,死亡現(xiàn)場離奇詭異热鞍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)薇宠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門澄港,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柄沮,“玉大人,你說我怎么就攤上這事狱意≌罚” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵藏姐,是天一觀的道長羔杨。 經(jīng)常有香客問我杨蛋,道長六荒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任卵皂,我火速辦了婚禮灯变,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘添祸。我一直安慰自己,他們只是感情好凡壤,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布亚侠。 她就那樣靜靜地躺著俗扇,像睡著了一般铜幽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狮杨,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天禾酱,我揣著相機(jī)與錄音绘趋,去河邊找鬼陷遮。 笑死垦江,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的绽族。 我是一名探鬼主播衩藤,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼赏表,長吁一口氣:“原來是場噩夢啊……” “哼匈仗!你這毒婦竟也來了悠轩?” 一聲冷哼從身側(cè)響起攻泼,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤忙菠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后音比,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氢惋,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焰望,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年熊赖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俱笛。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迎膜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出磕仅,到底是詐尸還是另有隱情簸呈,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布劫恒,位于F島的核電站兼贸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏溶诞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一喧务、第九天 我趴在偏房一處隱蔽的房頂上張望枉圃。 院中可真熱鬧,春花似錦坎穿、人聲如沸返劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亲配,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吼虎,已是汗流浹背洋丐。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工翩剪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓拳亿,卻偏偏與公主長得像肺魁,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹅经,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔瘾晃,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,739評(píng)論 2 9
  • 123.繼承 一個(gè)類可以從另外一個(gè)類繼承方法,屬性和其他特征劫拢。當(dāng)一個(gè)類繼承另外一個(gè)類時(shí), 繼承類叫子類, 被繼承的...
    無灃閱讀 1,383評(píng)論 2 4
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,216評(píng)論 0 4
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,829評(píng)論 0 6
  • 最近的睡眠出奇的好,晚上早早入睡牵寺,11點(diǎn)睡覺哆料,早上7點(diǎn)起床缸剪,睡滿7小時(shí)杏节,作息規(guī)律典阵。 醒來后覺得神清氣爽,還能在睡覺...
    謝謝微甜閱讀 919評(píng)論 0 13