GO基礎

Go是一種併發的、帶垃圾回收的、快速編譯的語言。數組

 

一個例子:數據結構

 

複製代碼
//當前程序的包名
    package main
    
    //導入其它的包
    import (
        "flag"
        "fmt"
        "os"
    )
    
    //常量定義
    const PI = 3.14
    
    //全局變量的聲明與賦值
    var name = "go"
    
    //通常類型的聲明
    type intType int
    
    //結構聲明
    type newType struct {
    }
    
    //接口聲明
    type newInterface interface {
    }
    
    //main函數,程序的入口
    func main() {
        fmt.Println(os.Args)
        flag.Parse()
        fmt.Println(flag.Args())
    }
複製代碼

 

 

說明:閉包

一、每一個go源代碼文件的開頭都是一個package聲明,表示該go代碼所屬的包;包是go語言中最基本的分發單位,也是工程管理中依賴關係的體現。要生成go可執行程序,必須創建一個名爲main的package,而且在該package中包含一個叫main()的函數;併發

二、Go語言的main()函數不能帶參數,也不能定義返回值,傳入的命令行參數在os.Args變量中保存;app

三、在包聲明以後,是一系列的import語句,用於導入改程序所依賴的包,但不能導入在源代碼文件中沒有用到的包,不然Go編譯器會報錯;函數

四、Go語言中,語句不用以分號結束;oop

 

編譯、運行:ui

go run test.go

使用這個命令,會將編譯、連接和運行3個步驟合併爲一步,運行完成後在當前目錄下看不到任何中間文件和最終的可執行文件。spa

若是隻要生成編譯結果,而不自動運行,可使用:命令行

go build test.go

 

 


 

變量

變量聲明語句:

var 變量名 變量類型

 

例子:

複製代碼
var v1 int            
var v2 string           // 字符串
var v3 [10]int               // 數組
var v4 []int                  // 數組切片
var v5 struct {         // 接口
    f int
}  
var v6 *int                  // 指針
var v7 map[string]int        // map,key爲string類型,value爲int類型
var v8 func(a int) int     
複製代碼

 

可使用一個var關鍵字同時聲明多個變量:

var (
    v1 int
    v2 string    
)

 

若是在聲明變量的同時還進行了初始化,則var關鍵字能夠省略,而且Go編譯器能夠從初始化表達式的右值推導出該變量應該聲明爲哪一種類型,這有點相似於動態類型,但Go其實是強類型的語言(靜態類型語言)。

以下所示三種用法的效果是同樣的:

var v1 int = 10
v2 int = 10    // 編譯器自動推導出v2的類型
v3 := 10      // 編譯器自動推導出v3的類型

 

說明:

":="用於明確表達同時進行變量聲明和初始化工做,但要注意出如今:=左側的變量不該該是已經被聲明過的,不然會致使編譯器報錯,以下:

var i int
i := 2     //error

 

常量

在Go語言中,常量是指編譯期間就已知且不可改變的值。

一、字面常量

-12       // 整型常量
3.14      // 浮點型常量
3.2+12i    // 複數類型的常量
true      // 布爾型常量
"foo"     // 字符串常量

在C語言中,常量一般有類型,好比-12在C語言中會認爲是一個int類型,若是要指定它爲long類型,須要寫成-12L。

在Go語言中,字面常量沒有類型,只要這個常量在相應類型的值域範圍內,就能夠做爲該類型的常量,好比上面的常量-12,能夠賦值給int、uint、int3二、int6四、float3二、float6四、complex6四、complex128等類型的變量。

 

二、const常量

經過const常量,能夠給字面常量指定一個友好的名字:

複製代碼
const Pi float64 = 3.1415
const zero = 0.0    //無類型浮點常量
cosnt (
    size int64 = 1024
    eof = -1      //無類型整型常量   
)
const u,v float32 = 0, 3
const a,b,c = 3,4,"foo"
const mask = 1<<3
複製代碼

 

常量的賦值是一個編譯期行爲,因此右值不能出現任何須要運行期才能得出結果的表達式,例如:

const HOME = os.GetEnv("HOME")    // error

 

三、預約義常量

Go語言預約義常量有:true、false、iota,前兩個爲bool常量;

iota是一個可被編譯器修改的常量,在每個const關鍵字出現時被重置爲0,而後在下一個const出現以前,每出現一次iota,其所表明的數字會自動增1,例如:

const (          // rest iota
    c0 = iota   // c0=0 
    c1 = iota   // c1=1 
    c2 = iota   // c2=2 
)

 

四、枚舉

在const後跟一對園括號的方式定義一組常量,例如:

複製代碼
const (         
    Sunday = iota   
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    numberofDays        // 這個常量沒有導出
    )
複製代碼

同Go語言的其餘符號同樣,以大寫字母開頭的常量在包外可見;

以上例子中,numberofDays爲包內私有,其它符號則可被其它包訪問。

 

 

類型

Go語言中使用的類型包括:

基礎類型

布爾類型(bool)

var b1 bool = true

整型

var v1 int = 12

浮點類型(float3二、float64)

var f1 float32 = 12.0

複數類型(complex6四、complex128)

var c1 complex64 = 3.2 + 12i

字符串(string)

var s string = 「sina」

字符類型(rune)

表明單個的unicode字符

錯誤類型(error)

 

 

 

複合類型

指針(pointer)

 

數組(array)

var array [32] byte

切片(slice)

 var slice [] int

字典(map)

var word_count map[string] int

通道(chan)

var ch chan int

結構體(struct)

 var s struct {}

接口(interface)

 

 

一、布爾類型

布爾類型不能接受其它類型的賦值,不支持自動或強制的類型轉換,如下的示例是一些錯誤的用法:

var b bool
b = 1          // error
b = bool(1)    // error

 

如下的用法是正確的:

var b bool
b = (1!=0)

 

二、整型

類型

長度

值範圍

int8

1

-128 ~ 127

uint8(即byte)

1

0 ~ 255

int16

2

-32768 ~ 32767

uint16

2

0 ~ 65535

int32

4

-2147483648 ~ 2147483647

uint32

4

0 ~ 4294967295

int64

8

(-2^63) ~ (2^63-1)

uint64

8

0 ~ (2^64-1)

int

平臺相關

平臺相關

uint

平臺相關

平臺相關

uintptr

同指針

32位平臺下爲4字節,64位平臺下爲8字節

 

須要注意的是,int和int32是不一樣的類型, 不能相互賦值,例如:

var val2 int32
val1 := 64     // val1會被自動推導爲int類型
var2 = val1    // error
var2 = int32(val1)  // ok

 

此外,不一樣類型的整型數不能直接比較,好比int8類型的數和int類型的數不能直接比較,但各類類型的整型變量均可以直接與字面常量(literal)進行比較,好比:

複製代碼
var i int32
var j int64

i,j = 1,2 

if i==j {         // error
    fmt.Println("i and j are equal.") 
}

if i==1 || j==2 {     // ok
    fmt.Println("i and j are equal.") 
}   
複製代碼

 

 

三、浮點型

Go語言中的float32和float64分別等價於C語言的float、double類型;

var i float32 = 12.1 
j := 64.0      // 自動推導爲float64類型
j = i        // error
j = float64(i)  // ok  

 

判斷兩個浮點數是否相等,是根據不一樣精度來的:

import "math"

func IsEqual(f1, f2, p float64) bool {
    return math.Fdim(f1, f2) < p
}

其中,p是用戶自定義的比較精度,好比p=0.00001。

 

四、字符類型

在Go語言中支持兩個字符類型,一個是byte(其實是uint8的別名),表明UTF-8字符串的單個字節的值;另外一個是rune,表明單個Unicode字符。

關於rune相關的操做,可查閱Go標準庫的unicode包;另外unicode/utf8包也提供了UTF8和Unicode之間的轉換。

 

五、字符串

字符串支持下標讀取操做:

str := "Hello world"
ch := str[0]
fmt.Printf("The length of \"%s\" is %d\n", str, len(str))
fmt.Printf("The 1st character of \"%s\" is '%c'\n", str, ch)

但字符串的內容在初始化後不能被修改,例如:

str := "Hello world"
str[0] = 'X'    // error

 

經常使用的字符串操做:

操做

含義

s1 + s2

字符串鏈接

len(s)

字符串長度

s[i]

取字符

 

字符串遍歷有兩種方式:

複製代碼

str := "Hello,世界"

// 以字節數組的方式遍歷

for i := 0; i<len(str); i++ {
  ch := str[i]
  fmt.Println(i, ch)
}

 

// 以unicode字符方式遍歷,每一個字符的類型是rune

for i, ch := range str {
  fmt.Println(i, ch) 
}

複製代碼

 

 

六、數組

數組的聲明方法比較多,好比:

[32] byte                // 字節數組
[2*N] struct {x, y int 32}     // 結構體數組
[1000] *float64           // 指針數組
[3][5] int             // 二維數組
[2][2][2] float64   
[...]int{1,2,3}      // 省略數組長度,Go會根據元素個數來計算長度

 

在聲明數組時長度能夠爲一個常量或一個常量表達式,數組長度在定義之後就不能夠再改變。

數組支持按下標讀寫元素,也支持range關鍵字的遍歷,例如:

複製代碼
var array = [5] int {10,20,30,40,50}

for i, v := range array {
    array[i] = v*2;
}

for i, v := range array {
    fmt.Println(i, v) 
}
複製代碼

 

另外,數組是值類型,若是將數組做爲函數參數傳遞,則在函數調用的時候該參數將發生數據複製,所以,在函數體中沒法修改傳入的數組的內容。

 

七、slice

數組切片相似於C++中STL的std::vector<>,支持動態擴展數組,而且能夠被做爲函數參數傳遞而不會致使元素被複制。

數組切片的數據結構能夠抽象爲如下3個變量:

  • 一個指向原生數組的指針;
  • 數組切片中的元素個數;
  • 數組切片已分配的存儲空間;

其結構大體以下:

type slice struct {
    first *T
    len int
    cap int
}

 

建立數組切片有下面多種方式:

一、基於數組建立的方式

var myArray [10] int = [10] int {1,2,3,4,5,6,7,8,9,10}

var s1 = myArray[:]    // 基於myArray的全部元素建立數組切片
var s2 = myArray[:5]   // 基於myArray的前5個元素建立數組切片
var s3 = myArray[5:]   // 基於myArray從第5個元素開始的全部元素建立數組切片

 

二、直接建立數組切片的方式

s1 := make([] int,5)    // 建立一個初始元素個數爲5的數組切片,元素初始值爲0
s2 := make([] int,5, 10) // 建立一個初始元素個數爲5的數組切片,元素初始值爲0,並預留10個元素的存儲空間
s3 := []int{1,2,3,4,5}  // 建立並初始化包含5個指定元素的數組切片

 

三、基於數組切片建立的方式

oldSlice := []int{1,2,3,4,5}
newSlice := oldSlice[:3]

 

操做數組元素的全部方法都適用於數組切片,好比數組切片也能夠按下標讀寫元素,用len()獲取元素個數,並支持使用range關鍵字來快速遍歷全部元素。

數組切片支持可動態增減元素,內置的cap()和len()函數,分別返回數組切片分配的空間大小、當前存儲的元素個數。

s := make([] int,5, 10)
fmt.Println("len(s)=",len(s))   // 5
fmt.Println("cap(s)=",cap(s))   // 10

 

使用append函數能夠在數組切片尾端添加新元素:

s = append(s, 1,2,3)

若是追加的內容長度超過當前已分配的存儲空間(即cap()返回值),數組切片會自動分配一塊足夠大的內存。

還能夠將另外一個數組切片追加到一個數組切片末端:

s2 := []int{8,9,10}
s = append(s, s2...)  // s2後面的省略號必需要有

 

前面提到Slice內部是一個指針,因此修改Slice的元素,會影響其所指向的數組,例如:

複製代碼
var myArray [5] int = [5] int {1,2,3,4,5}
var s1 = myArray[:]
var s2 = s1
s1[3] = 90

fmt.Println(myArray)    // [1 2 3 90 5]
fmt.Println(s1)         // [1 2 3 90 5] 
fmt.Println(s2)         // [1 2 3 90 5]
複製代碼

 

數組切片的複製,若是兩個slice不同大,就會按其中較小的slice的元素個數進行復制,例如:

s1 := []int {1,2,3,4,5}
s2 := []int {5,4,7}
copy(s1, s2)  //只複製s2的3個元素到s1的前3個位置
copy(s2, s1)  //只複製s1的前3個元素到s2中

 

 

八、map

map是key-value結構的一個字典,相似於C++中STL的std::map<>。

例子:

複製代碼
type PersonInfo struct {
    ID string
    Name string
    Address string
}


func main() {
    var personDB map[string] PersonInfo      // 變量聲明
    personDB = make(map[string] PersonInfo)    // 變量建立

    personDB["1"] = PersonInfo{"12345","Tom","Room 203"}  // 增長了一個鍵

    person, ok := personDB["1"]                // 查找
    if ok {
        fmt.Println("found person", person.Name, "with ID 1")
    } else {
        fmt.Println("Did not find person with ID 1")
    }
  delete(personDB, "1")        // 刪除一個鍵 }
複製代碼

 

遍歷map的例子:

m1 := map[string]int{"one": 1, "two": 2, "three": 3}
fmt.Println(m1) 

for key, val := range m1{
    fmt.Printf("%s => %d \n", key, val)
} 

 

注意:range支持對 string, array, slice, map, buffered chan的遍歷;

 

九、結構體

定義一個struct:

type Rect struct {
    x, y float64
    width, height float64
}

 

初始化的幾種方式:

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width:100, height:200}

在Go語言中,未進行顯式初始化的變量都會被初始化爲該類型的零值,例如bool類型的零值爲false,int類型的零值爲0,string類型的零值爲空字符串。

 

 

十、指針

Go支持指針,例如:

 

複製代碼
var i int = 1
var pInt *int = &i

//輸出:i=1     pInt=0xf8400371b0       *pInt=1
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)

*pInt = 2
//輸出:i=2     pInt=0xf8400371b0       *pInt=2
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)

i = 3
//輸出:i=3     pInt=0xf8400371b0       *pInt=3
fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)
複製代碼

 

 


 

package

Go程序是經過package來組織的:
  • 一個可執行程序有且僅有一個main包;
  • 只有package名稱爲main的包能夠包含main函數;
  • 每一個package均可以定義本身的init()函數,每一個包被導入時,會執行其init()函數;
  • import導入包時,是在$GOPATH變量定義的路徑裏面搜索;
  • 經過import關鍵字來導入其它非main包,有兩種方式:
    • import "flag"
      import "fmt"
      import "os"
      import (
          "flag"
          "fmt"
          "os"
      )

       

  • 導入包以後,使用<PackageName>.<FuncName>來對包中的函數進行調用;
  • 導入的包能夠重命名,例如 import alias "PackageName",而後使用<alias>.<FuncName>來對包中的函數進行調用;
  • 將包名重命名爲 ".",能夠省略調用,例如 import . "PackageName",而後能夠直接使用<FuncName>來對包中的函數進行調用;
  • import _ "packageName" 操做不直接使用包裏的函數,而是調用了該包的init函數;
  • 使用大小寫來決定該常量、變量、類型、接口、結構或函數是否能夠被外部包所調用——函數名首字母小寫即爲private,函數名首字母大寫即爲public;
 

 

 

 


 

內存分配

 

Go具備兩個分配內存的機制,分別是內建函數new()和make()。
new 是一個分配內存的內建函數,new(T)爲一個類型爲T的新對象分配了值爲零的存儲空間並返回其地址,也就是一個類型爲*T的值。用Go的術語來講,就是它返回了一個指向新分配的類型爲T的零值的指針。

make(T, args)僅用於建立slice、map和chan,並返回類型T(不是*T)的一個被初始化了的(不是零)實例。這三種類型實質上是對在使用前必須進行初始化的數據結構的引用:

slice := make([] type, len, cap)
map := make(map[key_type] value_type)
chan := make(chan type, len)

 

 

如下示例說明了new和make的不一樣:

var p *[]int = new([]int)     // 爲切片結構分配內存,*p == nil
var v  []int = make([]int, 10) // 切片v如今是對一個新的有10個整數的數組的引用

fmt.Println(*p) // []
fmt.Println(v)  // [0 0 0 0 0 0 0 0 0 0]

 

 

 

 


 

流程控制

一、選擇語句

複製代碼
if 條件表達式 {
  ...  
} else if{
  ...  
} else {
 ...
}

複製代碼

注意:

  • 條件表達式不須要使用括號();
  • 條件語句體必須使用花括號,且"{"必須與 if 或者 else處於同一行;
  • 在if 和條件表達式之間,能夠添加變量初始化語句,使用";"間隔;
  • 在有返回值的函數中,不容許將最終的return語句包含在if...else...結構中;

 

switch語句:

複製代碼
switch i {        // 左花括號"{"必須與switch處於同一行
    case 0:
        fmt.Printf("0")    
    case 1:
        fmt.Printf("0")    
    case 2:
        fallthrough
    case 3:
        fmt.Printf("3")    
    case 4,5,6:
        fmt.Printf("multi")    
    default:
        fmt.Printf("default")
}    
複製代碼
  • i=0時,輸出0;
  • i=1時,輸出1;
  • i=2時,輸出3,fallthrough關鍵字表示繼續執行下一個case;
  • i=3時,輸出3;
  • i=4/5/6時,輸出"multi",單個case後面能夠出現多個結果項;
  • i=其它任意值時,輸出"default"。

注意:

  • Go語言不須要用break來退出一個case,只有在case中添加fallthrough關鍵字,纔會繼續執行緊跟的下一個case;
  • 條件表達式不限制爲常量或者整數,條件表達式甚至也能夠沒有,例如:
複製代碼
Num := 8
switch {
    case 0<= Num && Num <=3:
        fmt.Println("0-3")
    case 4<= Num && Num <=6:
        fmt.Println("4-6")
    case 7<= Num && Num <=9:
        fmt.Println("7-9")
}
複製代碼

當缺失條件表達式時,整個switch結構與多個if...else...的邏輯做用相同。

 

 

二、循環語句

Go語言的循環控制只支持for語句,不支持while結構。

for循環語句的循環表達式也不須要使用()括起來,例如:

sum := 0
for i:=0; i<10; i++ {
    sum += i 
}

 

精簡的for循環語句:

i := 1
for i<10 {
   fmt.Println(i)
   i++ 
}

 

 

for循環也支持continue和break語句,例如:

複製代碼
sum := 0
for {  // 死循環
    sum ++ 
    if sum>100 {
        break
    }
}
複製代碼

 

對於嵌套循環,break還能夠選擇中斷哪一個循環,例如:

複製代碼
var i int
var j int
JLoop:
for j=0; j<5; j++ {
    for i=0; i<10; i++ {
        if i>5 {
            break JLoop
        }
    }
}
複製代碼

 

 

三、跳轉語句

Go語言仍支持使用goto關鍵字在函數體內進行跳轉,例如:

複製代碼
func foo() {
    i := 0
    HERE:
    i++
    if i<10 {
        goto HERE 
    }
}
複製代碼

 

 

 

 


 

函數

函數定義

函數聲明語句:

func 函數名(參數列表) (返回值列表) {
    // 函數體
}

注意:

一、  參數列表和返回值列表都是變量名在前,變量類型在後;

二、  Go函數支持多返回值,但並非全部返回值都必須賦值,在函數返回時沒有被明確賦值的返回值都會被設置爲默認值。

三、  函數左起的花括號」{」不能另起一行,不然會報錯;

 

 以一個簡單的計算加法的函數爲例:

複製代碼
func add(a int, b int) (ret int, err error) {
    if a<0 || b<0 {  // 假設這個函數只支持兩個非負數的加法
        err = errors.New("Should be non-negative numbers!") 
        return
    }
    return a+b, nil
}
複製代碼

 

若是參數列表中若干個相鄰的參數類型相同,則能夠在參數列表中省略前面變量的類型聲明,例如:

func add(a, b int) (ret int, err error) {
  ...
}

 

若是返回值列表中多個返回值的類型相同,也能夠用一樣的方式合併;另外,若是函數只有一個返回值,能夠這樣寫:

func Add(a, b int) int {
  ...  
}

 

Go語言支持多重賦值,好比:

i, j = j, i

用於交換兩個變量的值,在不支持多重賦值的語言中,交換兩個變量的內容須要引入一個臨時變量:

t = i; i = j; j = t

 

Go函數帶回多個返回值時,可使用多重賦值語句,將不一樣的返回值賦值給不一樣的變量,而且容許使用匿名變量("_")接受不須要使用的返回值,例如:

func GetName() (firstName, lastName, nickName string) {
    return "May", "Chan", "Chibi Maruko"
}

_, _, nickName := GetName

 

函數調用

函數調用很是方便,只要事先導入該函數所在的包,就能夠調用了:

import "mymath"
c := mymath.Add(1,2)

注意:小寫字母開頭的函數只在本包內可見,大寫字母開頭的函數才能被其它包使用。

 

 

不定參數

例如:

複製代碼
func foo(args ...int) {  // 接受不定數量的參數,這些參數都是int類型
    for _, arg := range args {
        fmt.Println(arg) 
    }
}

foo(2,3,4)
foo(1,3,7,13)   
複製代碼

形如"...type"格式的類型只能做爲函數的參數類型存在,而且必須是最後一個參數。

"...type"本質上是一個數組切片,也就是[]type,這也是爲何上面的參數args能夠用for循環來得到每一個傳入的參數。

 

若是但願不定參數傳任意類型,能夠指定類型爲interface{},如標準庫中的fmt.Printf()的函數原型:

func Printf(format string, args ...interface{}) {
    ...
}

 

例如:

複製代碼
func foo(args ...interface{}) {
    for _, arg := range args {
        switch arg.(type) {
            case int:
                fmt.Println(arg, "is an int value.") 
            case string:
                fmt.Println(arg, "is a string value.") 
            case float32:
                fmt.Println(arg, "is a float32 value.") 
            default:
                fmt.Println(arg, "is an unknown type.") 
        }
    }
}
複製代碼

 

 

匿名函數與閉包

匿名函數能夠直接賦值給一個變量,例如:

f := func(x, y int) int {
    return x+y
}

 

或者直接執行一個匿名函數:

func(ch chan int) {
    ch <- ACK
} (reply_chan)    // 花括號後面直接跟參數列表表示函數調用

 

閉包:當一個函數內部嵌套另外一個函數定義時,內部的函數體能夠訪問外部函數的局部變量。

複製代碼
a := func() (func()) {
    var i int = 10 
    return func(){
        fmt.Printf("i=%d\n", i)
        i++
    }
}

c1 := a() 
c2 := a() 
c1()        // 10
c1()        // 11
c1()        // 12
c2()        // 10
複製代碼

c1和c2是創建在同一個函數上,但做用在同一個局部變量的不一樣實例上的兩個不一樣的閉包。

相關文章
相關標籤/搜索