Golang 學習系列第三天:學習陣列、切片、Map、結構體、指標、函式、介面型別、channel 通道

dongguangming發表於2020-07-15

Golang學習系列第二天之後,今天開始學習資料型別之高階型別: 派生型別。

學過java的人都知道,java其實就8種基本型別:byte、short、int、long、float、double、char、boolean,但它有引用資料型別:字串、陣列、集合、類、介面等。

而golang也有這樣的劃分,基本型別(Golang學習系列第二天已學過)和派生型別(不叫引用型別),派生型別有以下幾種:陣列型別、切片型別、Map型別、結構體型別(struct)、指標型別(Pointer)、函式型別、介面型別(interface)、Channel 型別。

陣列是具有相同資料型別的元素序列。 陣列在宣告中定義了固定的長度,因此不能擴充套件超過該長度。 陣列宣告為

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

輸出結果

切片包裝陣列可為資料序列提供更通用,更強大和更方便的介面。 除了具有明確維數的項(例如轉換矩陣)外,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!")
    }
}

輸出結果

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

輸出結果

學過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
}

函式是基本的程式碼塊,用於執行一個任務。

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("我是一個匿名函式")
    } ()
 }

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

輸出結果

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

輸出結果

通道是一種型別化的管道,可以使用通道運算子<-傳送和接收值。

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測驗結果評分

  1. Golang Tutorial — from zero to hero milapneupane.com.np/2019/07/06/lea...

  2. Understanding Maps in Go www.digitalocean.com/community/tut...

  3. Golang Maps www.geeksforgeeks.org/golang-maps/

  4. Golang Tutorial – Learn Golang by Examples www.edureka.co/blog/golang-tutoria...

  5. The anatomy of Slices in Go medium.com/rungo/the-anatomy-of-sl...

  6. GoLang Tutorial - Structs and receiver methods - 2020 www.bogotobogo.com/GoLang/GoLang_S...

  7. Golang Cheatsheet: Functions ado.xyz/blog/golang-cheatsheet-fun...

  8. Ultimate Guide to Go Variadic Functions medium.com/m/global-identity?redir...

  9. Golang Methods Tutorial with Examples www.callicoder.com/golang-methods-...

  10. Go Best Practices: Should you use a method or a function? flaviocopes.com/golang-methods-or-...

  11. Methods that satisfy interfaces in golang suraj.io/post/golang-methods-inter...

  12. Pass by pointer vs pass by value in Go goinbigdata.com/golang-pass-by-poi...

  13. Go Data Structures: Interfaces research.swtch.com/interfaces

  14. How to Define and Implement a Go Interface code.tutsplus.com/tutorials/how-to...

  15. Methods and Interfaces in Go dev-pages.info/golang-interfaces/

  16. 理解 Golang 的 Channel 型別 studygolang.com/articles/25805

  17. Anatomy of Channels in Go - Concurrency in Go medium.com/rungo/anatomy-of-channe...

本作品採用《CC 協議》,轉載必須註明作者和本文連結

人生,不應設限

相關文章