繼Golang學習系列第二天:變數、常量、資料型別和流程陳述句之后,今天開始學習資料型別之高級型別: 派生型別,
學過java的人都知道,java其實就8種基本型別:byte、short、int、long、float、double、char、boolean,但它有參考資料型別:字串、陣列、集合、類、介面等,
而golang也有這樣的劃分,基本型別(Golang學習系列第二天已學過)和派生型別(不叫參考型別),派生型別有以下幾種:陣列型別、切片型別、Map型別、結構體型別(struct)、指標型別(Pointer)、函式型別、介面型別(interface)、Channel 型別,
1. 陣列型別
陣列是具有相同資料型別的元素序列, 陣列在宣告中定義了固定的長度,因此不能擴展超過該長度, 陣列宣告為
var variable_name [SIZE] variable_type
讓我們以代碼舉例如下
package main
import "fmt"
func main() {
var city [5]string
city[0] = "北京"
city[1] = "上海"
city[2] = "廣州"
city[3] = "深圳"
city[4] = "濮陽"
fmt.Println(city[0], city[1], city[2], city[3], city[4])
fmt.Println(city)
}
將變數city宣告為5個字串的陣列,執行輸出

?
不過也可以在宣告陣列時設定陣列條目,看簡潔版
package main
import "fmt"
func main() {
var city = [5]string{ "北京", "上海","廣州","深圳","濮陽"}
fmt.Println(city[0], city[1], city[2], city[3], city[4])
fmt.Println(city)
//簡寫版
othercity := [5]string{ "北京", "上海","廣州", "深圳","濮陽"}
fmt.Printf("%q", othercity)
}
輸出結果
?
甚至,當您傳遞值時,可以使用省略號來使用隱式長度
package main
import "fmt"
func main() {
var city = [5]string{ "北京", "上海","廣州","深圳","濮陽"}
fmt.Println(city[0], city[1], city[2], city[3], city[4])
fmt.Println(city)
//簡寫版
othercity := [5]string{ "北京", "上海","廣州", "深圳","濮陽"}
fmt.Printf("%q\n", othercity)
//隱士長度
other_city := [...]string{ "北京", "上海","廣州", "深圳","濮陽"}
fmt.Printf("%q", other_city)
}
輸出結果
?
以不同方式列印陣列
注意我們使用帶Printf的fmt包以及如何使用%q“動詞”來列印每個參考的元素,
如果我們使用Println或%s動詞,我們將得到不同的結果
package main
import "fmt"
func main() {
//不同方式列印陣列
other_city := [...]string{ "北京", "上海","廣州", "深圳","濮陽"}
fmt.Println("不同方式列印陣列")
fmt.Println(other_city)
fmt.Printf("%s\n", other_city)
fmt.Printf("%q", other_city)
}
執行后輸出如圖
?
多維陣列
您還可以創建多維陣列,示例代碼:
package main
import "fmt"
func main() {
var multi [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
multi[i][j] = i + j
}
}
fmt.Println("二維陣列: ", multi)
}
輸出結果![]()
?
2. 切片Slices型別
切片包裝陣列可為資料序列提供更通用,更強大和更方便的介面, 除了具有明確維數的項(例如轉換矩陣)外,Go中的大多數陣列編程都是使用切片而不是簡單陣列完成的,
切片包含對基礎陣列的參考,如果您將一個切片分配給另一個,則兩個切片都參考同一陣列, 如果函式采用slice引數,則對slice的元素所做的更改將對呼叫者可見,這類似于將指標傳遞給基礎陣列,
切片指向值陣列,并且還包含長度, 切片可以調整大小,因為它們只是另一個資料結構之上的包裝,
例如, []T is a slice with elements of type T 表示[] T是具有T型別元素的切片,
舉個小例子
package main
import "fmt"
func main() {
var city = []string{ "北京", "上海","廣州","深圳","濮陽"}
fmt.Println(city[0], city[1], city[2], city[3], city[4])
fmt.Println(city)
}
2.1 切分切片
可以對切片進行切片,以創建一個指向相同陣列的新切片值,運算式為
slice[start:end]
表示計算從start到end-1(含)的元素的一部分,
注意:start和end是表示索引的整數,
下面舉個小demo
package main
import "fmt"
func main() {
var city = []string{ "北京", "上海","廣州","深圳","濮陽","鄭洲"}
fmt.Println(city)
fmt.Println(city[1:4])
// missing low index implies 0
fmt.Println(city[:3])
// [2 3 5]
// missing high index implies len(s)
fmt.Println(city[4:])
}
輸出結果
?
2.2 制造切片
除了通過立即傳遞值(切片文字)來創建切片之外,還可以使用make關鍵字創建切片, 您創建一個特定長度的空切片,然后填充每個條目,
package main
import "fmt"
func main() {
fmt.Println("準備用make方式創建切片")
cities := make([]string, 3)
cities[0] = "鄭洲"
cities[1] = "濮陽"
cities[2] = "安陽"
fmt.Printf("%q", cities)
}
輸出結果:
?
它通過分配清零的陣列并回傳參考該陣列的切片來作業,
2.3 附加到切片
可以通過append方式附加值到切片中
package main
import "fmt"
func main() {
//附加到切片
other_cities := []string{}
other_cities = append(other_cities, "濮陽")
fmt.Println(other_cities)
}
看輸出結果
?
也可以同時附加多個值到切片中,示例代碼同時包括兩個城市鄭洲和濮陽
package main
import "fmt"
func main() {
//附加多值到切片
other_cities := []string{}
other_cities = append(other_cities, "鄭洲","濮陽")
fmt.Println(other_cities)
}
輸出結果
?
甚至您還可以使用省略號將切片附加到另一個切片
package main
import "fmt"
func main() {
fmt.Println("準備用make方式創建切片")
cities := make([]string, 3)
cities[0] = "鄭洲"
cities[1] = "濮陽"
cities[2] = "安陽"
fmt.Printf("%q\n", cities)
//附加切片到切片
other_cities := []string{"南京"}
other_cities = append(other_cities, cities...)
fmt.Println(other_cities)
}
輸出結果
?
注意: 省略號是該語言的內置功能,這意味著該元素是一個集合, 我們無法將字串型別([] string)型別的元素附加到字串切片中,只能附加字串, 但是,在切片后使用省略號(...),表示要附加切片的每個元素, 因為我們要從另一個片段追加字串,所以編譯器將接受操作,因為型別是匹配的,
您顯然無法將[] int型別的切片附加到[] string型別的另一個切片中,
2.4 復制切片
切片也可以復制, 在這里,我們創建一個與other_cities長度相同的空切片copycities,并從other_cities復制到copycities中,
package main
import "fmt"
func main() {
fmt.Println("準備用make方式創建切片")
cities := make([]string, 3)
cities[0] = "鄭洲"
cities[1] = "濮陽"
cities[2] = "安陽"
fmt.Printf("%q\n", cities)
//附加多值到切片
other_cities := []string{"南京"}
other_cities = append(other_cities, cities...)
fmt.Println(other_cities)
//拷貝切片
fmt.Println("準備用copy方式創建切片")
copycities := make([]string, len(other_cities))
copy(copycities, other_cities)
fmt.Println("copycities:", copycities)
}
輸出結果 
?
2.5 切片長度
您可以隨時使用len檢查切片的長度,
package main
import "fmt"
func main() {
fmt.Println("準備用make方式創建切片")
cities := make([]string, 3)
cities[0] = "鄭洲"
cities[1] = "濮陽"
cities[2] = "安陽"
fmt.Printf("%q\n", cities)
//附加多值到切片
other_cities := []string{"南京"}
other_cities = append(other_cities, cities...)
fmt.Println(other_cities)
//列印切片長度
fmt.Println(len(cities))
countries := make([]string, 42)
fmt.Println(len(countries))
}
輸出結果
?
2.6 零片
切片的零值為nil, 無切片的長度和容量為0,
package main
import "fmt"
func main() {
//零片
var temp []int
fmt.Println(temp, len(temp), cap(temp))
if temp == nil {
fmt.Println("nil!")
}
}
輸出結果
?
3. map型別
Map 是一種無序的鍵值對的集合,Map 最重要的一點是通過 key 來快速檢索資料,key 類似于索引,指向資料的值,
Map 是一種集合,所以我們可以像迭代陣列和切片那樣迭代它,不過,Map 是無序的,我們無法決定它的回傳順序,這是因為 Map 是使用 hash 表來實作的,
package main
import "fmt"
func main() {
users := map[string]int{
"admin": 0,
"donguangming": 1,
"student": 2,
}
fmt.Printf("%#v", users)
}
輸出結果
?
當不使用上述map時,使用前必須使用make(不是新的)創建map, nil映射為空,無法分配給它,
package main
import "fmt"
type User struct {
name string
age int
city string
}
var user map[string]User
func main() {
//通過make創建
user = make(map[string]User)
user["dgm"] = User{"董廣明", 99, "南京"}
fmt.Println(user["dgm"])
}
輸出結果
?
3.1 操作map
3.1.1 在map中插入或更新元素
m[key] = elem
3.1.2 查詢一個元素
elem = m[key]
3.1.3 洗掉一個元素
delete(m, key)
3.1.4 測驗鍵是否存在并且有值
elem, ok = m[key]
如果鍵在m中,則確定為true, 如果不是,則ok為false,elem為map元素型別的零值, 同樣,從map中讀取時(如果沒有按鍵)則結果是map元素型別的零值,
綜上合起來代碼如下
package main
import "fmt"
type User struct {
name string
age int
city string
}
var user map[string]User
func main() {
user = make(map[string]User)
//賦值
user["dgm"] = User{"董廣明", 99, "南京"}
fmt.Println("user:", user)
//查詢
fmt.Println(user["dgm"])
//洗掉
delete(user, "dgm")
fmt.Println("此時user:", user)
//測驗鍵
_, u := user["dgm"]
fmt.Println("u:", u)
var users = map[string]User{
"dmg": {"董廣明", 99, "南京"},
"dongguangming": {"董廣明", 88, "南京"},
}
fmt.Println(users)
}
輸出結果
?
3.2 map回圈
如何在map上進行迭代?您可以使用回圈范圍來迭代map, 因為映射是無序集合,所以此回圈的值可能會有所不同,
package main
import "fmt"
type User struct {
name string
age int
city string
}
var user map[string]User
func main() {
var users = map[string]User{
"dmg": {"董廣明", 99, "南京"},
"dongguangming": {"董廣明", 88, "南京"},
}
fmt.Println(users)
/*使用鍵輸出map鍵值 */
for username := range users {
fmt.Println(username, "用戶=", users [username])
}
}
輸出結果
?
4. 指標型別(Pointer)
學過C(上學時第一門編程語言就是C)的人都知道, 指標是存放值記憶體地址的地方, 指標由*定義,根據資料型別定義指標,go也是這么玩的,宣告格式如下
var var_name *var-type
var-type 為指標型別,var_name 為指標變數名,* 號用于指定變數是作為一個指標,
&運算子可用于獲取變數的地址,比如
var ap *int
a := 12
ap = &a
而指標指向的值可以使用*運算子進行訪問,示例如下
package main
import "fmt"
func main() {
var age int= 99 /* 宣告實際變數 */
var ip *int /* 宣告指標變數 */
ip = &age /* 指標變數的存盤地址 */
fmt.Printf("age 變數的地址是: %x\n", &age )
/* 指標變數的存盤地址 */
fmt.Printf("ip 變數儲存的指標地址: %x\n", ip )
/* 使用指標訪問值 */
fmt.Printf("*ip指標變數的值: %d\n", *ip )
}
執行以上代碼,輸出結果 
?
4.1 空指標
當一個指標被定義后沒有分配到任何變數時,它的值為 nil,nil 指標也稱為空指標,
nil在概念上和其它編程語言的null、None、nil、NULL一樣,都指代零值或空值,
一個指標變數通常縮寫為 ptr,空指標判斷方式:
if(ptr != nil) /* ptr 不是空指標 */
if(ptr == nil) /* ptr 是空指標 */
綜上示例代碼如下
package main
import "fmt"
func main() {
var age int =99 /* 宣告實際變數 */
var ptr *int /* 宣告指標變數 */
var other_ptr *int /* 宣告指標變數 */
ptr = &age /* 指標變數的存盤地址 */
fmt.Printf("age 變數的地址是: %x\n", &age )
/* 使用指標訪問值 */
fmt.Printf("*ptr指標變數的值: %d\n", *ptr )
if(ptr != nil) {
/* ptr 不是空指標 */
fmt.Printf("ptr指標變數儲存的指標地址: %x\n", ptr )
}
if(other_ptr == nil) {
/* other_ptr 是空指標 */
fmt.Printf("other_ptr指標變數儲存的指標地址: %x\n", other_ptr )
}
}
輸出結果
?
4.2 指標陣列
指標陣列:簡單點說它是一個陣列,陣列里面的每個元素都是指標,陣列占多少個位元組由陣列本身決定,它是“儲存指標的陣列”的簡稱,格式如下
var ptr [MAX]* type;
ptr 為type指標陣列,因此每個元素都指向了一個值,只是它的值是指標,
package main
import "fmt"
const MAX int = 5
func main() {
var city = [MAX]string{ "北京", "上海","廣州","深圳","濮陽"}
var i int
var othercity [MAX]*string;
for i = 0; i < MAX; i++ {
othercity[i] = &city[i] /* 字串地址賦值給指標陣列 */
}
for i = 0; i < MAX; i++ {
fmt.Printf("指標陣列:索引:%d 值:%s 值的記憶體地址:%d\n", i, *othercity[i] , othercity[i] )
}
}
輸出結果
?
4.3 指標的指標
如果一個指標變數存放的又是另一個指標變數的地址,則稱這個指標變數為指向指標的指標變數,
當定義一個指向指標的指標變數時,第一個指標存放第二個指標的地址,第二個指標存放變數的地址,
指向指標的指標變數宣告格式如下:
var ptr **type;
表示指向指標的指標變數為type,訪問指向指標的指標變數值需要使用兩個 * 號,
package main
import "fmt"
func main() {
var age int
var ptr *int
var pptr **int
age = 99
/* 指標 ptr 地址 */
ptr = &age
/* 指向指標 ptr 地址 */
pptr = &ptr
/* 獲取 pptr 的值 */
fmt.Printf("變數 age = %d\n", age )
fmt.Printf("指標變數 *ptr = %d,記憶體地址是:%d\n", *ptr, ptr )
fmt.Printf("指向指標的指標變數 **pptr = %d,記憶體地址是:%d\n", *pptr, pptr)
}
輸出結果
?
4.4 通過new函式創建指標
go支持通過new函式創建指標, new函式將型別作為引數,并回傳指向新分配的作為引數傳遞的型別的零值的指標,
package main
import "fmt"
func main() {
//通過new函式創建指標
size := new(int)
fmt.Printf("size的默認值= %d, 型別是: %T, 地址是: %v\n", *size, size, size)
*size = 99
fmt.Println("更改后新的值是:", *size)
}
輸出結果
?
4.5 指標引數
可以將指標傳遞給函式
package main
import "fmt"
func changeValue(val *int) {
*val = 66
}
func main() {
var age int
var ptr *int
var pptr **int
age = 99
/* 指標 ptr 地址 */
ptr = &age
/* 指向指標 ptr 地址 */
pptr = &ptr
/* 獲取 pptr 的值 */
fmt.Printf("變數 age = %d\n", age )
fmt.Printf("指標變數 *ptr = %d,記憶體地址是:%d\n", *ptr, ptr )
fmt.Printf("指向指標的指標變數 **pptr = %d,記憶體地址是:%d\n", *pptr, pptr)
//函式呼叫
changeValue(ptr)
fmt.Printf("變數age更改后的值 = %d\n", age )
//通過new函式創建指標
size := new(int)
fmt.Printf("size的默認值= %d, 型別是: %T, 地址是: %v\n", *size, size, size)
*size = 99
fmt.Println("更改后新的值是:", *size)
}
輸出結果
?
特別注意這這兩種傳參的區別
func change(val int) {
val = 88
}
func changeValue(val *int) {
*val = 66
}

?
5. 函式型別
函式是基本的代碼塊,用于執行一個任務,
Go 語言最少有個 main() 函式,
你可以通過函式來劃分不同功能,邏輯上每個函式執行的是指定的任務,
函式宣告告訴了編譯器函式的名稱,引數和回傳型別,格式如下
func function_name( [parameter list] ) [return_types] {
//函式體
}
函式定義決議:
- func:函式由關鍵字 func 開始宣告
- function_name:函式名稱,函式名和引數串列一起構成了函式簽名,
- parameter list:引數串列,引數就像一個占位符,當函式被呼叫時,你可以將值傳遞給引數,這個值被稱為實際引數,引數串列指定的是引數型別、順序、及引數個數,引數是可選的,也就是說函式也可以不包含引數,
- return_types:回傳型別,函式回傳一列值,return_types 是該列值的資料型別,有些功能不需要回傳值,這種情況下 return_types 不是必須的,
- 函式體:函式定義的代碼集合,
5.1 無參無回傳值函式
package main
import (
"fmt"
)
func hello(){
fmt.Println("Hello World")
}
func main() {
fmt.Println("函式開始了")
hello()
}
5.2 有參無回傳值函式
package main
import (
"fmt"
)
func hello(message string){
fmt.Println(message)
}
func main() {
fmt.Println("函式開始了")
hello("hello world")
}
5.3 無參有回傳值函式
package main
import (
"fmt"
)
func hello() string {
return "Hello world"
}
func main() {
fmt.Println("函式開始了")
greeting := hello()
fmt.Println(greeting)
}
5.4 有參有回傳值函式
package main
import (
"fmt"
)
func hello(message string) string {
return message
}
func main() {
fmt.Println("函式開始了")
greeting := hello("hello world")
fmt.Println(greeting)
}
5.5 多個引數多回傳值函式
package main
import (
"fmt"
)
func hello(name string, age int) (string, int) {
return name, age
}
func main() {
fmt.Println("函式開始了")
name, age := hello("董廣明", 99)
fmt.Printf("name=%s, age = %d\n", name,age )
}
5.6 在函式中預定義回傳值的函式
package main
import (
"fmt"
)
func hello() (message string) {
message = "hello world!"
return
}
func main() {
fmt.Println("函式開始了")
greeting := hello()
fmt.Printf(greeting)
}
message被定義為回傳變數, 因此,定義的變數message將自動回傳,而無需在最后的return陳述句中定義,
5.7 舍棄回傳值的函式
package main
import (
"fmt"
)
func hello() (string, string) {
return "hello world!", "hahaha"
}
func main() {
fmt.Println("函式開始了")
greeting, _ := hello()
fmt.Printf(greeting)
}
5.8 指標傳遞函式
package main
import (
"fmt"
)
func change(name *string) {
*name = "dongguangming"
}
func main() {
fmt.Println("函式開始了")
name := "董廣明"
fmt.Println(name)
change(&name)
fmt.Printf(name)
}
會改變原來的值,這很容易理解

?
5.9 可變引數函式
您可以使用Golang中的…運算子來傳遞陣列,也可以使用相同的…運算子來接收引數,
package main
import (
"fmt"
)
func print(items ...string) {
for _, v := range items {
fmt.Println(v)
}
}
func main() {
fmt.Println("函式開始了")
print("董廣明", "dongguangming", "dmg")
list := []string{"Hello", "World"}
print(list...) // An array argument
}
實際輸出結果
?
5.10 匿名函式
沒有名字的函式被稱為匿名函式
package main
import (
"fmt"
)
func main() {
fmt.Println("函式開始了")
func () {
fmt.Println("我是一個匿名函式")
} ()
}
6. 結構體Struct
golang的世界里沒有像java一樣有class類的概念,但它有像C語言一樣的結構體Struct,
結構體是不同欄位的型別化集合,結構體用于將資料分組在一起,
例如,如果我們要對User型別的資料進行分組,則定義一個user的屬性,其中可以包括姓名,年齡,性別,所在城市, 可以使用以下語法定義結構
package main
import "fmt"
type User struct {
name string
age int
gender string
city string
}
func main() {
fmt.Println("結構體開始了")
user := User{name: "董廣明", age: 31, gender: "man", city: "南京"}
fmt.Println(user)
//簡寫
u := User{"董廣明", 31, "man", "南京"}
fmt.Println(u)
//通過指標訪問
u1 := &User{"董廣明", 31, "man", "南京"}
fmt.Println(u1.name)
}
輸出結果
?
6.1 方法
方法是帶有接收器的一種特殊函式, 接收者可以是值或指標,
package main
import "fmt"
type User struct {
name string
age int
gender string
city string
}
// 方法定義
func (u *User) describe() {
fmt.Printf("%v 今年 %v歲了\n", u.name, u.age)
}
//指標引數
func (u *User) setAge(age int) {
u.age = age
}
//值引數
func (u User) setName(name string) {
u.name = name
}
func main() {
fmt.Println("結構體開始了")
user := User{name: "董廣明", age: 31, gender: "man", city: "南京"}
fmt.Println(user)
user.describe()
user.setAge(99)
fmt.Println(user.age)
user.setName("dongguangming")
fmt.Println(user.name)
user.describe()
}
輸出結果
?
如圖可以看到,現在可以使用點運算子user.describe呼叫該方法, 注意,接收者是一個指標, 使用指標,我們傳遞了對該值的參考,因此,如果對方法進行任何更改,它將反映在接收器user中,它也不會創建該物件的新副本,從而節省了記憶體,
請注意,在上面的示例中,age的值已更改,而name的值未更改,因為方法setName是接收者型別,而setAge是指標型別,
6.2 結構體域欄位匯出
如果欄位名以大寫字母開頭,則定義該結構的包外部的代碼將可對其進行讀寫, 如果該欄位以小寫字母開頭,則只有該結構包中的代碼才能讀取和寫入該欄位,
package main
import "fmt"
type User struct {
Name string
Type string
password string
}
func main() {
u := User{
Name: "董廣明",
Type: "1",
password: "secret",
}
fmt.Println(u.Name, " 級別:", u.Type)
fmt.Println("密碼是:", u.password)
}
在同一包中,我們可以訪問這些欄位,如本示例所示, 由于main也在主程式包中,因此它可以參考u.password并檢索存盤在其中的值, 通常在結構體中具有未匯出的欄位,并通過匯出的方法來訪問它們,類比java里的訪問權限private域,public訪問方法,
以上程式輸出結果
董廣明 級別: 1
密碼是: secret
6.3 創建匿名結構體
匿名僅創建新的結構變數,而不定義任何新的結構體型別,
package main
import "fmt"
func main() {
emp := struct {
firstName, lastName string
age, salary int
}{
firstName: "董",
lastName: "廣明",
age: 31,
salary: 5000,
}
fmt.Println("員工", emp)
}
以上程式代碼中,定義了一個匿名結構變數emp, 正如我們已經提到的,該結構稱為匿名結構,因為它僅創建一個新的結構變數emp,而不定義任何新的結構型別,
輸出結果
員工 {董 廣明 31 5000}
6.4 嵌套結構體
結構可能包含一個欄位,而該欄位又是一個結構, 這些型別的結構稱為嵌套結構,
比如我們經常網上購物時,填寫訂單接收地址(家里,公司,寄存點)
package main
import (
"fmt"
)
type Address struct {
city, state string
}
type Person struct {
name string
age int
address Address
}
func main() {
var p Person
p.name = "董廣明"
p.age = 31
p.address = Address {
city: "南京某街道",
state: "第一識訓地址",
}
fmt.Println("名字:", p.name)
fmt.Println("年齡:",p.age)
fmt.Println("識訓地址:",p.address.city)
fmt.Println("是否第一:",p.address.state)
}
上面程式中的Person結構具有一個欄位地址address,該欄位地址又是一個結構體,
6.5 指向結構體的指標
您可以使用&運算子獲取指向結構體的指標
package main
import "fmt"
type User struct {
name string
age int
gender string
city string
}
func main() {
fmt.Println("結構體開始了")
user := User{name: "董廣明", age: 31, gender: "man", city: "南京"}
fmt.Println(user)
// Pointer to the user struct
user_point := &user
fmt.Println(user_point)
// Accessing struct fields via pointer
fmt.Println((*user_point).name)
fmt.Println(user_point.name) // Same as above: No need to explicitly dereference the pointer
user_point.age = 99
fmt.Println(user_point)
}
以上示例所示,Go使您可以直接通過指標訪問結構體的欄位,
輸出結果
?

?
6.6 結構體是值型別
結構體是值型別,當您將一個結構體變數分配給另一個時,將創建并分配該結構的新副本, 同樣,當您將結構體傳遞給另一個函式時,該函式將獲得其自己的結構副本,
package main
import "fmt"
type User struct {
name string
age int
gender string
city string
}
func main() {
fmt.Println("結構體開始了")
user := User{name: "董廣明", age: 31, gender: "man", city: "南京"}
user2 := user // A copy of the struct `user` is assigned to `user2`
fmt.Println("user = ", user)
fmt.Println("user2 = ", user2)
//
user2.name = "dongguangming"
user2.age = 99
user2.city ="上海"
fmt.Println("\n更改user2:")
fmt.Println("user = ", user)
fmt.Println("user2 = ", user2)
}
輸出結果
?
7. 介面Interface型別
Go編程提供了另一種稱為介面的資料型別,它表示一組方法簽名,
struct資料型別實作介面中定義的方法,
package main
import "fmt"
import "math"
type Shape interface {
area() float64
}
type Rectangle struct{
height float64
width float64
}
type Circle struct{
radius float64
}
func (r Rectangle) area() float64 {
return r.height * r.width
}
func (c Circle) area() float64 {
return math.Pi * math.Pow(c.radius, 2)
}
func getArea(shape Shape) float64{
return shape.area()
}
func main() {
fmt.Println("介面開始了")
rect := Rectangle{20, 50}
circ := Circle{4}
fmt.Println("長方形面積 =", getArea(rect))
fmt.Println("圓的面積 =", getArea(circ))
}
輸出結果
?
8. Channels型別
通道是一種型別化的管道,可以使用通道運算子<-發送和接收值,
channel<- value // 發送value值到通道channel
value := <-channel // 從通道查詢,并把值賦給value
注意:資料按箭頭方向流動
和其他資料型別類似,通道使用前必須先創建,其初始值是 nil,創建通道的語法格式如下:
var c1 chan [value type]
c1 = make([channel type] [value type], [capacity])
- [value type] 定義的是 Channel 中所傳輸資料的型別,
- [channel type] 定義的是 Channel 的型別,其型別有以下三種:
- “chan” 可讀可寫——“chan int” 則表示可讀寫 int 資料的 channel
- "chan<-" 僅可寫——“chan<- float64” 則表示僅可寫64位 float 資料的 channel
- “<-chan” 僅可讀——“<-chan int” 則表示僅可讀 int 資料的 channel
- [capacity] 是一個可選引數,其定義的是 channel 中的快取區 (buffer),如果不填則默認該 channel 沒有緩沖區 (unbuffered),對于沒有緩沖區的 channel,訊息的發送和收取必須能同時完成,否則會造成阻塞并提示死鎖錯誤,
比如我們想創建了一個讀寫 int 型別,buffer 長度 100 的 channel c1,則如下:
var c1 chan int
c1 = make(chan int, 100)
通過此通道,我們可以發送int型別的資料, 我們可以在此通道中發送和接收資料
package main
import "fmt"
func main() {
ch := make(chan int)
go func() { ch <- 31 }()
age := <-ch
fmt.Println(age)
}
輸出結果:
?
接收方通道等待,直到發送方將資料發送到通道,
8.1 單向通道
在有些情況下,我們希望通過通道接收資料但不發送資料, 為此我們還可以創建一個單向通道, 讓我們看一個簡單的例子:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go sc(ch)
fmt.Println(<-ch)
}
func sc(ch chan<- string) {
ch <- "你好,董廣明"
}
在上面的示例代碼中,sc是go協程,該例程只能將訊息發送到通道,但不能接收訊息,
執行代碼輸出結果
$go run main.go
你好,董廣明
8.2 快取通道(Buffered channel)
在Golang中可以創建一個緩沖通道, 對于緩沖的通道,如果緩沖區已滿,則將阻止發送到該通道的訊息, 讓我們看一個小例子
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "hello"
ch <- "world"
ch <- "!!!" // 超了緩沖最大值要報錯
fmt.Println(<-ch)
}
實際上超過緩沖最大值,要報錯

?
那怎么辦呢,還好有以下解決方法
package main
import "fmt"
func main() {
ch := make(chan string, 2)
ch <- "hello"
ch <- "world"
//創建匿名函式
function := func(name string) { ch <- name }
//
go function("董廣明")
go function("donguangming")
go function("dgm")
go function("dgmdgm")
go function("3dgm")
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
輸出結果
?
測驗結果

?
總結: golang就是綜合性編程語言,幸好以前做過java、pyhon、JavaScript開發,上學時又學過C語言(第一編程語言),故學起golang很快,極少部分是golang本身特有的,
一句話概括:golang是幾種語言的混合體,外加自己的特性,如果不考慮語言本身,你會覺得像是在寫C,有時又像是寫python,寫函式時又像JavaScript中函式的變體,
參考:
-
Golang Tutorial?—?from zero to hero https://milapneupane.com.np/2019/07/06/learning-golang-from-zero-to-hero/
-
Understanding Maps in Go https://www.digitalocean.com/community/tutorials/understanding-maps-in-go
-
Golang Maps https://www.geeksforgeeks.org/golang-maps/
-
Golang Tutorial – Learn Golang by Examples https://www.edureka.co/blog/golang-tutorial/#map
-
The anatomy of Slices in Go https://medium.com/rungo/the-anatomy-of-slices-in-go-6450e3bb2b94
-
GoLang Tutorial - Structs and receiver methods - 2020 https://www.bogotobogo.com/GoLang/GoLang_Structs.php
-
Golang Cheatsheet: Functions https://ado.xyz/blog/golang-cheatsheet-functions/
-
Ultimate Guide to Go Variadic Functions https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Fblog.learngoprogramming.com%2Fgolang-variadic-funcs-how-to-patterns-369408f19085
-
Golang Methods Tutorial with Examples https://www.callicoder.com/golang-methods-tutorial/
-
Go Best Practices: Should you use a method or a function? https://flaviocopes.com/golang-methods-or-functions/
-
Methods that satisfy interfaces in golang https://suraj.io/post/golang-methods-interfaces/
-
Pass by pointer vs pass by value in Go https://goinbigdata.com/golang-pass-by-pointer-vs-pass-by-value/
-
Go Data Structures: Interfaces https://research.swtch.com/interfaces
-
How to Define and Implement a Go Interface https://code.tutsplus.com/tutorials/how-to-define-and-implement-a-go-interface--cms-28962
-
Methods and Interfaces in Go https://dev-pages.info/golang-interfaces/
-
Go (Golang) - understanding the object oriented features with structs, methods, and interfaces https://unixsheikh.com/articles/go-understanding-the-object-oriented-features-with-structs-methods-and-interfaces.html#interfaces-in-go
-
理解 Golang 的 Channel 型別 https://studygolang.com/articles/25805
-
Anatomy of Channels in Go - Concurrency in Go https://medium.com/rungo/anatomy-of-channels-in-go-concurrency-in-go-1ec336086adb
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/3657.html
標籤:Go
上一篇:Go的100天之旅-05常量
