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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

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

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

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

1.1 命名類型

類型可以通過標識符來表示,這種類型稱為命名類型( Named Type )。 Go 語言的基本類型中有 20 個預聲明簡單類型都是命名類型, Go 語言還有一種命名類型一一用戶自定義類型。

1.2 未命名類型

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

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

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

注意:前面所說的結構和接口是未命名類型,這里的結構和接口沒有使用 type 格式定義,具體見下方示例說明。

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 語言的命名類型和未命名類型總結如下:

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

2. 底層類型

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

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

例如:

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

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

底層類型在類型賦值和類型強制轉換時會使用,接下來就介紹這兩個主題。

3. 類型相同和類型賦值

3.1 類型相同

Go 是強類型的語言,編譯器在編譯時會進行嚴格的類型校驗。兩個命名類型是否相同,參考如下:

  1. 兩個命名類型相同的條件是兩個類型聲明的語句完全相同;
  2. 命名類型和未命名類型永遠不相同;
  3. 兩個未命名類型相同的條件是它們的類型聲明字面量的結構相同,井且內部元素的類型相同;
  4. 通過類型別名語句聲明的兩個類型相同;

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

3.2 類型賦值

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

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

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

  1. T1T2 類型相同;
  2. T1T2 具有相同的底層類型,并且 T1T2 里面至少有一個是未命名類型;
  3. T2 是接口類型, T1 是具體類型, T1 的方法集是 T2 方法集的超集;
  4. T1T2 都是通道類型,它們擁有相同的元素類型,并且 T1T2 中至少有一個是未命名類型;
  5. T1是預聲明標識符 nilT2pointerfuncitionslicemapchannelinterface 類型中的一個;
  6. a 是一個字面常量值,可以用來表示類型 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,但它們中沒有一個是未命名類型不能賦值, 如下語句不能通過編譯*/// var im iMap = mama.Print()// im.Print()var i interface {Print()} = mai.Print()s1 := []int{1, 2, 3}var s2 slices2 = s1s2.Print()
}

4. 類型強制轉換

由于 Go 是強類型的語言, 如果不滿足自動轉換的條件,則必須進行強制類型轉換。任意兩個不相干的類型如果進行強制轉換,則必須符合一定的規則。
強制類型的語法格式:

var a T = (T) (b)

使用括號將類型和要轉換的變量或表達式的值括起來。

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

繼續上一節使用的示例:

	/*im 與ma 雖然有相同的底層類型,但是二者中沒有一個是字面量類型,不能直接賦值,可以強制進行類型轉換*/var im iMap = (iMap)(ma)

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

字符串和字節切片之間的轉換最常見,示例如下:

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. 數值類型和 string 類型之間的相互轉換可能造成值部分丟失; 其他的轉換僅是類型的轉換,不會造成值的改變。

  2. string 和數字之間的轉換可使用標準庫 strconv

  3. Go 語言沒有語言機制支持指針和 interger 之間的直接轉換,可以使用標準庫中的 unsafe 包進行處理。

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

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

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)//錯誤參數傳遞,str是字符串類型,不能賦值給b類型變量SayB(str)
}

這段代碼在編譯時會出現如下錯誤:

cannot use str (type string) as type b in argument to SayB

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

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()}
}

輸出結果:

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// 這里的判等不合法,會引發編譯錯誤。// invalid operation: str == myStr1 (mismatched types string and MyString)// 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 different from type %T.\n", myStr1, str)strs := []string{"E", "F", "G"}var myStrs []MyString// 這里的類型轉換不合法,會引發編譯錯誤。// 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// 這里的賦值不合法,會引發編譯錯誤。// cannot use str (type string) as type MyString2 in assignment// myStr2 = str//myStr1 = myStr2 // 這里的賦值不合法,會引發編譯錯誤。//myStr2 = myStr1 // 這里的賦值不合法,會引發編譯錯誤。}
}

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

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

package mainimport ("fmt"
)// 根據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")
}// 函數參數接收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 方法。說明給類型別名新增方法后,原類型也能使用這個方法。從示例中可知,變量 t 可以賦值給 S 類型變量 s,所以類型別名是給原類型取了一個小名,本質上沒有發生任何變化。

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

cannot define new methods on non-local type string

參考書籍:

  1. Go 語言核心編程
  2. Go 語言圣經
  3. Go語言快速入門

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。