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
是一個元素型別為 T 的 slice。
一個 slice
會指向一個序列的值,並且包含了長度資訊。
說人話,slice
其實類似於 Java
裡的 ArrayList
不同點在於,Java
裡的 ArrayList
自己內部維護一個陣列,
slice
即可以內部維護一個陣列,也可以在現有的陣列上創造 slice
。
每次擴容 slice
會指向一個擴容後的陣列。
nil slice
slice
的零值是 nil
。
一個 nil
的 slice
的長度和容量是 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]
表示從n
到m-1
的slice 元素
,含兩端。 -
slice[n:n]
是空的,即nil
。 -
slice[n:n+1]
有一個元素。 -
slice[:m]
表示從0
到m-1
的slice 元素
,含兩端。 -
slice[n:]
表示從n
到len(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
在使用之前必須建立;值為 nil
的 map
是空的,並且不能賦值。
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
複製程式碼
介面
-
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 裡面沒有泛型,希望官方早點加入這個特性。
結構體
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。
如果 recv
是 struct_type
的例項,Method1
是它的方法名,那麼方法呼叫遵循傳統的 object.name
選擇器符號:recv.Method1()
。
如果 recv
一個指標,Go 會自動解引用。
如果方法不需要使用 recv
的值,可以用 _
替換它,比如:
func (_ struct_type) methodName(parameter_list) (return_value_list) { ... }
複製程式碼
recv
就像是面嚮物件語言中的 this
或 self
,但是 Go 中並沒有這兩個關鍵字。
可以使用 this 或self 作為 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
下定義一個lib2
下T1
的別名,
這樣第三方的引用就可以不用修改,也可以正常使用,
只需要相容一段時間,再徹底的去掉舊的package裡的型別相容,
這樣就可以漸進式的重構我們的程式碼,而不是一刀切。
型別定義
型別定義就是基於原本的型別建立了一個新型別,新型別和原本的型別是兩個型別了。
這個特性多用於,你想給型別 a
新增新方法,但是型別 a
非本地型別,就無法新增。
這時,你可以用型別定義將其定義為型別 b
,這樣就能新增新方法了。