日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Go 学习笔记(27)— type 关键字(类型定义、类型别名、类型查询、定义接口、定义结构体)

發(fā)布時(shí)間:2023/11/27 生活经验 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(27)— type 关键字(类型定义、类型别名、类型查询、定义接口、定义结构体) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 類型別名定義

定義類型別名的寫法為:

type TypeAlias = Type

類型別名規(guī)定: TypeAlias 只是 Type 的別名,本質(zhì)上 TypeAliasType 是同一個(gè)類型,就像一個(gè)孩子小時(shí)候有小名、乳名,上學(xué)后用學(xué)名,英語老師又會(huì)給他起英文名,但這些名字都指的是他本人。

2. 類型定義

類型定義語法如下:

type newType Type

其中 newType 是一種新的類型, newType 本身依然具備 Type 類型的特性。新類型與底層類型不能直接相互賦值和運(yùn)算,如果需要,需要顯式轉(zhuǎn)換。

var m int = 5
var n int32 = 6
var a MyInt = MyInt(m) // ok
var a MyInt = MyInt(n) // ok

類型聲明語句一般出現(xiàn)在包一級,因此如果新創(chuàng)建的類型名字的首字符大寫,則在包外部也可以使用。

一個(gè)類型聲明語句創(chuàng)建了一個(gè)新的類型名稱,和現(xiàn)有類型具有相同的底層結(jié)構(gòu)。新命名的類型提供了一個(gè)方法,用來分隔不同概念的類型,這樣即使它們底層類型相同也是不兼容的。

為了說明類型聲明,我們將不同溫度單位分別定義為不同的類型:

package tempconvtype Celsius float64    // 攝氏溫度
type Fahrenheit float64 // 華氏溫度const (AbsoluteZeroC Celsius = -273.15 // 絕對零度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)
}

我們在這個(gè)包聲明了兩種類型: CelsiusFahrenheit 分別對應(yīng)不同的溫度單位。它們雖然有著相同的底層類型 float64 ,但是它們是不同的數(shù)據(jù)類型,因此它們不可以被相互比較或混在一個(gè)表達(dá)式運(yùn)算。

刻意區(qū)分類型,可以避免一些像無意中使用不同單位的溫度混合計(jì)算導(dǎo)致的錯(cuò)誤;因此需要一個(gè)類似 Celsius(t)Fahrenheit(t) 形式的顯式轉(zhuǎn)型操作才能將 float64 轉(zhuǎn)為對應(yīng)的類型。

Celsius(t)Fahrenheit(t) 是類型轉(zhuǎn)換操作,它們并不是函數(shù)調(diào)用。類型轉(zhuǎn)換不會(huì)改變值本身,但是會(huì)使它們的語義發(fā)生變化。另一方面, CToFFToC 兩個(gè)函數(shù)則是對不同溫度單位下的溫度進(jìn)行換算,它們會(huì)返回不同的值。

對于每一個(gè)類型 T ,都有一個(gè)對應(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)換的。

例如,將一個(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á)方式,也決定是否可以像底層類型一樣對內(nèi)置運(yùn)算符的支持。這意味著, CelsiusFahrenheit 類型的算術(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)?cg 都是零值。

一個(gè)命名的類型可以提供書寫方便,特別是可以避免一遍又一遍地書寫復(fù)雜類型(譯注:例如用匿名的結(jié)構(gòu)體定義變量)。雖然對于像 float64 這種簡單的底層類型沒有簡潔很多,但是如果是復(fù)雜的類型將會(huì)簡潔很多,特別是我們即將討論的結(jié)構(gòu)體類型。

命名類型還可以為該類型的值定義新的行為。這些行為表示為一組關(guān)聯(lián)到該類型的函數(shù)集合,我們稱為類型的方法集。

下面的聲明語句, Celsius 類型的參數(shù) c 出現(xiàn)在了函數(shù)名的前面,表示聲明的是 Celsius 類型的一個(gè)名叫 String 的方法,該方法返回該類型對象 c 帶著 °C 溫度單位的字符串:

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) 
}

許多類型都會(huì)定義一個(gè) String 方法,因?yàn)楫?dāng)使用 fmt 包的打印方法時(shí),將會(huì)優(yōu)先使用該類型對應(yīng)的 String 方法返回的結(jié)果打印。

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. 類型別名與類型定義差異

類型別名與類型定義表面上看只有一個(gè)等號的差異,那么它們之間實(shí)際的區(qū)別有哪些呢?下面通過一段代碼來理解。

package mainimport ("fmt"
)// 將NewInt定義為int類型
// 通過 type 關(guān)鍵字的定義,NewInt 會(huì)形成一種新的類型,NewInt 本身依然具備 int 類型的特性。
type NewInt int// 將int取一個(gè)別名叫IntAlias, 將 IntAlias 設(shè)置為 int 的一個(gè)別名,使 IntAlias 與 int 等效。
type IntAlias = intfunc 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
}

結(jié)果顯示 a 的類型是 main.NewInt ,表示 main 包下定義的 NewInt 類型,b 類型是 intIntAlias 類型只會(huì)在代碼中存在,編譯完成時(shí),不會(huì)有 IntAlias 類型。

4. 非本地類型不能定義方法

能夠隨意地為各種類型起名字,是否意味著可以在自己包里為這些類型任意添加方法呢?參見下面的代碼演示:

package mainimport ("time"
)// 定義time.Duration的別名為MyDuration
type MyDuration = time.Duration// 為 MyDuration 添加一個(gè)方法
func (m MyDuration) EasySet(a string) {}func main() {}

錯(cuò)誤信息:

./hello.go:11:6: cannot define new methods on non-local type time.Duration

編譯器提示:不能在一個(gè)非本地的類型 time.Duration 上定義新方法,非本地類型指的就是 time.Duration 不是在 main 包中定義的,而是在 time 包中定義的,與 main 包不在同一個(gè)包中,因此不能為不在一個(gè)包中的類型定義方法。

修改方案為將第 8 行類型別名修改為類型定義,如下:

type MyDuration time.Duration

5. 在結(jié)構(gòu)體成員嵌入時(shí)使用別名

當(dāng)類型別名作為結(jié)構(gòu)體嵌入的成員時(shí)會(huì)發(fā)生什么情況呢?請參考下面的代碼。

package mainimport ("fmt""reflect"
)// 定義商標(biāo)結(jié)構(gòu)
type Brand struct {
}// 為商標(biāo)結(jié)構(gòu)添加Show()方法
func (t Brand) Show() {
}// 為Brand定義一個(gè)別名FakeBrand
type FakeBrand = Brand// 定義車輛結(jié)構(gòu)
type Vehicle struct {// 嵌入兩個(gè)結(jié)構(gòu)FakeBrandBrand
}func main() {// 聲明變量a為車輛類型var a Vehicle// 指定調(diào)用FakeBrand的Showa.FakeBrand.Show()// 取a的類型反射對象ta := reflect.TypeOf(a)// 遍歷a的所有成員for i := 0; i < ta.NumField(); i++ {// a的成員信息f := ta.Field(i)// 打印成員的字段名和類型fmt.Printf("FieldName: %v, FieldType: %v\n", f.Name, f.Type.Name())}
}

輸出結(jié)果:

FieldName: FakeBrand, FieldType: Brand
FieldName: Brand, FieldType: Brand

這個(gè)例子中,FakeBrand 是 Brand 的一個(gè)別名,在 Vehicle 中嵌入 FakeBrand 和 Brand 并不意味著嵌入兩個(gè) Brand,FakeBrand 的類型會(huì)以名字的方式保留在 Vehicle 的成員中。

如果嘗試將第 33 行改為:

a.Show()

編譯器將發(fā)生報(bào)錯(cuò):

ambiguous selector a.Show

在調(diào)用 Show() 方法時(shí),因?yàn)閮蓚€(gè)類型都有 Show() 方法,會(huì)發(fā)生歧義,證明 FakeBrand 的本質(zhì)確實(shí)是 Brand 類型。

6. 函數(shù)也是類型,可以作為參數(shù)傳遞給別的函數(shù)

package maintype math func(int, int) int //定義一個(gè)函數(shù)類型,兩個(gè) int 參數(shù),一個(gè) int 返回值//定義一個(gè)函數(shù) add,這個(gè)函數(shù)兩個(gè) int 參數(shù)一個(gè) int 返回值,與 math 類型相符
func add(i int, j int) int {return i + j
}//再定義一個(gè) multiply,這個(gè)函數(shù)同樣符合 math 類型
func multiply(i, j int) int {return i * j
}//foo 函數(shù),需要一個(gè) math 類型的參數(shù),用 math 類型的函數(shù)計(jì)算第 2 和第 3 個(gè)參數(shù)數(shù)字,并返回計(jì)算結(jié)果
//稍后在 main 中我們將 add 函數(shù)和 multiply 分別作為參數(shù)傳遞給它
func foo(m math, n1, n2 int) int {return m(1, 2)
}func main() {//傳遞 add 函數(shù)和兩個(gè)數(shù)字,計(jì)算相加結(jié)果n := foo(add, 1, 2)println(n)//傳遞 multply 和兩個(gè)數(shù)字,計(jì)算相乘結(jié)果n = foo(multiply, 1, 2)println(n)
}

7. type 類型用法

type 有如下幾種用法:

  • 定義結(jié)構(gòu)體
  • 定義接口
  • 類型定義
  • 類型別名
  • 類型查詢

7.1 定義結(jié)構(gòu)體

結(jié)構(gòu)體是用戶自定義的一種抽象的數(shù)據(jù)結(jié)構(gòu), Golangstruct 類似于 Java 語言中的 class ,在程序設(shè)計(jì)中,有著舉足輕重的地位。結(jié)構(gòu)體的用法,將會(huì)在 struct 關(guān)鍵字中詳細(xì)的介紹。下邊來看一下定義一個(gè)結(jié)構(gòu)體的語法格式:

type name struct {Field1  dataTypeField2  dataTypeField3  dataType
}

7.2 定義接口

接口相關(guān)知識(shí)點(diǎn),將會(huì)在 interface 關(guān)鍵字中詳細(xì)介紹,下邊來看一段定義接口的語法格式:

type name interface{Read()Write()
}

7.3 類型定義

使用類型定義定義出來的類型與原類型不相同,所以不能使用新類型變量賦值給原類型變量,除非使用強(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)
}

7.4 類型別名

類型別名這個(gè)特性在 Golang 1.9 中引入。使用類型別名定義出來的類型與原類型一樣,即可以與原類型變量互相賦值,又擁有了原類型的所有方法集。給 strng 類型取一個(gè)別名,別名名稱是 name

type name = string

類型別名與類型定義不同之處在于,使用類型別名需要在別名和原類型之間加上賦值符號( = );使用類型別名定義的類型與原類型等價(jià),而使用類型定義出來的類型是一種新的類型。

如下邊示例:

package main
import ("fmt"
)
type a = string
type b string
func SayA(str a) {fmt.Println(str)
}
func SayB(str b) {fmt.Println(str)
}
func main() {var str = "test"SayA(str)//錯(cuò)誤參數(shù)傳遞,str是字符串類型,不能賦值給b類型變量SayB(str)
}

這段代碼在編譯時(shí)會(huì)出現(xiàn)如下錯(cuò)誤:

.\main.go:21:6: cannot use str (type string) as type b in argument to SayB

從錯(cuò)誤信息可知, str 為字符串類型,不能當(dāng)做 b 類型參數(shù)傳入 SayB 函數(shù)中。而 str 卻可以當(dāng)做 a 類型參數(shù)傳入到 SayA 函數(shù)中。由此可見,使用類型別名定義的類型與原類型一致,而類型定義定義出來的類型,是一種新的類型。

給類型別名新增方法,會(huì)添加到原類型方法集中

給類型別名新增方法后,原類型也能使用這個(gè)方法。下邊請看一段示例代碼:

package main
import ("fmt"
)
// 根據(jù)string類型,定義類型S
type S string
func (r *S) Hi() {fmt.Println("S hi")
}
// 定義S的類型別名為T
type T = S
func (r *T) Hello() {fmt.Println("T hello")
}
// 函數(shù)參數(shù)接收S類型的指針變量
func exec(obj *S) {obj.Hello()obj.Hi()
}
func main() {t := new(T)s := new(S)exec(s)// 將T類型指針變量傳遞給S類型指針變量exec(t)
}

輸出信息是:

T hello
S hi
T hello
S hi

上邊的示例中,S 是原類型,T 是 S 類型別名。在給 T 增加了 Hello 方法后,S 類型的變量也可以使用 Hello 方法。說明給類型別名新增方法后,原類型也能使用這個(gè)方法。從示例中可知,變量 t 可以賦值給 S 類型變量 s,所以類型別名是給原類型取了一個(gè)小名,本質(zhì)上沒有發(fā)生任何變化。

類型別名,只能對同一個(gè)包中的自定義類型產(chǎn)生作用。舉個(gè)例子,Golang SDK 中有很多個(gè)包,是不是我們可以使用類型別名,給 SDK 包中的結(jié)構(gòu)體類型新增方法呢?答案是:不行。請牢記一點(diǎn):類型別名,只能對包內(nèi)的類型產(chǎn)生作用,對包外的類型采用類型別名,在編譯時(shí)將會(huì)提示如下信息:

cannot define new methods on non-local type string

7.5 類型查詢

類型查詢,就是根據(jù)變量,查詢這個(gè)變量的類型。為什么會(huì)有這樣的需求呢?

Goalng 中有一個(gè)特殊的類型 interface{} ,這個(gè)類型可以被任何類型的變量賦值,如果想要知道到底是哪個(gè)類型的變量賦值給了 interface{} 類型變量,就需要使用類型查詢來解決這個(gè)需求,示例代碼如下:

package main
import ("fmt"
)
func main() {// 定義一個(gè)interface{}類型變量,并使用string類型值”abc“初始化var a interface{} = "abc"// 在switch中使用 變量名.(type) 查詢變量是由哪個(gè)類型數(shù)據(jù)賦值。switch v := a.(type) {case string:fmt.Println("字符串")case int:fmt.Println("整型")default:fmt.Println("其他類型", v)}
}

如果使用 .(type) 查詢類型的變量不是 interface{} 類型,則在編譯時(shí)會(huì)報(bào)如下錯(cuò)誤:

cannot type switch on non-interface value a (type string)

如果在 switch 以外地方使用 .(type) ,則在編譯時(shí)會(huì)提示如下錯(cuò)誤:

use of .(type) outside type switch

所以,使用 type 進(jìn)行類型查詢時(shí),只能在 switch 中使用,且使用類型查詢的變量類型必須是 interface{}

總結(jié)

以上是生活随笔為你收集整理的Go 学习笔记(27)— type 关键字(类型定义、类型别名、类型查询、定义接口、定义结构体)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。