1. 程式人生 > 程式設計 >Go語言學習筆記-資料型別

Go語言學習筆記-資料型別

記錄一下 GO 裡面的一些資料型別。

陣列

型別 [n]T 是一個有 n 個型別為 T 的值的陣列。

表示式:

var a [10]int
複製程式碼

定義變數 a 是一個有十個整數的陣列。

陣列的長度是其型別的一部分,因此陣列不能改變大小。

var a [2]string
	a[0] = "Hello"
	a[1] = "World"

b := [6]int{2,3,5,7,11,13}		//陣列 宣告並賦值
複製程式碼

切片

切片即 slice

[]T 是一個元素型別為 Tslice

一個 slice 會指向一個序列的值,並且包含了長度資訊。

說人話,slice 其實類似於 Java 裡的 ArrayList

,就是一個動態陣列。

不同點在於,Java 裡的 ArrayList 自己內部維護一個陣列, slice 即可以內部維護一個陣列,也可以在現有的陣列上創造 slice

每次擴容 slice 會指向一個擴容後的陣列。

nil slice

slice 的零值是 nil

一個 nilslice 的長度和容量是 0

構造 slice


/*
	 * 切片不儲存任何資料,它只描述底層陣列的一部分。 更改切片的元素會修改其基礎陣列的相應元素。 共享相同底層陣列的其他切片將看到這些更改。
	 * 切片就像是對陣列的引用。
     */
     //陣列
	names := [5]string{
		"0_John"
,"1_Paul","2_George","3_Ringo",} //構造一個對 names 陣列的切片,[n:m] 說明引用哪部分 slice := names[0:] //slice 由函式 make 建立。這會分配一個零長度的陣列並且返回一個 slice 指向這個陣列: a := make([]int,5) // len(a)=5 //為了指定容量,可傳遞第三個引數到 `make`: b := make([]int,0,5) // len(b)=0,cap(b)=5 b = b[:cap(b)] // len(b)=5,cap(b)=5 b = b[1:] // len(b)=4,cap(b)=4
複製程式碼

slice [n:m] 用法

  • slice[n:m] 表示從 nm-1slice 元素含兩端

  • slice[n:n]空的,即 nil

  • slice[n:n+1]一個元素

  • slice[:m] 表示從 0m-1slice 元素含兩端

  • slice[n:] 表示從 nlen(slice)slice 元素含兩端

向 slice 新增元素

/*
     * 將新元素附加到一個切片上是很常見的,因此Go提供了一個內建的append函式。
     func append(s T,vs.T)T的第一個引數s是一個型別T的切片,其餘的是T值附加到片上。
     * append的結果是一個包含原始切片的所有元素和新提供的值的切片。
     如果s的後備陣列太小,不足以容納所有給定的值,那麼就會分配更大的陣列。
     返回的切片將指向新分配的陣列。
	 */
    slice = append(slice,2,4)

    /*slice1為另一個切片,
    *後面必須加 ...,*這樣才能將該切片指向的陣列裡的元素放入 append
    */
    slice = append(slice,slice1...)
複製程式碼

map

map 對映鍵到值。

map 在使用之前必須建立;值為 nilmap 是空的,並且不能賦值

map 變數宣告

//map[鍵的型別]值的型別
var m map[string]int
複製程式碼

map 構造

/*
 *  就是鍵值對兒,map的零值為nil。
 nil的map沒有鍵,也不能新增鍵。
 make函式會 返回 指定型別的map,初始化並準備好使用。 
 make(map[鍵的型別]值的型別)
 */
m = make(map[string]int)
    
var ma = map[string]int{
	"Bell Labs":1,"Google": 2,}
//如果頂級型別只是一個型別名,可以省略它。
//如下:省略了Ver(結構體)。
var mapa = map[string]Ver{
	"Bell Labs": {40.68433,-74.39967},"Google":    {37.42202,-122.08408},}
複製程式碼

用法

//設定鍵值對,沒有key就新增 key:value 對,
//key 已存在就覆蓋 value 值   m[鍵]= 值 
    m["Bell Labs"] = 1
    
// 取值 v 為 key 對應的 value ,ok為該key是否存在的 bool 值,存在為 true,
//否則 false。 key 不存在時 v 為其型別對應的零值。
v,ok := m["Answer"]

//刪除 key:value 
delete(m,"Answer")
複製程式碼

iota

Go 的列舉用法

//1、iota常量 自動生成器,每隔一行,自動累加1
   //2、iota給常量賦值使用
   const (
   	a = iota //0
   	b = iota //1
   	c = iota //2
   )
   //3、iota遇到const,重置為0
   const d = iota //0
   //4、可以只寫一個iota
   const (
   	e = iota //0
   	f        //1
   	g        //2
   )
   //5,如果是在同一行,值都一樣
   const (
   	h = iota //0
   	i,j,k = iota,iota,iota //1,1,1
   	l=iota  //2
   )
複製程式碼

感覺這是 Go 的一個 bug 吧,要是別人傳個數值型的進來就 gg 了。

error

error 是一個介面。

標準庫裡的定義如下:

// The error built-in interface type is the conventional interface for
// representing an error condition,with the nil value representing no error.
type error interface {
	Error() string
}
複製程式碼

只要實現了這個介面都是實現了 error 。

很多時候都要在函式的多返回值裡接收error,判斷是否有錯誤。

在 Go 裡面 error 基本就是個字串,用慣了 Java 的異常機制,還真不習慣這個。

指標

GO 裡的指標不像 c/c++ 沒有指標運算。

指標的用法: *變數=變數的值 &變數=變數的記憶體地址

i,j := 42,2701

	p := &i         // point to i  					指向i

	fmt.Println(*p) // read i through the pointer	通過指標讀取i
	fmt.Println(&p) // p的記憶體地址
	fmt.Println(&i) // i的記憶體地址

	*p = 21         // set i through the pointer	通過指標設定i
	fmt.Println(i)  // see the new value of i		看下i的新值

	p = &j         // point to j					指向j
	*p = *p / 37   // divide j through the pointer	通過指標進行除法運算
    fmt.Println(j) // see the new value of j		看下j的新值
複製程式碼

函式

函式也是值。它們可以像其他值一樣傳遞。函式值可以用作函式引數和返回值。

//compute 函式 的引數 是一個函式,
//給這個函式 取了個變數名fn,fn函式的型別為 float64,
//compute函式的返回值也是float64。
func compute(fn func(float64,float64) float64) float64 {
	return fn(3,4)
}
複製程式碼
//這裡是定義了一個函式hypot,兩個引數 
//和 一個返回值 全為float64型別。 x*x+y*y,再開方
	hypot := func(x,y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
    fmt.Println(hypot(5,12))  // 5*5+12*12,開方  結果為13

	fmt.Println(compute(hypot))		//把hypot這個函式 作為引數 傳給函式compute 。3*3+4*4=25,開方,結果為5。
	fmt.Println(compute(math.Pow))	//內建函式 Pow(x,y float64) 計算x的y次方,3的4次方 結果為81。
複製程式碼

閉包

 //函式的返回值是一個匿名函式,返回一個函式型別 func(int) int
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

/*
     * 返回值為一個匿名函式,返回一個函式型別,
     通過f來呼叫返回的匿名函式,f來呼叫閉包函式。
     * 它不關心這些捕獲了的變數和常量是否已超出作用域,
     只要閉包還在使用它,這些變數就還會存在。
	 */
	f:=adder()
	fmt.Println(f(1))	//結果1
	fmt.Println(f(2))	//結果3
	fmt.Println(f(3))	//結果6
複製程式碼

介面

參考檔案第11章:介面(interface)與反射(reflection)

  • Go 的介面和 Java 類似,也是宣告瞭一些方法,用來約束實現該介面的類(結構體)的行為。

  • Go 的介面不需要顯示繼承,只要結構體實現了介面宣告的方法,就是實現了該介面。

  • Go 中介面可以通過組合的方式將其他介面宣告的方法巢狀進介面當中。

定義

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
    ...
}

//巢狀介面
type Interface interface {
	Len() int
	Less(i,j int) bool
	Swap(i,j int)
}

type Interface interface {
	sort.Interface
	Push(x interface{}) 
	Pop() interface{}   
}

//  等於
type Interface interface {
	Len() int
	Less(i,j int)
	Push(x interface{}) 
	Pop() interface{}  
}
複製程式碼

上面的 Namer 是一個 介面型別

(按照約定,只包含一個方法的)介面的名字由方法名加 [e]r 字尾組成,例如Printer、Reader、Writer、Logger、Converter 等等。還有一些不常用的方式(當字尾 er 不合適時),比如Recoverable,此時介面名以 able 結尾,或者以 I 開頭(像 .NET 或 Java 中那樣)。

Go 語言中的介面都很簡短,通常它們會包含 0 個、最多 3 個方法。

不像大多數面向物件程式語言,在 Go 語言中介面可以有值,一個介面型別的變數或一個 介面值 :var ai Namer,ai是一個多字(multiword)資料結構,它的值是 nil。它本質上是一個指標,雖然不完全是一回事。指向介面值的指標是非法的,它們不僅一點用也沒有,還會導致程式碼錯誤。

介面型別

介面也是一種型別,所以該型別的變數可以被賦值該介面的實現。

package main

import "fmt"

type Shaper interface {
    Area() float32
}

type Square struct {
    side float32
}

func (sq *Square) Area() float32 {
    return sq.side * sq.side
}

func main() {
    sq1 := new(Square)
    sq1.side = 5

    // var areaIntf Shaper
    // areaIntf = sq1
    // shorter,without separate declaration:
    // areaIntf := Shaper(sq1)
    // or even:
    areaIntf := sq1
    fmt.Printf("The square has area: %f\n",areaIntf.Area())
}
複製程式碼

型別斷言

介面變數裡的型別可能任何型別的值,如果需要在執行時判斷出究竟是什麼型別,可以用型別斷言。

/*
* T 是你需要斷言的型別,
*當 ok 為 true ,v 就是轉換到 T 型別的值,*當 ok 為false ,v 就是 T 型別的零值。
*/
if v,ok := varI.(T); ok {  // checked type assertion
    Process(v)
    return
}
// varI is not of type T
複製程式碼

型別判斷:type-switch

介面變數的型別也可以使用一種特殊形式的 swtich 來檢測:type-swtich

switch t := areaIntf.(type) {
case *Square:
    fmt.Printf("Type Square %T with value %v\n",t,t)
case *Circle:
    fmt.Printf("Type Circle %T with value %v\n",t)
case nil:
    fmt.Printf("nil value: nothing to check?\n")
default:
    fmt.Printf("Unexpected type %T\n",t)
}
複製程式碼

type-switch 不允許有 fallthrough

空介面

空介面不宣告任何方法。類似於 Java 中的 Object 類,Go 中任何型別都實現了空介面。

用法也和 Java 中的 Object 類似,畢竟現在 Go 裡面沒有泛型,希望官方早點加入這個特性。

結構體

參考檔案:第10章:結構(struct)與方法(method)

Go 裡面沒有類的概念,自然也沒有繼承的概念。

所幸還有介面,所以可以用結構體+介面+方法+組合,實現oop。

定義結構體

/*
 *  結構的定義
 */
type Vertex struct {
	X int
	Y int
}
//帶標籤(tag)的結構體
type TagType struct { // tags
    field1 bool   "An important answer"
    field2 string "The name of the thing"
    field3 int    "How much there are"
}
複製程式碼

標籤(tag): 它是一個附屬於欄位的字串,可以是檔案或其他的重要標記。

標籤的內容不可以在一般的程式設計中使用,只有包 reflect 能獲取它。

它可以在執行時自省型別、屬性和方法。

比如:在一個變數上呼叫 reflect.TypeOf() 可以獲取變數的正確型別。

如果變數是一個結構體型別,就可以通過 Field索引結構體的欄位,然後就可以使用 Tag 屬性。

匿名欄位和內嵌結構體

結構體可以包含一個或多個 匿名(或內嵌)欄位

即這些欄位沒有顯式的名字,只有欄位的型別是必須的,此時型別也就是欄位的名字。

匿名欄位本身可以是一個結構體型別,即 結構體可以包含內嵌結構體

可以粗略地將這個和麵向物件語言中的繼承概念相比較,隨後將會看到它被用來模擬類似繼承的行為。

Go 語言中的繼承是通過內嵌或組合來實現的,所以可以說,在 Go 語言中,相比較於繼承,組合更受青睞。

示例1:

package main

import "fmt"

type innerS struct {
    in1 int
    in2 int
}

type outerS struct {
    b    int
    c    float32
    int  // anonymous field
    innerS //anonymous field
}

func main() {
    outer := new(outerS)
    outer.b = 6
    outer.c = 7.5
    outer.int = 60
    outer.in1 = 5
    outer.in2 = 10

    fmt.Printf("outer.b is: %d\n",outer.b)
    fmt.Printf("outer.c is: %f\n",outer.c)
    fmt.Printf("outer.int is: %d\n",outer.int)
    fmt.Printf("outer.in1 is: %d\n",outer.in1)
    fmt.Printf("outer.in2 is: %d\n",outer.in2)

    // 使用結構體字面量
    outer2 := outerS{6,7.5,60,innerS{5,10}}
    fmt.Printf("outer2 is:",outer2)
}
複製程式碼

輸出:

outer.b is: 6
outer.c is: 7.500000
outer.int is: 60
outer.in1 is: 5
outer.in2 is: 10
outer2 is:{6 7.5 60 {5 10}}
複製程式碼

示例2:

package main

import "fmt"

type A struct {
    ax,ay int
}

type B struct {
    A
    bx,by float32
}

func main() {
    b := B{A{1,2},3.0,4.0}
    fmt.Println(b.ax,b.ay,b.bx,b.by)
    fmt.Println(b.A)
}
複製程式碼

輸出:

1 2 3 4
{1 2}
複製程式碼

通過型別 outer.int 的名字來獲取儲存在匿名欄位中的資料,

於是可以得出一個結論:在一個結構體中對於每一種資料型別只能有一個匿名欄位。

方法

一般 Go 裡有函式和方法兩種說法,本質上是一樣的,只是方法是獨屬於一個結構體的函式,只能通過結構體的變數用 . 去呼叫。

定義如下:

func (recv struct_type) methodName(parameter_list) (return_value_list) { ... }
複製程式碼

在方法名之前,func 關鍵字之後的括號中指定 receiver。

如果 recvstruct_type 的例項,Method1 是它的方法名,那麼方法呼叫遵循傳統的 object.name 選擇器符號:recv.Method1()

如果 recv 一個指標,Go 會自動解引用。

如果方法不需要使用 recv 的值,可以用 _ 替換它,比如:

func (_ struct_type) methodName(parameter_list) (return_value_list) { ... }
複製程式碼

recv 就像是面嚮物件語言中的 thisself,但是 Go 中並沒有這兩個關鍵字。

可以使用 thisself 作為 receiver 的名字。

使用結構體

v1.X = 4				//設定結構變數的值
fmt.Println("第 2 行:",v1.X)	//讀取結構變數的值

p := &v1		//p指向 v1的記憶體地址
p.X = 1e9		//為p的X變數 重新賦值
fmt.Println("第 3 行:",v1)	//看下v1的新值
複製程式碼

container

Go 提供的容器資料型別,在container包中。

heap

堆結構

Go 中只定義了介面,沒有給出實現。

type Interface interface {
	sort.Interface
	Push(x interface{}) // add x as element Len()
	Pop() interface{}   // remove and return element Len() - 1.
}
複製程式碼

如果想使用堆,只要實現以下五個方法就能實現該介面。

    Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i,j int) bool
	// Swap swaps the elements with indexes i and j.
    Swap(i,j int)
    Push(x interface{}) // add x as element Len()
	Pop() interface{}   // remove and return element Len() - 1.
複製程式碼

list

雙向連結串列

標準庫裡的定義如下:

// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
	root Element // sentinel list element,only &root,root.prev,and root.next are used
	len  int     // current list length excluding (this) sentinel element
}
複製程式碼
// Element is an element of a linked list.
type Element struct {
	// Next and previous pointers in the doubly-linked list of elements.
	// To simplify the implementation,internally a list l is implemented
	// as a ring,such that &l.root is both the next element of the last
	// list element (l.Back()) and the previous element of the first list
	// element (l.Front()).
	next,prev *Element

	// The list to which this element belongs.
	list *List

	// The value stored with this element.
	Value interface{}
}
複製程式碼

list對應的方法有:

type Element
    func (e *Element) Next() *Element
    func (e *Element) Prev() *Element
type List
    func New() *List
    func (l *List) Back() *Element   // 最後一個元素
    func (l *List) Front() *Element  // 第一個元素
    func (l *List) Init() *List  // 連結串列初始化
    func (l *List) InsertAfter(v interface{},mark *Element) *Element // 在某個元素後插入
    func (l *List) InsertBefore(v interface{},mark *Element) *Element  // 在某個元素前插入
    func (l *List) Len() int // 在連結串列長度
    func (l *List) MoveAfter(e,mark *Element)  // 把e元素移動到mark之後
    func (l *List) MoveBefore(e,mark *Element)  // 把e元素移動到mark之前
    func (l *List) MoveToBack(e *Element) // 把e元素移動到佇列最後
    func (l *List) MoveToFront(e *Element) // 把e元素移動到佇列最頭部
    func (l *List) PushBack(v interface{}) *Element  // 在佇列最後插入元素
    func (l *List) PushBackList(other *List)  // 在佇列最後插入接上新佇列
    func (l *List) PushFront(v interface{}) *Element  // 在佇列頭部插入元素
    func (l *List) PushFrontList(other *List) // 在佇列頭部插入接上新佇列
    func (l *List) Remove(e *Element) interface{} // 刪除某個元素
複製程式碼

ring

雙向迴圈連結串列

雙向連結串列和雙向迴圈連結串列的結構示意圖:

標準庫裡的定義如下:

// A Ring is an element of a circular list,or ring.
// Rings do not have a beginning or end; a pointer to any ring element
// serves as reference to the entire ring. Empty rings are represented
// as nil Ring pointers. The zero value for a Ring is a one-element
// ring with a nil Value.
//
type Ring struct {
	next,prev *Ring
	Value      interface{} // for use by client; untouched by this library
}
複製程式碼

環的結構有點特殊,環的尾部就是頭部,所以每個元素實際上就可以代表自身的這個環。 它不需要像list一樣保持list和element兩個結構,只需要保持一個結構就行。

ring提供的方法有:

type Ring
    func New(n int) *Ring  // 初始化環
    func (r *Ring) Do(f func(interface{}))  // 迴圈環進行操作
    func (r *Ring) Len() int // 環長度
    func (r *Ring) Link(s *Ring) *Ring // 連線兩個環
    func (r *Ring) Move(n int) *Ring // 指標從當前元素開始向後移動或者向前(n可以為負數)
    func (r *Ring) Next() *Ring // 當前元素的下個元素
    func (r *Ring) Prev() *Ring // 當前元素的上個元素
    func (r *Ring) Unlink(n int) *Ring // 從當前元素開始,刪除n個元素
複製程式碼

型別別名&型別定義

參考資料飛雪無情的部落格

定義

type D = int   // 型別別名
type I int    // 型別宣告
複製程式碼

型別別名有 = 號,型別宣告沒有。

型別別名

型別別名其實就是給原本的型別起多一個名字。 原本的型別該怎麼用,該型別基本就怎麼用(除非用型別別名改變了可匯出性)。

這個特性的主要目的是用於已經定義的型別,在package之間的移動時的相容。 比如我們有一個匯出的型別flysnow.org/lib/T1,現在要遷移到另外一個 package flysnow.org/lib2/T1中。

沒有type alias的時候我們這麼做,會導致其他第三方引用舊的package路徑的程式碼,要統一修改,不然無法使用。

有了type alias就不一樣了,型別T1的實現我們可以遷移到lib2下,

同時我們在原來的lib下定義一個lib2T1的別名,

這樣第三方的引用就可以不用修改,也可以正常使用,

只需要相容一段時間,再徹底的去掉舊的package裡的型別相容,

這樣就可以漸進式的重構我們的程式碼,而不是一刀切。

型別定義

型別定義就是基於原本的型別建立了一個新型別,新型別和原本的型別是兩個型別了。

這個特性多用於,你想給型別 a 新增新方法,但是型別 a 非本地型別,就無法新增。

這時,你可以用型別定義將其定義為型別 b ,這樣就能新增新方法了。