Golang學習筆記


一、基礎

1. Hello World程序

demo:

package main
import "fmt" // 注釋
//注釋
func main() {
    fmt.Printf("Hello World\n")
}

執行:
go run demo.go

編譯成可執行文件
go build demo.go

2. 聲明和賦值

func main() {
    var a int
    var b string = "aaaa"
    var (
        c int
        d bool
    )
    conse i = 10
    e := 15
    a = 1
    b = "hello"
    c = 2
    d = false
    f,g:=12,13
    if (a + c + e < 100 && d) {
        fmt.Printf("Hello World\n")
    } else {
        fmt.Printf(b)
    }

}
  • 變量的類型在變量名后面,所以不能同時聲明和賦值
  • 在2.4后,支持a:=1這種類型,類似於動態類型的聲明了,這時會自動識別變量的類型
  • 可以在var里面聲明多個變量
  • 聲明了的變量一定要用,不然編譯會錯誤
  • const定義常量,類似var,而已可以定義多個

字符串轉換

func main() {
    var s string ="aaaaaa"
    sb:=[]byte(s)
    s1:=string(sb)
    fmt.Printf("%c\n",sb[0])
    fmt.Printf("%s\n",s1)
    fmt.Printf("%s\n",s)
}

直接訪問字符串的下標是不可以的,需要先轉換為byte類型,通過string函數轉換回來。

其他操作符

Precedence Operator(s)
Highest :

* / % << >> & &^
+ - | ^
== != < <= > >=
<-
&&

Lowest

||

3. 控制結構

3.1. if

func main() {
    if a:=1;a<0{
        fmt.Printf("a is less 0")
    }else{
        fmt.Printf("a is bigger 0")
    }
}
  • and &&
  • or ||
  • un !=
  • ==
  • <= >=

2. for

for有三種形式

  • for init;condition;post {} 類似C的for
  • for condition {} 類似C的while
  • for {} 類似C的for(;😉 死循環

golang中沒有do和while

func main() {
    for i := 0; i < 10; i++ {
        fmt.Printf("i:%d\n", i)
    }
    j := 0
    for j < 10 {
        fmt.Printf("j:%d\n", j)
        j++
    }
    k := 0
    for {
        fmt.Printf("k:%d\n", k)
        k++
        if k > 9 {
            break
        }
    }
}

rang可以方便地遍歷對象

l := []string{"a", "b", "c"}
for k, v := range l {
    fmt.Printf("pos:%d,value:%s\n", k, v)
}

switch

func main() {
    i := 0
    switch i {
    case -1, 0:
        fmt.Printf("is -1 or 0")
    case 1:
        fmt.Printf("is 1")
    default:
        fmt.Printf("default")
    }
}
  • case中逗號表示或

4. 預定義的函數

Table 2.3. Go 中的預定義函數
close new panic complex
closed make recover real
len append print imag
cap copy println
  • len 和cap 可用於不同的類型,len 用於返回字符串、slice 和數組的長度。參閱”array、slices和map” 小節了解更多關於slice、數組和函數cap 的詳細信息。
  • new 用於各種類型的內存分配。參閱”用new 分配內存” 在59 頁。
  • make 用於內建類型(map、slice 和channel)的內存分配。參閱”用make 分配內存” 在59
    頁。
  • copy 用於復制slice。append 用於追加slice。參閱本章的”slice”。
  • panic 和recover 用於異常處理機制。參閱”恐慌(Panic)和恢復(Recover)” 在37 頁了
    解更多信息。
  • print 和println 是底層打印函數,可以在不引入fmt 包的情況下使用。它們主要用於調
    試。
  • complex、real和imag 全部用於處理復數。有了之前給的簡單的例子,不用再進一步討論
    復數了。

5. array,slice和map

array就是Python的數組
map就是Python的字典

5.1 array

array使用[n] 定義,其中n是數組的大小,type是元素的類型。n是可選的。
數組的定義和使用。

l1:=[]string{"a","b"}
var l2 [2]int
l2[0]=1
l2[1]=2
var l3 [2][3]int
l3[0][0]=1
print(l1[0])
print(l2[0])
print(l3[0][0])

當傳遞一個array給函數的時候,函數得到的是一個array的副本,即傳值。

5.2 slice(切片)

slice和array類似,不同的是slice是array的一個指針,所以修改slice,是會影響array的,而且傳遞一個slice給函數的時候,傳遞的是指針,所以是傳址。

l1:=[]string{"a","b","c","d"}
s1:=l1[1:2]
s2:=l1[:] //類似l1[0:4]
s3:=l1[:2] //類似l1[0:2]
print(s1[0])
print(s2[0])
print(s3[0])

append用戶向切片中添加元素,返回新的切片,新的切片的內存地址可能和之前的不一樣。

l1:=[]string{"a","b","c","d"}
s2:=append(l1,"e","f")
print(s2[4])
print(s2[5])

5.3 map

map的定義:map[<from type>]<to type>

var map1 = make(map[string]int)
map2:=map[string]int{
    "k1":11,"k2":12,
}
print(map2)
map1["k1"] = 12
v, ok := map1["k1"] //12 true
print(v, ok,"\n")
v1, ok1 := map1["k2"] //0 false
print(v1, ok1,"\n")
//map1["k2"] = 0, false //刪除,不知道為什么測試失敗

遍歷:

map2:=map[string]int{
    "k1":11,"k2":12,
}
for k,v:=range map2{
    print(k,v,"\n")
}

二、函數

func (p mytype) funcname(q int) (r,s int) { return 0,0 }
  1. 保留字func 用於定義一個函數;
  2. 函數可以定義用於特定的類型,這類函數更加通俗的稱呼是method。這部分稱
    作receiver 而它是可選的。它將在6 章使用;
  3. funcname是你函數的名字;
  4. int 類型的變量q 是輸入參數。參數用pass-by-value 方式傳遞,意味着它們會被復
    制;
  5. 變量r 和s 是這個函數的named return parameters。在Go 的函數中可以返回多個
    值。參閱”多個返回值” 在32。如果想要返回無命名的參數,只需要提供類型:(int,
    int)。如果只有一個返回值,可以省略圓括號。如果函數是一個子過程,並且沒有任
    何返回值,也可以省略這些內容;
  6. 這是函數體,注意return 是一個語句,所以包裹參數的括號是可選的。

DEMO:

func add(a int,b int) (int,int,int){
    return a+b,a,b
}
func main() {
    a,b,c:=add(12,13)
    print(a,b,c)

}

函數的參數都是傳值的形式。

1. 命名返回的參數

func add(a int,b int) (int){
    sum:=a+b
    return sum
}
func add(a int,b int) (sum int){
    sum=a+b
    return
}

在定義函數的返回類型的時候,加上類型對應的變量名,然后在函數體中,return后面不帶參數,這樣go就會找到函數體中的變量sum,然后返回。注意,由於定義函數的時候已經定義了sum變量,所以后面修改的時候不需要加冒號。

2. 定義函數退出前執行的函數

例如在打開文件的時候,每一次返回都需要關閉文件描述符,這樣會有大量代碼重復,在go中,可以定義函數退出前執行的函數。

func test(a int) (sum int){
    defer print("test done")
    if a<0{
        return -1
    }else{
        return 1
    }
}

這樣無論a是大於還是小於0,都會輸出文字。

3.可變參數

類似於Python的*args

func test(args ...int) (int) {
    for i, v := range args {
        print(i, v, "\n")
    }
    return 1
}
func main() {
    test(1, 2, 3, 4, 5)
}

4. 快速定義函數

類似於Python的lambda

add_one := func(i int) int {
    return 1 + i
}
print(add_one(2))

5.函數作為參數

func test(i int, fun func(int) int) (int) {
    i++
    return fun(i)
}
func main() {
    add_one := func(i int) int {
        return 1 + i
    }
    print(test(2,add_one))
}

最后的值是4,

6.恐慌和恢復

go中沒有異常的處理,只有恐慌和恢復

func thrownPanic(fun func() int) (b bool) {
    defer func() {
        if r := recover(); r != nil {
            b = true
        }
    }()
    fun()
    return
}
func main() {
    add_one := func() int {
        a := []int{1, 2, 3}
        print(a[0])
        return 1
    }
    print(thrownPanic(add_one))
}

在thrownPanic中,會調用fun,然后在函數結束前執行defer的函數,如果fun中產生了異常,r會為非nil,這樣返回true,否則返回false
這樣外層的函數就能知道調用fun是否產生了異常。

    "runtime/debug"
    "reflect"
    "fmt"
)
func test_func(){
    defer func(){
        if err:=recover();err!=nil{
            fmt.Println("Panic ",err,reflect.TypeOf(err))
            debug.PrintStack()
        }
    }()
    list:=[]int{1}
    println(list[1])
}
func main(){
    test_func()

程序在執行println(list[1])的時候,會產生恐慌,也就是異常,但是程序不會立刻退出,還會執行defer的函數,這時,通過revocer函數,可以catch住這個異常,然后把異常信息打印出來,這樣程序可以繼續正常運行,其實跟try exept差不多。

三、包

包說明

目錄結構

/
    test.go
    /util
        util1.go
        util2.go

util1.go:

package util

func add(a int, b int) int {
    //私有函數,只能在包內被調用
    return a + b
}
func Add(a int, b int) int {
    //公有函數,可以在其他包中調用
    return a + b
}

test.go

package main
import "./util" // 注釋
//注釋
func main() {
    print(util.Add(12,13))
}

有多個地方用到util這個名字:

  1. test.go中的import
  2. test.go中調用Add時的前綴
  3. utils1.go中的package 名字
  4. utils1.go的文件名

其中1,2,3需要一樣,4可以不一樣

在包中,變量或者函數名,根據首字母是否大寫來判斷該變量或函數是否公有的
在一個包中,也就是文件夾,不同的文件中的變量或函數名不能重復。

別名

import u "./util" // 注釋
//注釋
func main() {
    print(u.Add(12,13))
}

導入util並設置別名為u

package main
import . "./util" // 注釋
//注釋
func main() {
    print(Add(12,13))
}

別名設置為點,就不需要名字了

導入路徑

上面的導入方法是相對路徑導入,即在util前面加上./
還有絕對路徑的導入

 import   "shorturl/model"       //加載GOROOT/src/shorturl/model模塊

包的文檔

每個包都應該包含文檔,如果包中有多個文件,文檔可以在任意一個。格式:

/*
The regexp package implements a simple library for
regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation '|' concatenation
*/
package regexp

單元測試

在util目錄下面創建文件util_test.go:

package util
import "testing"
func TestAdd(t *testing.T){
    if Add(12,13)!=24 {
        t.Log("test Add 12 13 fail")
        t.Fail()
    }
}

然后cd到util目錄,執行go test,這樣go就會調用所有*_test.go文件里面的Test*函數。在函數里面,如果測試失敗,就調用t.Fail()

常用的包

標准的Go 代碼庫中包含了大量的包,並且在安裝Go 的時候多數會伴隨一起安裝。瀏
覽$GOROOT/src/pkg 目錄並且查看那些包會非常有啟發。無法對每個包就加以解說,不過下
面的這些值得討論:b

  • fmt
    包fmt 實現了格式化的I/O 函數,這與C 的printf 和scanf 類似。格式化短語派生於C
    。一些短語(%-序列)這樣使用:
    %v
    默認格式的值。當打印結構時,加號(%+v)會增加字段名;
    %#v
    Go 樣式的值表達;
    %T
    帶有類型的Go 樣式的值表達;
  • io
    這個包提供了原始的I/O 操作界面。它主要的任務是對os 包這樣的原始的I/O 進行封
    裝,增加一些其他相關,使其具有抽象功能用在公共的接口上。
  • bufio
    這個包實現了緩沖的I/O。它封裝於io.Reader 和io.Writer 對象,創建了另一個對象
    (Reader 和Writer)在提供緩沖的同時實現了一些文本I/O 的功能。
  • sort
    sort 包提供了對數組和用戶定義集合的原始的排序功能。
    b描述來自包的godoc。額外的解釋用斜體。
    54 Chapter 4: 包
  • strconv
    strconv 包提供了將字符串轉換成基本數據類型,或者從基本數據類型轉換為字符串
    的功能。
  • os
    os 包提供了與平台無關的操作系統功能接口。其設計是Unix 形式的。
  • sync
    sync 包提供了基本的同步原語,例如互斥鎖。
  • flag
    flag 包實現了命令行解析。參閱”命令行參數” 在第92 頁。
  • json
    json 包實現了編碼與解碼RFC 4627 [22] 定義的JSON 對象。
  • template
    數據驅動的模板,用於生成文本輸出,例如HTML。
    將模板關聯到某個數據結構上進行解析。模板內容指向數據結構的元素(通常結構的
    字段或者map 的鍵)控制解析並且決定某個值會被顯示。模板掃描結構以便解析,而
    “游標”@ 決定了當前位置在結構中的值。
  • http
    http 實現了HTTP 請求、響應和URL 的解析,並且提供了可擴展的HTTP 服務和基本
    的HTTP 客戶端。
  • unsafe
    unsafe 包包含了Go 程序中數據類型上所有不安全的操作。通常無須使用這個。
  • reflect
    reflect 包實現了運行時反射,允許程序通過抽象類型操作對象。通常用於處理靜態類
    型interface{} 的值,並且通過Typeof 解析出其動態類型信息,通常會返回一個有接
    口類型Type 的對象。包含一個指向類型的指針,StructType、IntType 等等,描述
    了底層類型的詳細信息。可以用於類型轉換或者類型賦值。參閱6,第”自省和反射”
    節。
  • exec
    exec 包執行外部命令。

四、進階

1. 指針

go中也有指針,但是和C有區別,不能進行指針運算。

var p *int; //p=nil
//*p = 8; //這樣會報錯,因為p還沒有分配內存
var i int;
p = &i; //令p的值等於i的內存值
*p = 8; //相當於修改i的值
print(p, &i, i)//看到,p和*i是一樣的,i=8
  • 在類型的前面加星號,表示定義一個該類型的指針,定義之后,沒有為指針分配內存,指針為nil

2. 內存分配

go中有兩種方法可以分配內存:new和make

2.1 new

new是聲明一個變量,返回變量的指針。

var p1 *int; //p=nil
p2 := new(int)
var p3 int
print(p1,"\n")
print(*p2,"\n")
print(p3,"\n")

p1是一個指針,但是它還沒有初始化
p2也是一個指針,它已經初始化了,初始化值為0
p3是一個變量,已經初始化,初始化值為0

2.2 make

make只能聲明slice,map和channel。返回值,而不是指針。
也可以使用new來聲明指針,然后使用make來初始化:

p2 := new([]int)
//(*p2)[0]=1 //這樣是不行的,因為p2還沒有初始化
*p2=make([]int,11)
(*p2)[0]=1
print((*p2)[0])

make([]int,11)聲明了一個長度為11的切片slice

2.3 結構定義

go的結構和C的結構類似,然后使用new來定義

type Person struct {
    name string
    age int
}
p1:=new(Person)
p1.name="kevin"
p1.age=23
println(p1.name)
println(p1.age)

定義結構的方法:

type Person struct {
    name string
    age  int
}

func (p *Person) SetAge(v int) int {
    p.age = v
    return v
}
func main() {
    p1 := new(Person)
    p1.SetAge(12)
    println(p1.age)

}

五、接口

六、並發

1. goroutine

go中一個很重要的概念是goroutine,協程的英文是coroutine,第一個字母不同,即goroutine類似於協程,但是又有所不同,是go特殊的概念。
goroutine的特點:

  • goroutine 並行執行的,有着相同地址空間的函數。
  • 輕量的
  • 初始化的代價很低

一個並發的DEMO:

package main

import "time"
//注釋

func f(name string) {
    for i := 0; i < 10; i++ {
        println(name, i)
        time.Sleep(1 * 1e9)
    }
}
func main() {
    go f("f 1")
    go f("f 2")
    time.Sleep(15 * 1e9)
}

類似於線程,然后啟動的方法也比較方便,只需要在前面加一個go的關鍵字。
1e9是一個內部的常量,是秒的意思

2. channel

goroutine之間通過channel來通訊,channel類似於隊列,即Python中的Queue。
定義channel的時候,需要指定channel接受的類型,可以為int,string,和interface等。

var c chan int;  //定義一個接受int類型的channel
c = make(chan int)
c<-1//向c中put一個對象
item:=<- c //從c中取一個對象

所以上面的並發程序可以改為:

func f(name string) {
    for i := 0; i < 10; i++ {
        println(name, add(i))
        time.Sleep(1 * 1e9)
    }
    c <- 1//向c中put一個對象
}
func main() {
    c = make(chan int)
    go f("f 1")
    go f("f 2")
    <-c
    <-c
}

在goroutine中,可以put,也可以get。

2.1 緩沖

上面的channel是無緩沖的,也就是put完之后,goroutine就會阻塞,直到有goroutine取走。
定義有緩沖的channel:

ch:=make(chan int,1)

1就是緩沖的數量

獲取的時候,也是阻塞的,可以使用非阻塞的方法:

v,ok:=<-c

如果有值,ok為true,否則為false

3.並發問題

盡管是叫並發,但是同一時刻,只有一個goroutine在執行,也就是占用CPU,類似Python的線程和協程。
可以通過runtime.GOMAXPROCS(n)來設置同一個時刻運行的goroutine的數量,也可以修改環境變量GOMAXPROCS。

七、通訊

1. 文件

讀取文件

import "os"

func main() {
    buf := make([]byte, 1024)
    f ,_:= os.Open("./test.data")
    defer f.Close()
    for {
        n, _ := f.Read(buf)
        if n == 0 {
            break
        }
        os.stdout.write(buf[0:n]) //必須使用write,如果使用println,會輸出切片的內存地址
    }
}

通過bufio讀取文件

import "os"
import "bufio"
//注釋

func main() {
    buf := make([]byte, 1024)
    f, _ := os.Open("/etc/passwd")
    defer f.Close()
    r := bufio.NewReader(f)
    w := bufio.NewWriter(os.Stdout)
    defer w.Flush()
    for {
        n, _ := r.Read(buf)
        if n == 0 { break }
        w.Write(buf[0:n])
    }
}

創建目錄

if _, e := os.Stat("name"); e != nil {
    os.Mkdir("name", 0755)
} else {
    // error
}

2. 命令行參數

import "os"
import "flag"
import "fmt"
//注釋

func main() {
    dnssec := flag.Bool("dnssec", false, "Request DNSSEC records")
    port := flag.String("port", "53", "Set the query port")
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] [name ...]\n", os.Args[0])
        flag.PrintDefaults()
    }
    flag.Parse()
    println(*dnssec,*port)
}

3. 網絡

八、性能測試

go的特點就是高性能和高並發

測試用例:
從1加到1000,執行一百萬次,計算需要的時間。

使用linux的time命令來進行計時。

1. go

sum.go

package main
func sum(num int)int{
    sum:=0
    for i:=1;i<=num;i++{
        sum+=i
    }
    return sum
}
func main(){
    sum_:=0
    for j:=0;j<1000000;j++{
        sum_+=sum(1000)
    }
    println(sum_)
    println(sum(1000))

}

編譯:

go build sum.go 

執行

time ./sum

結果:

real    0m0.464s
user    0m0.460s
sys     0m0.001s

2. C

sum.c

#include <stdio.h>
long int sum(int num){
    long int sum=0;
    int i =0;
    for( i=1;i<=num;i++){
        sum=sum+i;
    };
    return sum;
}
int main(){
    int i ;
    long int sum_=0;
    for (i=0;i<1000000;i++){
        sum_+=sum(1000);
    }
    printf("%ld\n",sum(1000));
    printf("%ld\n",sum_);
//    printf(sum_);
    return 0;

};

編譯:

 gcc sum.c  -fPIC -shared -o sum.so

執行

time ./sumc 

結果:

real    0m2.874s
user    0m2.856s
sys     0m0.000s

3. Python

test_sum.py

def sum(num):
    s = 0
    for i in xrange(1,num+1):
        s += i
    return s

def main():
    sum_=0
    for i in range(1000000):
        sum_+=sum(1000)
    print sum_

if __name__ == '__main__':
    main()

執行

time python test_sum.py

結果

real    0m35.146s
user    0m34.814s
sys     0m0.125s
  1. 在Python中調用C
    test_sum_c.py
from ctypes import cdll
c_lib = cdll.LoadLibrary('./sum.so')


if __name__ == '__main__':
    c_lib.main()

執行

time python test_sum_c.py

結果

real    0m2.899s
user    0m2.874s
sys     0m0.006s

5. 比較

語言|go|C|Python|Python調用c
---|
用時:|0.464s|2.874s|35.146s|2.899s

  • go竟然比c還快,而且快很多
  • Python非常慢

博文為作者原創,未經允許,禁止轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM