1. Go項目的構建
一個Go工程中主要包含以下三個目錄:
src:源代碼文件,編寫程序代碼xxx.go,執(zhí)行命令go build xxx.go會生成*.exe文件;執(zhí)行go run xxx.go可以直接運行文件
pkg:包文件,執(zhí)行go install name會在此目錄下生成*.a文件,用于import
bin:相關bin文件,執(zhí)行go install xxx會在此母名生成*.exe文件,可以直接運行
go的基本命令如下:
image.png
2. 變量和常量
Go的程序是保存在多個.go文件中,文件的第一行就是package XXX聲明,用來說明該文件屬于哪個包(package),package聲明下來就是import聲明,再下來是類型,變量,常量,函數(shù)的聲明。Go語言的變量聲明格式為
var?變量名?變量類型?[?=?表達式或值]
變量聲明以關鍵字var開頭,變量類型放在變量的后面,行尾無需分號。舉個例子:
var?name?string var?age?int //批量聲明,一個var帶多個不同類型的變量聲明 var?( ?a?string ?b?int ?c?bool ?d?float32 )
類型推斷
我們可以將變量的類型省略,編譯器會根據(jù)等號右邊的值來推導變量的類型完成初始化
在函數(shù)內(nèi)部,可以使用更簡略的 := 方式(省略var和type)聲明并初始化變量。但是有限制:
不能用在函數(shù)外
:=?操作符的左邊至少有一個變量是尚未聲明的
常量的聲明和變量聲明非常類似,只是把var換成了const,常量在定義的時候必須賦值。const同時聲明多個常量時,如果省略了值則表示和上面一行的值相同。例如:
const?( ????????n1?=?100 ????????n2 ????????n3 ????)
3 內(nèi)置數(shù)據(jù)類型
類型 | 長度(字節(jié)) | 默認值 | 說明 |
---|---|---|---|
bool | 1 | false | ? |
byte | 1 | 0 | uint8 |
rune | 4 | 0 | 代表一個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 | ? | 復數(shù),實部和虛部為32位,創(chuàng)建方式:- 使用函數(shù)complex創(chuàng)建- a := 6 + 7i |
complex128 | 16 | ? | 復數(shù),實部和虛部為64位 |
uintptr | 4或8 | ? | 以存儲指針的 uint32 或 uint64 整數(shù) |
array | ? | ? | 值類型 |
struct | ? | ? | 值類型 |
string | ? | "" | UTF-8 字符串 |
slice | ? | nil | 引用類型 |
map | ? | nil | 引用類型 |
channel | ? | nil | 引用類型 |
interface | ? | nil | 接口 |
function | ? | nil | 函數(shù) |
nil | ? | ? | 空指針 |
3.1 格式化打印
fmt包支持如下幾種打印方式
fmt.Println:打印一行內(nèi)容,類似std::cout,難以設置格式
fmt.Print:打印內(nèi)容,并不換行
fmt.Printf:格式化打印,與C語言printf同理
fmt.Sprintf:格式化打印,不同之處使返回string類型,不是打印到屏幕
格式化打印支持的格式符:
image.png
fmt.Printf("type?of?a?is?%T,?size?of?a?is?%d",?a,?unsafe.Sizeof(a))?//?a?的類型和大小
3.2 類型轉(zhuǎn)換
Go語言中只有強制類型轉(zhuǎn)換,沒有隱式類型轉(zhuǎn)換。該語法只能在兩個類型之間支持相互轉(zhuǎn)換的時候使用。強制類型轉(zhuǎn)換的基本語法如下:
T(表達式)
4 基本語句
4.1 if語句
//可省略條件表達式括號。 //持初始化語句,可定義代碼塊局部變量。? //代碼塊左?括號必須在條件表達式尾部。 if?布爾表達式?{ ????//。。。 }?else?{?//else不能單獨一行,golang的自動分號插入機制導致的 ??//。。。 } //另一種格式,在條件判斷前執(zhí)行一條指令 if?statement;?condition?{?? }
4.2 switch語句
switch?var1?{ ????case?val1: ????????... ????case?val2,val3,val4://通過用逗號分隔,可以在一個?case?中包含多個表達式 ????????... ????default: ????????... } //可以看到每個case不需要break來分割 //switch?語句還可以被用于type-switch?來判斷某個interface?變量中實際存儲的變量類型 switch?x.(type){ ????case?type: ???????statement(s)?????? ????case?type: ???????statement(s) ????/*?你可以定義任意個數(shù)的case?*/ ????default:?/*?可選?*/ ???????statement(s) }
注意:
case可以是字符、字符串,表達式,不一定是常量
每個case語句塊自動結束退出switch,不需要使用break
如果需要接著執(zhí)行下一個case的內(nèi)容,需要使用**fallthrough?**
4.3 for循環(huán)
三種形式
for?init;?condition;?post?{?} for?condition?{?} for?{?} //init:?一般為賦值表達式,給控制變量賦初值; //condition:?關系表達式或邏輯表達式,循環(huán)控制條件; //post:?一般為賦值表達式,給控制變量增量或減量。
range循環(huán)語句:range類似迭代器操作,返回 (索引, 值) 或 (鍵, 值)
for?key,?value?:=?range?oldMap?{ ????newMap[key]?=?value }
5 函數(shù)
5.1 函數(shù)定義
在 Go 語言中,函數(shù)聲明通用語法如下:
func?functionname(parametername?type)?returntype?{?? ????//?函數(shù)體(具體實現(xiàn)的功能) } //如果有連續(xù)若干個函數(shù)參數(shù),它們的類型一致,那么無須一一羅列,只需在最后一個參數(shù)后添加該類型。
Go 語言支持一個函數(shù)可以有多個返回值(也用括號包含),并且可以給返回值命名,這樣可以不在return里添加需要返回的變量:
func?rectProps(length,?width?float64)(float64,?float64)?{//兩個括號,一個函數(shù)參數(shù),一個返回列表?? ????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?//?不需要明確指定返回值,默認返回?area,?perimeter?的值 }
_?在 Go 中被用作空白符,可以用作表示任何類型的任何值,通常用在接收函數(shù)多返回值,過濾掉不需要的返回值:
area,?_?:=?rectProps(10.8,?5.6)?//?返回值周長被丟棄
5.2 可變參數(shù)
如果函數(shù)最后一個參數(shù)被記作?...T?,這時函數(shù)可以接受任意個?T?類型參數(shù)作為最后一個參數(shù)。可變參數(shù)函數(shù)的工作原理是把可變參數(shù)轉(zhuǎn)換為一個新的切片。
func?find(num?int,?nums?...int)?{ ????fmt.Printf("type?of?nums?is?%T ",?nums)//nums相當于整型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ù)多個參數(shù) ????nums?:=?[]int{89,?90,?95} ????find(89,?nums...)//傳入一個slice }
5.3 返回error信息
我們可以使用errors包或fmt包來生成error類型的對象,用于返回函數(shù)的內(nèi)部錯誤:
//實現(xiàn)自定義函數(shù)同時返回err和其他返回值 package?main import?( ?"errors" ?"fmt" ) func?f1()?(int,?error)?{?//設置多返回值 ?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ù)
對于需要在函數(shù)內(nèi)部修改的參數(shù),需要使用傳址參數(shù),GO中指針和C語言使一樣的,基本符號也是***和&**。
//指針傳址參數(shù),和函數(shù)返回指針
package?main
import?"fmt"
func?fun1(value?*int)?*float64?{
?*value?+=?10
?myFloat?:=?98.5
?//雖然myFloat是局部變量,但GO并不會釋放它,因為所有權被轉(zhuǎn)移到函數(shù)外了
?return?&myFloat
}
func?main()?{
?number?:=?10
?ret?:=?fun1(&number)
?fmt.Println(number,?"??",?*ret)
}6 數(shù)組
一個數(shù)組的表示形式為[n]T。n表示數(shù)組中元素的數(shù)量,T代表每個元素的類型。使用示例如下:
var?a?[3]int?//所有元素有默認值0 a?:=?[3]int{12,?78,?50}//簡要聲明,賦值 a?:=?[3]int{12}?//只給第一個元素賦值 var?b?=?[...]int{1,?2,?3}?//?定義長度為3的int型數(shù)組,?元素為?1,?2,?3 fmt.Println(a)?//數(shù)組可以直接打印出來 fmt.Println(len(a))?//打印數(shù)組長度 //打印內(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ù)組是值類型而不是引用類型。一個數(shù)組變量即表示整個數(shù)組,它并不是隱式的指向第一個元素的指針(比如C語言的數(shù)組)。這意味著當數(shù)組賦值給一個新的變量時,該變量會得到一個原始數(shù)組的一個副本。如果對新變量進行更改,則不會影響原始數(shù)組。
a?:=?[...]string{"USA",?"China",?"India",?"Germany",?"France"} b?:=?a?//?a?copy?of?a?is?assigned?to?b b[0]?=?"Singapore"?//修改b,a不會改變,這不是C++的數(shù)組基地址指針
數(shù)組的長度是數(shù)組類型的一個部分,不同長度或不同類型的數(shù)據(jù)組成的數(shù)組都是不同的類型,因此在Go語言中很少直接使用數(shù)組(不同長度的數(shù)組因為類型不同無法直接賦值),因此推薦使用切片。
7 slice切片
切片是由數(shù)組建立的一種方便、靈活且功能強大的包裝(Wrapper),切片本身不擁有任何數(shù)據(jù)。它們只是對現(xiàn)有數(shù)組的引用??梢岳斫鉃?strong>簡化版的動態(tài)數(shù)組,slice才是C++的數(shù)組指針類似的存在,修改slice就是修改原數(shù)組。
7.1 創(chuàng)建slice
帶有T類型元素的切片由[]T表示,切片的長度是切片中的元素數(shù),切片的容量是從創(chuàng)建切片的索引開始算起到數(shù)組末尾的元素數(shù)。創(chuàng)建slice如下:
var?( ????a?[]int???????????????//?nil切片,?和?nil?相等,?一般用來表示一個不存在的切片 ????b?=?[]int{}???????????//?空切片,?和?nil?不相等,?一般用來表示一個空的集合 ????c?=?[]int{1,?2,?3}????//?有3個元素的切片,?len和cap都為3 ????d?=?c[:2]?????????????//?有2個元素的切片,?len為2,?cap為3 ????e?=?c[0:2:cap(c)]?????//?有2個元素的切片,?len為2,?cap為3 ????f?=?c[:0]?????????????//?有0個元素的切片,?len為0,?cap為3 ????g?[]int?=?a[1:4]?//?creates?a?slice?from?a[1]?to?a[3] ????g?=?make([]int,?3)????//?有3個元素的切片,?len和cap都為3 ????i?=?make([]int,?2,?3)?//?有2個元素的切片,?len為2,?cap為3 ????j?=?make([]int,?0,?3)?//?有0個元素的切片,?len為0,?cap為3 )
7.2 修改slice
切片自己不擁有任何數(shù)據(jù)。它只是底層數(shù)組的一種表示。對切片所做的任何修改都會反映在底層數(shù)組中。當多個切片共用相同的底層數(shù)組時,每個切片所做的更改將反映在數(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ù)組長度會變化(不是不能改變長度嗎??)。其原理是當新的元素被添加到slice時,會創(chuàng)建一個新的數(shù)組?,F(xiàn)有數(shù)組的元素被復制到這個新數(shù)組中,并返回這個新數(shù)組的新切片引用,新切片的容量是舊切片的兩倍。
//在切片尾部追加元素 var?a?[]int a?=?append(a,?1)???????????????//?追加1個元素 a?=?append(a,?1,?2,?3)?????????//?追加多個元素,?手寫解包方式 a?=?append(a,?[]int{1,2,3}...)?//?追加一個切片,?切片需要解包
刪除切片元素:
//刪除尾部元素 a?=?[]int{1,?2,?3} a?=?a[:len(a)-1]???//?刪除尾部1個元素 a?=?a[:len(a)-N]???//?刪除尾部N個元素 //刪除開頭元素,徐婭移動指針位置 a?=?a[1:]?//?刪除開頭1個元素 a?=?a[N:]?//?刪除開頭N個元素
對于刪除中間的元素,需要對剩余的元素進行一次整體挪動,同樣可以用append或copy原地完成:
a?=?[]int{1,?2,?3,?...} a?=?append(a[:i],?a[i+1:]...)?//?刪除中間1個元素 a?=?append(a[:i],?a[i+N:]...)?//?刪除中間N個元素 a?=?a[:i+copy(a[i:],?a[i+1:])]??//?刪除中間1個元素 a?=?a[:i+copy(a[i:],?a[i+N:])]??//?刪除中間N個元素
7.3 slice的內(nèi)存優(yōu)化
假設我們有一個非常大的數(shù)組,我們只想處理它的一小部分。然后,我們由這個數(shù)組創(chuàng)建一個切片,并開始處理切片。這里需要重點注意的是,在切片引用時數(shù)組仍然存在內(nèi)存中。只要切片在內(nèi)存中,數(shù)組就不能被垃圾回收??梢允褂胏opy函數(shù)獲取一個原始slice的的副本,這樣原始slice和原數(shù)組都可以被自動釋放了。
func?countries()?[]string?{ ????countries?:=?[]string{"USA",?"Singapore",?"Germany",?"India",?"Australia"} ????neededCountries?:=?countries[:len(countries)-2] ????countriesCpy?:=?make([]string,?len(neededCountries)) ????copy(countriesCpy,?neededCountries)?//復制slice ????return?countriesCpy } 另外,更嚴重的是:假設切片里存放的是指針對象,那么下面刪除末尾的元素后,被刪除的元素依然被切片底層數(shù)組引用,從而導致不能及時被自動垃圾回收器回收。保險的方式是先將需要自動內(nèi)存回收的元素設置為nil,保證自動回收器可以發(fā)現(xiàn)需要回收的對象,然后再進行切片的刪除操作:
var?a?[]*int{?...?}
a[len(a)-1]?=?nil?//?GC回收最后一個元素內(nèi)存
a?=?a[:len(a)-1]??//?從切片刪除最后一個元素8 map
通過向?make?函數(shù)傳入鍵和值的類型,可以創(chuàng)建 map,map默認是空指針nil,必須使用make進行初始化。make(map[type of key]type of value)?是創(chuàng)建 map 的語法:
?
//先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)建時添加key-value func?main()?{?? ????personSalary?:=?map[string]int?{ ????????"steve":?12000, ????????"jamie":?15000, ????} ????personSalary["mike"]?=?9000 ????fmt.Println("personSalary?map?contents:",?personSalary) }
如果獲取一個不存在的元素,map 會返回該元素類型的零值。既然無法通過返回值判斷key是否存在,我們應該這么做:
value,?ok?:=?map[key] //如果 ok 是 true,表示 key 存在,key對應的值就是value ,反之表示 key 不存在。
刪除?map?中?key?的語法是?delete(map, key)。這個函數(shù)沒有返回值。和 slices 類似,map 也是引用類型。當 map 被賦值為一個新變量的時候,它們指向同一個內(nèi)部數(shù)據(jù)結構。因此,改變其中一個變量,就會影響到另一變量。
9 字符串和rune
Go 語言中的字符串是一個字節(jié)切片或rune切片,可以使用index獲取每個字符,并且使用 UTF-8 進行編碼。字符串是不可變的。一旦一個字符串被創(chuàng)建,那么它將無法被修改。為了修改字符串,可以把字符串轉(zhuǎn)化為一個 rune 切片。然后這個切片可以進行任何想要的改變,然后再轉(zhuǎn)化為一個字符串。
func?mutate(s?[]rune)?string?{?//接收一個rune切片,修改后返回string ????s[0]?=?'a'? ????return?string(s) } func?main()?{?? ????h?:=?"hello" ????fmt.Println(mutate([]rune(h))) } ****注意:在 UTF-8 編碼中,一個代碼點可能會占用超過一個字節(jié)的空間,如果超過一個字節(jié)還使用普通string類型的話,就會出現(xiàn)亂碼。對于這種情況,應該使用rune類型的slice。**rune 是 Go 語言的內(nèi)建類型,它也是 int32 的別稱。在 Go 語言中,rune 表示一個代碼點。代碼點無論占用多少個字節(jié),都可以用一個 rune 來表示。舉例如下:
func?printChars(s?string)?{ ?runes?:=?[]rune(s)?//先將string轉(zhuǎn)換為rune ?for?i?:=?0;?i?10 結構體
下面示例為如何創(chuàng)建結構體并初始化:
type?Employee?struct?{?//命名結構體 ????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)建匿名結構體,并直接生成一個結構體對象 ????emp3?:=?struct?{ ????????firstName,?lastName?string ????????age,?salary?????????int ????}{ ????????firstName:?"Andreah", ????????lastName:??"Nikola", ????????age:???????31, ????????salary:????5000, ????} ????fmt.Println("Employee?3",?emp3) }點號操作符?.?用于訪問結構體的字段。
10.1 匿名字段
當我們創(chuàng)建結構體時,字段可以只有類型,而沒有字段名。這樣的字段稱為匿名字段(Anonymous Field)。雖然匿名字段沒有名稱,但其實匿名字段的名稱就默認為它的類型。以下代碼創(chuàng)建一個?Person?結構體,它含有兩個匿名字段?string?和?int。
type?Person?struct?{?? ????string ????int }10.2 導出結構體和字段
如果結構體名稱以大寫字母開頭,則它是其他包可以訪問的導出類型(Exported Type)。同樣,如果結構體里的字段首字母大寫,它也能被其他包訪問到。
10.3 結構體比較
結構體是值類型。如果它的每一個字段都是可比較的,則該結構體也是可比較的。如果兩個結構體變量的對應字段相等,則這兩個變量也是相等的。
如果結構體包含不可比較的字段,則結構體變量也不可比較 ?
要使用CGO特性,需要安裝C/C++構建工具鏈,在macOS和Linux下是要安裝GCC,在windows下是需要安裝MinGW工具。同時需要保證環(huán)境變量CGO_ENABLED被設置為1,這表示CGO是被啟用的狀態(tài)。
11、cgo啟用語句
11.1、import "C"
通過import "C"語句啟用CGO特性,緊跟在這行語句前面的注釋是一種特殊語法,里面包含的是正常的C語言代碼。當確保CGO啟用的情況下,還可以在當前目錄中包含C/C++對應的頭文件。
示例如下:
//第一個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)建的字符串需要手動釋放。 ?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"語句前的注釋中可以通過#cgo語句設置編譯階段和鏈接階段的相關參數(shù)。編譯階段的參數(shù)主要用于定義相關宏和指定頭文件檢索路徑。鏈接階段的參數(shù)主要是指定庫文件檢索路徑和要鏈接的庫文件。#cgo語句主要影響CFLAGS、CPPFLAGS、CXXFLAGS、FFLAGS和LDFLAGS幾個編譯器環(huán)境變量。
CFLAGS:對應C語言編譯參數(shù)(以.c后綴名)
CPPFLAGS:對應C/C++ 代碼編譯參數(shù)(.c,.cc,.cpp,.cxx)
CXXFLAGS:對應純C++編譯參數(shù)(.cc,.cpp,*.cxx)
LDFLAGS:對應靜態(tài)庫和動態(tài)庫鏈接選項,必須使用絕對路徑(cgo 中的 ${SRCDIR} 為當前目錄的絕對路徑)
使用示例如下:
//使用C庫,編譯時GCC會自動找到libnumber.a或libnumber.so進行鏈接 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指令還支持條件選擇,當滿足某個操作系統(tǒng)或某個CPU架構類型時后面的編譯或鏈接選項生效。比如下面是分別針對windows和非windows下平臺的編譯和鏈接選項:
//?#cgo?windows?CFLAGS:?-DX86=1 //?#cgo?!windows?LDFLAGS:?-lm12、C與Go之間類型映射
12.1、基本類型轉(zhuǎn)換
Go語言中數(shù)值類型和C語言數(shù)據(jù)類型基本上是相似的,以下是它們的對應關系:
C語言類型 | CGO類型 | Go語言類型 |
---|---|---|
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、結構體、聯(lián)合、枚舉類型
C語言的結構體、聯(lián)合、枚舉類型不能作為匿名成員被嵌入到Go語言的結構體中。在Go語言中,我們可以通過C.struct_xxx來訪問C語言中定義的struct xxx結構體類型。
/* struct?A?{ ????int???type;??//?type?是?Go?語言的關鍵字,此項被屏蔽 ????float?_type;?//?將屏蔽CGO對?type?成員的訪問 }; */ import?"C" import?"fmt" func?main()?{ ????var?a?C.struct_A ????fmt.Println(a._type)?//?_type?對應?_type }
對于聯(lián)合類型,我們可以通過C.union_xxx來訪問C語言中定義的union xxx類型。但是Go語言中并不支持C語言聯(lián)合類型,它們會被轉(zhuǎn)為對應大小的字節(jié)數(shù)組。對于枚舉類型,我們可以通過C.enum_xxx來訪問C語言中定義的enum xxx結構體類型。
/* 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語言和C語言之間數(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也針對
/* #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、一個完整的封裝C函數(shù)的例子
該例子的重點是,在封裝C函數(shù)的模塊里要提供外部類型和函數(shù)指針類型給其他go模塊使用,不能直接在其他模塊使用封裝模塊中的C類型,因為不同模塊cgo編譯后C類型并不是統(tǒng)一類型,無法進行類型轉(zhuǎn)換。封裝C標準庫qsort函數(shù):
//封裝C標準庫函數(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包中的類型通過Go語言類型代替,在內(nèi)部調(diào)用C函數(shù)時重新轉(zhuǎn)型為C函數(shù)需要的類型 //因此外部用戶將不再依賴qsort包內(nèi)的虛擬C包,消除用戶對CGO代碼的直接依賴 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庫的其他庫文件:
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提供的類型,不直接使用C空間函數(shù)指針 ??qsort.CompareFunc(C.go_qsort_compare), ?) ?fmt.Println(values) }
15、中間生成文件
在一個Go源文件中,如果出現(xiàn)了import "C"指令則表示將調(diào)用cgo命令生成對應的中間文件。下圖是cgo生成的中間文件的簡單示意圖:
16、Cgo內(nèi)存訪問
如果在CGO處理的跨語言函數(shù)調(diào)用時涉及到了指針的傳遞,則可能會出現(xiàn)Go語言和C語言共享某一段內(nèi)存的場景。我們知道C語言的內(nèi)存在分配之后就是穩(wěn)定的,但是Go語言因為函數(shù)棧的動態(tài)伸縮可能導致棧中內(nèi)存地址的移動(這是Go和C內(nèi)存模型的最大差異)。如果C語言持有的是移動之前的Go指針,那么以舊指針訪問Go對象時會導致程序崩潰。
16.1 Go訪問C內(nèi)存
C語言空間的內(nèi)存是穩(wěn)定的,只要不是被人為提前釋放,那么在Go語言空間可以放心大膽地使用。比如下面示例,我們可以在Go中調(diào)用C的malloc和free創(chuàng)建、使用和釋放內(nèi)存,不用考慮內(nèi)存地址移動的問題。
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)建一個超大的內(nèi)存用于切片 ????s[len(s)-1]?=?255 ????print(s[len(s)-1]) ????freeByteSlice(s) }
16.2 Go內(nèi)存?zhèn)魅隒語言函數(shù)
C/C++很多庫都是需要通過指針直接處理傳入的內(nèi)存數(shù)據(jù)的,因此cgo中也有很多需要將Go內(nèi)存?zhèn)魅隒語言函數(shù)的應用場景。Go的內(nèi)存是不穩(wěn)定的,goroutinue棧因為空間不足的原因可能會發(fā)生擴展,導致了原來的Go語言內(nèi)存被移動到了新的位置,如果這時候還按照原來地址訪問內(nèi)存,就會導致內(nèi)存越界。為了簡化并高效處理向C語言傳入Go語言內(nèi)存的問題,cgo針對該場景定義了專門的規(guī)則:在CGO調(diào)用的C語言函數(shù)返回前,cgo保證傳入的Go語言內(nèi)存在此期間不會發(fā)生移動,C語言函數(shù)可以大膽地使用Go語言的內(nèi)存!
package?main /* #includevoid?printString(const?char*?s,?int?n)?{ ????int?i; ????for(i?=?0;?i? 編輯:黃飛
?
評論
查看更多