一、基礎
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]
數組的定義和使用。
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 }
- 保留字func 用於定義一個函數;
- 函數可以定義用於特定的類型,這類函數更加通俗的稱呼是method。這部分稱
作receiver 而它是可選的。它將在6 章使用; - funcname是你函數的名字;
- int 類型的變量q 是輸入參數。參數用pass-by-value 方式傳遞,意味着它們會被復
制; - 變量r 和s 是這個函數的named return parameters。在Go 的函數中可以返回多個
值。參閱”多個返回值” 在32。如果想要返回無命名的參數,只需要提供類型:(int,
int)。如果只有一個返回值,可以省略圓括號。如果函數是一個子過程,並且沒有任
何返回值,也可以省略這些內容; - 這是函數體,注意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這個名字:
- test.go中的import
- test.go中調用Add時的前綴
- utils1.go中的package 名字
- 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
- 在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非常慢
博文為作者原創,未經允許,禁止轉載。