1. Go項(xiàng)目的構(gòu)建
一個(gè)Go工程中主要包含以下三個(gè)目錄:
src:源代碼文件,編寫(xiě)程序代碼xxx.go,執(zhí)行命令go build xxx.go會(huì)生成*.exe文件;執(zhí)行g(shù)o run xxx.go可以直接運(yùn)行文件
pkg:包文件,執(zhí)行g(shù)o install name會(huì)在此目錄下生成*.a文件,用于import
bin:相關(guān)bin文件,執(zhí)行g(shù)o install xxx會(huì)在此母名生成*.exe文件,可以直接運(yùn)行
go的基本命令如下:

image.png
2. 變量和常量
Go的程序是保存在多個(gè).go文件中,文件的第一行就是package XXX聲明,用來(lái)說(shuō)明該文件屬于哪個(gè)包(package),package聲明下來(lái)就是import聲明,再下來(lái)是類(lèi)型,變量,常量,函數(shù)的聲明。Go語(yǔ)言的變量聲明格式為
var?變量名?變量類(lèi)型?[?=?表達(dá)式或值]
變量聲明以關(guān)鍵字var開(kāi)頭,變量類(lèi)型放在變量的后面,行尾無(wú)需分號(hào)。舉個(gè)例子:
var?name?string var?age?int //批量聲明,一個(gè)var帶多個(gè)不同類(lèi)型的變量聲明 var?( ?a?string ?b?int ?c?bool ?d?float32 )
類(lèi)型推斷
我們可以將變量的類(lèi)型省略,編譯器會(huì)根據(jù)等號(hào)右邊的值來(lái)推導(dǎo)變量的類(lèi)型完成初始化
在函數(shù)內(nèi)部,可以使用更簡(jiǎn)略的 := 方式(省略var和type)聲明并初始化變量。但是有限制:
不能用在函數(shù)外
:=?操作符的左邊至少有一個(gè)變量是尚未聲明的
常量的聲明和變量聲明非常類(lèi)似,只是把var換成了const,常量在定義的時(shí)候必須賦值。const同時(shí)聲明多個(gè)常量時(shí),如果省略了值則表示和上面一行的值相同。例如:
const?( ????????n1?=?100 ????????n2 ????????n3 ????)
3 內(nèi)置數(shù)據(jù)類(lèi)型
| 類(lèi)型 | 長(zhǎng)度(字節(jié)) | 默認(rèn)值 | 說(shuō)明 |
|---|---|---|---|
| bool | 1 | false | ? |
| byte | 1 | 0 | uint8 |
| rune | 4 | 0 | 代表一個(gè)UTF8字符, int32 |
| int, uint | 4或8 | 0 | 32 或 64 位 |
| int8, uint8 | 1 | 0 | -128 ~ 127, 0 ~ 255,byte是uint8 的別名 |
| int16, uint16 | 2 | 0 | -32768 ~ 32767, 0 ~ 65535 |
| int32, uint32 | 4 | 0 | -21億~ 21億, 0 ~ 42億,rune是int32 的別名 |
| int64, uint64 | 8 | 0 | ? |
| float32 | 4 | 0.0 | ? |
| float64 | 8 | 0.0 | ? |
| complex64 | 8 | ? | 復(fù)數(shù),實(shí)部和虛部為32位,創(chuàng)建方式:- 使用函數(shù)complex創(chuàng)建- a := 6 + 7i |
| complex128 | 16 | ? | 復(fù)數(shù),實(shí)部和虛部為64位 |
| uintptr | 4或8 | ? | 以存儲(chǔ)指針的 uint32 或 uint64 整數(shù) |
| array | ? | ? | 值類(lèi)型 |
| struct | ? | ? | 值類(lèi)型 |
| string | ? | "" | UTF-8 字符串 |
| slice | ? | nil | 引用類(lèi)型 |
| map | ? | nil | 引用類(lèi)型 |
| channel | ? | nil | 引用類(lèi)型 |
| interface | ? | nil | 接口 |
| function | ? | nil | 函數(shù) |
| nil | ? | ? | 空指針 |
3.1 格式化打印
fmt包支持如下幾種打印方式
fmt.Println:打印一行內(nèi)容,類(lèi)似std::cout,難以設(shè)置格式
fmt.Print:打印內(nèi)容,并不換行
fmt.Printf:格式化打印,與C語(yǔ)言printf同理
fmt.Sprintf:格式化打印,不同之處使返回string類(lèi)型,不是打印到屏幕
格式化打印支持的格式符:

image.png
fmt.Printf("type?of?a?is?%T,?size?of?a?is?%d",?a,?unsafe.Sizeof(a))?//?a?的類(lèi)型和大小
3.2 類(lèi)型轉(zhuǎn)換
Go語(yǔ)言中只有強(qiáng)制類(lèi)型轉(zhuǎn)換,沒(méi)有隱式類(lèi)型轉(zhuǎn)換。該語(yǔ)法只能在兩個(gè)類(lèi)型之間支持相互轉(zhuǎn)換的時(shí)候使用。強(qiáng)制類(lèi)型轉(zhuǎn)換的基本語(yǔ)法如下:
T(表達(dá)式)
4 基本語(yǔ)句
4.1 if語(yǔ)句
//可省略條件表達(dá)式括號(hào)。
//持初始化語(yǔ)句,可定義代碼塊局部變量。?
//代碼塊左?括號(hào)必須在條件表達(dá)式尾部。
if?布爾表達(dá)式?{
????//。。。
}?else?{?//else不能單獨(dú)一行,golang的自動(dòng)分號(hào)插入機(jī)制導(dǎo)致的
??//。。。
}
//另一種格式,在條件判斷前執(zhí)行一條指令
if?statement;?condition?{??
}
4.2 switch語(yǔ)句
switch?var1?{
????case?val1:
????????...
????case?val2,val3,val4://通過(guò)用逗號(hào)分隔,可以在一個(gè)?case?中包含多個(gè)表達(dá)式
????????...
????default:
????????...
}
//可以看到每個(gè)case不需要break來(lái)分割
//switch?語(yǔ)句還可以被用于type-switch?來(lái)判斷某個(gè)interface?變量中實(shí)際存儲(chǔ)的變量類(lèi)型
switch?x.(type){
????case?type:
???????statement(s)??????
????case?type:
???????statement(s)
????/*?你可以定義任意個(gè)數(shù)的case?*/
????default:?/*?可選?*/
???????statement(s)
}
注意:
case可以是字符、字符串,表達(dá)式,不一定是常量
每個(gè)case語(yǔ)句塊自動(dòng)結(jié)束退出switch,不需要使用break
如果需要接著執(zhí)行下一個(gè)case的內(nèi)容,需要使用**fallthrough?**
4.3 for循環(huán)
三種形式
for?init;?condition;?post?{?}
for?condition?{?}
for?{?}
//init:?一般為賦值表達(dá)式,給控制變量賦初值;
//condition:?關(guān)系表達(dá)式或邏輯表達(dá)式,循環(huán)控制條件;
//post:?一般為賦值表達(dá)式,給控制變量增量或減量。
range循環(huán)語(yǔ)句:range類(lèi)似迭代器操作,返回 (索引, 值) 或 (鍵, 值)
for?key,?value?:=?range?oldMap?{ ????newMap[key]?=?value }
5 函數(shù)
5.1 函數(shù)定義
在 Go 語(yǔ)言中,函數(shù)聲明通用語(yǔ)法如下:
func?functionname(parametername?type)?returntype?{?? ????//?函數(shù)體(具體實(shí)現(xiàn)的功能) } //如果有連續(xù)若干個(gè)函數(shù)參數(shù),它們的類(lèi)型一致,那么無(wú)須一一羅列,只需在最后一個(gè)參數(shù)后添加該類(lèi)型。
Go 語(yǔ)言支持一個(gè)函數(shù)可以有多個(gè)返回值(也用括號(hào)包含),并且可以給返回值命名,這樣可以不在return里添加需要返回的變量:
func?rectProps(length,?width?float64)(float64,?float64)?{//兩個(gè)括號(hào),一個(gè)函數(shù)參數(shù),一個(gè)返回列表??
????var?area?=?length?*?width
????var?perimeter?=?(length?+?width)?*?2
????return?area,?perimeter//返回多返回值
}
//返回值命名
func?rectProps(length,?width?float64)(area,?perimeter?float64)?{??
????area?=?length?*?width
????perimeter?=?(length?+?width)?*?2
????return?//?不需要明確指定返回值,默認(rèn)返回?area,?perimeter?的值
}
_?在 Go 中被用作空白符,可以用作表示任何類(lèi)型的任何值,通常用在接收函數(shù)多返回值,過(guò)濾掉不需要的返回值:
area,?_?:=?rectProps(10.8,?5.6)?//?返回值周長(zhǎng)被丟棄
5.2 可變參數(shù)
如果函數(shù)最后一個(gè)參數(shù)被記作?...T?,這時(shí)函數(shù)可以接受任意個(gè)?T?類(lèi)型參數(shù)作為最后一個(gè)參數(shù)。可變參數(shù)函數(shù)的工作原理是把可變參數(shù)轉(zhuǎn)換為一個(gè)新的切片。
func?find(num?int,?nums?...int)?{
????fmt.Printf("type?of?nums?is?%T
",?nums)//nums相當(dāng)于整型slice
????found?:=?false
????for?i,?v?:=?range?nums?{
????????if?v?==?num?{
????????????fmt.Println(num,?"found?at?index",?i,?"in",?nums)
????????????found?=?true
????????}
????}
????if?!found?{
????????fmt.Println(num,?"not?found?in?",?nums)
????}
????fmt.Printf("
")
}
func?main()?{
????find(89,?89,?90,?95)//傳入數(shù)多個(gè)參數(shù)
????nums?:=?[]int{89,?90,?95}
????find(89,?nums...)//傳入一個(gè)slice
}
5.3 返回error信息
我們可以使用errors包或fmt包來(lái)生成error類(lèi)型的對(duì)象,用于返回函數(shù)的內(nèi)部錯(cuò)誤:
//實(shí)現(xiàn)自定義函數(shù)同時(shí)返回err和其他返回值
package?main
import?(
?"errors"
?"fmt"
)
func?f1()?(int,?error)?{?//設(shè)置多返回值
?err?:=?errors.New("I?am?the?error")?//使用errors包生成error
?return?1,?err
}
func?f2()?(int,?error)?{
?//使用fmt包生成error
?err?:=?fmt.Errorf("I?am?a?error?created?by?fmt")
?return?2,?err
}
func?main()?{
?a,?err?:=?f1()
?if?err?!=?nil?{
??fmt.Println(err.Error())
?}
?fmt.Println(a)
?b,?err?:=?f2()
?if?err?!=?nil?{
??fmt.Println(err.Error())
?}
?fmt.Println(b)
}5.4 指針傳址參數(shù)
對(duì)于需要在函數(shù)內(nèi)部修改的參數(shù),需要使用傳址參數(shù),GO中指針和C語(yǔ)言使一樣的,基本符號(hào)也是***和&**。
//指針傳址參數(shù),和函數(shù)返回指針
package?main
import?"fmt"
func?fun1(value?*int)?*float64?{
?*value?+=?10
?myFloat?:=?98.5
?//雖然myFloat是局部變量,但GO并不會(huì)釋放它,因?yàn)樗袡?quán)被轉(zhuǎn)移到函數(shù)外了
?return?&myFloat
}
func?main()?{
?number?:=?10
?ret?:=?fun1(&number)
?fmt.Println(number,?"??",?*ret)
}6 數(shù)組
一個(gè)數(shù)組的表示形式為[n]T。n表示數(shù)組中元素的數(shù)量,T代表每個(gè)元素的類(lèi)型。使用示例如下:
var?a?[3]int?//所有元素有默認(rèn)值0
a?:=?[3]int{12,?78,?50}//簡(jiǎn)要聲明,賦值
a?:=?[3]int{12}?//只給第一個(gè)元素賦值
var?b?=?[...]int{1,?2,?3}?//?定義長(zhǎng)度為3的int型數(shù)組,?元素為?1,?2,?3
fmt.Println(a)?//數(shù)組可以直接打印出來(lái)
fmt.Println(len(a))?//打印數(shù)組長(zhǎng)度
//打印內(nèi)容
for?i?:=?range?a?{
????fmt.Printf("a[%d]:?%d
",?i,?a[i])
}
for?i,?v?:=?range?b?{
????fmt.Printf("b[%d]:?%d
",?i,?v)
}
Go中的數(shù)組是值類(lèi)型而不是引用類(lèi)型。一個(gè)數(shù)組變量即表示整個(gè)數(shù)組,它并不是隱式的指向第一個(gè)元素的指針(比如C語(yǔ)言的數(shù)組)。這意味著當(dāng)數(shù)組賦值給一個(gè)新的變量時(shí),該變量會(huì)得到一個(gè)原始數(shù)組的一個(gè)副本。如果對(duì)新變量進(jìn)行更改,則不會(huì)影響原始數(shù)組。
a?:=?[...]string{"USA",?"China",?"India",?"Germany",?"France"}
b?:=?a?//?a?copy?of?a?is?assigned?to?b
b[0]?=?"Singapore"?//修改b,a不會(huì)改變,這不是C++的數(shù)組基地址指針
數(shù)組的長(zhǎng)度是數(shù)組類(lèi)型的一個(gè)部分,不同長(zhǎng)度或不同類(lèi)型的數(shù)據(jù)組成的數(shù)組都是不同的類(lèi)型,因此在Go語(yǔ)言中很少直接使用數(shù)組(不同長(zhǎng)度的數(shù)組因?yàn)轭?lèi)型不同無(wú)法直接賦值),因此推薦使用切片。
7 slice切片
切片是由數(shù)組建立的一種方便、靈活且功能強(qiáng)大的包裝(Wrapper),切片本身不擁有任何數(shù)據(jù)。它們只是對(duì)現(xiàn)有數(shù)組的引用。可以理解為簡(jiǎn)化版的動(dòng)態(tài)數(shù)組,slice才是C++的數(shù)組指針類(lèi)似的存在,修改slice就是修改原數(shù)組。
7.1 創(chuàng)建slice
帶有T類(lèi)型元素的切片由[]T表示,切片的長(zhǎng)度是切片中的元素?cái)?shù),切片的容量是從創(chuàng)建切片的索引開(kāi)始算起到數(shù)組末尾的元素?cái)?shù)。創(chuàng)建slice如下:
var?(
????a?[]int???????????????//?nil切片,?和?nil?相等,?一般用來(lái)表示一個(gè)不存在的切片
????b?=?[]int{}???????????//?空切片,?和?nil?不相等,?一般用來(lái)表示一個(gè)空的集合
????c?=?[]int{1,?2,?3}????//?有3個(gè)元素的切片,?len和cap都為3
????d?=?c[:2]?????????????//?有2個(gè)元素的切片,?len為2,?cap為3
????e?=?c[0:2:cap(c)]?????//?有2個(gè)元素的切片,?len為2,?cap為3
????f?=?c[:0]?????????????//?有0個(gè)元素的切片,?len為0,?cap為3
????g?[]int?=?a[1:4]?//?creates?a?slice?from?a[1]?to?a[3]
????g?=?make([]int,?3)????//?有3個(gè)元素的切片,?len和cap都為3
????i?=?make([]int,?2,?3)?//?有2個(gè)元素的切片,?len為2,?cap為3
????j?=?make([]int,?0,?3)?//?有0個(gè)元素的切片,?len為0,?cap為3
)
7.2 修改slice
切片自己不擁有任何數(shù)據(jù)。它只是底層數(shù)組的一種表示。對(duì)切片所做的任何修改都會(huì)反映在底層數(shù)組中。當(dāng)多個(gè)切片共用相同的底層數(shù)組時(shí),每個(gè)切片所做的更改將反映在數(shù)組中。
func?main()?{ ????numa?:=?[3]int{78,?79?,80} ????nums1?:=?numa[:]?//?creates?a?slice?which?contains?all?elements?of?the?array ????nums2?:=?numa[:] ????fmt.Println("array?before?change?1",?numa) ????nums1[0]?=?100 ????fmt.Println("array?after?modification?to?slice?nums1",?numa) ????nums2[1]?=?101 ????fmt.Println("array?after?modification?to?slice?nums2",?numa) } //輸出 //array?before?change?1?[78?79?80]?? //array?after?modification?to?slice?nums1?[100?79?80]?? //array?after?modification?to?slice?nums2?[100?101?80] append函數(shù)可以追加新元素,原數(shù)組長(zhǎng)度會(huì)變化(不是不能改變長(zhǎng)度嗎??)。其原理是當(dāng)新的元素被添加到slice時(shí),會(huì)創(chuàng)建一個(gè)新的數(shù)組?,F(xiàn)有數(shù)組的元素被復(fù)制到這個(gè)新數(shù)組中,并返回這個(gè)新數(shù)組的新切片引用,新切片的容量是舊切片的兩倍。
//在切片尾部追加元素
var?a?[]int
a?=?append(a,?1)???????????????//?追加1個(gè)元素
a?=?append(a,?1,?2,?3)?????????//?追加多個(gè)元素,?手寫(xiě)解包方式
a?=?append(a,?[]int{1,2,3}...)?//?追加一個(gè)切片,?切片需要解包
刪除切片元素:
//刪除尾部元素
a?=?[]int{1,?2,?3}
a?=?a[:len(a)-1]???//?刪除尾部1個(gè)元素
a?=?a[:len(a)-N]???//?刪除尾部N個(gè)元素
//刪除開(kāi)頭元素,徐婭移動(dòng)指針位置
a?=?a[1:]?//?刪除開(kāi)頭1個(gè)元素
a?=?a[N:]?//?刪除開(kāi)頭N個(gè)元素
對(duì)于刪除中間的元素,需要對(duì)剩余的元素進(jìn)行一次整體挪動(dòng),同樣可以用append或copy原地完成:
a?=?[]int{1,?2,?3,?...}
a?=?append(a[:i],?a[i+1:]...)?//?刪除中間1個(gè)元素
a?=?append(a[:i],?a[i+N:]...)?//?刪除中間N個(gè)元素
a?=?a[:i+copy(a[i:],?a[i+1:])]??//?刪除中間1個(gè)元素
a?=?a[:i+copy(a[i:],?a[i+N:])]??//?刪除中間N個(gè)元素
7.3 slice的內(nèi)存優(yōu)化
假設(shè)我們有一個(gè)非常大的數(shù)組,我們只想處理它的一小部分。然后,我們由這個(gè)數(shù)組創(chuàng)建一個(gè)切片,并開(kāi)始處理切片。這里需要重點(diǎn)注意的是,在切片引用時(shí)數(shù)組仍然存在內(nèi)存中。只要切片在內(nèi)存中,數(shù)組就不能被垃圾回收??梢允褂胏opy函數(shù)獲取一個(gè)原始slice的的副本,這樣原始slice和原數(shù)組都可以被自動(dòng)釋放了。
func?countries()?[]string?{
????countries?:=?[]string{"USA",?"Singapore",?"Germany",?"India",?"Australia"}
????neededCountries?:=?countries[:len(countries)-2]
????countriesCpy?:=?make([]string,?len(neededCountries))
????copy(countriesCpy,?neededCountries)?//復(fù)制slice
????return?countriesCpy
}
另外,更嚴(yán)重的是:假設(shè)切片里存放的是指針對(duì)象,那么下面刪除末尾的元素后,被刪除的元素依然被切片底層數(shù)組引用,從而導(dǎo)致不能及時(shí)被自動(dòng)垃圾回收器回收。保險(xiǎn)的方式是先將需要自動(dòng)內(nèi)存回收的元素設(shè)置為nil,保證自動(dòng)回收器可以發(fā)現(xiàn)需要回收的對(duì)象,然后再進(jìn)行切片的刪除操作:
var?a?[]*int{?...?}
a[len(a)-1]?=?nil?//?GC回收最后一個(gè)元素內(nèi)存
a?=?a[:len(a)-1]??//?從切片刪除最后一個(gè)元素8 map
通過(guò)向?make?函數(shù)傳入鍵和值的類(lèi)型,可以創(chuàng)建 map,map默認(rèn)是空指針nil,必須使用make進(jìn)行初始化。make(map[type of key]type of value)?是創(chuàng)建 map 的語(yǔ)法:
?
//先make,再添加key-value
func?main()?{
????personSalary?:=?make(map[string]int)
????personSalary["steve"]?=?12000
????personSalary["jamie"]?=?15000
????personSalary["mike"]?=?9000
????fmt.Println("personSalary?map?contents:",?personSalary)
}
//創(chuàng)建時(shí)添加key-value
func?main()?{??
????personSalary?:=?map[string]int?{
????????"steve":?12000,
????????"jamie":?15000,
????}
????personSalary["mike"]?=?9000
????fmt.Println("personSalary?map?contents:",?personSalary)
}
如果獲取一個(gè)不存在的元素,map 會(huì)返回該元素類(lèi)型的零值。既然無(wú)法通過(guò)返回值判斷key是否存在,我們應(yīng)該這么做:
value,?ok?:=?map[key] //如果 ok 是 true,表示 key 存在,key對(duì)應(yīng)的值就是value ,反之表示 key 不存在。
刪除?map?中?key?的語(yǔ)法是?delete(map, key)。這個(gè)函數(shù)沒(méi)有返回值。和 slices 類(lèi)似,map 也是引用類(lèi)型。當(dāng) map 被賦值為一個(gè)新變量的時(shí)候,它們指向同一個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)。因此,改變其中一個(gè)變量,就會(huì)影響到另一變量。
9 字符串和rune
Go 語(yǔ)言中的字符串是一個(gè)字節(jié)切片或rune切片,可以使用index獲取每個(gè)字符,并且使用 UTF-8 進(jìn)行編碼。字符串是不可變的。一旦一個(gè)字符串被創(chuàng)建,那么它將無(wú)法被修改。為了修改字符串,可以把字符串轉(zhuǎn)化為一個(gè) rune 切片。然后這個(gè)切片可以進(jìn)行任何想要的改變,然后再轉(zhuǎn)化為一個(gè)字符串。
func?mutate(s?[]rune)?string?{?//接收一個(gè)rune切片,修改后返回string
????s[0]?=?'a'?
????return?string(s)
}
func?main()?{??
????h?:=?"hello"
????fmt.Println(mutate([]rune(h)))
}
****注意:在 UTF-8 編碼中,一個(gè)代碼點(diǎn)可能會(huì)占用超過(guò)一個(gè)字節(jié)的空間,如果超過(guò)一個(gè)字節(jié)還使用普通string類(lèi)型的話,就會(huì)出現(xiàn)亂碼。對(duì)于這種情況,應(yīng)該使用rune類(lèi)型的slice。**rune 是 Go 語(yǔ)言的內(nèi)建類(lèi)型,它也是 int32 的別稱(chēng)。在 Go 語(yǔ)言中,rune 表示一個(gè)代碼點(diǎn)。代碼點(diǎn)無(wú)論占用多少個(gè)字節(jié),都可以用一個(gè) rune 來(lái)表示。舉例如下:
func?printChars(s?string)?{
?runes?:=?[]rune(s)?//先將string轉(zhuǎn)換為rune
?for?i?:=?0;?i?
10 結(jié)構(gòu)體
下面示例為如何創(chuàng)建結(jié)構(gòu)體并初始化:
type?Employee?struct?{?//命名結(jié)構(gòu)體
????firstName,?lastName?string
????age,?salary?????????int
}
func?main()?{
????//creating?structure?using?field?names
????emp1?:=?Employee{
????????firstName:?"Sam",
????????age:???????25,
????????salary:????500,
????????lastName:??"Anderson",
????}
????//creating?structure?without?using?field?names
????emp2?:=?Employee{"Thomas",?"Paul",?29,?800}
????fmt.Println("Employee?1",?emp1)
????fmt.Println("Employee?2",?emp2)
????//創(chuàng)建匿名結(jié)構(gòu)體,并直接生成一個(gè)結(jié)構(gòu)體對(duì)象
????emp3?:=?struct?{
????????firstName,?lastName?string
????????age,?salary?????????int
????}{
????????firstName:?"Andreah",
????????lastName:??"Nikola",
????????age:???????31,
????????salary:????5000,
????}
????fmt.Println("Employee?3",?emp3)
}
點(diǎn)號(hào)操作符?.?用于訪問(wèn)結(jié)構(gòu)體的字段。
10.1 匿名字段
當(dāng)我們創(chuàng)建結(jié)構(gòu)體時(shí),字段可以只有類(lèi)型,而沒(méi)有字段名。這樣的字段稱(chēng)為匿名字段(Anonymous Field)。雖然匿名字段沒(méi)有名稱(chēng),但其實(shí)匿名字段的名稱(chēng)就默認(rèn)為它的類(lèi)型。以下代碼創(chuàng)建一個(gè)?Person?結(jié)構(gòu)體,它含有兩個(gè)匿名字段?string?和?int。
type?Person?struct?{??
????string
????int
}
10.2 導(dǎo)出結(jié)構(gòu)體和字段
如果結(jié)構(gòu)體名稱(chēng)以大寫(xiě)字母開(kāi)頭,則它是其他包可以訪問(wèn)的導(dǎo)出類(lèi)型(Exported Type)。同樣,如果結(jié)構(gòu)體里的字段首字母大寫(xiě),它也能被其他包訪問(wèn)到。
10.3 結(jié)構(gòu)體比較
結(jié)構(gòu)體是值類(lèi)型。如果它的每一個(gè)字段都是可比較的,則該結(jié)構(gòu)體也是可比較的。如果兩個(gè)結(jié)構(gòu)體變量的對(duì)應(yīng)字段相等,則這兩個(gè)變量也是相等的。
如果結(jié)構(gòu)體包含不可比較的字段,則結(jié)構(gòu)體變量也不可比較 ?
要使用CGO特性,需要安裝C/C++構(gòu)建工具鏈,在macOS和Linux下是要安裝GCC,在windows下是需要安裝MinGW工具。同時(shí)需要保證環(huán)境變量CGO_ENABLED被設(shè)置為1,這表示CGO是被啟用的狀態(tài)。
11、cgo啟用語(yǔ)句
11.1、import "C"
通過(guò)import "C"語(yǔ)句啟用CGO特性,緊跟在這行語(yǔ)句前面的注釋是一種特殊語(yǔ)法,里面包含的是正常的C語(yǔ)言代碼。當(dāng)確保CGO啟用的情況下,還可以在當(dāng)前目錄中包含C/C++對(duì)應(yīng)的頭文件。
示例如下:
//第一個(gè)cgo的例子,使用C/C++的函數(shù)
package?main
//
//?引用的C頭文件需要在注釋中聲明,緊接著注釋需要有import?"C",且這一行和注釋之間不能有空格
//
/*
#include??//自定義頭文件
#include?
#include?
void?myprint(char*?s);//聲明頭文件中的函數(shù)
*/
import?"C"
import?(
?"fmt"
?"unsafe"
)
func?main()?{
?//使用C.CString創(chuàng)建的字符串需要手動(dòng)釋放。
?cs?:=?C.CString("Hello?World
")
?C.myprint(cs)
?C.free(unsafe.Pointer(cs))
?fmt.Println("call?C.sleep?for?3s")
?C.sleep(3)
?return
}
11.2、cgo
在import "C"語(yǔ)句前的注釋中可以通過(guò)#cgo語(yǔ)句設(shè)置編譯階段和鏈接階段的相關(guān)參數(shù)。編譯階段的參數(shù)主要用于定義相關(guān)宏和指定頭文件檢索路徑。鏈接階段的參數(shù)主要是指定庫(kù)文件檢索路徑和要鏈接的庫(kù)文件。#cgo語(yǔ)句主要影響CFLAGS、CPPFLAGS、CXXFLAGS、FFLAGS和LDFLAGS幾個(gè)編譯器環(huán)境變量。
CFLAGS:對(duì)應(yīng)C語(yǔ)言編譯參數(shù)(以.c后綴名)
CPPFLAGS:對(duì)應(yīng)C/C++ 代碼編譯參數(shù)(.c,.cc,.cpp,.cxx)
CXXFLAGS:對(duì)應(yīng)純C++編譯參數(shù)(.cc,.cpp,*.cxx)
LDFLAGS:對(duì)應(yīng)靜態(tài)庫(kù)和動(dòng)態(tài)庫(kù)鏈接選項(xiàng),必須使用絕對(duì)路徑(cgo 中的 ${SRCDIR} 為當(dāng)前目錄的絕對(duì)路徑)
使用示例如下:
//使用C庫(kù),編譯時(shí)GCC會(huì)自動(dòng)找到libnumber.a或libnumber.so進(jìn)行鏈接
package?main
/*#cgo?CFLAGS:?-I./c_library
#cgo?LDFLAGS:?-L${SRCDIR}/c_library?-l?number
#include?"number.h"
*/
import?"C"
import?"fmt"
func?main()?{
?fmt.Println(C.number_add_mod(10,?5,?12))
}
#cgo指令還支持條件選擇,當(dāng)滿足某個(gè)操作系統(tǒng)或某個(gè)CPU架構(gòu)類(lèi)型時(shí)后面的編譯或鏈接選項(xiàng)生效。比如下面是分別針對(duì)windows和非windows下平臺(tái)的編譯和鏈接選項(xiàng):
//?#cgo?windows?CFLAGS:?-DX86=1
//?#cgo?!windows?LDFLAGS:?-lm
12、C與Go之間類(lèi)型映射
12.1、基本類(lèi)型轉(zhuǎn)換
Go語(yǔ)言中數(shù)值類(lèi)型和C語(yǔ)言數(shù)據(jù)類(lèi)型基本上是相似的,以下是它們的對(duì)應(yīng)關(guān)系:
| C語(yǔ)言類(lèi)型 | CGO類(lèi)型 | Go語(yǔ)言類(lèi)型 |
|---|---|---|
| char | C.char | byte |
| singed char | C.schar | int8 |
| unsigned char | C.uchar | uint8 |
| short | C.short | int16 |
| unsigned short | C.ushort | uint16 |
| int | C.int | int32 |
| unsigned int | C.uint | uint32 |
| long | C.long | int32 |
| unsigned long | C.ulong | uint32 |
| long long int | C.longlong | int64 |
| unsigned long long int | C.ulonglong | uint64 |
| float | C.float | float32 |
| double | C.double | float64 |
| size_t | C.size_t | uint |
12.2、結(jié)構(gòu)體、聯(lián)合、枚舉類(lèi)型
C語(yǔ)言的結(jié)構(gòu)體、聯(lián)合、枚舉類(lèi)型不能作為匿名成員被嵌入到Go語(yǔ)言的結(jié)構(gòu)體中。在Go語(yǔ)言中,我們可以通過(guò)C.struct_xxx來(lái)訪問(wèn)C語(yǔ)言中定義的struct xxx結(jié)構(gòu)體類(lèi)型。
/*
struct?A?{
????int???type;??//?type?是?Go?語(yǔ)言的關(guān)鍵字,此項(xiàng)被屏蔽
????float?_type;?//?將屏蔽CGO對(duì)?type?成員的訪問(wèn)
};
*/
import?"C"
import?"fmt"
func?main()?{
????var?a?C.struct_A
????fmt.Println(a._type)?//?_type?對(duì)應(yīng)?_type
}
對(duì)于聯(lián)合類(lèi)型,我們可以通過(guò)C.union_xxx來(lái)訪問(wèn)C語(yǔ)言中定義的union xxx類(lèi)型。但是Go語(yǔ)言中并不支持C語(yǔ)言聯(lián)合類(lèi)型,它們會(huì)被轉(zhuǎn)為對(duì)應(yīng)大小的字節(jié)數(shù)組。對(duì)于枚舉類(lèi)型,我們可以通過(guò)C.enum_xxx來(lái)訪問(wèn)C語(yǔ)言中定義的enum xxx結(jié)構(gòu)體類(lèi)型。
/*
enum?C?{
????ONE,
????TWO,
};
*/
import?"C"
import?"fmt"
func?main()?{
????var?c?C.enum_C?=?C.TWO
????fmt.Println(c)
????fmt.Println(C.ONE)
????fmt.Println(C.TWO)
}
12.3、字符串和數(shù)組轉(zhuǎn)換
CGO的C虛擬包提供了以下一組函數(shù),用于Go語(yǔ)言和C語(yǔ)言之間數(shù)組和字符串的雙向轉(zhuǎn)換:
//?Go?string?to?C?string,?C.free?is?needed). func?C.CString(string)?*C.char //?Go?[]byte?slice?to?C?array,?C.free?is?needed). func?C.CBytes([]byte)?unsafe.Pointer //?C?string?to?Go?string func?C.GoString(*C.char)?string //?C?data?with?explicit?length?to?Go?string func?C.GoStringN(*C.char,?C.int)?string //?C?data?with?explicit?length?to?Go?[]byte func?C.GoBytes(unsafe.Pointer,?C.int)?[]byte
13、C函數(shù)如何返回errno?
CGO也針對(duì)
/* #include?static?int?div(int?a,?int?b)?{ ????if(b?==?0)?{ ????????errno?=?EINVAL; ????????return?0; ????} ????return?a/b; } */ import?"C" import?"fmt" func?main()?{ ????v0,?err0?:=?C.div(2,?1) ????fmt.Println(v0,?err0) ????v1,?err1?:=?C.div(1,?0) ????fmt.Println(v1,?err1) }
14、一個(gè)完整的封裝C函數(shù)的例子
該例子的重點(diǎn)是,在封裝C函數(shù)的模塊里要提供外部類(lèi)型和函數(shù)指針類(lèi)型給其他go模塊使用,不能直接在其他模塊使用封裝模塊中的C類(lèi)型,因?yàn)椴煌Kcgo編譯后C類(lèi)型并不是統(tǒng)一類(lèi)型,無(wú)法進(jìn)行類(lèi)型轉(zhuǎn)換。封裝C標(biāo)準(zhǔn)庫(kù)qsort函數(shù):
//封裝C標(biāo)準(zhǔn)庫(kù)函數(shù)qsort,給其他go文件或模塊使用 package?qsort /* #include?//qsort的比較函數(shù)指針 typedef?int?(*qsort_cmp_func_t)(const?void*?a,?const?void*?b); */ import?"C" import?"unsafe" //將虛擬C包中的類(lèi)型通過(guò)Go語(yǔ)言類(lèi)型代替,在內(nèi)部調(diào)用C函數(shù)時(shí)重新轉(zhuǎn)型為C函數(shù)需要的類(lèi)型 //因此外部用戶將不再依賴(lài)qsort包內(nèi)的虛擬C包,消除用戶對(duì)CGO代碼的直接依賴(lài) type?CompareFunc?C.qsort_cmp_func_t //封裝qsort的go?Sort函數(shù) func?Sort(base?unsafe.Pointer,?num?int,?size?int,?cmp?CompareFunc)?{ ?C.qsort(base,?C.size_t(num),?C.size_t(size),?C.qsort_cmp_func_t(cmp)) }
使用上面qsort庫(kù)的其他庫(kù)文件:
package?main
//extern?int?go_qsort_compare(void*?a,?void*?b);
import?"C"
import?(
?"fmt"
?"qsort"
?"unsafe"
)
//export?go_qsort_compare
func?go_qsort_compare(a,?b?unsafe.Pointer)?C.int?{
?pa,?pb?:=?(*C.int)(a),?(*C.int)(b)
?return?C.int(*pa?-?*pb)
}
func?main()?{
?values?:=?[]int32{42,?9,?101,?95,?27,?25}
?qsort.Sort(unsafe.Pointer(&values[0]),
??len(values),?int(unsafe.Sizeof(values[0])),
????????//轉(zhuǎn)換一下函數(shù)指針,使用qsort提供的類(lèi)型,不直接使用C空間函數(shù)指針
??qsort.CompareFunc(C.go_qsort_compare),
?)
?fmt.Println(values)
}
15、中間生成文件
在一個(gè)Go源文件中,如果出現(xiàn)了import "C"指令則表示將調(diào)用cgo命令生成對(duì)應(yīng)的中間文件。下圖是cgo生成的中間文件的簡(jiǎn)單示意圖:

16、Cgo內(nèi)存訪問(wèn)
如果在CGO處理的跨語(yǔ)言函數(shù)調(diào)用時(shí)涉及到了指針的傳遞,則可能會(huì)出現(xiàn)Go語(yǔ)言和C語(yǔ)言共享某一段內(nèi)存的場(chǎng)景。我們知道C語(yǔ)言的內(nèi)存在分配之后就是穩(wěn)定的,但是Go語(yǔ)言因?yàn)楹瘮?shù)棧的動(dòng)態(tài)伸縮可能導(dǎo)致棧中內(nèi)存地址的移動(dòng)(這是Go和C內(nèi)存模型的最大差異)。如果C語(yǔ)言持有的是移動(dòng)之前的Go指針,那么以舊指針訪問(wèn)Go對(duì)象時(shí)會(huì)導(dǎo)致程序崩潰。
16.1 Go訪問(wèn)C內(nèi)存
C語(yǔ)言空間的內(nèi)存是穩(wěn)定的,只要不是被人為提前釋放,那么在Go語(yǔ)言空間可以放心大膽地使用。比如下面示例,我們可以在Go中調(diào)用C的malloc和free創(chuàng)建、使用和釋放內(nèi)存,不用考慮內(nèi)存地址移動(dòng)的問(wèn)題。
package?main /* #include?void*?makeslice(size_t?memsize)?{ ????return?malloc(memsize); } */ import?"C" import?"unsafe" func?makeByteSlize(n?int)?[]byte?{ ????p?:=?C.makeslice(C.size_t(n)) ????return?((*[1?<31]byte)(p))[0n] } func?freeByteSlice(p?[]byte)?{ ????C.free(unsafe.Pointer(&p[0])) } func?main()?{ ????s?:=?makeByteSlize(1<<32+1)?//創(chuàng)建一個(gè)超大的內(nèi)存用于切片 ????s[len(s)-1]?=?255 ????print(s[len(s)-1]) ????freeByteSlice(s) }
16.2 Go內(nèi)存?zhèn)魅隒語(yǔ)言函數(shù)
C/C++很多庫(kù)都是需要通過(guò)指針直接處理傳入的內(nèi)存數(shù)據(jù)的,因此cgo中也有很多需要將Go內(nèi)存?zhèn)魅隒語(yǔ)言函數(shù)的應(yīng)用場(chǎng)景。Go的內(nèi)存是不穩(wěn)定的,goroutinue棧因?yàn)榭臻g不足的原因可能會(huì)發(fā)生擴(kuò)展,導(dǎo)致了原來(lái)的Go語(yǔ)言?xún)?nèi)存被移動(dòng)到了新的位置,如果這時(shí)候還按照原來(lái)地址訪問(wèn)內(nèi)存,就會(huì)導(dǎo)致內(nèi)存越界。為了簡(jiǎn)化并高效處理向C語(yǔ)言傳入Go語(yǔ)言?xún)?nèi)存的問(wèn)題,cgo針對(duì)該場(chǎng)景定義了專(zhuān)門(mén)的規(guī)則:在CGO調(diào)用的C語(yǔ)言函數(shù)返回前,cgo保證傳入的Go語(yǔ)言?xún)?nèi)存在此期間不會(huì)發(fā)生移動(dòng),C語(yǔ)言函數(shù)可以大膽地使用Go語(yǔ)言的內(nèi)存!
package?main /* #includevoid?printString(const?char*?s,?int?n)?{ ????int?i; ????for(i?=?0;?i? 編輯:黃飛
?
電子發(fā)燒友App





評(píng)論