變量定義
變量定義語法
- 使用var關(guān)鍵字,可放在函數(shù)內(nèi),也可放在包內(nèi)
// var + 變量名 + 數(shù)據(jù)類型(有默認(rèn)值)
var a int
var b string = "string"
// 通過賦值自動判斷類型
var c = true
// 集中定義
var (
x = 1
y = 2
)
- 使用:=定義變量,只能在函數(shù)內(nèi)使用
// := 用于賦初值
a, b := 1, 2
內(nèi)建變量類型(builtin)
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
- byte, rune
- float32, float64, complex64, complex128
強(qiáng)制類型轉(zhuǎn)換
Golang要求強(qiáng)制類型轉(zhuǎn)換,無隱式轉(zhuǎn)換
a, b := 3, 4
var c int
c = int(math.Sqrt(float64(a * a + b * b)))
常量
使用const關(guān)鍵字定義常量,const數(shù)值可作為各種類型使用。
// 不確定類型
const a = 3
var b float64
b = a
// 合并定義
const (
c = 1
d = 2
)
枚舉
const (
spring = 0
summer = 1
autumn = 2
winter = 3
)
// iota用在取值為0的const變量,后續(xù)變量依次加1
const (
spring = iota
summer
autumn
winter
)
變量定義要點(diǎn)
- 變量類型寫在變量名之后
- 編譯器可以推測變量類型
- Golang沒有char,使用rune
- 原生支持復(fù)數(shù)類型
分支
if
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(contents)
}
// 可以在if條件中賦值,賦值變量的作用域在if語句中
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Println(contents)
}
switch
switch后可以沒有表達(dá)式,case結(jié)束后自動break,通過fallthrough不使用自動break。
func grade(score int) {
grade := ""
switch {
case score < 0:
panic(fmt.Sprintf("Wrong score: %d\n", score))
case score < 60:
grade = "C"
case score < 80:
grade = "B"
case score < 90:
grade = "A"
case score < 100:
// 自動break,除非出現(xiàn)fallthrough
fallthrough
case score >= 100:
grade = "S"
}
fmt.Println(grade)
}
循環(huán)
for條件沒有括號,沒有while。
func convertToBin(n int) string {
result := ""
for ; n > 0; n /= 2 {
lsb := n % 2
result = strconv.Itoa(lsb) + result
}
return result
}
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func forever() {
// while true
for {
fmt.Println("forever")
}
}
函數(shù)
- 函數(shù)聲明的順序?yàn)椋宏P(guān)鍵字,函數(shù)名,參數(shù),返回類型,函數(shù)體。
- 函數(shù)允許有多個返回值,并能在函數(shù)體內(nèi)拿到返回值。
- 函數(shù)可以作為另一個函數(shù)的參數(shù)
- 函數(shù)支持可變參數(shù)列表
func div(a, b int) (q, r int) {
// q = a / b
// r = a % b
// return
return a / b, a % b
}
// 使用_拋棄返回值
a, _ := div(12, 7)
// 使用另一個函數(shù)作為參數(shù)
func apply(operation func(a, b int), a, b int) {
operation(a, b)
}
// 使用匿名函數(shù)
apply(func(a, b int) {
fmt.Println(a, b)
}, 1, 2)
// 可變參數(shù)列表
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
指針
Golang只有值傳遞一種方式。
// 使用指針操作
func change(pa *int) {
*pa++
}
a := 5
change(&a)
func swap(x, y *int) {
*x, *y = *y, *x
}
數(shù)組
數(shù)組長度寫在類型之前,[10]int和[20]int是不同的類型。數(shù)組是值復(fù)制。
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6}
var arr4 [4][5]int
遍歷數(shù)組
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}
for i := range arr3 {
fmt.Println(arr3[i])
}
// 按下標(biāo)和值遍歷
for i, v := range arr3 {
fmt.Println(i, v)
}
切片
數(shù)組切片相當(dāng)于數(shù)據(jù)的一個視圖,可以通過切片改變數(shù)組的值。
func updateSlice(s []int) {
s[0] = 100
}
func main() {
array := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := array[2:6]
s2 := array[2:]
s3 := array[:6]
s4 := array[:]
fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7]
updateSlice(s2)
fmt.Println(s2) // [100 3 4 5 6 7]
updateSlice(s3)
fmt.Println(s3) // [100 1 100 3 4 5]
// 切片擴(kuò)展
s5 := s3[6:8]
fmt.Println(s5)
// 添加元素
s6 := append(s5, 8)
fmt.Println(s6, array)
fmt.Println(array[:8])
// 通過make創(chuàng)建切片
// 長度為10的切片
x := make([]int, 10)
// 長度為10,容量為16的切片
y := make([]int, 10, 16)
// 拷貝數(shù)組元素
copy(x, y)
// 截取數(shù)組元素
z := append(x[:2], x[3:]...)
}
切片底層維護(hù)了一個數(shù)組,ptr指向切片的首個元素,len表示切片的長度,cap表示底層數(shù)組的從ptr到最末的元素?cái)?shù)量。切片可以向后擴(kuò)展,不可向前擴(kuò)展。切片添加元素如果會超過底層數(shù)組的cap,Golang會分配更大的底層數(shù)組(原容量*2進(jìn)行擴(kuò)展),由于值傳遞的關(guān)系,必須接收append方法的返回值。
map
通過map[K]v的形式定義map。除了slice、map、function的內(nèi)建類型和struct類型都可以作為map的key。
m := map[string]string{
"name": "wch",
"age": "23",
}
m2 := make(map[string]int)
// 無序遍歷map
for k, v := range m {
fmt.Println(k, v)
}
// map讀值
name := m["name"]
// 判斷key是否存在
grade, exist := m["grade"]
// 刪除
delete(m, "name")
rune
Golang的rune相當(dāng)于java的char,中文字符在Golang中占3個字節(jié),使用 utf8.RuneCountInString 獲取字符數(shù),用 len 獲取字節(jié)長度,使用 []byte 獲取字節(jié)。
s := "學(xué)習(xí)Golang!"
fmt.Println(s)
// ch是rune類型,中文字符占3個字節(jié)
for i, ch := range s {
fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()
fmt.Println("rune count in string:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("size= %d, rune=%c\n", size, ch)
}
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()
面向?qū)ο?/h3>
Golang僅支持封裝,不支持繼承和多態(tài),沒有class,只有struct,通過struct來定義對象。
type treeNode struct {
value int
left, right *treeNode
}
func createNode(value int) *treeNode {
return &treeNode{value: value}
}
func main() {
// 通過多種方式聲明對象
var root treeNode
root = treeNode{value: 0}
root.left = &treeNode{}
root.right = &treeNode{1, nil, nil}
root.right.left = new(treeNode)
root.right.right = createNode(3)
}
Golang支持顯式定義方法接收者,值/指針接收者均可接收值/指針。當(dāng)需要改變內(nèi)容或結(jié)構(gòu)過大時需要使用指針接收者,值接收者是Golang特有的
// 指定方法接收者
func (node treeNode) print() {
fmt.Println(node.value)
}
func (node *treeNode) setValue(value int) {
node.value = value
}
func main() {
node := treeNode{}
node.setValue(100)
node.print()
pNode := &node
pNode.setValue(101)
pNode.print()
}
封裝
Golang的可見性(針對包)使用首字母來控制,首字母大寫表示public,首字母小寫表示private。每個目錄都是一個包,main包包含可執(zhí)行入口。為結(jié)構(gòu)定義的方法必須放在同一個包內(nèi),可以是不同的文件。Golang提供了兩種擴(kuò)展系統(tǒng)類型和已封裝類型的方法:
- 定義別名:通過type為已有類型定義一個別名,針對新的結(jié)構(gòu)體進(jìn)行擴(kuò)展。
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
- 使用組合:定義新的結(jié)構(gòu)體,其中一個成員是需要擴(kuò)展的類型的指針。
type EnhanceNode struct {
node *Node
}
func (enhanceNode *EnhanceNode) PostOrder() {
if nil == enhanceNode || nil == enhanceNode.node {
return
}
left := EnhanceNode{enhanceNode.node.Left}
right := EnhanceNode{enhanceNode.node.Right}
left.PostOrder()
right.PostOrder()
enhanceNode.PostOrder()
}
GOROOT、GOPATH、go get
GOROOT一般為下載的sdk路徑,GOPATH為用戶可定義的路徑,用戶源碼和通過go get命令下載的第三方庫在GOPATH的src目錄下。
gopm是go get的鏡像工具,通過 go get -g -v github.com/gpmgo/gopm 下載gopm。
// 下載
gopm get -g -v Golang.org/x/tools/cmd/goimports
// 更新
gopm get -g -v -u Golang.org/x/tools/cmd/goimports
// 安裝到GOPATH的bin目錄
go install Golang.org/x/tools/cmd/goimports
接口
接口的實(shí)現(xiàn)是隱式的,只要實(shí)現(xiàn)接口的方法。
duck typing
“像鴨子走路,像鴨子叫(長得像鴨子),那么就是鴨子”,意為描述事物的外部行為而非內(nèi)部結(jié)構(gòu)。嚴(yán)格說go屬于結(jié)構(gòu)化類型系統(tǒng),類似duck typing。
接口變量里有什么
- 接口自帶指針
- 接口變量同樣采用值傳遞,幾乎不需要使用接口的指針
- 指針接收者只能以指針的方式使用,值接收者都可以
- interface{}可以代表任何類型
- 定義接口
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}
- 實(shí)現(xiàn)接口
type Retriever struct {
UserAgent string
Timeout time.Duration
}
func (r *Retriever) Get(url string) string {
resp, err := http.Get(url)
if nil != err {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if nil != err {
panic(err)
}
return string(result)
}
- 調(diào)用接口
var r Retriever
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
Timeout: time.Minute,
}
fmt.Println(download(r))
// Type Assertion
if retriever, ok := r.(*real.Retriever); ok {
fmt.Printf("%T %v\n", retriever, retriever)
}
- 接口組合
type HttpExecute interface {
Retriever
Poster
}
函數(shù)式編程
- 函數(shù)是一等公民:參數(shù),變量,返回值都可以是函數(shù)。
- 高階函數(shù):函數(shù)的參數(shù)可以是函數(shù)
- 函數(shù)->閉包
func adder() func(int) int {
sum := 0
return func(i int) int {
sum += i
return sum
}
}
func main() {
// f中不僅是一個函數(shù),還有對變量sum的引用
f := adder()
for i := 0; i < 10; i++ {
fmt.Println(f(i))
}
}
錯誤處理和資源管理
defer
用于指定在函數(shù)結(jié)束之前執(zhí)行,可以用于關(guān)閉文件、釋放鎖等。
func tryDefer() {
// 在函數(shù)借結(jié)束之前執(zhí)行
defer fmt.Println("execute last...")
defer fmt.Println("execute before last...")
fmt.Println("execute")
}
panic
- 停止當(dāng)前函數(shù)執(zhí)行
- 一直向上返回,執(zhí)行每一層的defer
- 如果沒有遇見recover,程序退出
recover
- 僅在defer調(diào)用中使用
- 獲取panic的值
- 如果無法處理,重新panic
func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("error:", err)
} else {
panic(fmt.Sprintf("unknown error: %v", r))
}
}()
panic(errors.New("this is an error"))
}
測試
測試格式
- 測試文件與待測試代碼放在同一目錄下
- 測試文件名以
_test.go結(jié)尾 - 測試函數(shù)名為
TestXxx或Test_xxx的格式,使用其它類型測試,如性能測試則函數(shù)名為BenchmarkXxx或 ```Benchmark_xxx`` 的格式
傳統(tǒng)測試
- 傳統(tǒng)測試數(shù)據(jù)與邏輯混在一起
- 傳統(tǒng)測試的出錯信息不明確
- 傳統(tǒng)測試一旦出錯測試即全部結(jié)束
表格驅(qū)動測試
- 測試代碼
func calcTriangle(a, b int) int {
return int(math.Sqrt(float64(a*a + b*b)))
}
- 表格驅(qū)動測試
分離了測試數(shù)據(jù)和測試邏輯,明確了出錯信息,可以部分失敗。
func TestCalcTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{5, 12, 13},
{8, 15, 18},
}
for _, tt := range tests {
if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c)
}
}
}
性能測試
執(zhí)行b.N次,由Golang決定具體次數(shù),測試完成后在控制臺打印執(zhí)行次數(shù)和平均執(zhí)行時間。
func BenchmarkTriangle(b *testing.B) {
x, y, z := 8, 15, 17
for i := 0; i < b.N; i++ {
actual := calcTriangle(x, y)
if actual != z {
b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z)
}
}
}
測試命令行工具
# 測試命令
go test
# 代碼覆蓋率測試
go test -cover
# 生成代碼測試覆蓋率文件
go test -coverprofile c.out
# 代碼覆蓋率測試html
go tool cover -html=c.out
# 性能測試
go test -bench .
# 生成性能報(bào)告
go test -bench . -cpuprofile cpu.out
文檔
Golang提供 go doc 命令查看由注釋組成的文檔,通過 godoc -http :6060 命令在本地6060端口生成html文檔。在test文件中建立名為 ExampleXxx_xxx 的函數(shù),通過特定格式生成代碼實(shí)例文檔。
func ExampleQueue_Pop() {
queue := Queue{}
queue.Push(1)
fmt.Println(queue.Pop())
// Output:
// 1
}
go routine
協(xié)程Coroutine
- 輕量級“線程”
- 非搶占式多任務(wù)處理,由協(xié)程主動交出控制權(quán)
- 編譯器/解釋器/虛擬機(jī)層面的多任務(wù)
- 多個協(xié)程可能在一個或多個線程上運(yùn)行
goroutine定義
- 調(diào)用任何函數(shù)前只需加上
go關(guān)鍵字就可以交給調(diào)度器執(zhí)行 - 不需要在定義時區(qū)分是否為異步函數(shù)
- 調(diào)度器會在合適的點(diǎn)進(jìn)行切換(I/O、select、channel、等待鎖、函數(shù)調(diào)用等),或手動調(diào)用
runtime.Gosched()進(jìn)行切換 - 使用race來檢測數(shù)據(jù)訪問沖突,
go run -race xxx.go
channel
相對于通過共享內(nèi)存來通信,channel通過通信來共享內(nèi)存。
- chanel可以作為參數(shù)傳遞
- 可以設(shè)置channel的buffer
- 發(fā)送方負(fù)責(zé)關(guān)閉channel,接收方通過可以通過
range判斷channel是否傳遞完畢
使用select進(jìn)行調(diào)度
select可以用來監(jiān)聽IO操作,可以同時監(jiān)聽多個channel的消息狀態(tài)。