title: go語言入門
date: 2019-02-12 22:03:27
前言
因項(xiàng)目需要和個(gè)人喜好概行,決定系統(tǒng)入門go語言。
go是由Google開發(fā)、開源、強(qiáng)類型的編譯型語言徘公。與c語言類似,不同的是哮针,go中每行語句結(jié)束不用加 ;
:-)
本筆記主要參考 《Go語言實(shí)戰(zhàn)》 关面。
后加:本文所基于的GO語言版本較低(1.10),當(dāng)時(shí)還并未支持 Go Modules
十厢。
一等太、Hello world
編寫hello.go
文件:
package main // 程序入口包
import (
"fmt"
)
// 程序入口函數(shù)
func main() {
fmt.Println("Hello world")
}
在命令行中輸入:go run hello.go
二、基礎(chǔ)語言
1. 變量
1.1. 基本變量類型
// 布爾型
bool
// 字符串型
string
// 整型
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
// byte型
byte // uint8
// 表示一個(gè) Unicode 碼點(diǎn)
rune // int32 的別名
// 浮點(diǎn)型
float32 float64
// 復(fù)數(shù)型
complex64 complex128
1.2. 變量聲明
特性:
- 使用
var
關(guān)鍵字聲明(像js)寿烟,變量類型在變量名后 - 短變量聲明 (像python)
- 使用
()
一次聲明多個(gè)變量 - 若聲明的變量沒被使用澈驼,會(huì)報(bào)錯(cuò)
// 一般聲明
var v_name1 int
var v_name2 = 1 // 根據(jù)值自行判別變量類型
// 短變量聲明辛燥。不能用于聲明全局變量
v_name3 := "hello"
// 多變量聲明
var vname1, vname2, vname3 int
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
)
// 指針變量聲明
var p *int
v_p := &v_name1
*v_p = 233
注:和c
類似筛武,go也分 全局變量(函數(shù)外、包內(nèi)) 和 局部變量(函數(shù)內(nèi)/控制語句內(nèi))
1.3. 變量零值
變量聲明時(shí)沒有賦予初始值挎塌,則默認(rèn)被賦予零值徘六。
- 布爾型零值:
false
- 字符串型零值:
""
- 數(shù)值型零值:
0
- 指針型零值:
nil
1.4. 強(qiáng)制類型轉(zhuǎn)換
表達(dá)式T(v)
將值v
轉(zhuǎn)換為類型T
。
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
1.5. 類型推導(dǎo)
當(dāng)不指定數(shù)據(jù)類型時(shí)榴都,系統(tǒng)會(huì)自行推導(dǎo)變量類型待锈。如下:
var i int
j := i // j 也是一個(gè) int
// 初始值為常量,則取決于常量的精度
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
1.6. 變量輸出
使用fmt
包中的函數(shù):fmt.Printf
和fmt.Println
嘴高。
格式化輸出fmt.Printf
== c語言中的printf
竿音。%T
輸出數(shù)據(jù)的類型和屎,%v
輸出任意數(shù)據(jù)的值,%p
輸出地址數(shù)據(jù)春瞬,%d
輸出整型數(shù)據(jù)柴信,等等。
直接輸出fmt.Println
== python中的print
宽气。
2. 常量
用const
關(guān)鍵字常量随常。可以不指定常量的數(shù)據(jù)類型萄涯。
const b string = "abc"
const b = "abc"
const Pi = 3.14
3. 運(yùn)算符
運(yùn)算符與c
語言類似绪氛。具體如下,優(yōu)先級從高到低:
分類 | 描述 | 關(guān)聯(lián)性 | ||
---|---|---|---|---|
后綴 |
() [] -> . ++ --
|
左到右 | ||
一元 |
+ - ! ~ (type) * & sizeof()
|
右到左 | ||
乘法 |
* / %
|
左到右 | ||
加法 |
+ -
|
左到右 | ||
移位 |
<< >>
|
左到右 | ||
關(guān)系 |
< <= > >=
|
左到右 | ||
相等 |
== !=
|
左到右 | ||
按位AND | & |
左到右 | ||
按位XOR | ^ |
左到右 | ||
按位OR | ` | ` | 左到右 | |
邏輯AND | && |
左到右 | ||
邏輯OR | ` | ` | 左到右 | |
條件 | ?: |
右到左 | ||
分配 |
= += -= *= /= %= >>= <<= &= ^= ` |
=` | 右到左 | |
逗號 | , |
左到右 |
4. 語句
go中if
和for
語句不需要加()
涝影;語句大括號{
不需要換行枣察。
4.1. if語句
特性:
- 可以初始化變量,僅在
if
語句中使用袄琳。
// if...else...
if numA < 20 {
fmt.Printf("a小于20\n" );
} else {
fmt.Printf("numA 不小于 20\n" );
}
// if語句的分號前面询件,相當(dāng)于初始化一個(gè)變量,僅在if語句內(nèi)使用
if i := 30; numA < i {
fmt.Println(i)
}
4.2. switch語句
特性:
- case語句結(jié)束自動(dòng)break
- case可以同時(shí)匹配多個(gè)值唆樊,如:
case v1,v2,v3:
- 匹配一個(gè)case成功后宛琅,可以使用
fallthrough
強(qiáng)制匹配下一個(gè)case -
switch
可以沒有條件
var grade string
var marks int = 90
// case語句結(jié)束自動(dòng)break。
// 可以同時(shí)case多個(gè)值逗旁,case v1,v2,v3:
switch marks {
case 90: grade = "A"
case 80: grade = "B"
case 50,60,70 : grade = "C"
default: grade = "D"
}
fmt.Printf("你的等級是 %s\n", grade );
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
switch {
case false:
fmt.Println("1嘿辟、case 條件語句為 false")
fallthrough
case true:
fmt.Println("2、case 條件語句為 true")
fallthrough
case false:
fmt.Println("3片效、case 條件語句為 false")
fallthrough
case true:
fmt.Println("4红伦、case 條件語句為 true")
case false:
fmt.Println("5、case 條件語句為 false")
fallthrough
default:
fmt.Println("6淀衣、默認(rèn) case")
}
/*
輸出:
2昙读、case 條件語句為 true
3、case 條件語句為 false
4膨桥、case 條件語句為 true
*/
4.3. select語句
select
語句類似于switch
語句蛮浑。
但是,區(qū)別在于:
- 每個(gè)case必須是一個(gè)通信操作(數(shù)據(jù)結(jié)構(gòu)通道的操作)只嚣,要么是發(fā)送要么是接收沮稚。
- select隨機(jī)執(zhí)行一個(gè)未堵塞的case。如果所有case都堵塞册舞,它將等待蕴掏,直到有case可以通行。
var c1, c2, c3 chan int
var i1, i2 int
// 隨機(jī)執(zhí)行一個(gè)case,若所有case都堵塞盛杰,則直到有case可以通行為止
select {
case i1 = <-c1:
fmt.Println("received ", i1, " from c1")
case c2 <- i2:
fmt.Println("sent ", i2, " to c2")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Println("received ", i3, " from c3")
} else {
fmt.Println("c3 is closed")
}
default:
fmt.Println("no communication")
}
/*
輸出:
no communication
*/
4.4. for語句
特性:
-
for
只帶條件判斷相當(dāng)于while
-
for
中使用range
關(guān)鍵字挽荡,可以遍歷順序結(jié)構(gòu),返回key
和value
for i := 0; i < 10; i++ {
fmt.Printf("i 的值為: %d\n", i)
if i > 5{
break
}
}
// 相當(dāng)于c的while
for numA < numB {
numA++
fmt.Printf("numA 的值為: %d\n", numA)
}
numbers := [6]int{1, 2, 3, 5}
// range關(guān)鍵字即供,可以對 slice徐伐、map、數(shù)組募狂、字符串等進(jìn)行迭代
for key, value := range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", key, value)
}
5. 函數(shù) 引用-數(shù)據(jù)類型
go語言的一大“神奇”特點(diǎn)办素,就是喜歡把原本前面的東西放到后面,函數(shù)也不例外祸穷。
特性:
- 函數(shù)以關(guān)鍵字
func
進(jìn)行聲明 - 返回類型(和值)性穿,放在參數(shù)項(xiàng)的后面
- 允許先聲明返回值
- 多值返回
-
defer
。defer
語句會(huì)將指定函數(shù)推遲到外層函數(shù)返回之后再執(zhí)行雷滚。并且算谈,被推遲的函數(shù)將被壓入一個(gè)棧中篮迎。
//==========函數(shù)============
func add(x int, y int) int {
return x + y
}
// 先聲明返回值蕉毯,直接用return返回蚓挤。適用于短函數(shù)
func subtract(x int, y int) (z int) {
z = x - y
return
}
// 返回多值
func swap(x, y string) (string, string) {
return y, x
}
/*
使用:
stringA, stringB = swap(stringA, stringB)
*/
// 傳指針
func swap_p(x, y *int) {
var temp int
temp = *x /* 保持 x 地址上的值 */
*x = *y /* 將 y 值賦給 x */
*y = temp /* 將 temp 值賦給 y */
}
// defer棧
func deferTest() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
/*
輸出
counting
done
9
8
7
6
5
4
3
2
1
0
*/
另外,跟JavaScript
一樣车份,go語言也有:
- 函數(shù)變量谋减,函數(shù)也是屬于一類數(shù)據(jù)類型。
- 函數(shù)做參扫沼。多態(tài)性的一種體現(xiàn)出爹。
- 函數(shù)作為返回值(函數(shù)閉包)。得以使用函數(shù)的局部變量缎除。
- 匿名函數(shù)
- 函數(shù)自調(diào)用
// 函數(shù)做參严就。函數(shù)參數(shù),要指明函數(shù)參數(shù)的參數(shù)類型和返回值類型
func handleNum(num float64, fn func (float64) float64) {
fmt.Println("函數(shù)做參開始")
fmt.Println(fn(num))
fmt.Println("函數(shù)做參結(jié)束")
}
// 閉包函數(shù)(將函數(shù)作為返回值)
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
func main() {
// 函數(shù)變量
getSquareRoot := func(x float64) float64 {
return math.Sqrt(x)
}
fmt.Println(getSquareRoot(9))
// 函數(shù)做參
handleNum(16, getSquareRoot)
// 函數(shù)閉包
nextNumber := getSequence() // nextNumber 為一個(gè)函數(shù)器罐,函數(shù) i 為 0
fmt.Println(nextNumber()) // 調(diào)用 nextNumber 函數(shù)梢为,i 變量自增 1 并返回
fmt.Println(nextNumber())
fmt.Println(nextNumber())
nextNumber1 := getSequence() // 創(chuàng)建新的函數(shù) nextNumber1,并查看結(jié)果
fmt.Println(nextNumber1()) // 輸出 1
fmt.Println(nextNumber1()) // 輸出 2
// 匿名函數(shù)和函數(shù)自調(diào)用
func (count int) {
fmt.Println("匿名函數(shù)開始")
for i := 0; i < count; i++ {
fmt.Println(i)
}
fmt.Println("匿名函數(shù)結(jié)束")
} (3)
}
輸出:
函數(shù)做參開始
4
函數(shù)做參結(jié)束
1
2
3
1
2
匿名函數(shù)開始
0
1
2
匿名函數(shù)結(jié)束
6. 包
6.1. 包的特性
與java
相似轰坊,每個(gè).go
文件開頭需要用package
關(guān)鍵字聲明文件所屬于的包铸董。并且,包的名字需要與目錄名字相同衰倦。main
包除外袒炉。
例如旁理,項(xiàng)目中有一個(gè)routers/
目錄樊零,routers/
目錄下有一個(gè)router.go
文件,那router.go
文件開頭必須聲明所屬于的包:
package routers
6.2. main包
每個(gè)程序(項(xiàng)目)都必須有一個(gè)main
包,編譯器會(huì)根據(jù)main
包找到main()
函數(shù)驻襟,這是程序的入口函數(shù)夺艰。若沒找到main()
函數(shù),程序則不會(huì)執(zhí)行沉衣。
6.3. 導(dǎo)入包
import
關(guān)鍵字用于導(dǎo)入一個(gè)外包郁副。格式如下:
import "fmt"
或者導(dǎo)入多個(gè)包時(shí):
import (
"fmt"
"math"
"net/http"
)
根據(jù)以上包名,編譯器會(huì)依次在以下目錄中查找:
1)GOROOT/src
豌习,安裝路徑下的src
目錄
2)GOPATH/src
存谎,工作空間下的src
目錄
3)若以上都沒找到,且包路徑中包含URL
肥隆,那么會(huì)從網(wǎng)上獲取包既荚,并保存到GOPATH/src
目錄下。比如:
import "github.com/99MyCql/chatRoom/routers"
6.4. 命名導(dǎo)入
導(dǎo)入包的名字默認(rèn)為包名栋艳,但如果出現(xiàn)重名情況恰聘,我們可以通過給包重新命名來化解。
import (
"fmt"
myfmt "mylib/fmt" // myfmt為該包的新名字
)
go語言中若導(dǎo)入了某包(會(huì)調(diào)用該包中的init()
函數(shù))吸占,而又沒使用該包晴叨,編譯器則會(huì)報(bào)錯(cuò)。
解決這個(gè)問題可以使用空白標(biāo)識符_
來重命名這個(gè)包矾屯,表明導(dǎo)入該包卻不使用該包兼蕊。如:
import "github.com/99MyCql/chatRoom/routers"
6.5. init()函數(shù)
一個(gè)包中,可以有一個(gè)或多個(gè)init()
函數(shù)(多個(gè)init()函數(shù)不能在同一個(gè).go
文件中)件蚕。
init()
函數(shù)會(huì)在main()
函數(shù)執(zhí)行前被調(diào)用遍略。
每個(gè)被導(dǎo)入的包(不管有沒有被使用),都會(huì)調(diào)用包中的所有init()
函數(shù)骤坐。通常绪杏,init()
函數(shù)被用來進(jìn)行一些初始化操作。
使用空白標(biāo)識符_
纽绍,可以讓包中的init()
函數(shù)被調(diào)度使用蕾久,同時(shí)編譯器不會(huì)因?yàn)榘鼪]被使用而報(bào)錯(cuò)。
6.6. 包中名的可見性(special)
在一個(gè)包內(nèi)拌夏,所有文件的全局變量是共享的僧著。
對于包外,以大寫字母開頭的全局變量和函數(shù)是公開的障簿,以小寫字母開頭的是私有的盹愚。如:
-
fmt.Println()
是調(diào)用fmt
包中公開的Println()
函數(shù)。 -
fmt.Println(math.pi)
輸出math
包中變量站故,會(huì)報(bào)錯(cuò)皆怕,因?yàn)樵撟兞渴撬接械摹?/li>
6.7. 使用另一個(gè)包中的變量和函數(shù)
通過 <package name>.<var/fun>
格式(跟C++使用類中變量函數(shù)相似)毅舆,來使用另一個(gè)包中公開的變量和函數(shù)。如:
fmt.Println() // 使用 fmt 包中 Println() 函數(shù)
math.PI // 使用 math 包中 PI 變量
三愈腾、進(jìn)階數(shù)據(jù)結(jié)構(gòu)
1. 指針 值-數(shù)據(jù)類型
與c語言指針類似憋活,go指針指向?qū)?yīng)類型的變量。
但不同的是:
go語言指針不能進(jìn)行運(yùn)算
指針變量的聲明中虱黄,標(biāo)識符
*
必須貼近變量類型悦即,而不貼近變量名。如:var name *T
指針類型的零值為
nil
// 指針變量聲明
var p *int
v_p := &v_name1
*v_p = 233
2. 數(shù)組 值-數(shù)據(jù)類型
go語言數(shù)組跟c語言的相似橱乱,但也有不同辜梳。
特性:
格式
var name [len]T
,如:var a [2]string
go語言的數(shù)組是一種數(shù)據(jù)類型泳叠,而且是一種值類型冗美。即數(shù)組名是一個(gè)值,包含著整個(gè)數(shù)組的數(shù)據(jù)
需要編譯器自己識別數(shù)組長度時(shí)析二,不能使
[]
中空閑粉洼,而必須使用[...]
指向數(shù)組的指針格式為:
var name *[len]T
,如:var arrp *[5]int
//====================數(shù)組=====================
// 變量 a 是一個(gè)值類型叶摄,而不是引用類型属韧。包含著整個(gè)數(shù)組的數(shù)據(jù)
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
// 使用字面量聲明數(shù)組
array1 := [5]int{10,20,30,40,50}
array2 := [...]int{1,2,3,4,5} // ... 可以使編譯器根據(jù)元素?cái)?shù)量,自動(dòng)確定數(shù)組長度
fmt.Println(array1, array2)
// 數(shù)組元素類型為指針
array3 := [5]*int{0:new(int), 1:new(int)} // 用 下標(biāo):... 進(jìn)行特定位置的初始化
*array3[0] = 10
*array3[1] = 20
fmt.Println(*array3[0])
fmt.Println(array3)
// 多維數(shù)組
var array4 [4][2]int // 4行2列的二維數(shù)組蛤吓,即有4行宵喂,每行有2個(gè)int
// 指向數(shù)組變量的指針
arrP := &a
fmt.Printf("a 's type is %T\n", a)
fmt.Printf("arrp 's type is %T\n", arrP)
(*arrP)[1] = "Today" // arrP[1] = "Today" 也可以
fmt.Println(*arrP)
// new 指向數(shù)組的指針
var arrp *[5]int // 此時(shí)為nil
fmt.Println(arrp) // 輸出 nil
arrp = new([5]int) // 分配相應(yīng)的數(shù)組空間,并返回指針
fmt.Println(arrp) // 輸出 &[0 0 0 0 0]
// 與c語言的不同会傲,go的數(shù)組名是值而不是指針锅棕。以下按照c語言的思路,在go中是錯(cuò)誤的
// var p *int
// p = a
// 數(shù)組是值類型而不是引用類型的示例
arrA := [2]int{1,2}
arrB := arrA // 相當(dāng)于賦值了整個(gè)數(shù)組的值
arrB[1] = 3
fmt.Println(arrA, arrB) // 輸出 [1 2] [1 3]
輸出:
Hello World
[Hello World]
[10 20 30 40 50] [1 2 3 4 5]
10
[0xc42008a018 0xc42008a030 <nil> <nil> <nil>]
[[0 20] [0 0] [0 60] [0 0]]
a 's type is [2]string
arrp 's type is *[2]string
[Hello Today]
<nil>
&[0 0 0 0 0]
[1 2] [1 3]
注意:
由于在go語言中淌山,數(shù)組類型是值類型而不是引用類型裸燎。所以,在函數(shù)傳參時(shí)泼疑,我們需要傳入指向數(shù)組的指針德绿,而不是數(shù)組值。但退渗,若是希望拿到該數(shù)組的副本移稳,則可以選擇使用傳入值。
同時(shí)会油,指向數(shù)組的指針還必須指明數(shù)組的長度个粱,這其實(shí)十分不方便。但切片可以很好地解決這個(gè)問題翻翩。
// 10的6次方數(shù)組
var array [1e6]int
foo(&array)
// 函數(shù)接受一個(gè)指向包含100萬個(gè)整型值數(shù)組的指針
func foo(array *[1e6]int) {
...
}
3. 切片 引用-數(shù)據(jù)類型
切片是go自帶的數(shù)據(jù)類型都许,圍繞動(dòng)態(tài)數(shù)組的概念來構(gòu)建稻薇。
同時(shí),切片是一個(gè)引用類型梭稚,所以切片的零值為nil
。
3.1. 切片的內(nèi)部實(shí)現(xiàn)
切片其實(shí)是一個(gè)很小的結(jié)構(gòu)體絮吵,對底層數(shù)組進(jìn)行了抽象弧烤。“切片結(jié)構(gòu)體”包含三個(gè)屬性:
指向底層數(shù)組的指針蹬敲。底層數(shù)組會(huì)一直存在暇昂,直到?jīng)]有指向它的切片
切片的長度。動(dòng)態(tài)數(shù)組的長度
切片的容量伴嗡。容量相當(dāng)于動(dòng)態(tài)數(shù)組的長度上限
3.2. 切片的創(chuàng)建和初始化
格式為:
name []T
急波。注意[]
中無值,有值為數(shù)組未初始化的切片為“空指針”瘪校,零值為
nil
用
make
關(guān)鍵字創(chuàng)建澄暮,還可以聲明切片的長度和容量。推薦通過數(shù)組或切片
[x:y]
來創(chuàng)建切片(包含x
位元素阱扬,排除y
位元素)泣懊,可以使用[x:]
、[:y]
等麻惶,還可以通過[x:y:z]
規(guī)定切片的容量(z
)
// 切片的創(chuàng)建和初始化
// 注意切片和數(shù)組的區(qū)別
var slice1 []int // nil切片(空指針)馍刮,指向底層數(shù)組的指針為空
if slice1 == nil {
fmt.Println("slice1 is nil")
}
slice2 := []int{0,1,2,3,5:6} // 創(chuàng)建并初始化,跟數(shù)組很像
slice3 := make([]int, 0) // 空切片(不是nil切片)窃蹋,長度和容量為0
slice4 := make([]string, 5) // 用make創(chuàng)建字符串切片卡啰,長度和容量都為5
slice5 := make([]int, 3, 5) // 長度為3,容量為5
fmt.Println(slice1, slice2, slice3, slice4, slice5)
arr := [5]int{0,1,2,3,4}
arrSlice1 := arr[1:3] // 用數(shù)組創(chuàng)建切片警没。此時(shí)切片指向該數(shù)組下標(biāo)1位置匈辱,并且長度為2、容量為4(下標(biāo)1到原數(shù)組結(jié)束)
arrSlice2 := arr[1:3:3] // 規(guī)定容量為3(容量不可超過原數(shù)組)杀迹。此時(shí)梅誓,arrSlice1和arrSlice2共享同一個(gè)底層數(shù)組
newSlice1 := slice2[1:3] // 用切片構(gòu)建切片。兩個(gè)切片共享一個(gè)底層數(shù)組
newSlice2 := newSlice1
fmt.Println(arr, arrSlice1, arrSlice2, newSlice1, newSlice2)
輸出:
slice1 is nil
[] [0 1 2 3 0 6] [] [ ] [0 0 0]
[0 1 2 3 4] [1 2] [1 2] [1 2] [1 2]
3.3. 切片的使用
len()
函數(shù)獲取切片長度佛南,cap()
函數(shù)獲取切片容量func copy(dst, src []T) int
將src
切片的內(nèi)容拷貝到dst
切片中梗掰,拷貝的長度為兩個(gè)slice中長度較小的長度值func append(s []T, x ...T) []T
返回一個(gè)新切片。當(dāng)原切片容量不足時(shí)嗅回,append
函數(shù)會(huì)創(chuàng)建一個(gè)新的容量更大的底層數(shù)組及穗,并將原切片的底層數(shù)組復(fù)制到新數(shù)組里,再追加新的值绵载。append(dist, x, y)
追加多個(gè)值(x
,y
...)到dist
切片埂陆。append(dist, src...)
將整個(gè)src
切片追加到dist
切片尾苛白。切片的多維和遍歷/迭代,與數(shù)組一樣
// len() 和 cap()
fmt.Printf("slice5 is %v, len is %d, capacity is %d\n", slice5, len(slice5), cap(slice5))
fmt.Printf("arrSlice1 is %v, len is %d, capacity is %d\n", arrSlice1, len(arrSlice1), cap(arrSlice1))
// func copy(dst, src []T) int
copy(slice5, arrSlice1) // copy()函數(shù)會(huì)根據(jù)長度復(fù)制
fmt.Printf("slice5 is %v, len is %d, capacity is %d\n", slice5, len(slice5), cap(slice5))
// func append(s []T, x ...T) []T 返回一個(gè)新切片
// 當(dāng)追加后焚虱,目標(biāo)切片長度超過容量時(shí)购裙,append函數(shù)會(huì)創(chuàng)建一個(gè)新的容量更大的底層數(shù)組,將原本數(shù)組復(fù)制到新數(shù)組中鹃栽,再追加新的值
slice1 = append(slice1, 10)
fmt.Printf("slice1 is %v, len is %d, capacity is %d\n", slice1, len(slice1), cap(slice1))
slice1 = append(slice1, slice2...) // 用標(biāo)識符 ... 將整個(gè)切片追加到另一個(gè)切片
fmt.Printf("slice1 is %v, len is %d, capacity is %d\n", slice1, len(slice1), cap(slice1))
輸出:
slice5 is [0 0 0], len is 3, capacity is 5
arrSlice1 is [1 2], len is 2, capacity is 4
slice5 is [1 2 0], len is 3, capacity is 5
slice1 is [10], len is 1, capacity is 1
slice1 is [10 0 1 2 3 0 6], len is 7, capacity is 8
3.4. 切片的“陷阱”
切片賦值后躏率,兩個(gè)切片會(huì)共享同一個(gè)底層數(shù)組,一個(gè)切片修改值時(shí)會(huì)影響到另一個(gè)數(shù)組民鼓。切片共享底層數(shù)組示例圖:
[圖片上傳失敗...(image-b37feb-1601651171851)]
// 切片賦值后薇芝,會(huì)共用一個(gè)底層數(shù)組
sliceA := []string{"hello", "world", "!", "!", "!"}
fmt.Printf("sliceA is %v\n", sliceA)
sliceB := sliceA[:3]
sliceB[2] = "?" // 由于sliceB和sliceA共享一個(gè)底層數(shù)組,通過sliceB修改底層數(shù)組丰嘉,會(huì)影響到sliceA
fmt.Printf("sliceB is %v\n", sliceB)
fmt.Printf("sliceA changs : %v\n", sliceA)
輸出:
sliceA is [hello world ! ! !]
sliceB is [hello world ?]
sliceA changs : [hello world ? ! !]
4. 映射 引用-數(shù)據(jù)類型
映射又稱map夯到、鍵值對,基于特定的hash函數(shù)/散列函數(shù)饮亏。
映射也是引用類型耍贾,零值為nil
。
4.1. 映射的創(chuàng)建和初始化
格式:
name map[keyT]valueT
未初始化的聲明會(huì)創(chuàng)建
nil
映射路幸。nil 映射既沒有鍵逼争,也不能添加鍵用
make()
函數(shù)進(jìn)行創(chuàng)建,產(chǎn)生空映射而非nil
映射用字面量初始化聲明映射劝赔,采用換行的形式誓焦,需要在最后一個(gè)鍵值對后加
,
// 映射的創(chuàng)建和初始化
var dictNil map[string]int // 聲明了一個(gè)nil映射,nil 映射既沒有鍵着帽,也不能添加鍵
if dictNil == nil {
fmt.Println("dictNil is nil")
}
// dictNil["red"] = 1 運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
dict1 := make(map[string]int) // 用make()函數(shù)創(chuàng)建的map杂伟,是空映射,而不是nil映射仍翰。映射/鍵值對中鍵的類型不能是切片赫粥、函數(shù)等引用數(shù)據(jù)類型
dict1["red"] = 1
dict2 := map[string]string {
"Red": "#da1337",
"Orange": "#e95a22", // 最后一行需要加 ,
}
fmt.Println(dict1, dict2)
輸出:
dictNil is nil
map[red:1] map[Orange:#e95a22 Red:#da1337]
4.2. 映射的操作
獲取值:
value=map[key]
。通過雙賦值檢測某個(gè)鍵是否存在:value, ok = map[key]
予借,若key
在map
中越平,ok
為true
;否則灵迫,ok
為false
秦叛;若key
不在映射中,那么value
是該映射元素類型的零值瀑粥。增加鍵值對:
map[key]=value
刪除鍵值對挣跋,用
delete()
函數(shù):delete(map, key)
fmt.Println(dict1["red"]) // 獲取鍵值對
dict1["blue"] = 2 // 增加鍵值對
fmt.Println(dict1)
delete(dict1, "red") // 刪除鍵值對
fmt.Println(dict1)
輸出:
1
map[red:1 blue:2]
map[blue:2]
5. 結(jié)構(gòu)體 值-數(shù)據(jù)類型
- 結(jié)構(gòu)體類型使用關(guān)鍵字
struct
進(jìn)行定義,用type
進(jìn)行命名狞换,如:type user struct{}
避咆。
結(jié)構(gòu)體定義:
type user struct {
name string
email string
age int
}
type admin struct {
person user // 嵌套另一個(gè)結(jié)構(gòu)體
level int
}
結(jié)構(gòu)體使用:
var bill user // 初始化后舟肉,結(jié)構(gòu)體中所有成員都會(huì)被賦零值
// 短變量聲明
lisa := user{
name: "Lisa",
email: "lisa@email.com",
age: 19, // 采用換行形式時(shí)最后一個(gè)也需要加 ,
}
tom := user{name:"Tom"} // 單獨(dú)聲明某一個(gè)成員
fmt.Println(bill, lisa, tom)
ad := admin{lisa, 10} // 直接按照結(jié)構(gòu)體成員順序,傳入對應(yīng)的值
adP := &ad // 獲取指向結(jié)構(gòu)體的指針
fmt.Println(ad, adP)
fmt.Println(ad.person.name) // 結(jié)構(gòu)體值訪問內(nèi)部成員
fmt.Println(adP.person.name) // 結(jié)構(gòu)體指針訪問內(nèi)部成員
輸出:
{ 0} {Lisa lisa@email.com 19} {Tom 0}
{{Lisa lisa@email.com 19} 10} &{{Lisa lisa@email.com 19} 10}
Lisa
Lisa
6. new 和 make
在go語言中有兩個(gè)用于內(nèi)存分配的函數(shù):new
和make
查库。
6.1. new
函數(shù)原型:
func new(Type) *Type
說明:
主要給值類型數(shù)據(jù)分配空間路媚,并返回指向該數(shù)據(jù)空間的指針。這與c
語言中malloc
類似樊销。但是整慎,new()
函數(shù)不能指定個(gè)數(shù)和大小,只能傳入指定的數(shù)據(jù)類型(包括用戶自定義的數(shù)據(jù)類型)现柠。
示例:
// var i *int
// *i=10 錯(cuò)誤院领,野指針
var i *int
i = new(int)
arrP := new([5]int) // 分配長度為5的數(shù)組空間弛矛,并返回?cái)?shù)組指針
6.2. make
函數(shù)原型:
func make(t Type, size ...IntegerType) Type
說明:
make也是用于內(nèi)存分配的够吩,但是和new不同,它只用于chan
丈氓、map
以及slice
的內(nèi)存創(chuàng)建周循,而且它返回的類型值,而不是他們的指針万俗。同時(shí)湾笛,make()
函數(shù)還能對這三個(gè)類型的相關(guān)屬性進(jìn)行初始化。
示例:
slice := make([]int, 3, 5) // 長度為3闰歪,容量為5嚎研,單位為int的slice
dict := make(map[string]int) // 鍵為string,值為int類型的map
buffer := make(chan string, 10)// 緩沖為10的字符串類型通道
7. 淺談引用類型
在Go語言中库倘,引用類型可以看作一個(gè)指針临扮,它并不包含實(shí)際數(shù)據(jù)。比如教翩,切片 slice
只是一個(gè)如下的類型:
type Slice struct {
point Point // 指向底層數(shù)據(jù)的指針
len int // 底層數(shù)據(jù)的長度
cap int // 底層數(shù)據(jù)的容量(最大長度)
}
當(dāng)引用類型作為函數(shù)參數(shù)時(shí)杆勇,你可以通過引用類型修改所指向的數(shù)據(jù)(退出函數(shù)后依然有效)。但是饱亿,你不可以修改引用類型本身(退出函數(shù)后修改無效)蚜退。
以 map
為例:
func mapAdd(m map[string]interface{}) {
m["name"] = "dounine"
}
func mapAdd2(m map[string]interface{}) {
mp := map[string]interface{}{
"name": "dounine",
}
m = mp
}
func main() {
data3 := make(map[string]interface{})
mapAdd(data3)
fmt.Println("type:", reflect.TypeOf(data3), "; value:", reflect.ValueOf(data3))
data4 := make(map[string]interface{})
mapAdd2(data4)
fmt.Println("type:", reflect.TypeOf(data4), "; value:", reflect.ValueOf(data4))
}
輸出:
type: map[string]interface {} ; value: map[name:dounine]
type: map[string]interface {} ; value: map[]
再以 slice
為例:
func sliceAdd(s []int) {
s = append(s, 6)
}
func sliceUpdate(s []int) {
s[0] = 1
}
func main() {
slice1 := []int{0, 1, 2, 3, 5}
sliceAdd(slice1)
fmt.Println(slice1)
sliceUpdate(slice1)
fmt.Println(slice1)
}
輸出:
[0 1 2 3 5]
[1 1 2 3 5]
原因是:append
函數(shù)會(huì)修改 slice
類型本身的 len
屬性,退出函數(shù)后失效彪笼;而修改 slice
類型指向的數(shù)組的值钻注,退出函數(shù)后依然有效。
四配猫、面向?qū)ο?/h2>
在go
語言中是沒有關(guān)鍵字class
的队寇,也就是說,go
語言中沒有類也沒有繼承章姓。但佳遣,go
卻是一個(gè)面向?qū)ο蟮恼Z言识埋,那它究竟如何實(shí)現(xiàn)面向?qū)ο竽兀?/p>
首先,go
通過結(jié)構(gòu)體的成員來定義類的屬性零渐,結(jié)構(gòu)體名即類名窒舟;
其次,通過語法格式讓函數(shù)與結(jié)構(gòu)體關(guān)聯(lián)诵盼,實(shí)現(xiàn)類方法惠豺;
然后,通過關(guān)鍵字interface
與結(jié)構(gòu)體結(jié)合风宁,實(shí)現(xiàn)接口和多態(tài)洁墙;
接著,通過結(jié)構(gòu)體實(shí)名內(nèi)嵌的形式戒财,來實(shí)現(xiàn)對象內(nèi)嵌另一個(gè)對象的has-a
模式热监;
最后,通過結(jié)構(gòu)體匿名域內(nèi)嵌的形式饮寞,來實(shí)現(xiàn)“繼承”孝扛,即is-a
模式。
各功能的具體實(shí)現(xiàn)幽崩,下文一一講解苦始。
1. 類方法
1.1. 定義
格式:
// 用戶類型
type user struct {
name string
email string
}
// notify 方法,以值為接收者
func (u user) notify() {
fmt.Printf("Sending User Email To %s<%s>\n",
u.name,
u.email)
}
// changeEmail 方法慌申,以指針為接收者
func (u *user) changeEmail(email string) {
u.email = email
}
說明:
一個(gè)類型的方法的聲明陌选,必須跟類型在同一個(gè)包內(nèi)。
方法的聲明與函數(shù)類似蹄溉,不同的是咨油,需要在
func
與方法名之間加上接收者參數(shù),指明方法所從屬的類型类缤。接收者有兩種:值接收者 和 指針接收者臼勉。
1.2. 調(diào)用類方法
調(diào)用類型的方法:<類型值/指針>.<方法>
,如:boss.notify()
餐弱。不管是值類型宴霸,還是指向類型的指針,都使用這種格式膏蚓。
示例:
// 值類型
boss := user{
name: "aaaaa",
email: "123456",
age: 20,
}
boss.notify()
boss.changeEmail("2222") // go語言隱式轉(zhuǎn)換瓢谢,(&boss).changeEmail()
// 指針類型
bossP := &boss
bossP.notify() // 隱式轉(zhuǎn)換,(*bossP).notify()
bossP.changeEmail("1111")
1.3. 指針接收者和值接收者
類型的值 使用 指針接收者聲明的方法驮瞧,和 類型的指針使用 值接收者聲明的方法時(shí)氓扛,go語言都會(huì)進(jìn)行隱式轉(zhuǎn)換。所以,不管是以什么接收者聲明的方法采郎,值類型和指針類型都能調(diào)用千所。
示例:
boss := user{
name: "aaaaa",
email: "123456",
age: 20,
}
boss.notify()
boss.changeEmail("2222") // go語言隱式轉(zhuǎn)換,(&boss).changeEmail()
fmt.Println(boss)
bossP := &boss
bossP.notify() // 隱式轉(zhuǎn)換蒜埋,(*bossP).notify()
bossP.changeEmail("1111")
fmt.Println(bossP)
值接收者 和 指針接收者 的區(qū)別:
- 值接收者得到類型的副本淫痰,修改副本值不會(huì)對原本值起作用;
- 指針接收者得到指向類型值的指針整份,所以待错,在指針接收者的方法中修改類型數(shù)據(jù),會(huì)影響到原本的值烈评。
示例:
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
// 值接收者
func (v Vertex) Scale1(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
// 指針接收者
func (v *Vertex) Scale2(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
fmt.Println(v) // 輸出 {3,4}
v.Scale1(10)
fmt.Println(v) // 輸出 {3,4}
v.Scale2(10)
fmt.Println(v) // 輸出 {30,40}
}
2. 接口 引用-數(shù)據(jù)類型
2.1. 聲明
接口是一系列方法的集合火俄。它的格式如下:
type <接口名> interface {
方法1名(方法參數(shù)) 方法返回值
...
}
同時(shí),也可以組合(嵌入)其它接口形成新接口:
type <接口名> interface {
接口名1
接口名2
...
方法1名(方法參數(shù)) 方法返回值
...
}
嵌入進(jìn)來的接口讲冠,相當(dāng)于把它的方法都復(fù)制到新接口中瓜客。
2.2. 實(shí)現(xiàn)接口
如果想要某個(gè)類型實(shí)現(xiàn)某個(gè)接口,只需要將接口中所有方法實(shí)現(xiàn)為類方法即可沟启。示例:
type I interface {
M()
N()
}
type T struct {
S string
}
// 以下兩個(gè)方法意味著: type T implements the interface I
func (t T) M() {
fmt.Println(t.S)
}
func (t T) N() {
fmt.Println(t.S)
}
2.3. 使用接口
如果某個(gè)類型實(shí)現(xiàn)了某個(gè)接口類型的所有方法忆家,那么就可以將該類型的值或指針賦給這個(gè)接口類型的值犹菇。
但要注意:
- 值類型 和 指針類型 能使用 值接收者 實(shí)現(xiàn)的方法德迹;
- 但是,值類型 不能使用 指針接收者 實(shí)現(xiàn)的方法揭芍,指針類型 才能使用胳搞。
示例如下:
// 接口 I
type I interface {
M()
}
// 類型 T
type T struct {
S string
}
// 值接收者 實(shí)現(xiàn)的方法
func (t T) M() {
fmt.Println(t.S)
}
func main() {
// 值類型 使用 值接收者 實(shí)現(xiàn)的方法
var i I = T{"hello"}
i.M()
// 指針類型 使用 值接收者 實(shí)現(xiàn)的方法
var i I = &T{"hello"}
i.M() // 隱式轉(zhuǎn)換:(*i).M()
}
// 接口 I
type I interface {
M()
}
// 類型 T
type T struct {
S string
}
// 指針接收者 實(shí)現(xiàn)的方法
func (t *T) M() {
fmt.Println(t.S)
}
func main() {
// 值類型 不能使用 指針接收者 實(shí)現(xiàn)的方法
// Error: cannot use T literal (type T) as type I in assignment:
// T does not implement I (M method has pointer receiver)
var i1 I = T{"hello"}
i1.M()
// 指針類型 才能使用 指針接收者 實(shí)現(xiàn)的方法
var i2 I = &T{"hello"}
i2.M()
}
因此,我們通常定義類型指針來操作類型称杨。
2.4. nil接口
如果沒有為接口賦值肌毅,而調(diào)用接口中的方法,那將會(huì)報(bào)錯(cuò)姑原,因?yàn)榻涌谥禐?code>nil:
type I interface {
M()
}
func main() {
var i I
// panic: runtime error: invalid memory address or nil pointer dereference
i.M()
}
2.5. 空接口 - 泛型
空接口相當(dāng)于C++中的泛型悬而。格式如下:
interface{}
使用示例:
package main
import "fmt"
func main() {
var i interface{}
describe(i) // 輸出:(<nil>, <nil>)
i = 42
describe(i) // 輸出:(42, int)
i = "hello"
describe(i) // 輸出:(hello, string)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i) // %v:變量的值,%T:變量的類型
}
2.6. 類型斷言與空接口(泛型)
類型斷言可以判斷變量是否為該類型锭汛。格式如下:
t := i.(T)
若不是笨奠,將報(bào)錯(cuò)中斷程序。如果不想中斷唤殴,則使用如下格式:
t, ok := i.(T)
若不是般婆,則ok
值為false
。
使用示例:
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic: interface conversion: interface {} is string, not float64
fmt.Println(f)
}
實(shí)現(xiàn)一個(gè)類型判斷函數(shù):
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
2.7. 自定義輸出格式 - Stringers 接口
Stringers
接口定義如下:
type Stringer interface {
String() string
}
它允許用戶自定義變量打印格式朵逝。示例如下:
type Person struct {
Name string
Age int
}
// type Person implements the interface Stringer
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age) // 返回一個(gè)字符串
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z) // 輸出:Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
}
2.8. 自定義錯(cuò)誤處理 - error 接口
error
接口定義如下:
type error interface {
Error() string
}
使用如下:
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
// type MyError implements the interface error
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
// 返回一個(gè) error 接口類型變量
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err) // at 2009-11-10 23:00:00 +0000 UTC m=+0.000000001, it didn't work
}
}
3. 嵌入類型 - 繼承
3.1. 聲明
嵌入類型將已有類型直接聲明在新的結(jié)構(gòu)里蔚袍,新的類型被稱為外部類型,被嵌入的類型被稱為內(nèi)部類型配名。如下:
type <類型名> struct {
內(nèi)部類型1名
...
屬性1名
...
}
3.2. 創(chuàng)建
注意:創(chuàng)建時(shí)啤咽,依然需要區(qū)分出內(nèi)部類型晋辆。因?yàn)橥獠款愋陀锌赡軙?huì)覆蓋內(nèi)部類型中的標(biāo)識符抵知。 示例如下:
type user struct {
name string
email string
}
type admin struct {
user // 嵌入 user 類型按脚,相當(dāng)于 admin 繼承了 user 類型
level string
}
func main() {
// Error: cannot use promoted field user.name in struct literal of type admin
// Error: cannot use promoted field user.email in struct literal of type admin
// ad := admin{
// name: "john",
// email: "qq.com",
// level: "1",
// }
// 創(chuàng)建類型,需要區(qū)別出內(nèi)部類型
ad := admin{
user: user{
name: "john",
email: "qq.com",
},
level: "1",
}
}
3.2. 繼承屬性和方法
內(nèi)部類型中的標(biāo)識符(屬性和方法)都會(huì)提升到外部類型中授段,就像直接在外部類型中聲明了一樣没陡。
延續(xù)3.1中的例子:
// 可以通過內(nèi)部類型訪問內(nèi)部類型的屬性
fmt.Println(ad.user.name) // 輸出:john
// 也可以直接訪問內(nèi)部類型的屬性
fmt.Println(ad.name) // 輸出:john
3.3. 覆蓋屬性和方法
外部類型也可以通過聲明與內(nèi)部類型同名的標(biāo)識符涩哟,來覆蓋內(nèi)部標(biāo)識符的屬性或方法。這樣盼玄,內(nèi)部類型中對應(yīng)的標(biāo)識符將不會(huì)被提升贴彼,但其值依然存在。
示例:
type user struct {
name string
email string
}
type admin struct {
user
name string // 覆蓋 user 類型中的 name 屬性
level string
}
func main() {
// cannot use promoted field user.name in struct literal of type admin
// cannot use promoted field user.email in struct literal of type admin
ad := admin{
user: user{
name: "john",
email: "qq.com",
},
name: "tom",
level: "1",
}
fmt.Println(ad.name) // 輸出:tom
fmt.Println(ad.user.name) // 輸出:john
}
4. 公開或未公開的標(biāo)識符 - 私有與公有
要使用另一個(gè)包中的類型時(shí)埃儿,類型名首字母需要大寫器仗,調(diào)用格式為:<package>.<name>
(package
為包名,name
為類型名)童番。
若要調(diào)用公開類型中的屬性和方法時(shí)精钮,屬性和方法名的首字母也必須是大寫。
示例:
package main
import (
"fmt"
"study/my_study/obj" // 導(dǎo)入另一個(gè)包
)
func main() {
// Person 為 study/my_study/obj 包中的類型
boss := obj.Person{
Name: "aaaaa",
Email: "123456",
}
// 調(diào)用 Person 類型的公開方法
boss.Notify()
boss.ChangeEmail("2222")
fmt.Println(boss)
}