1. 程式人生 > 實用技巧 >Golang基礎語法

Golang基礎語法

基本資料型別

資料型別

  • bit計算機內部資料儲存最小單位
  • byte 計算機中資料處理的基本單位
  • 計算機中以位元組位單位儲存和解釋資訊,規定一個位元組由八個二進位制位構成, 即一個位元組等於8個位元(1Byte=8bit)

檢視變數的資料型別

    var n = 100
    fmt.Printf("n 的資料型別是 %T", n)

檢視變數的位元組大小

    var n = 100
    fmt.Printf("n 的位元組大小是 %d byte", unsafe.Sizeof(n))

整數型別

整型分為以下兩個大類:

  • 按長度分為:int8、int16、int32、int64
  • 還有對應的無符號整型:uint8、uint16、uint32、uint64
型別 有無符號 佔用儲存空間 表數範圍 備註
int 32byte/64byte 根據計算機架構決定
int8 1byte=8bit -128 ~ 127
int16 2byte=16bit -32768 ~ 32767
int32 4byte=32bit -2147483648 ~ 2147483647
int64 8byte=64bit -9223372036854775808 ~ 9223372036854775807
uint 32byte/64byte 根據計算機架構決定
uint8 1byte=8bit 0 ~ 255
uint16 1byte=16bit 0 ~ 65535
int32 4byte=32bit 0 ~ 4294967295
int64 8byte=64bit 0 ~ 18446744073709551615
uintptr 沒有指定具體的bit大小 用於存放一個指標
  • uintptr型別只有在底層程式設計是才需要,特別是Go語言和C語言函式庫或作業系統介面相互動的地方。

  • 其中,uint8 就是我們熟知的 byte 型,int16 對應C語言中的 short 型,int64 對應C語言中的 long 型。

  • Go 語言也有自動匹配特定平臺整型長度的型別—— int 和 uint。

  • 邏輯對整型範圍沒有特殊需求時可以使用 int 和 uint。反之,在二進位制傳輸、讀寫檔案的結構描述時,為了保持檔案的結構不會受到不同編譯目標平臺位元組長度的影響,不要使用 int 和 uint。

  • 整型變數在使用時,遵守保小不保大的原則,即:在保證程式正確執行下,儘量使用佔用空間小的資料型別。

byte與rune

byte與rune都屬於別名型別。byte是uint8的別名型別,而rune是int32的別名型別。
一個rune的型別值即可表示一個Unicode字元。一個Unicode程式碼點通常由"U+"和一個以十六進位制表示法表示的整數表示,例如英文字母'A'的Unicode程式碼點為"U+0041"。
byte 等同於int8,常用來處理ascii字元
rune 等同於int32,常用來處理unicode或utf-8字元

型別 描述 用途
byte 類似 uint8 常用來處理ascii字元
rune 類似 int32 常用來處理unicode或utf-8字元

浮點型別

Go語言支援兩種浮點型數:float32 和 float64。這兩種浮點型資料格式遵循 IEEE 754 標準:

  • float32 的浮點數的最大範圍約為 3.4e38,可以使用常量定義:math.MaxFloat32。
  • float64 的浮點數的最大範圍約為 1.8e308,可以使用一個常量定義:math.MaxFloat64。
  • 浮點型的儲存分為三部分:符號位+指數位+尾數位,在儲存過程中,尾數部分可能丟失,造成精度損失
  • golang的浮點型預設為float64型別
  • 通常情況下,應該使用float64,因為它比float32更精確
  • 0.123可以簡寫成.123,也支援科學計數法表示:5.1234e2 等價於512.34
型別 描述 精度
float32 IEEE-754 32位浮點型數 單精度
float64 IEEE-754 64位浮點型數 雙精度

列印浮點數時,可以使用 fmt 包配合動詞%f,程式碼如下:

    package main
    import (
            "fmt"
            "math"
    )
    func main() {
            fmt.Printf("%f\n", math.Pi)     //按預設寬度和精度輸出整型。
            fmt.Printf("%.2f\n", math.Pi)   //按預設寬度,2 位精度輸出(小數點後的位數)。
    }

複數型別

複數型別有兩個:complex64和complex128。實際上,complex64型別的值會由兩個float32型別的值分別表示複數的實數部分和虛數部分。而complex128型別的值會由兩個float64型別的值表示複數的實數部分和虛數部分。
複數型別的值一般由浮點數表示的實數部分、加號"+"、浮點數表示的虛數部分以及小寫字母"i"組成,比如3.9E+1 + 9.99E-2i。

型別 結構
complex32 float32實部+虛部
complex64 float64實部+虛部

字元型別

Golang中沒有專門的字元型別,如果要儲存單個字元(字母),一般使用byte來儲存。
字串就是一串固定長度的字元連線起來的字元序列。Go的字串是由單個位元組連線起來的,也就是說對於傳統的字串是由字元組成的,而Go的字串不同,它是由位元組組成的。

  • 字元只能被單引號包裹,不能用雙引號,雙引號包裹的是字串
  • 當我們直接輸出type值時,就是輸出了對應字元的ASCII碼值
  • 當我們希望輸出對應字元,需要使用格式化輸出
  • Go語言的字元使用UTF-8編碼,英文字母佔一個字元,漢字佔三個字元
  • 在Go中,字元的本質是一個整數,直接輸出時,是該字元對應的UTF-8編碼的碼值。
  • 可以直接給某個變數賦一個數字,然後按格式化輸出時%c,會輸出該數字對應的unicode字元
  • 字元型別是可以運算的,相當於一個整數,因為它們都有對應的unicode碼

但是如果我們儲存的字元大於255,比如儲存漢字,這時byte型別就無法儲存,此時可以使用uint或int型別儲存

字元型別本質探討

字元型儲存到計算機中,需要將字元對應的碼值(整數)找出來
儲存:字元 --> 碼值 --> 二進位制 --> 儲存
讀取: 二進位制 -->碼值 --> 字元 --> 讀取

字元和碼值的對應關係是通過字元編碼表決定的(是規定好的)
Go語言的編碼都統一成了UTF-8。非常的方便,很統一,再也沒有編碼亂碼的困擾了

布林型別

布林型資料在 Go 語言中以 bool 型別進行宣告,布林型資料只有 true(真)和 false(假)兩個值。

  • bool型別佔1個位元組
  • bool型別適用於邏輯運算,一般用於流程控制
  • Go 語言中不允許將整型強制轉換為布林型, cannot convert n (type bool) to type int
  • 布林型無法參與數值運算,也無法與其他型別進行轉換。

字串型別

字串就是一串固定長度的字元連線起來的字元序列。Go的字串是由單個位元組連線起來的。Go語言的字串的位元組使用UTF-8編碼標識Unicode文字

  • 字串一旦賦值了,就不能修改了:在Go中字串是不可變的
  • 字串的兩種標識形式
  • 雙引號,會識別轉義字元
    var str = "abc\nabc" //輸出時會換行
  • 反引號,以字串的原生形式輸出,包括換行和特殊字元,可以實現防止攻擊、輸出原始碼等效果
    var str string = `abc\nabc` //輸出時原樣輸出,不會轉義
  • 字串拼接方式"+"
    var str string = "hello " + "world"
    str += "!"
  • 當一行字串太長時,需要使用到多行字串,可以使用如下處理
    //正確寫法
    str := "hello" + 
            " world!"
    fmt.Println(str)

    //錯誤寫法
    str := "hello "
            + "world!"
    fmt.Println(str)

基本資料型別預設值與資料型別轉換

基本資料型別預設值

在Golang中,資料型別都有一個預設值,當程式設計師沒有賦值時,就會保留預設值,在Golang中,預設值也叫做零值。
基本資料型別預設值如下:

資料型別 預設值
整型 0
浮點型 0
字串 ""
布林型別 false

資料型別轉換

Golang和Java/C不同,Golang在不同型別的變數之間賦值時需要顯式轉換。也就是Golang中資料型別不能自動轉換。

基本語法:
表示式var_type(var_name) 將值v轉換為型別var_type
var_type:就是資料型別,比如int32, int64, float32等等
var_name:就是需要轉換的變數

    var num int = 42
    var float float64 = float64(num)
    var ui uint8 = uint8(float)
    fmt.Println(num, float, ui)

注意事項

  • Go中,資料型別的轉換可以是從表示範圍小-->表示範圍大,也可以 範圍大—>範圍小
  • 被轉換的是變數儲存的資料(即值),變數本身的資料型別並沒有變化!
  • 在轉換中,比如將int64轉成int8,編譯時不會報錯,只是轉換的結果是按溢位處理,和我們希望的結果不一樣。
  • 資料的轉換必須顯式轉換,不能自動轉換
  • 定義一個int8型別的整數(var num int8 = 0),如果一直自加1,這個變數的值會是(0...127 -128 -127... 0 ...127)迴圈往復下去,而不會超過型別最大值的範圍
  • 其他基本型別轉string型別

在程式開發中,我們經常需要將數值型轉成string型別,或者將string型別轉成數值型。

  • 方式1:
   func Sprintf(format string, a ...interface{}) string

Sprintf根據format引數生成格式化的字串並返回該字串。
示例

   package main
   import "fmt"
   
   func main() {
       var num1 int = 99;
       var num2 float64 = 23.456
       var isTrue bool = true 
       var char byte = 'A'
   
       var str string
   
       str = fmt.Sprintf("%d", num1)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   
       str = fmt.Sprintf("%f", num2)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   
       str = fmt.Sprintf("%t", isTrue)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   
       str = fmt.Sprintf("%d", char)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   }

輸出結果為

 str型別為 string str = "99"
 str型別為 string str = "23.456000"
 str型別為 string str = "true"
 str型別為 string str = "65"
  • 方式2:使用strconv包的函式
   package main
   import (
      "fmt"
      "strconv"
   )
   
   func main() {
       var num1 int = 99;
       var num2 float64 = 23.456
       var isTrue bool = true 
       var str string
       str = strconv.FormatInt(int64(num1), 10)
       str = strconv.Itoa(num1)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   
       str = strconv.FormatFloat(num2, 'f', 10, 64)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   
       str = strconv.FormatBool(isTrue)
       fmt.Printf("str型別為 %T str = %q\n",str, str)
   }

輸出結果為

   str型別為 string str = "99"
   str型別為 string str = "23.4560000000"
   str型別為 string str = "23.4560000000"
   str型別為 string str = "true"
  • string型別轉其他基本型別
  • 方式1:使用strconv包的函式
   package main
   import (
       "fmt"
       "strconv"
   )
   
   func main() {
       var str string = "true"
       var str1 string = "123456"
       var str2 string = "123.456"
   
       var isTrue bool
       var num int64
       var num2 float64
   
       isTrue, _ = strconv.ParseBool(str)
       fmt.Printf("str型別為 %T str = %v\n",isTrue, isTrue)
   
       num, _ = strconv.ParseInt(str1, 10, 64)
       fmt.Printf("str型別為 %T str = %v\n",num, num)
   
       num2, _ = strconv.ParseFloat(str2, 64)
       fmt.Printf("str型別為 %T str = %v\n",num2, num2) 
   }

資料結果為:

   str型別為 bool str = true
   str型別為 int64 str = 123456
   str型別為 float64 str = 123.456

注意:在將string型別轉成其它基本資料型別時,要確保string型別能夠轉成有效的資料。比如,我們可以把”123“轉成數字123,但是不能把”hello“轉成一個整數,如果這樣做,Golang直接將其轉成0,其它型別也是一樣的道理,float => 0, bool => false

引用資料型別

指標

獲取變數的地址,用&,比如var num int,獲取num的地址:&num

    var a int = 10
    fmt.Println("a 的地址=" + &a)

指標型別,指標變數存的是一個地址,這個地址指向的空間存的才是值,比如:var ptr int = &num
獲取指標型別所指向的值,使用:
,比如,var ptr int,使用ptr獲取ptr指向的值

    var a int = 10                  //變數
    var ptr *int = &a               //指標變數
    fmt.Printf("ptr 的地址=%v \n", &ptr)
    fmt.Printf("ptr的值是 a 的地址=%v \n", &a)
    fmt.Printf("ptr 所指向地址的值=%v \n", *ptr)

值型別,都有對應的指標型別,形式為 *資料型別, 值型別包括:基本資料型別陣列結構體struct

    var a int = 10                  //變數
    var ptr *int = &a               //指標變數
    fmt.Printf("ptr 的地址=%v \n", &ptr)

    var b float64 = .123
    var piont *float64 = &b
    fmt.Printf("piont 的地址=%v \n", &piont)

值型別與引用型別

區分

  • 值型別:基本資料型別(int系列、float系列、bool、string)、陣列和結構體
  • 引用型別:指標、slice切片、map、管道chan、interface等都是引用型別

使用特點

  • 值型別:變數直接儲存值,記憶體通常中分配
  • 引用型別:變數儲存的是一個地址,這個地址對應的空間才真正儲存資料(值),記憶體通常上分配,當沒有任何變數應用這個地址時,該地址對應的資料空間就成為一個垃圾,由GC來回收。

陣列

陣列可以存放多個同一型別資料。陣列也是一種資料型別,在 Go 中,陣列是值型別。

  • 定義與賦值

var 陣列名 [陣列大小]資料型別

    var intArr [3]int //int佔8個位元組
	//當我們定義完陣列後,其實陣列的各個元素有預設值 0
	//賦值
	intArr[0] = 10
	intArr[1] = 20
	intArr[2] = 30

    //四種初始化陣列的方式
	var numArr01 [3]int = [3]int{1, 2, 3}

	var numArr02 = [3]int{5, 6, 7}

	var numArr03 = [...]int{8, 9, 10}  //這裡的 [...] 是規定的寫法,不確定大小

	var numArr04 = [...]int{1: 800, 0: 900, 2:999} //下標賦值

    //型別推導
    strArr05 := [...]string{1: "tom", 0: "jack", 2:"mary"}

  • 陣列在記憶體佈局
  1. 陣列的地址可以通過陣列名來獲取 &intArr
  2. 陣列的第一個元素的地址,就是陣列的首地址
  3. 陣列的各個元素的地址間隔是依據陣列的型別決定,比如 int64 -> 8 int32->4.
  • 陣列遍歷
  1. 常規 for迴圈
  2. for-range結構遍歷:這是 Go 語言一種獨有的結構,可以用來遍歷訪問陣列的元素。
    heroes  := [...]string{"宋江", "吳用", "盧俊義"}
	for index, value := range heroes {
		fmt.Printf("index=%v value=%v\n", index , value)
		fmt.Printf("heroes[%d]=%v\n", index, heroes[index])
	}

	for _, v := range heroes {
		fmt.Printf("元素的值=%v\n", v)
	}

      1. 第一個返回值 index是陣列的下標
      2. 第二個value是在該下標位置的值
      3. 他們都是僅在 for迴圈內部可見的區域性變數
      4. 遍歷陣列元素的時 候,如果不想使用下標index,可以直接把下標index標為下劃線_
      5. index和value的名稱不是固定的,即程式設計師可以自行指定.一般命名為index和value

  • 注意事項
  1. 陣列是多個相同型別資料的組合,一個數組一旦宣告/定義了,其長度是固定的, 不能動態變化
  2. var arr []int 宣告一個數組沒有定義長度,arr 就是一個 slice 切片
  3. 陣列中的元素可以是任何資料型別,包括值型別和引用型別,但是不能混用。
  4. 陣列建立後,如果沒有賦值,有預設值(零值)
    數值型別陣列:預設值為 0
    字串陣列:預設值為 ""
    bool 陣列: 預設值為 false
  1. 使用陣列的步驟 1. 宣告陣列並開闢空間 2 給陣列各個元素賦值(預設零值) 3 使用陣列
  2. 陣列的下標是從 0 開始的
  3. 陣列下標必須在指定範圍內使用,否則報 panic:陣列越界
  4. Go 的陣列屬值型別, 在預設情況下是值傳遞, 因此會進行值拷貝。陣列間不會相互影響
  5. 如想在其它函式中,去修改原來的陣列,可以使用引用傳遞(指標方式)
  6. 長度是陣列型別的一部分,在傳遞函式引數時 需要考慮陣列的長度

多維陣列

二維陣列

  • 使用方式
  1. 先宣告/定義,再賦值
    語法: var 陣列名 [大小][大小]型別
    //定義/宣告二維陣列
	var arr [2][3]int
	//賦初值
	arr[1][2] = 1
	arr[2][1] = 2
	arr[2][3] = 3
  1. 直接初始化
    var 陣列名 [大小][大小]型別 = [大小][大小]型別{{初值..},{初值..}}

    二維陣列在宣告/定義時也對應有四種寫法[和一維陣列類似]
    var 陣列名 [大小][大小]型別 = [大小][大小]型別{{初值..},{初值..}}
    var 陣列名 [大小][大小]型別 = [...][大小]型別{{初值..},{初值..}}
    var 陣列名 = [大小][大小]型別{{初值..},{初值..}}
    var 陣列名 = [...][大小]型別{{初值..},{初值..}}

  1. 二維陣列在記憶體的存在形式
    var arr [2][3]int //以這個為例來分析arr2在記憶體的佈局!!
	arr[1][1] = 10

    fmt.Println(arr) //[[0 0 0] [0 10 0]]
	fmt.Printf("arr[0]的地址%p\n", &arr[0]) //arr[0]的地址0xc000018090
	fmt.Printf("arr[1]的地址%p\n", &arr[1]) //arr[1]的地址0xc0000180a8
    //0xc000018090和 0xc0000180a8 相差 3x8: 3個int元素 (1個int 8byte)

	fmt.Printf("arr[0][0]的地址%p\n", &arr[0][0]) //arr[0][0]的地址0xc000018090
    //&arr2[0] == &arr[0][0] 

	fmt.Printf("arr[1][0]的地址%p\n", &arr[1][0]) //arr[1][0]的地址0xc0000180a8
    &arr[1] == &arr[1][0]

    總結
    1. 二維陣列記憶體形式儲存的是指標
    2. 二維陣列第一組儲存的第一組第一個元素的地址,第二組儲存的是第二組第一個元素的地址,依次類推
    3. 二維陣列兩組地址相差的是一組元素所佔的位元組
  1. 二維陣列的遍歷
  2. 雙層 for 迴圈完成遍歷
    var arr  = [2][3]int{{1,2,3}, {4,5,6}}

	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Printf("%v\t", arr[i][j])
		}
	}
  1. for-range 方式完成遍歷
    var arr  = [2][3]int{{1,2,3}, {4,5,6}}
	for i, v := range arr {
		for j, v2 := range v {
			fmt.Printf("arr[%v][%v]=%v \t",i, j, v2)
		}
	}

切片

  • 定義
  1. 切片的英文是 slice
  2. 切片是陣列的一個引用,因此切片是引用型別,在進行傳遞時,遵守引用傳遞的機制。
  3. 切片的使用和陣列類似,遍歷切片、訪問切片的元素和求切片長度 len(slice)都一樣。
  4. 切片的長度是可以變化的,因此切片是一個可以動態變化陣列。
  5. 切片定義的基本語法:
    //var 切片名 []型別
    var a [] int
  • 切片的記憶體形式
  1. slice 的確是一個引用型別
  2. slice 從底層來說,其實就是一個數據結構(struct 結構體)
    type slice struct {
        ptr *[2]int //擷取陣列開始位置的地址
        len int //擷取的長度
        cap  //容量
    }
  • 切片的使用
  1. 定義一個切片,然後讓切片去引用一個已經建立好的陣列
    var slice = arr[startIndex:endIndex]
    說明:從 arr 陣列下標為 startIndex,取到 下標為 endIndex 的元素(不含 arr[endIndex])。

    var slice = arr[0:end] 可以簡寫 var slice = arr[:end]
    var slice = arr[start:len(arr)] 可以簡寫: var slice = arr[start:]
    var slice = arr[0:len(arr)] 可以簡寫: var slice = arr[:]
  1. 通過 make 來建立切片.
    基本語法:var 切片名 []type = make([]type, len, [cap])
    引數說明: type: 就是資料型別 len : 大小 cap :指定切片容量,可選(如果你分配了 cap, 則 cap>=len)

    1. 通過 make 方式建立切片可以指定切片的大小和容量
    2. 如果沒有給切片的各個元素賦值,那麼就會使用預設值[int , float=> 0   string =>""  bool =>false]
    3. 通過 make 方式建立的切片對應的陣列是由 make 底層維護,對外不可見,即只能通過 slice 去訪問各個元素.
  1. 定義一個切片,直接就指定具體陣列,使用原理類似 make 的方式
    var strSlice []string = []string{"tom", "jack", "mary"}

方式 1 和方式 2 的區別

方式1是直接引用陣列,這個陣列是事先存在的,程式設計師是可見的。
方式2是通過make未建立切片,make也會建立一個數組,是由切片在底層進行維護,程式設計師是看不見的。

  • 切片的遍歷
  1. for 迴圈常規方式遍歷
    var arr [5]int = [...]int{10, 20, 30, 40, 50}
	slice := arr[1:4]
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v]=%v ", i, slice[i])
	}
  1. for-range 結構遍歷切片
    var arr [5]int = [...]int{10, 20, 30, 40, 50}
	slice := arr[1:4]
	for i, v := range slice {
		fmt.Printf("i=%v v=%v \n", i, v)
	}
  • 注意事項
  1. 切片初始化時 var slice = arr[startIndex:endIndex]
    說明:從 arr 陣列下標為 startIndex,取到 下標為 endIndex 的元素(不含 arr[endIndex])。
  2. 切片初始化時,仍然不能越界。範圍在 [0-len(arr)] 之間,但是可以動態增長.
  3. cap 是一個內建函式,用於統計切片的容量,即最大可以存放多少個元素。
  4. 切片定義完後,還不能使用,因為本身是一個空的,需要讓其引用到一個數組,或者 make一個空間供切片來使用
  5. 切片可以繼續切片
  6. 用 append 內建函式,可以對切片進行動態追加
    var slice []int = []int{100, 200, 300}
	//通過append直接給slice3追加具體的元素
	slice = append(slice, 400, 500, 600)
	//通過append將切片slice追加給slice
	slice = append(slice, slice...) 

    切片 append 操作的底層原理分析:
    1. 切片 append 操作的本質就是對陣列擴容
    2. go 底層會建立一下新的陣列 newArr(安裝擴容後大小)
    3. 將 slice 原來包含的元素拷貝到新的陣列 newArr
    4. slice 重新引用到 newArr
    5. 注意 newArr 是在底層來維護的,程式設計師不可見.

  1. 切片的拷貝:切片使用 copy 內建函式完成拷貝
    var slice1 []int = []int{1, 2, 3, 4, 5}
	var slice2 = make([]int, 10)
	copy(slice2, slice1)
	fmt.Println("slice1=", slice1)// 1, 2, 3, 4, 5
	fmt.Println("slice2=", slice2) // 1, 2, 3, 4, 5, 0 , 0 ,0,0,0

    對上面程式碼的說明:
    1. copy(para1, para2) 引數的資料型別是切片
    2. 按照上面的程式碼來看, slice4 和 slice5 的資料空間是獨立,相互不影響,也就是說 slice4[0]= 999,slice5[0] 仍然是 1
  1. 切片是引用型別,所以在傳遞時,遵守引用傳遞機制。
    var arr [5]int = [...]int{10, 20, 30, 40, 50}
	slice1 := arr[1:4] // 20, 30, 40
    slice2 := slice1[1:2] //[30]
	slice2[0] = 100  // 因為arr , slice1 和slice2 指向的資料空間是同一個,因此slice2[0]=100,其它的都變化
  • string 和 slice
  1. string 底層是一個 byte 陣列,因此 string 也可以進行切片處理
  2. string 的記憶體形式
    type slice struct {
        ptr *[4]byte //擷取陣列開始位置的地址
        len int //擷取的長度
    }
  1. string 是不可變的,也就說不能通過 str[0] = 'z' 方式來修改字串
  2. 如果需要修改字串,可以先將 string -> []byte / 或者 []rune -> 修改 -> 重寫轉成 string
    str := "hello@world"
    arr1 := []byte(str) 
	arr1[0] = 'z'
	str = string(arr1)

    //我們轉成[]byte後,可以處理英文和數字,但是不能處理中文
	// 原因是 []byte 位元組來處理 ,而一個漢字,是3個位元組,因此就會出現亂碼
	// 解決方法是 將  string 轉成 []rune 即可, 因為 []rune是按字元處理,相容漢字

    arr1 := []rune(str) 
	arr1[0] = '北'
	str = string(arr1)

map

map 是 key-value 資料結構,又稱為欄位或者關聯陣列。類似其它程式語言的集合

  • 基本語法
    var 變數名 map[keytype]valuetype
    
    keytype:
    golang 中的 map,的 key 可以是很多種型別,比如 bool, 數字,string, 指標, channel , 還可以是隻包含前面幾個型別的 介面, 結構體, 陣列 通常 key 為 int 、string
    注意: slice, map, function 不可以,因為這幾個沒法用 == 來判斷

    valuetype:
    valuetype 的型別和 key 基本一樣
  • 宣告和使用
  1. 宣告:
    var a map[string]string
    var a map[string]int
    var a map[int]string
    var a map[string]map[string]string
    注意:宣告是不會分配記憶體的,初始化需要 make ,分配記憶體後才能賦值和使用。
  1. 使用:
    方式一:
        var a map[string]string
        //在使用map前,需要先make , make的作用就是給map分配資料空間
        a = make(map[string]string, 10)
        a["no1"] = "宋江" 
        a["no2"] = "吳用" 
        a["no1"] = "武松" 
        a["no3"] = "吳用" 
    
    方式二:
        cities := make(map[string]string)
        cities["no1"] = "北京"
        cities["no2"] = "天津"
        cities["no3"] = "上海"
    
    方式三:
        heroes := map[string]string{
            "hero1" : "宋江",
            "hero2" : "盧俊義",
            "hero3" : "吳用",
        }
    
  • map 的增刪改查操作
    cities := make(map[string]string)
    //增
	cities["no1"] = "北京" //如果 key 還沒有,就是增加,如果 key 存在就是修改。
	cities["no2"] = "天津"
	cities["no3"] = "上海"

	//刪
	delete(cities, "no1")
	//當delete指定的key不存在時,刪除不會操作,也不會報錯
	delete(cities, "no4")

    //改
    //因為 no3這個key已經存在,因此下面的這句話就是修改
	cities["no3"] = "西安" 

	//查
	val, ok := cities["no2"]
	if ok {
		fmt.Printf("有no1 key 值為%v\n", val)
	} else {
		fmt.Printf("沒有no1 key\n")
	}

	//如果希望一次性刪除所有的key
	//1. 遍歷所有的key,逐一刪除 [遍歷]
	//2. 直接make一個新的空間
	cities = make(map[string]string)
  • map 遍歷
    只能使用for-range遍歷
    cities := make(map[string]string)
	cities["no1"] = "北京"
	cities["no2"] = "天津"
	cities["no3"] = "上海"
	
	for k, v := range cities {
		fmt.Printf("k=%v v=%v\n", k, v)
	}
  • map 切片
    //1. 宣告一個map切片
	var monsters []map[string]string
	monsters = make([]map[string]string, 2)

    //2. 增加第一個妖怪的資訊
	if monsters[0] == nil {
		monsters[0] = make(map[string]string, 2)
		monsters[0]["name"] = "牛魔王"
		monsters[0]["age"] = "500"
	}

    //3. 切片的append函式,可以動態的增加monster
	newMonster := map[string]string{
		"name" : "新的妖怪~火雲邪神",
		"age" : "200",
	}
	monsters = append(monsters, newMonster)
  • map 排序
    map1 := make(map[int]int, 10)
	map1[10] = 100
	map1[1] = 13
	map1[4] = 56
	map1[8] = 90

    //如果按照map的key的順序進行排序輸出
    //1. 先將map的key 放入到 切片中
	var keys []int
	for k, _ := range map1 {
		keys = append(keys, k)
	}

	//2. 對切片排序 
	sort.Ints(keys)
	fmt.Println(keys)

    //3. 遍歷切片,然後按照key來輸出map的值
	for _, k := range keys{
		fmt.Printf("map1[%v]=%v \n", k, map1[k])
	}
  • 使用細節
  1. map 是引用型別,遵守引用型別傳遞的機制,在一個函式接收 map,修改後,會直接修改原來
  2. map 的容量達到後,再想 map 增加元素,會自動擴容,並不會發生 panic,也就是說 map 能動態的增長 鍵值對(key-value)
  3. map 的 value 也經常使用 struct 型別,更適合管理複雜的資料(比前面 value 是一個 map 更好),

結構體

  • Golang 語言面向物件程式設計說明
  1. Golang 也支援面向物件程式設計(OOP),但是和傳統的面向物件程式設計有區別,並不是純粹的面嚮物件語言。所以我們說 Golang 支援面向物件程式設計特性是比較準確的。
  2. Golang 沒有類(class),Go 語言的結構體(struct)和其它程式語言的類(class)有同等的地位,你可以理解 Golang 是基於 struct 來實現 OOP 特性的。
  3. Golang 面向物件程式設計非常簡潔,去掉了傳統 OOP 語言的繼承、方法過載、建構函式和解構函式、隱藏的 this 指標等等
  4. Golang 仍然有面向物件程式設計的繼承,封裝和多型的特性,只是實現的方式和其它 OOP 語言不一樣,比如繼承 :Golang 沒有 extends 關鍵字,繼承是通過匿名欄位來實現。
  5. Golang 面向物件(OOP)很優雅,OOP 本身就是語言型別系統(type system)的一部分,通過介面(interface)關聯,耦合性低,也非常靈活。後面同學們會充分體會到這個特點。也就是說在 Golang 中面向介面程式設計是非常重要的特性。
  • 結構體和結構體變數(例項)的區別和聯絡
  1. 結構體是自定義的資料型別,代表一類事物.
    type Cat struct {
        Name string 
        Age int 
        Color string 
        Hobby string
        Scores [3]int
    }
  1. 結構體變數(例項)是具體的,實際的,代表一個具體變數
    var cat1 Cat
	
	cat1.Name = "小白"
	cat1.Age = 3
	cat1.Color = "白色"
	cat1.Hobby = "吃<・)))><<"
  • 結構體變數(例項)在記憶體的佈局
    var cat1 Cat  // var a int
	
	cat1.Name = "小白"
	cat1.Age = 3
	cat1.Color = "白色"
	cat1.Hobby = "吃<・)))><<"
	fmt.Printf("cat1的地址=%p\n", &cat1)

	fmt.Printf("cat1.Name的地址=%p\n", &cat1.Name)
	fmt.Printf("cat1.Age的地址=%p\n", &cat1.Age)
	fmt.Printf("cat1.Color的地址=%p\n", &cat1.Color)
	fmt.Printf("cat1.Hobby的地址=%p\n", &cat1.Hobby)

    結果:
    cat1的地址=0xc00007e000    //824634236928
    cat1.Name的地址=0xc00007e000 //824634236928 //string佔位 16byte
    cat1.Age的地址=0xc00007e010 //824634236944 //int佔位 8byte
    cat1.Color的地址=0xc00007e018 //824634236952
    cat1.Hobby的地址=0xc00007e028 //824634236968

    總結:
    &cat1 == &cat1.Name 
    &cat1.Name + 16 = &cat1.Age
    &cat1.Age + 8 = &cat1.Color
  • 宣告結構體
    基本語法
    type 結構體名稱 struct {
        欄位1 type //結構體欄位 = 屬性 = field 
        欄位2 type
    }
    舉例:
    type Student struct { //結構體和欄位名大寫代表public,小寫表示private
        Name string
        Age int
        Score float32
    }

    欄位 :欄位是結構體的一個組成部分,一般是基本資料型別、陣列,也可是引用型別。
    欄位細節說明
    1) 欄位宣告語法同變數,示例:欄位名 欄位型別
    2) 欄位的型別可以為:基本型別、陣列或引用型別
    3) 在建立一個結構體變數後,如果沒有給欄位賦值,都對應一個零值(預設值),規則同前面講的一樣:
        布林型別是 false ,數值是 0 ,字串是 ""。
        陣列型別的預設值和它的元素型別相關,比如 score [3]int 則為[0, 0, 0]
        指標,slice,和 map 的零值都是 nil ,即還沒有分配空間。
    4) 不同結構體變數的欄位是獨立,互不影響,一個結構體變數欄位的更改,不影響另外一個, 結構體是值型別。
  • 建立結構體變數和訪問結構體欄位
  1. 直接宣告
    var person Person
    var person Person = Person{}

    舉例:
    p2 := Person{"mary", 20}
    var person *Person = new (Person)
    (*p3).Name = "smith"  //(*p3).Name = "smith" 也可以這樣寫 p3.Name = "smith"
    /*
    * 原因: go的設計者 為了程式設計師使用方便,底層會對 p3.Name = "smith" 進行處理
	* 會給 p3 加上 取值運算 (*p3).Name = "smith"
    */
    var person *Person = &Person{}
    //var person *Person = &Person{"mary", 60} 
    (*person).Name = "scott"  //person.Name = "scott"
	(*person).Age = 88  //person.Age = 88
    
  • 注意事項
  1. 結構體的所有欄位在記憶體中是連續的
    假如有兩個 Point型別,這個兩個Point型別的本身地址也是連續的,但是他們指向的地址不一定是連續
  2. 結構體是使用者單獨定義的型別,和其它型別進行轉換時需要有完全相同的欄位(名字、個數和型別)
  3. 結構體進行 type 重新定義(相當於取別名),Golang 認為是新的資料型別,但是相互間可以強轉
  4. struct 的每個欄位上,可以寫上一個 tag, 該 tag 可以通過反射機制獲取,常見的使用場景就是序列化和反序列化。
    package main 
    import "fmt"
    import "encoding/json"

    type Monster struct{
        Name string `json:"name"` // `json:"name"` 就是 struct tag
        Age int `json:"age"`
        Skill string `json:"skill"`
    }
    func main() {
        //1. 建立一個Monster變數
        monster := Monster{"牛魔王", 500, "芭蕉扇~"}

        //2. 將monster變數序列化為 json格式字串
        //   json.Marshal 函式中使用反射,這個講解反射時,我會詳細介紹
        jsonStr, err := json.Marshal(monster)
        if err != nil {
            fmt.Println("json 處理錯誤 ", err)
        }
        fmt.Println("jsonStr", string(jsonStr))
    }

方法

Golang 中的方法是作用在指定的資料型別上的(即:和指定的資料型別繫結),因此自定義型別,都可以有方法,而不僅僅是 struct。

  • 宣告
    func (recevier type) methodName(引數列表) (返回值列表){
        方法體
        return 返回值
    }

    說明:
    1. 引數列表:表示資料型別呼叫傳遞給方法的引數
    2. recevier type : 表示這個方法和 type 這個型別進行繫結,或者說該方法作用於 type 型別
    3. receiver type : type 可以是結構體,也可以其它的自定義型別
    4. receiver : 就是 type 型別的一個變數(例項),比如 :Person 結構體 的一個變數(例項)
    5. 返回值列表:表示返回的值,可以多個
    6. 方法主體:表示為了實現某一功能程式碼塊
    7. return 語句不是必須的。

  • 呼叫
    type A struct {
        Num int
    }
    func (a A) test() {
        fmt.Println(a.Num)
    }
    func main() {
        var a A
        a.test() //呼叫方法
    }

    說明
    1. func (a A) test() {} 表示 A 結構體有一方法,方法名為 test
    2. (a A) 體現 test 方法是和 A 型別繫結的
    3. test 方法只能通過 A 型別的變數來呼叫,而不能直接呼叫,也不能使用其它型別變數來呼叫
    4.  func (a A) test() {}... a 表示哪個 A 變數呼叫,這個 a 就是它的副本, 這點和函式傳參非常相似。
  • 方法的呼叫和傳參機制原理
    方法的呼叫和傳參機制和函式基本一樣,不一樣的地方是方法呼叫時,會將呼叫方法的變數,當做實參也傳遞給方法(如果變數是值型別,則進行值拷貝,如果變數是引用型別,則進行地址拷貝)。
  • 注意事項
  1. 結構體型別是值型別,在方法呼叫中,遵守值型別的傳遞機制,是值拷貝傳遞方式
  2. 如希望在方法中,修改結構體變數的值,可以通過結構體指標的方式來處理
  3. Golang 中的方法作用在指定的資料型別上的(即:和指定的資料型別繫結),因此自定義型別,都可以有方法,而不僅僅是 struct, 比如 int , float32 等都可以有方法
  4. 方法的訪問範圍控制的規則,和函式一樣。方法名首字母小寫,只能在本包訪問,方法首字母大寫,可以在本包和其它包訪問。
  5. 如果一個型別實現了 String()這個方法,那麼 fmt.Println 預設會呼叫這個變數的 String()進行輸出

方法和函式區別

  1. 呼叫方式不一樣
    函式的呼叫方式: 函式名(實參列表)
    方法的呼叫方式: 變數.方法名(實參列表)
  2. 對於普通函式,接收者為值型別時,不能將指標型別的資料直接傳遞,反之亦然
  3. 對於方法(如 struct 的方法),接收者為值型別時,可以直接用指標型別的變數呼叫方法,反過來同樣也可以
    type Person struct {
        Name string
    } 

    //函式
    func test01(p Person) {
        fmt.Println(p.Name)
    }
    func test02(p *Person) {
        fmt.Println(p.Name)
    }

    //方法
    func (p Person) test03() {
        p.Name = "jack"
        fmt.Println("test03() =", p.Name) // jack
    }
    func (p *Person) test04() {
        p.Name = "mary"
        fmt.Println("test03() =", p.Name) // mary
    }

    func main() {
        p := Person{"tom"}
        test01(p)
        test02(&p)

        p.test03()
        fmt.Println("main() p.name=", p.Name) // tom
        
        (&p).test03() // 從形式上是傳入地址,但是本質仍然是值拷貝
        fmt.Println("main() p.name=", p.Name) // tom

        (&p).test04()
        fmt.Println("main() p.name=", p.Name) // mary
        p.test04() // 等價 (&p).test04 , 從形式上是傳入值型別,但是本質仍然是地址拷貝
        fmt.Println("main() p.name=", p.Name) // mary
    }

    總結:
    1. 不管呼叫形式如何,真正決定是值拷貝還是地址拷貝,看這個方法是和哪個型別繫結.
    2. 如果是和值型別,比如(p Person) , 則是值拷貝; 如果和指標型別,比如是 (p *Person) 則是地址拷貝。

管道

介面

變數與常量

變數(Variable)

變量表示記憶體中的一個儲存區域,該區域有自己的名稱(變數名)和型別(資料型別)。

方法 1
    var a int       //宣告          聲明後若不賦值,使用預設值
    a = 10          //賦值
    fmt.Println(a)  //使用

方法 2
    var a = 10      //宣告並賦值    根據值自行判定資料型別(型別推導)
    fmt.Println(a)  //使用  

方法 3
    a := 10         //宣告並賦值    “:=” 方式賦值時,必須是一個沒有宣告過的變數,否則會導致編譯錯誤 no new variables on left side of :=
    fmt.Println(a)  //使用

golang 提供多變數宣告與賦值

//一次性宣告多個全域性變數[在go中函式外部定義變數就是全域性變數]
方式一:
    var a = 1
    var b = 2
    fmt.Println(a, b) 
 
方式二:
    var (
        a    = 1
        b    = 2
    )
    fmt.Println(a, b) 

方式三:
    var a, b = 1, 2
	fmt.Println(a, b)

方法四:
    a, b := 1, 2
	fmt.Println(a, b)

匿名變數(anonymous variable)

在使用多重賦值時,如果不需要在左值中接收變數,可以使用匿名變數
匿名變數的表現是一個下畫線_,使用匿名變數時,只需要在變數宣告的地方使用下畫線替換即可。
匿名變數不佔用名稱空間,不會分配記憶體。匿名變數與匿名變數之間也不會因為多次宣告而無法使用。

提示:在 Lua 等程式語言裡,匿名變數也被叫做啞元變數。

    func GetData() (int, int) {
        return 100, 200
    }
    a, _ := GetData()
    _, b := GetData()
    fmt.Println(a, b)

常量(constant)

golang中,常量是指編譯期間運算得出且不可改變的值。
golang常量定義的關鍵字為const。
常量中的資料型別只能是布林型、數字型(整數型、浮點型和複數)和字串型。

    // 定義單個常量
    const Pi float64 = 3.14159265358979323846

    // 定義多個常量
    const (
        Size int64 = 1024
        Eof  int64 = -1
    )

golang常量定義可以限定常量型別,也可以不限定。如果常量定義時沒有限定型別,那麼它與字面常量一樣,是一個無型別常量。

    // 定義單個常量
    const Pi = 3.14159265358979323846 // 無型別浮點常量

    // 定義多個常量
    const (
        Size = 1024 // 無型別整型常量
        Eof  = -1   // 無型別整型常量
    )

無論是變數還是常量,不同型別的都不能顯式的宣告在一行:

    var a int, b float32 = 1, 2.4   //編譯器不通過
    const c int, d float32 = 3, 4.4 //編譯器不通過
    const c, d float32 = 3, 4 //編譯通過(此時c和d都是float32型別)
    const c, d = 3, 4.4  //編譯通過(此時c是int型別,d是float64型別)

當我們定義常量時,如果多個常量的值相同,後面的常量可以直接不賦值,預設等同於上面已賦值的常量的值

package main

import "fmt"
const (
    a = "itbsl"
    c
    d
)
func main() {
    fmt.Println(a, c, d)
}

結果

itbsl itbsl itbsl

我們可以通過reflect.Typeof(變數名)列印變數或常量的型別

常量可以用len()、cap()、unsafe.Sizeof()常量計算表示式的值。常量表達式中,函式必須是內建函式,否則編譯不通過,因為在編譯期間自定義函式均屬於未知,因此無法用於常量的賦值

golang常量定義的右值可以是一個在編譯期運算的常量表達式,這與c語言中巨集的性質是一樣的。

    const Mask = 1 << 3            // correct
    const Path = os.Getenv("PATH") // incorrect : const initializer os.Getenv("PATH") is not a constant

字面常量(literal)

字面常量(literal),是指程式中硬編碼的常量。
golang中字面常量是無型別的,只要該字面常量在相應型別的值域範圍內,就可作為該型別的常量。

預定義常量

golang預定義了這些常量:true、false和iota

  • true和false

預定義常量true和false所屬的基礎型別為bool

  • iota

預定義常量iota所屬的基礎型別為int
iota可認為是一個可被編譯器修改的常量:在每一個const關鍵字出現時值重置為0,然後在下一個const出現之前,每出現一次iota,其所代表的數字會自動增1
如果兩個const賦值語句的表示式一樣,那麼可以省略後一個賦值表示式

列舉

golang並不支援眾多其他語言中支援的enum關鍵字。
在golang中定義列舉值的方式:在const後跟一對圓括號的方式定義一組常量。

識別符號與關鍵字

識別符號

Golang對各種變數、方法、函式等命名時使用的字元序列成為識別符號

Go中函式、變數、常量、型別、語句標籤和包的名稱遵循一個簡單的規則:名稱的開頭是一個字母(Unicode中的字元即可)或下劃線,後面可以跟任意數量的字元數字下劃線,並區分大小寫

如果一個實體在函式中聲名,她只在函式區域性有效。如果在函式外宣告,它將對包裡面的所有原始檔可見。實體第一個字母的大小寫決定其可見性是否跨包。如果名稱以大寫字母開頭,它是匯出的,意味著它對包外是可見和可訪問的,可以被自己包之外的其他程式所引用,像fmt包中的Printf。包名總是由小寫字母組成。

包名:保持package的名字和目錄保持一致,儘量採取有意義的包名,簡短、有意義,不要和標準庫衝突
變數名,函式名,常量名:儘量使用駝峰法
首字母大寫可被其他包訪問(類似Public),首字母小寫只能在本包內使用(類似Private)

關鍵字

Go有25個關鍵字,只能用在語法允許的地方,他們不能作為變數名稱:

break            default            func            interface
select           case               defer           go
map              struct             chan            else
goto             package            switch          const
fallthrough      if                 range           type
continue         for                import          return
var

預定義識別符號

另外,還有36個內建的預宣告的常量、型別和函式:(還有一種說法叫做預定義識別符號)

  • 常量:
true            false            iota            nil
  • 型別:
int        int8        int16        int32        int64
uint       uint8       uint16       uint32       uint64      uintptr
float32    float64     complex64    complex128
bool       byte        rune         string       error
  • 函式:
make            len            cap            new            append           copy       
close           delete         complex        real           imag             panic        
recover