1. 程式人生 > >go 學習 ---資料型別

go 學習 ---資料型別

25個關鍵字

  程式宣告:import, package

  程式實體宣告和定義:chan, const, func, interface, map, struct, type, var

  程式流程控制:go, select, break, case, continue, default, defer, else, fallthrough, for, goto, if, range, return

 

型別

  18個基本型別:bool, string, rune, byte, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64, complex64, complex128

  7個複合型別:array, struct, function, interface, slice, map, channel

  其中,切片、字典、通道型別都是引用型別

  型別的宣告一般以 type 關鍵字開始,然後是自定義的識別符號名稱,然後是基本型別的名稱或複合型別的定義。

  Unicode字元rune型別是和int32等價的型別,通常用於表示一個Unicode碼點。這兩個名稱可以互換使用。同樣byte也是uint8型別的等價型別,byte型別一般用於強調數值是一個原始的資料而不是一個小的整數。

  最後,還有一種無符號的整數型別uintptr,沒有指定具體的bit大小但是足以容納指標。uintptr型別只有在底層程式設計是才需要,特別是Go語言和C語言函式庫或作業系統介面相互動的地方。 

  一個float32型別的浮點數可以提供大約6個十進位制數的精度,而float64則可以提供約15個十進位制數的精度;通常應該優先使用float64型別,因為float32型別的累計計算誤差很容易擴散,並且float32能精確表示的正整數並不是很大

    rune 官方的解釋如下:rune  is an alias for int32 and is equivalent to int32 in all ways. It is  used, by convention, to distinguish character values from integer values. (rune 是int32的別名,幾乎在所有方面等同於int32  它用來區分字元值和整數值)

 type rune = int32

我們通過一個簡單的例子來看下rune的作用。先來看下下面這塊程式碼執行結果是什麼?

package main

import "fmt"

func main() {

    var str = "hello 你好"
    fmt.Println("len(str):", len(str))

}

  我們猜測結果應該是:8:5個字元1個空格2個漢字。那麼正確答案是多少呢?

len(str): 12

  結果居然是12,這是為什麼呢!?

golang中string底層是通過byte陣列實現的。中文字元在unicode下佔2個位元組,在utf-8編碼下佔3個位元組,而golang預設編碼正好是utf-8。

那麼?如果我們預期想得到一個字串的長度,而不是字串底層佔得位元組長度,該怎麼辦呢???

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {

    var str = "hello 你好"

    //golang中string底層是通過byte陣列實現的,直接求len 實際是在按位元組長度計算  所以一個漢字佔3個位元組算了3個長度
    fmt.Println("len(str):", len(str))
    
    //以下兩種都可以得到str的字串長度
    
    //golang中的unicode/utf8包提供了用utf-8獲取長度的方法
    fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))

    //通過rune型別處理unicode字元
    fmt.Println("rune:", len([]rune(str)))
}

 執行結果:

len(str): 12
RuneCountInString: 8
rune: 8

 

golang中還有一個byte資料型別與rune相似,它們都是用來表示字元型別的變數型別。它們的不同在於:

  • byte 等同於int8,常用來處理ascii字元
  • rune 等同於int32,常用來處理unicode或utf-8字元

 

操作符

  列舉一些特殊的操作符,注意下面的位操作符

&      位運算 AND
|      位運算 OR
^      位運算 XOR
&^     位清空 (AND NOT)
<<     左移
>>     右移

  可以通過 Printf 函式的 %b 引數來輸出二進位制格式的數字。

 

特殊的空識別符號

  下劃線 _ 作為一個特殊的識別符號,可以用於 import 語句中,僅執行匯入包中的 init 方法。也可以作為賦值語句的左邊,表示該變數並不關心且不使用。

  此外,識別符號首字母的大小寫,在GO語言中被用來控制變數或函式的訪問許可權,類似於其它語言的 public\private。

 

型別斷言

  比較特殊的表示式有型別斷言,如果判斷一個表示式 element 的型別是 T 的話,表示式為 element.(T),意思是 element 不為 nil 且儲存在其中的值是T型別。這裡有兩種情況,如果 T 不是一個介面型別,則 element 必須要為介面型別的值,比如判斷 100 是 int 型,不能使用 100.(int),而要用 interface{}(100).(int) ; 如果T 是一個介面型別,表示斷言 element 實現了 T 這個介面。如果函式的引數是一個空介面,則必須斷言傳入的介面實現型別,才能使用其對應的方法。

 

可變參函式

  最後一個引數為 ...T 的形式的函式即為可變參函式,意味著可變引數都是 T 型別(或實現了T的型別)如:func CallFunction(first string, t ...string),GO語言會在呼叫可變參函式時,建立一個切片,然後將這些可變引數放入切片中,但如果傳入的可變參部分就是一個元素型別為T的切片的話,則直接把傳入切片賦值給建立的切片,且在呼叫寫法上也有區別,為: CallFunction("hello", []string{"x","y"}...) 

 

陣列型別

  array: 宣告一個長度為 n 、元素型別為 T 的陣列為: [n]T, 元素型別可以為基本型別也可以為複合型別,也可以不指定 n ,由推導得出,如: [...]string{"a","b"} , 陣列長度 n = len([...]string{"a","b"}),另外如果指定了陣列長度,但定義的陣列長度小於宣告的長度,則以宣告長度為準,不足的元素補預設值。同一元素型別,但陣列長度不同,則視為不同型別。

 

切片型別

  slice: 切片型別的宣告為 []T陣列,切片型別裡沒有關於長度的規定,其它跟陣列一樣,切片型別的零值是 nil。切片總是對應於一個數組,其對應的陣列稱為底級陣列。切片和其底層陣列的關係是引用關係,如果有修改都會影響到對方。

  切片的資料結構包含了指向其底層陣列的指標、切片長度和切片容量。切片的長度很容易理解,切片的容量是什麼呢,它是切片第一個元素到底層陣列最後一個元素的長度。

 

字典型別

  map: 定義一個雜湊表的格式為 map[K]V,其中 K 表示鍵的型別,V表示值的型別,如: map[string]bool{"IsOK":true, "IsError":false}

 

介面型別

  定義了一組方法宣告,介面中可以包含介面

  GO語言對介面型別的實現是非侵入式的(備註:侵入式是指使用者程式碼與框架程式碼有依賴,而非侵入式則沒有依賴,或者說耦合),只要一個型別定義了某個介面中宣告的所有方法,就認為它實現了該介面。

  一個特殊的介面: interface{} 是一個空介面,不包含任何方法宣告,所以GO語言所有的型別都可以看成是它的實現型別,我們就可以使用它實現類似其它語言中的公共基類的功能。比如宣告一個字典,鍵是字串,值是不確定型別,就可以使用 map[string]interface{} 

  判斷一個型別是否實現了一個介面,可以通過型別斷言來確定: _, ok := interface{}(MyType{}).(MyInterface)

 

函式與方法

  GO語言中,函式跟方法是有區別的,函式就是我們通常理解的函式,而方法是附屬於某個自定義資料型別的函式,即存在某個接收者。

  func (self MyType) Len() int {}     這裡的 (self MyInterface) 是表示方法接收者。

  值方法和指標方法,值方法是指接收者是一個物件,而指標方法是指接收者是一個物件指標。兩者的區別是,值方法中對接收者的改變是副本級別的,而指標方法中對接收者的改變是地址級別的。所以其實一般都推薦使用指標方法,因為大多數情況下我們在方法內部修改接收者,都是為了真實的改變它,而不是改變一個副本。但是,對於引用型別的接收者來說,兩者並無區別。

  匿名函式由函式字面量表示,函式是作為值存在的,可以賦給函式型別的變數。如:

    var myfunc func(int, int) int
    myfunc = func(x, y int) (result int) {
        result = x + y
        return
    }
    log.Println(myfunc(3, 4))

  一個方法的型別是一個函式型別,即把接收者放到函式的第一個引數位置即可。

  非常遺憾,GO語言不支援函式和方法過載。

 

結構體

  可以包含欄位,也可以包含匿名欄位,一般匿名欄位是介面型別,這樣就相當於該結構體包含了該介面中的方法,另外也可以在結構裡重寫隱藏介面中的方法。

  

指標

  有一個專門用於儲存記憶體地址的型別 unitptr,它與 int/unit 一樣屬於數值型別,儲存32/64位無符號整數。

  可以在一個型別前面使用*號,來表示對應的指標型別,也可以在可定址的變數前面使用&,來獲取其地址。

 

常量

  定義常量和多個常量如下:

複製程式碼
    const PP = iota        //0
    const QQ = iota          //0

    const (
        A = 1
        B = 2
        C
        D = iota
        E
        F 
    )
    log.Print(A, B, C, D, E, F)
//輸出是: 1 2 2 3 4 5
複製程式碼

  注意,iota 只能在 const 裡使用,它是 const 的行數索引器,其值是所在const組中的索引值,單常量定義時 iota 就等於 0。另外,const組中某一項如果不賦值,則預設和上一個值一樣,但如果前面是 iota ,則為上一個值+1。使用 iota 可以實現其它語言中的列舉。

 

變數

  變數的宣告語句以 var 開始,宣告多個變數時,和宣告多個const的方法相同。

var x string = "df"
var x = "df"
x := "df"        //此為簡寫形式。

 

資料初始化

  GO語言的資料初始化有兩種方法,一是使用 new ,一是使用 make ,其中 new 是為某個型別分配記憶體,並設定零值後返回地址,如 new(T) 返回的就是指向T型別值指標值,即*T。如 new([3]int) 其實相當於 [3]int{0,0,0},所以它是一種很乾淨的記憶體分配策略。

  make 只用於建立切片型別、字典型別和通道型別(注意這三個型別的特點,都是引用型別),它對這些型別建立之後,還會進行必要的初始化,與 new 不同,它返回的就是指T型別的值,而不是指標值。

定義常量的方式是使用 const ,如 const PI = 3.14,如果定義多個常量可以使用 

編譯器會自動選擇在棧上還是在堆上分配區域性變數的儲存空間,但可能令人驚訝的是,這個選擇並不是由用var還是new宣告變數的方式決定的。 如果一個函式裡宣告一個區域性變數,但是將其指標賦給一個全域性變數,那麼則不能將此區域性變數放在棧中,而只能放在堆中,我們可以稱之為該區域性變數逃逸了,所以關於變數是分配在棧上還是堆上,是由編譯器根據情況來選擇的。

 

內建函式

  close 只接受通道型別的值

  len函式,可以應用於字串、切片、陣列、字典、通道型別

  cap函式,可以應用於切片、陣列、通道型別

  new函式和make函式

  append函式和copy函式,應用於切片

  delete函式,根據字典的鍵刪除某一項

  complex\real\imag函式,複數相關

  panic 函式,異常相關,它的引數一般是某個error介面的某個實現型別;recover 函式,不接受任何引數,返回 interface{} 型別,也就是意味著,它可以返回任意型別。recover返回的內容是與panic相關的,就是panic的引數內容。

  print\println 函式,這兩個函式支援基本型別引數,且是可變引數。但輸出格式是固定的,且GO語言不保證以後會保留這兩個函式,所以知道就好,不推薦使用。可以使用 fmt.Print 和 fmt.Println 來代替,效果更佳。

 

合併書寫:

和 var/const 類似,多個 type 定義可合併成組,如下:

複製程式碼
type (
    Person struct {
        Name string
        Age  int32
    }   

    myfunc func(string) bool
)
複製程式碼

 尤其是在函式內部定義一堆臨時型別時,可集中書寫,可讀性更好。

 

 

自增/自減運算子不同於其它語言,不能用作表示式,只能當獨立的語句使用,且不能前置,只有後置形式,以儘可能避免該運算子帶的複雜性問題。

 

 

unsafe.Pointer 與 uintptr 的區別:

前者類似 C 語言中的 void* 萬能指標,能安全持有物件,但後者不行,後者只是一種特殊整型,並不引用目標物件,無法阻止垃圾回收器回收物件記憶體。