go語言基礎(chǔ)學(xué)習(xí)
## Go 語言背景
Go 是一個開源的編程語言,它能讓構(gòu)造簡單、可靠且高效的軟件變得容易。
Go 是從2007年末由 Robert Griesemer, Rob Pike, Ken Thompson 主持開發(fā),后來還加入了 Ian Lance Taylor, Russ Cox 等人,并最終于2009年11月開源,在2012年早些時候發(fā)布了 Go 1穩(wěn)定版本?,F(xiàn)在 Go 的開發(fā)已經(jīng)是完全開放的,并且擁有一個活躍的社區(qū)。
### go 語言特色
-? ?簡潔、快速、安全
-? ?并行、有趣、開源
-? ?內(nèi)存管理、數(shù)組安全、編譯迅速
### go 語言環(huán)境安裝:
Go 語言支持以下系統(tǒng):
-? ?Linux
-? ?FreeBSD
-? ?Mac OS X(也稱為 Darwin)
-? ?Windows
安裝包下載地址為:[https://go.dev/dl/](https://go.dev/dl/)。
如果打不開可以使用這個地址:[https://golang.google.cn/dl/](https://golang.google.cn/dl/)。
#### windows 下安裝:
Windows 下可以使用 .msi 后綴(在下載列表中可以找到該文件,如 go1.4.2.windows-amd64.msi)的安裝包來安裝。
默認(rèn)情況下 **.msi** 文件會安裝在 **c:\Go** 目錄下。你可以將 **c:\Go\bin** 目錄添加到 **Path** 環(huán)境變量中。添加后你需要重啟命令窗口才能生效。
#### linux 類系統(tǒng)安裝:
以下介紹了在 UNIX/Linux/Mac OS X, 和 FreeBSD 系統(tǒng)下使用源碼安裝方法:
1、下載二進制包:go1.4.linux-amd64.tar.gz。
2、將下載的二進制包解壓至 /usr/local目錄。
tar -C /usr/local -xzf go1.4.linux-amd64.tar.gz
3、將 /usr/local/go/bin 目錄添加至 PATH 環(huán)境變量:
export PATH=$PATH:/usr/local/go/bin
以上只能暫時添加 PATH,關(guān)閉終端下次再登錄就沒有了。
我們可以編輯 ~/.bash_profile 或者 /etc/profile,并將以下命令添加該文件的末尾,這樣就永久生效了:
export PATH=$PATH:/usr/local/go/bin
添加后需要執(zhí)行:
source ~/.bash_profile
或
source /etc/profile
> **注意:**MAC 系統(tǒng)下你可以使用 **.pkg** 結(jié)尾的安裝包直接雙擊來完成安裝,安裝目錄在 **/usr/local/go/** 下。
## go 語言程序編寫
### Go 的語言結(jié)構(gòu):
Go 的語言基礎(chǔ)組成由以下幾個部分:
1. 包聲明;
2. 引入包;
3. 函數(shù);
4. 變量;
5. 語句&表達(dá)式;
6. 注釋
第一個 hello. go
```
package main??
??
import "fmt"??
??
func main() {??
/* 這是我的第一個簡單的程序 */
? ? fmt.Println("Hello, World!")??
}
```
1.? 第一行代碼 _package main_ 定義了包名。你必須在源文件中非注釋的第一行指明這個文件屬于哪個包,如:package main。package main 表示一個可獨立執(zhí)行的程序,每個 Go 應(yīng)用程序都包含一個名為 main 的包。
? ??
2.? 下一行 _import "fmt"_ 告訴 Go 編譯器這個程序需要使用 fmt 包(的函數(shù),或其他元素),fmt 包實現(xiàn)了格式化 IO(輸入/輸出)的函數(shù)。
? ??
3.? 下一行 _func main()_ 是程序開始執(zhí)行的函數(shù)。main 函數(shù)是每一個可執(zhí)行程序所必須包含的,一般來說都是在啟動后第一個執(zhí)行的函數(shù)(如果有 init() 函數(shù)則會先執(zhí)行該函數(shù))。
? ??
4.? 下一行 /*...*/ 是注釋,在程序執(zhí)行時將被忽略。單行注釋是最常見的注釋形式,你可以在任何地方使用以 // 開頭的單行注釋。多行注釋也叫塊注釋,均已以 /* 開頭,并以 */ 結(jié)尾,且不可以嵌套使用,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段。
? ??
5.? 下一行 _fmt.Println(...)_ 可以將字符串輸出到控制臺,并在最后自動增加換行字符 \n。??
? ? 使用 fmt.Print("hello, world\n") 可以得到相同的結(jié)果。??
? ? Print 和 Println 這兩個函數(shù)也支持使用變量,如:fmt.Println(arr)。如果沒有特別指定,它們會以默認(rèn)的打印格式將變量 arr 輸出到控制臺。
? ??
6.? 當(dāng)標(biāo)識符(包括常量、變量、類型、函數(shù)名、結(jié)構(gòu)字段等等)以一個大寫字母開頭,如:Group1,那么使用這種形式的標(biāo)識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導(dǎo)入這個包),這被稱為導(dǎo)出(像面向?qū)ο笳Z言中的 public);標(biāo)識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內(nèi)部是可見并且可用的(像面向?qū)ο笳Z言中的 protected )。
運行代碼:
go run hello.go
生成二進制文件:
go build hello.go
注意:
1. 需要注意的是 { 不能單獨放在一行。
2.? ? 文件名與包名沒有直接關(guān)系,不一定要將文件名與包名定成同一個。
3.? ? 文件夾名與包名沒有直接關(guān)系,并非需要一致。
4.? ? 同一個文件夾下的文件只能有一個包名,否則編譯報錯。
### go 的基礎(chǔ)語法
#### 行分隔符:
go 跟 python 一樣不使用;作為行分隔符。
如果時多個語句寫在同一行那就需要;作為分隔。
#### 注釋:
go 的注釋和其他語言一樣。
#### 標(biāo)識符:
go 的標(biāo)識符也和其他語言差不多。
標(biāo)識符用來命名變量、類型等程序?qū)嶓w。一個標(biāo)識符實際上就是一個或是多個字母(A~Z 和 a~z)數(shù)字(0~9)、下劃線_組成的序列,但是第一個字符必須是字母或下劃線而不能是數(shù)字。
#### 字符串:
go 的字符串連接和 python 的一樣。
Go 語言的字符串連接可以通過 + 實現(xiàn):
```
fmt.Println("Google" + "Runoob")
```
#### 關(guān)鍵字:
25 個關(guān)鍵字和保留字:
![[Pasted image 20230720112138.png]]
36 個預(yù)定義標(biāo)識符:
![[Pasted image 20230720112214.png]]
#### go 語言中的空格:
跟其他語言差不多。
#### 格式化字符串:
go 中使用 fmt. Sprintf 或 fmt. Printf 格式化字符串并復(fù)制給新串:
Sprintf 根據(jù)格式化參數(shù)生成格式化的字符串并返回該字符串。
Printf 根據(jù)格式化參數(shù)生成格式化的字符串并寫入標(biāo)準(zhǔn)輸出。
```
?var stockcode=123??
? ? var enddate="2020-12-31"??
? ? var url="Code=%d&endDate=%s"??
? ? var target_url=fmt.Sprintf(url,stockcode,enddate)??
? ? fmt.Println(target_url)
? ??
var stockcode=123??
? ? var enddate="2020-12-31"??
? ? var url="Code=%d&endDate=%s"??
? ? fmt.Printf(url,stockcode,enddate)
? ?輸出結(jié)果為:Code=123&endDate=2020-12-31
```
### go 語言數(shù)據(jù)類型
在 go 中,數(shù)據(jù)類型用于聲明函數(shù)和變量。
數(shù)據(jù)類型的出現(xiàn)是為了把數(shù)據(jù)分成所需內(nèi)存大小不同的數(shù)據(jù),需要用大數(shù)據(jù)的時候申請大內(nèi)存,不需要可以申請小內(nèi)存,提高內(nèi)容利用率。
按類別 go 有下面幾種數(shù)據(jù)類型:
1、布爾型
布爾型的值只可以是 true 或者 false。 var b bool=true
2、數(shù)字類型
整形 int 浮點型 float32 和 float 64,go 支持整形和浮點型數(shù)字,并支持復(fù)數(shù),位的運算采用補碼。
3、字符串類型
go 的字符串由單個字節(jié)拼接,使用 utf-8 編碼標(biāo)識的 unicode 文本。
4、派生類型
1. 指針類型
2. 數(shù)組類型
3. 結(jié)構(gòu)化類型(struct)
4. channel 類型
5. 函數(shù)類型
6. 切片類型
7. 接口類型
8. map 類型
#### 數(shù)字類型:
##### 整型:
uint 8:無符號 8 位整型(2 到 255)
uint 16:無符號 16 位整型(0 到 65535)
uint 32:無符號 32 位整型(0 到 4294967295)
int 8:有符號 8 位整型(-127 到 127)
int 16:有符號 16 位整型(-32768 到 32767)
int 32:有符號 32 位整型(-2147483648 到 2147483647)
int 64:有符號 64 位整型 (-9223372036854775808 到 9223372036854775807)
##### 浮點型:
float 32:IEEE-754 標(biāo)準(zhǔn) 32 位浮點型整數(shù);
float 64:IEEE-754 標(biāo)準(zhǔn) 64 位浮點數(shù);
complex:32 位實數(shù)和虛數(shù);
complex 128:64 位實數(shù)和虛數(shù);
##### 其他數(shù)字類型:
byte:類似 uint8;
rune:類似 int32;
uint:32 位或 64 位;
int:和 uint 一樣大??;
uintptr:無符號整型用于存放一個指針;
注意:
在 go1.9 版本后,無需定義 int、float 32、float 64,系統(tǒng)會自動識別,類似 python 的處理方式。
### go 語言變量
go 語言變量名由字母、數(shù)字、下劃線組成,其中首個字符不能為數(shù)字。
聲明變量一般使用 var 關(guān)鍵字,可以一次聲明多個變量。
變量聲明:
1、智能變量類型,如果沒有初始化,則變量默認(rèn)為零值(沒有做初始化時系統(tǒng)默認(rèn)設(shè)置的值)。
對于零值:
- 數(shù)值類型(包括 complex64/128)為 **0**
- 布爾類型為 **false**
- 字符串為 **""**(空字符串)
- 以下幾種類型為 **nil**:
- var a *int
- var a []int
- var a map[string] int
- var a chan int
- var a func(string) int
- var a error // error 是接口
2、根據(jù)值自行判定變量類型
3、如果變量已經(jīng)使用 var 聲明過了,再使用 := 聲明變量,就產(chǎn)生編譯錯誤。
### go 語言常量
常量是一個簡單值的標(biāo)識符,在程序運行時,不會被修改的量。常量中的數(shù)據(jù)類型只可以時布爾型、數(shù)字型(整數(shù)型、浮點數(shù)型和復(fù)數(shù))和字符串型。
> const identifier [type] = value
-? ?顯式類型定義: `const b string = "abc"`??
-? ?隱式類型定義: `const b = "abc"`
**iota**
iota,特殊常量,可以認(rèn)為是一個可以被編譯器修改的常量。
iota 在 const 關(guān)鍵字出現(xiàn)時將被重置為 0(const 內(nèi)部的第一行之前),const 中每新增一行常量聲明將使 iota 計數(shù)一次(iota 可理解為 const 語句塊中的行索引)。
### go 語言運算符
go 語言內(nèi)置,和其他語言基本一致。
-? ?算術(shù)運算符
-? ?關(guān)系運算符
-? ?邏輯運算符
-? ?位運算符
-? ?賦值運算符
-? ?其他運算符
算術(shù)運算符:
![[Pasted image 20230720145941.png]]
關(guān)系運算符:
![[Pasted image 20230720150018.png]]
邏輯運算符:
![[Pasted image 20230720150053.png]]
位運算符:
![[Pasted image 20230720150437.png]]
![[Pasted image 20230720150451.png]]
賦值運算符:
![[Pasted image 20230720150517.png]]
其他運算符:
![[Pasted image 20230720150550.png]]
運算符優(yōu)先級:
![[Pasted image 20230720150658.png]]
注意:
go 的自增自檢智能作為表達(dá)式使用,不能用于賦值語句,不然會編譯出錯。
### go 語言條件語句:
![[Pasted image 20230720151010.png]]
switch 語句:
```
switch var1 {
? ? case val1:
? ? ? ? ...
? ? case val2:
? ? ? ? ...
? ? default:
? ? ? ? ...
}
```
select 語句:
```
Select {
? Case <- channel 1:
? ? // 執(zhí)行的代碼
? Case value := <- channel 2:
? ? // 執(zhí)行的代碼
? Case channel 3 <- value:
? ? // 執(zhí)行的代碼
? ? // 你可以定義任意數(shù)量的 case
? Default:
? ? // 所有通道都沒有準(zhǔn)備好,執(zhí)行的代碼
}
```
### go 語言循環(huán)語句
#### for 循環(huán):
跟 c 語言中的一樣
```
For init; condition; post { }
```
-? ?init: 一般為賦值表達(dá)式,給控制變量賦初值;
-? ?condition: 關(guān)系表達(dá)式或邏輯表達(dá)式,循環(huán)控制條件;
-? ?post: 一般為賦值表達(dá)式,給控制變量增量或減量。
多層嵌套:
```
for [condition |? ( init; condition; increment ) | Range]??
{??
? ?for [condition |? ( init; condition; increment ) | Range]??
? ?{??
? ? ? statement(s);??
? ?}??
? ?statement(s);??
}
```
#### 循環(huán)中的 break:
在 Go 語言中,break 語句用于終止當(dāng)前循環(huán)或者 switch 語句的執(zhí)行,并跳出該循環(huán)或者 switch 語句的代碼塊。
break 語句可以用于以下幾個方面:。
-? ?用于循環(huán)語句中跳出循環(huán),并開始執(zhí)行循環(huán)之后的語句。
-? ?break 在 switch 語句中在執(zhí)行一條 case 后跳出語句的作用。
-? ?break 可應(yīng)用在 select 語句中。
-? ?在多重循環(huán)中,可以用標(biāo)號 label 標(biāo)出想 break 的循環(huán)。
```
Package main
Import "fmt"
Func main () {
? ? For i := 0; i < 10; i++ {
? ? ? ? If i == 5 {
? ? ? ? ? ? Break // 當(dāng) i 等于 5 時跳出循環(huán)
? ? ? ? }
? ? ? ? Fmt.Println (i)
? ? }
}
```
#### continue 語句:
Go 語言的 continue 語句有點像 break 語句。但是 continue 不是跳出循環(huán),而是跳過當(dāng)前循環(huán)執(zhí)行下一次循環(huán)語句。
for 循環(huán)中,執(zhí)行 continue 語句會觸發(fā) for 增量語句的執(zhí)行。
在多重循環(huán)中,可以用標(biāo)號 label 標(biāo)出想 continue 的循環(huán)。
#### goto 語句:
Go 語言的 goto 語句可以無條件地轉(zhuǎn)移到過程中指定的行。
goto 語句通常與條件語句配合使用??捎脕韺崿F(xiàn)條件轉(zhuǎn)移, 構(gòu)成循環(huán),跳出循環(huán)體等功能。
但是,在結(jié)構(gòu)化程序設(shè)計中一般不主張使用 goto 語句, 以免造成程序流程的混亂,使理解和調(diào)試程序都產(chǎn)生困難。
```
goto label;
..
.
label: statement;
```
```
Package main
Import "fmt"
Func main () {
? ?/* 定義局部變量 */
? ?Var a int = 10
? ?/* 循環(huán) */
? ?LOOP: for a < 20 {
? ? ? If a == 15 {
? ? ? ? ?/* 跳過迭代 */
? ? ? ? ?A = a + 1
? ? ? ? ?Goto LOOP
? ? ? }
? ? ? Fmt.Printf ("a 的值為 : %d\n", a)
? ? ? A++? ? ?
? ?}??
}
```
### go 語言函數(shù):
函數(shù)定義:
```
func function_name( [parameter list] ) [return_types] {
? ?函數(shù)體
}
```
函數(shù)定義解析:
-? ?func:函數(shù)由 func 開始聲明
-? ?function_name:函數(shù)名稱,參數(shù)列表和返回值類型構(gòu)成了函數(shù)簽名。
-? ?parameter list:參數(shù)列表,參數(shù)就像一個占位符,當(dāng)函數(shù)被調(diào)用時,你可以將值傳遞給參數(shù),這個值被稱為實際參數(shù)。參數(shù)列表指定的是參數(shù)類型、順序、及參數(shù)個數(shù)。參數(shù)是可選的,也就是說函數(shù)也可以不包含參數(shù)。
-? ?return_types:返回類型,函數(shù)返回一列值。return_types 是該列值的數(shù)據(jù)類型。有些功能不需要返回值,這種情況下 return_types 不是必須的。
-? ?函數(shù)體:函數(shù)定義的代碼集合。
```
/* 函數(shù)返回兩個數(shù)的最大值 */??
func max(num1, num2 int) int {??
? ?/* 聲明局部變量 */??
? ?var result int??
??
? ?if (num1 > num2) {??
? ? ? result = num1??
? ?} else {??
? ? ? result = num2??
? ?}??
? ?return result??
}
```
函數(shù)里面也要注意變量操作,在函數(shù)里面改了變量,主函數(shù)也會受影響。
![[Pasted image 20230720152721.png]]
### go 語言變量作用域
-? ?函數(shù)內(nèi)定義的變量稱為局部變量
-? ?函數(shù)外定義的變量稱為全局變量
-? ?函數(shù)定義中的變量稱為形式參數(shù)
### go 語言數(shù)組
```
var arrayName [size]dataType
```
初始化數(shù)組:
```
var numbers [5]int
var numbers = [5]int{1, 2, 3, 4, 5}
numbers := [5]int{1, 2, 3, 4, 5}
```
如果長度不確定,可以使用... 來代替數(shù)據(jù)的長度,編譯器會根據(jù)元素個數(shù)自行推斷數(shù)組的長度。
```
var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
或
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
```
### go 語言指針
go 語言指針和 c 語言中的類似
### go 語言結(jié)構(gòu)體
結(jié)構(gòu)體有點類似類的概念。
定義結(jié)構(gòu)體
結(jié)構(gòu)體定義需要使用 type 和 struct 語句。struct 語句定義一個新的數(shù)據(jù)類型,結(jié)構(gòu)體中有一個或多個成員。type 語句設(shè)定了結(jié)構(gòu)體的名稱。結(jié)構(gòu)體的格式如下:
```
Type struct_variable_type struct {
? ?Member definition
? ?Member definition
? ?...
? ?Member definition
}
```
```
Variable_name := structure_variable_type {value 1, value 2... Valuen}
或
Variable_name := structure_variable_type { key 1: value 1, key 2: value 2..., keyn: valuen}
```
### go 語言切片
切片時數(shù)組的衍生抽象。
go 數(shù)組長度不可變,于是切片來實現(xiàn)一種動態(tài)數(shù)組的功能。
**定義切片:**
```
var identifier []type
```
```
Var slice 1 []type = make ([]type, len)
也可以簡寫為
Slice 1 := make ([]type, len)
也可以指定容量,capacity 是可選參數(shù)
Make ([]T, length, capacity)
```
**切片初始化:**
```
s :=[] int {1,2,3 }
直接初始化切片,[] 表示是切片類型,**{1,2,3}** 初始化值依次是 1,2,3,其 **cap=len=3**。
s := arr[:]
初始化切片 **s**,是數(shù)組 arr 的引用。
s := arr[startIndex:endIndex]
將 arr 中從下標(biāo) startIndex 到 endIndex-1 下的元素創(chuàng)建為一個新的切片。
s := arr[startIndex:]
默認(rèn) endIndex 時將表示一直到arr的最后一個元素。
s := arr[:endIndex]
默認(rèn) startIndex 時將表示從 arr 的第一個元素開始
s1 := s[startIndex: endIndex]
通過切片 s 初始化切片 s1。
s :=make([]int,len,cap)
通過 make()函數(shù)初始化
```
len() 方法獲取長度
cap() 可以測量切片最長可以達(dá)到多少
一個切片在未初始化之前默認(rèn)為 nil,長度為 0
切片擴容方法:從拷貝切片的 copy 方法和向切片追加新元素的 append 方法
### go 語言范圍(range)
go 里面的 range 和 python 里面的 range 類似。
```
for key, value := range oldMap {
? ? newMap[key] = value
}
```
如果只想讀 key,也可以
```
for key := range oldMap
for key, _ := range oldMap
```
如果只想讀 value
```
for _, value := range oldMap
```
### go 語言 map(集合)
go 中的 map 類似 python 里面的 tupple 元組。
map 是一種無序的鍵值對的集合,可以通過 key 來快速檢索數(shù)據(jù),key 類似于索引,指向數(shù)據(jù)的值。
map 是一種集合,可以像迭代數(shù)組那樣進行迭代,但它是無序的,遍歷 map 時返回的鍵值對的順序不確定。
在獲取 map 的值時,如果鍵不存在,則反饋該類型的零值,如 int 類型的零值是 0,string 類型的零值是""。
**定義 map:**
```
/* 使用 make 函數(shù) */
Map_variable := make (map[KeyType]ValueType, initialCapacity)
```
```
// 創(chuàng)建一個空的 Map
M := make (map[string]int)
// 創(chuàng)建一個初始容量為 10 的 Map
M := make (map[string]int, 10)
```
```
// 使用字面量創(chuàng)建 Map
M := map[string]int{
? ? "apple": 1,
? ? "banana": 2,
? ? "orange": 3,
}
獲取元素:
// 獲取鍵值對
V 1 := m["apple"]
V 2, ok := m["pear"]? // 如果鍵不存在,ok 的值為 false,v 2 的值為該類型的零值
修改元素:
// 修改鍵值對
M["apple"] = 5
獲取 map 的長度:
// 獲取 Map 的長度
Len := len (m)
遍歷 map:
// 遍歷 Map
For k, v := range m {
? ? Fmt.Printf ("key=%s, value=%d\n", k, v)
}
刪除元素:
// 刪除鍵值對
Delete (m, "banana")
```
### go 語言遞歸函數(shù)
go 的遞歸函數(shù)跟其他語言類似。
### go 語言類型轉(zhuǎn)換
整體上跟其他語言類似。
```
Type_name (expression)
type_name 為類型,expression 為表達(dá)式。
```
**數(shù)值類型轉(zhuǎn)換:**
```
Var a int = 10
Var b float 64 = float 64 (a)
```
**字符串類型轉(zhuǎn)換:**
```
Var str string = "10"
Var num int
Num, _ = strconv.Atoi (str)
以上代碼將字符串變量 str 轉(zhuǎn)換為整型變量 num。
注意,strconv.Atoi 函數(shù)返回兩個值,第一個是轉(zhuǎn)換后的整型值,第二個是可能發(fā)生的錯誤,我們可以使用空白標(biāo)識符 _ 來忽略這個錯誤
```
**接口類型轉(zhuǎn)換:**
接口類型轉(zhuǎn)換有兩種情況**:類型斷言**和**類型轉(zhuǎn)換**。
類型斷言用于將接口類型轉(zhuǎn)換為指定類型,其語法為:
```
Value. (type)?
或者?
Value. (T)
```
其中 value 是接口類型的變量,type 或 T 是要轉(zhuǎn)換成的類型。
如果類型斷言成功,它將返回轉(zhuǎn)換后的值和一個布爾值,表示轉(zhuǎn)換是否成功。
```
Package main
Import "fmt"
Func main () {
? ? Var i interface{} = "Hello, World"
? ? str, ok := i.(string)
? ? If ok {
? ? ? ? Fmt.Printf ("'%s' is a string\n", str)
? ? } else {
? ? ? ? Fmt.Println ("conversion failed")
? ? }
}
```
以上實例中,我們定義了一個接口類型變量 i,并將它賦值為字符串 "Hello, World"。然后,我們使用類型斷言將 i 轉(zhuǎn)換為字符串類型,并將轉(zhuǎn)換后的值賦值給變量 str。最后,我們使用 ok 變量檢查類型轉(zhuǎn)換是否成功,如果成功,我們打印轉(zhuǎn)換后的字符串;否則,我們打印轉(zhuǎn)換失敗的消息。
類型轉(zhuǎn)換用于將一個接口類型的值轉(zhuǎn)換為另一個接口類型,其語法為:
```
T(value)
```
T 是目標(biāo)接口類型,value 是要轉(zhuǎn)換的值。
在類型轉(zhuǎn)換中,我們必須保證要轉(zhuǎn)換的值和目標(biāo)接口類型之間是兼容的,否則編譯器會報錯。
```
Package main
Import "fmt"
Type Writer interface {
? ? Write ([]byte) (int, error)
}
Type StringWriter struct {
? ? Str string
}
Func (sw *StringWriter) Write (data []byte) (int, error) {
? ? Sw. Str += string (data)
? ? Return len (data), nil
}
Func main () {
? ? Var w Writer = &StringWriter{}
? ? sw := w.(*StringWriter)
? ? Sw. Str = "Hello, World"
? ? Fmt.Println (sw. Str)
}
```
以上實例中,我們定義了一個 Writer 接口和一個實現(xiàn)了該接口的結(jié)構(gòu)體 StringWriter。然后,我們將 StringWriter 類型的指針賦值給 Writer 接口類型的變量 w。接著,我們使用類型轉(zhuǎn)換將 w 轉(zhuǎn)換為 StringWriter 類型,并將轉(zhuǎn)換后的值賦值給變量 sw。最后,我們使用 sw 訪問 StringWriter 結(jié)構(gòu)體中的字段 str,并打印出它的值。
### go 語言接口
接口真的就感覺像類的概念。
Go 語言提供了另外一種數(shù)據(jù)類型即接口,它把所有的具有共性的方法定義在一起,任何其他類型只要實現(xiàn)了這些方法就是實現(xiàn)了這個接口。
接口可以讓我們將不同的類型綁定到一組公共的方法上,從而實現(xiàn)多態(tài)和靈活的設(shè)計。
Go 語言中的接口是隱式實現(xiàn)的,也就是說,如果一個類型實現(xiàn)了一個接口定義的所有方法,那么它就自動地實現(xiàn)了該接口。因此,我們可以通過將接口作為參數(shù)來實現(xiàn)對不同類型的調(diào)用,從而實現(xiàn)多態(tài)。
```
Package main
Import (
? ? "fmt"
)
Type Phone interface {
? ? Call ()
}
Type NokiaPhone struct {
}
Func (nokiaPhone NokiaPhone) call () {
? ? Fmt.Println ("I am Nokia, I can call you!")
}
Type IPhone struct {
}
Func (iPhone IPhone) call () {
? ? Fmt.Println ("I am iPhone, I can call you!")
}
Func main () {
? ? Var phone Phone
? ? phone = new(NokiaPhone)
? ? phone.call()
? ? phone = new(IPhone)
? ? phone.call()
}
```
在上面的例子中,我們定義了一個接口 **Phone**,接口里面有一個方法 call()。然后我們在 **main** 函數(shù)里面定義了一個 **Phone** 類型變量,并分別為之賦值為 **NokiaPhone** 和 **IPhone**。然后調(diào)用 call() 方法,輸出結(jié)果如下:
### go 錯誤處理
go 語言有一個內(nèi)置的簡單錯誤處理機制。
error 類型是一個接口類型:
```
Type error interface {
? ? Error () string
}
func Sqrt(f float64) (float64, error) {
? ? if f < 0 {
? ? ? ? return 0, errors.New("math: square root of negative number")
? ? }
? ? // 實現(xiàn)
}
```
### go 并發(fā)
go 支持并發(fā),只需要通過 goroutine 即可。
goroutine 是輕量級線程,它的調(diào)度是 go 運行時進行管理的。
goruntine 語法格式:
```
go 函數(shù)名( 參數(shù)列表 )
go f(x, y, z)
```
f(x, y, z),聲明之后,每一個調(diào)用都是一個新的線程,但是所有線程都是共享一個地址空間,這里需要注意。
P
```
ackage main
Import (
? ? ? ? "fmt"
? ? ? ? "time"
)
Func say (s string) {
? ? ? ? For i := 0; i < 5; i++ {
? ? ? ? ? ? ? ? Time.Sleep (100 * time. Millisecond)
? ? ? ? ? ? ? ? Fmt.Println (s)
? ? ? ? }
}
Func main () {
? ? ? ? Go say ("world")
? ? ? ? Say ("hello")
}
```
**通道(channel)**
通道是用來傳遞數(shù)據(jù)的一個數(shù)據(jù)結(jié)構(gòu),一個通道相當(dāng)于一個先進先出的隊列。
通道可用于兩個 goroutine 之間通過傳遞一個指定類型的值來同步運行和通訊。操作符 `<-` 用于指定通道的方向,發(fā)送或接收。如果未指定方向,則為雙向通道。
```
ch <- v? ? // 把 v 發(fā)送到通道 ch
v := <-ch? // 從 ch 接收數(shù)據(jù)
? ? ? ? ? ?// 并把值賦給 v
```
聲明一個通道很簡單,我們使用chan關(guān)鍵字即可,通道在使用前必須先創(chuàng)建:
```
ch := make(chan int)
```
單向通道:
```
Var WriteChan = make (chan<- interface{}, 1) // 只能發(fā)送不能接收的通道
Var ReadChan = make (<-chan interface{}, 1) // 只能接收不能發(fā)送的通道
```
**注意**:默認(rèn)情況下,通道是不帶緩沖區(qū)的。發(fā)送端發(fā)送數(shù)據(jù),同時必須有接收端相應(yīng)的接收數(shù)據(jù)。
**通道緩沖區(qū):**
```
Ch 1 := make (chan string, 3)
-? ?`chan` 是表示通道類型的關(guān)鍵字
-? ?`string` 表示該通道類型的元素類型
-? ?`3` 表示該通道的容量為 3,最多可以緩存 3 個元素值。
```
帶緩沖區(qū)的通道允許發(fā)送端的數(shù)據(jù)發(fā)送和接收端的數(shù)據(jù)獲取處于異步狀態(tài),就是說發(fā)送端發(fā)送的數(shù)據(jù)可以放在緩沖區(qū)里面,可以等待接收端去獲取數(shù)據(jù),而不是立刻需要接收端去獲取數(shù)據(jù)。
不過由于緩沖區(qū)的大小是有限的,所以還是必須有接收端來接收數(shù)據(jù)的,否則緩沖區(qū)一滿,數(shù)據(jù)發(fā)送端就無法再發(fā)送數(shù)據(jù)了。
**注意**:如果通道不帶緩沖,發(fā)送方會阻塞直到接收方從通道中接收了值。如果通道帶緩沖,發(fā)送方則會阻塞直到發(fā)送的值被拷貝到緩沖區(qū)內(nèi);如果緩沖區(qū)已滿,則意味著需要等待直到某個接收方獲取到一個值。接收方在有值可以接收之前會一直阻塞。
**非緩沖通道:**
非緩沖通道是容量為0的通道,不能緩存數(shù)據(jù)。
非緩沖通道的數(shù)據(jù)傳遞是同步的,發(fā)送操作或者接收操作在執(zhí)行后就會阻塞,需要對應(yīng)的接收操作或者發(fā)送操作執(zhí)行才會繼續(xù)傳遞。由此可以看出緩沖通道使用的是異步方式進行數(shù)據(jù)傳遞。
```
Package main
Import (
? ? "fmt"
)
Func main () {
? ? Str 1 := []string{"hello","world", "!"}
Ch 1 := make (chan string, 0)
? ? go func() {
? ? ? ? for _, str := range str1 {
? ? ? ? ? ? ch1 <- str
? ? ? ? }
? ? }()
for i := 0; i < len(str1); i++ {
elem := <- ch1
fmt.Println(elem)
}
}
```
**通道關(guān)閉:**
可以使用close()方法來關(guān)閉通道,通道關(guān)閉后,不能再對通道進行發(fā)送操作,可以進行接收操作。
如果通道關(guān)閉時,里面還有元素,進行接收操作時,返回的通道關(guān)閉標(biāo)志仍然為 true,由于通道的這種特性,可以讓發(fā)送方來關(guān)閉通道。
```
Package main
Import (
? ? "fmt"
)
Func main () {
? ? Str 1 := []string{"hello","world", "!"}
Ch 1 := make (chan string, 0)
? ? go func() {
? ? ? ? for _, str := range str1 {
? ? ? ? ? ? ch1 <- str
? ? ? ? }
? ? ? ? close(ch1)
? ? }()
for i := 0; i < len(str1); i++ {
elem := <- ch1
fmt.Println(elem)
}
}
```