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

歡迎訪問(wèn) 生活随笔!

生活随笔

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

生活经验

Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)

發(fā)布時(shí)間:2023/11/27 生活经验 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

1. 命名類型和未命名類型

1.1 命名類型

類型可以通過(guò)標(biāo)識(shí)符來(lái)表示,這種類型稱為命名類型( Named Type )。 Go 語(yǔ)言的基本類型中有 20 個(gè)預(yù)聲明簡(jiǎn)單類型都是命名類型, Go 語(yǔ)言還有一種命名類型一一用戶自定義類型。

1.2 未命名類型

一個(gè)類型由預(yù)聲明類型、關(guān)鍵字和操作符組合而成,這個(gè)類型稱為未命名類型( Unamed Type )。未命名類型又稱為類型字面量( Type Literal )。

Go 語(yǔ)言的基本類型中的復(fù)合類型:數(shù)組( array )、切片( slice )、字典( map )、通道( channel )、指針( pointer ) 、函數(shù)字面量( function )、結(jié)構(gòu)( struct )和接口( interface )都屬于類型字面量,也都是未命名類型。

所以 *int , []int , [2]int , map[k]v 都是未命名類型。

注意:前面所說(shuō)的結(jié)構(gòu)和接口是未命名類型,這里的結(jié)構(gòu)和接口沒(méi)有使用 type 格式定義,具體見(jiàn)下方示例說(shuō)明。

package mainimport "fmt"// Person 使用 type 聲明的是命名類型
type Person struct {name stringage  int
}func main() {// 使用 struct 字面量聲明的是未命名類型a := struct {name stringage  int}{"Jim", 20}fmt.Printf("%T\n", a) // struct { name string; age int }fmt.Printf("%v\n", a) // {Jim 20}b := Person{"Tom", 22}fmt.Printf("%T\n", b) // main.Personfmt.Printf("%v\n", b) // {Tom 22}}

Go 語(yǔ)言的命名類型和未命名類型總結(jié)如下:

  1. 未命名類型和類型字面量是等價(jià)的,我們通常所說(shuō)的 Go 語(yǔ)言基本類型中的復(fù)合類型就是類型字面量,所以未命名類型、類型字面量和 Go 語(yǔ)言基本類型中的復(fù)合類型三者等價(jià)。
  2. 通常所說(shuō)的 Go 語(yǔ)言基本類型中的簡(jiǎn)單類型就是這 20 個(gè)預(yù)聲明類型,它們都屬于命名類型。
  3. 預(yù)聲明類型是命名類型的一種,另一類命名類型是自定義類型。

2. 底層類型

所有“類型”都有一個(gè) underlying type (底層類型)。底層類型的規(guī)則如下:

  1. 簡(jiǎn)單類型和復(fù)合類型的底層類型是它們自身。
  2. 自定義類型 type newtype oldtypenewtype 的底層類型是逐層遞歸向下查找的,直到查到的 oldtype 是簡(jiǎn)單類型或復(fù)合類型為止。

例如:

type T1 string
type T2 Tl
type T3 []string
type T4 T3
type T5 []T1
type T6 T5

T1T2 的底層類型都是 stringT3T4 的底層類型都是 []stringT5T6 的底層類型都是 []T1 。特別注意這里的 T6T5T3T4 的底層類型是不一樣的, 一個(gè)是 []T1 ,另一個(gè)是 []string

底層類型在類型賦值和類型強(qiáng)制轉(zhuǎn)換時(shí)會(huì)使用,接下來(lái)就介紹這兩個(gè)主題。

3. 類型相同和類型賦值

3.1 類型相同

Go 是強(qiáng)類型的語(yǔ)言,編譯器在編譯時(shí)會(huì)進(jìn)行嚴(yán)格的類型校驗(yàn)。兩個(gè)命名類型是否相同,參考如下:

  1. 兩個(gè)命名類型相同的條件是兩個(gè)類型聲明的語(yǔ)句完全相同;
  2. 命名類型和未命名類型永遠(yuǎn)不相同;
  3. 兩個(gè)未命名類型相同的條件是它們的類型聲明字面量的結(jié)構(gòu)相同,井且內(nèi)部元素的類型相同;
  4. 通過(guò)類型別名語(yǔ)句聲明的兩個(gè)類型相同;

Go 1.9 引入了類型別名語(yǔ)法 type T1 = T2 , T1 的類型完全和 T2 一樣。

3.2 類型賦值

不同類型的變量之間一般是不能直接相互賦值的,除非滿足一定的條件。類型為 T1 的變量 a 可以賦值給類型為 T2 的變量 b , 稱為類型 T1 可以賦值給類型 T2 ,偽代碼表述如下:

// a 是類型為T1 的變量,或者a 本身就是一個(gè)字面常量或 nil
// 如果如下語(yǔ)句可以執(zhí)行,則稱之為類型 Tl 可以賦值給類型T2
var b T2 = a

a 可以賦值給變量 b 必須要滿足如下條件中的一個(gè):

  1. T1T2 類型相同;
  2. T1T2 具有相同的底層類型,并且 T1T2 里面至少有一個(gè)是未命名類型;
  3. T2 是接口類型, T1 是具體類型, T1 的方法集是 T2 方法集的超集;
  4. T1T2 都是通道類型,它們擁有相同的元素類型,并且 T1T2 中至少有一個(gè)是未命名類型;
  5. T1是預(yù)聲明標(biāo)識(shí)符 nilT2pointer 、 funcitionslicemap 、 channelinterface 類型中的一個(gè);
  6. a 是一個(gè)字面常量值,可以用來(lái)表示類型 T 的值;

示例如下:

package mainimport "fmt"type Map map[string]stringfunc (m Map) Print() {for _, v := range m {fmt.Println("v is ", v)}
}type iMap Map// 只要底層類型是slice 、map 等支持range 的類型字面量,新類型仍然可以使用range 迭代
func (m iMap) Print() {for _, v := range m {fmt.Println("v is ", v)}
}type slice []intfunc (s slice) Print() {for _, v := range s {fmt.Println("v is ", v)}
}func main() {mp := make(map[string]string, 10)mp["hi"] = "hello"// mp 與ma 有相同的底層類型map[string]stirng ,并且mp 是未命名類型// 所以mp 可以直接賦值給mavar ma Map = mp/*im 與 ma 雖然有相同的底層類型map[string]stirng,但它們中沒(méi)有一個(gè)是未命名類型不能賦值, 如下語(yǔ)句不能通過(guò)編譯*/// var im iMap = mama.Print()// im.Print()var i interface {Print()} = mai.Print()s1 := []int{1, 2, 3}var s2 slices2 = s1s2.Print()
}

4. 類型強(qiáng)制轉(zhuǎn)換

由于 Go 是強(qiáng)類型的語(yǔ)言, 如果不滿足自動(dòng)轉(zhuǎn)換的條件,則必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換。任意兩個(gè)不相干的類型如果進(jìn)行強(qiáng)制轉(zhuǎn)換,則必須符合一定的規(guī)則。
強(qiáng)制類型的語(yǔ)法格式:

var a T = (T) (b)

使用括號(hào)將類型和要轉(zhuǎn)換的變量或表達(dá)式的值括起來(lái)。

非常量類型的變量 x 可以強(qiáng)制轉(zhuǎn)化并傳遞給類型 T , 需要滿足如下任一條件:
(1) x 可以直接賦值給 T 類型變量;
(2) x 的類型和 T 具有相同的底層類型;

繼續(xù)上一節(jié)使用的示例:

	/*im 與ma 雖然有相同的底層類型,但是二者中沒(méi)有一個(gè)是字面量類型,不能直接賦值,可以強(qiáng)制進(jìn)行類型轉(zhuǎn)換*/var im iMap = (iMap)(ma)

(3) x 的類型和 T 都是未命名的指針類型,并且指針指向的類型具有相同的底層類型;
(4) x 的類型和 T 都是整型,或者都是浮點(diǎn)型;
(5) x 的類型和 T 都是復(fù)數(shù)類型;
(6) x 是整數(shù)值或 []byte 類型的值, Tstring 類型;
(7) x 是一個(gè)字符串, T[]byte[]rune

字符串和字節(jié)切片之間的轉(zhuǎn)換最常見(jiàn),示例如下:

func main() {s := "hello,你好"var a []bytea = []byte(s)var b stringb = string(a)var c []runec = []rune(s)fmt.Printf("%T\n", a) // []uint8 	byte 是 int8 的別名fmt.Printf("%T\n", b) // stringfmt.Printf("%T\n", c) // []int32 	rune 是 int32 的別名
}

注意:

  1. 數(shù)值類型和 string 類型之間的相互轉(zhuǎn)換可能造成值部分丟失; 其他的轉(zhuǎn)換僅是類型的轉(zhuǎn)換,不會(huì)造成值的改變。

  2. string 和數(shù)字之間的轉(zhuǎn)換可使用標(biāo)準(zhǔn)庫(kù) strconv 。

  3. Go 語(yǔ)言沒(méi)有語(yǔ)言機(jī)制支持指針和 interger 之間的直接轉(zhuǎn)換,可以使用標(biāo)準(zhǔn)庫(kù)中的 unsafe 包進(jìn)行處理。

5. 類型別名和新聲明類型

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

package mainimport ("fmt"
)type a = string
type b stringfunc 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ò)誤:

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ù)中。由此可見(jiàn),使用類型別名定義的類型與原類型一致,而類型定義定義出來(lái)的類型,是一種新的類型。

5.1 類型別名

示例代碼:

package mainimport "fmt"func main() {// 示例1。{type MyString = stringstr := "BCD"myStr1 := MyString(str)myStr2 := MyString("A" + str)fmt.Printf("%T(%q) == %T(%q): %v\n", str, str, myStr1, myStr1, str == myStr1)fmt.Printf("%T(%q) > %T(%q): %v\n", str, str, myStr2, myStr2, str > myStr2)fmt.Printf("Type %T is the same as type %T.\n", myStr1, str)fmt.Println()strs := []string{"E", "F", "G"}myStrs := []MyString(strs)fmt.Printf("A value of type []MyString: %T(%q)\n", myStrs, myStrs)fmt.Printf("Type %T is the same as type %T.\n", myStrs, strs)fmt.Println()}
}

輸出結(jié)果:

string("BCD") == string("BCD"): true
string("BCD") > string("ABCD"): true
Type string is the same as type string.A value of type []MyString: []string(["E" "F" "G"])
Type []string is the same as type []string.

5.2 新聲明類型

示例代碼:

package mainimport "fmt"func main() {{type MyString stringstr := "BCD"myStr1 := MyString(str)myStr2 := MyString("A" + str)_ = myStr2// 這里的判等不合法,會(huì)引發(fā)編譯錯(cuò)誤。// invalid operation: str == myStr1 (mismatched types string and MyString)// fmt.Printf("%T(%q) == %T(%q): %v\n", str, str, myStr1, myStr1, str == myStr1)// 這里的比較不合法,會(huì)引發(fā)編譯錯(cuò)誤。// fmt.Printf("%T(%q) > %T(%q): %v\n", str, str, myStr2, myStr2, str > myStr2)fmt.Printf("Type %T is different from type %T.\n", myStr1, str)strs := []string{"E", "F", "G"}var myStrs []MyString// 這里的類型轉(zhuǎn)換不合法,會(huì)引發(fā)編譯錯(cuò)誤。// cannot convert strs (type []string) to type []MyString// myStrs = []MyString(strs)//fmt.Printf("A value of type []MyString: %T(%q)\n", myStrs, myStrs)fmt.Printf("Type %T is different from type %T.\n", myStrs, strs)fmt.Println()}}

5.3 類型別名和新聲明類型相互賦值

package mainfunc main() {{type MyString1 = stringtype MyString2 stringstr := "BCD"myStr1 := MyString1(str)myStr2 := MyString2(str)myStr1 = MyString1(myStr2)myStr2 = MyString2(myStr1)myStr1 = str// 這里的賦值不合法,會(huì)引發(fā)編譯錯(cuò)誤。// cannot use str (type string) as type MyString2 in assignment// myStr2 = str//myStr1 = myStr2 // 這里的賦值不合法,會(huì)引發(fā)編譯錯(cuò)誤。//myStr2 = myStr1 // 這里的賦值不合法,會(huì)引發(fā)編譯錯(cuò)誤。}
}

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

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

package mainimport ("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 方法。說(shuō)明給類型別名新增方法后,原類型也能使用這個(gè)方法。從示例中可知,變量 t 可以賦值給 S 類型變量 s,所以類型別名是給原類型取了一個(gè)小名,本質(zhì)上沒(méi)有發(fā)生任何變化。

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

cannot define new methods on non-local type string

參考書籍:

  1. Go 語(yǔ)言核心編程
  2. Go 語(yǔ)言圣經(jīng)
  3. Go語(yǔ)言快速入門

總結(jié)

以上是生活随笔為你收集整理的Go 学习笔记(32)— 类型系统(命名类型、未命名类型、底层类型、类型强制转换、类型别名和新声明类型)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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