Google Go Primer(一)

Go語言是什麼?

Google最近發佈新型的編程語言,Go。它被設計爲將現代編程語言的先進 性帶入到目前仍由C語言佔統治地位的系統層面。然而,這一語言仍在試驗階段並在不斷演變。html

Go語言的設計者計劃設計一門簡單、高效、安全和 併發的語言。這門語言簡單到甚至不須要有一個符號表來進行詞法分析。它能夠快速地編譯;整個工程的編譯時間在秒如下的狀況是常事。它具有垃圾回收功能,因 此從內存的角度是安全的。它進行靜態類型檢查,而且不容許強制類型轉換,於是對於類型而言是安全的。同時語言還內建了強大的併發實現機制。程序員

閱讀Go

Go的語法傳承了與C同樣的風格。程序由函數組成,而函數體是一系列的語句序列。一段代碼塊用花括號括起來。這門語言保留有限的關鍵字。表達式使用 一樣的中綴運算符。語法上並沒有 太多出奇之處。golang

Go語言的做者在設計這一語言時堅持一個單一的指導原則:簡單明瞭至上。一些新的語法構件提供了簡明地表達一些約定俗成的概 唸的方式,相較之下用C表達顯得冗長。而其餘方面則是針對幾十年的使用所呈現出來的一些不合理的語言選擇做出了改進。編程

變量聲明

變量是以下聲明的:數組

var sum int // 簡單聲明
var total int = 42 // 聲明並初始化

最值得注意的是,這些聲明裏的類型跟在變量名的後面。乍一看有點怪,但這更清晰明瞭。好比,如下面這個C片斷來講:安全

int* a, b;

它並明瞭,但這裏實際的意思是a是一個指針,但b不是。若是要將二者都聲明爲指針,必需要重複星號。而後在Go語言裏,經過以下方式能夠將二者都 聲明爲指針:閉包

var a, b *int

若是一個變量初始化了,編譯器一般能推斷它的類型,因此程序員沒必要顯式的敲出來:併發

var label = "name"

然而,在這種狀況下var幾乎顯得是多餘了。所以,Go的做者引入了一個新的運算符來 聲明和初始化一個新的變量:app

name := "Samuel"

條件語句

Go語言當中的條件句與C當中所熟知的if-else構造同樣,但條件不須要被打包在括號內。這樣能夠減小閱讀代碼時的視覺上的混亂。編程語言

括號並非惟一被移去的視覺干擾。在條件之間能夠包括一個簡單的語句,因此以下的代碼:

result := someFunc();
if result > 0 {
/* Do something */
} else {
/* Handle error */
}

能夠被精簡成:

if result := someFunc(); result > 0 { 
/* Do something */
} else {
/* Handle error */
}

然而,在後面這個例子當中,result只在條件塊內部有效??而前者 中,它在整個包含它的上下文中都是可存取的。

分支語句

分支語句一樣是似曾相識,但也有加強。像條件語句同樣,它容許一個簡單的語句位於分支的表達式以前。然而,他們相對於在C語言中的分支而言走得更遠。

首先,爲了讓分支跳轉更簡明,做了兩個修改。狀況能夠是逗號分隔的列表,而fall-throuth也再也不是默認的行爲。

所以,以下的C代碼:

int result;
switch (byte) {
case 'a':
case 'b':
{
result = 1
break
}

default:
result = 0
}

在Go裏就變成了這樣:

var result int
switch byte {
case 'a', 'b':
result = 1
default:
result = 0
}

第二點,Go的分支跳轉能夠匹配比整數和字符更多的內容,任何有效的表達式均可以做爲跳轉語句值。只要它與分支條件的類型是同樣的。

所以以下的C代碼:

int result = calculate();
if (result < 0) {
/* negative */
} else if (result > 0) {
/* positive */
} else {
/* zero */
}

在Go裏能夠這樣表達:

switch result := calculate(); true {
case result < 0:
/* negative */
case result > 0:
/* positive */
default:
/* zero */
}

這些都是公共的約定俗成,好比若是分支值省略了,就是默認爲真,因此上面的代碼能夠這樣寫:

switch result := calculate(); {
case result < 0:
/* negative */
case result > 0:
/* positive */
default:
/* zero */
}

循環

Go只有一個關鍵字用於引入循環。但它提供了除do-while外C語言當中全部可用的循環方式。

條件

for a > b { /* ... */ }

初始,條件和步進

for i := 0; i < 10; i++ { /* ... */ }

範圍

range語句右邊的表達式必須是arrayslicestring或者map, 或是指向array的指針,也能夠是channel

for i := range "hello" { /* ... */ }

無限循環

for { /* ever */ }

函數

聲明函數的語法與C不一樣。就像變量聲明同樣,類型是在它們所描述的術語以後聲明的。在C語言中:

int add(int a, b) { return a + b }

在Go裏面是這樣描述的:

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

多返回值

在C語言當中常見的作法是保留一個返回值來表示錯誤(好比,read()返回0),或 者保留返回值來通知狀態,並將傳遞存儲結果的內存地址的指針。這容易產生了不安全的編程實踐,所以在像Go語言這樣有良好管理的語言中是不可行的。

認識到這一問題的影響已超出了函數結果與錯誤通信的簡單需求的範疇,Go的做者們在語言中內建了函數返回多個值的能力。

做爲例子,這個函數將返回整數除法的兩個部分:

func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

有了多個返回值,有良好的代碼文檔會更好??而Go容許你給返回值命名,就像參數同樣。你能夠對這些返回的變量賦值,就像其它的變量同樣。因此咱們能夠重寫divide

func divide(a, b int) (quotient, remainder int) {
quotient = a / b
remainder = a % b
return
}

多返回值的出現促進了"comma-ok"的模式。有可能失敗的函數能夠返回第二個布爾結果來表示成功。做爲替代,也能夠返回一個錯誤對象,所以像下面這樣的代碼也就不見怪了:

if result, ok := moreMagic(); ok {
/* Do something with result */
}

匿名函數

有了垃圾收集器意味着爲許多不一樣的特性敞開了大門??其中就包括匿名函數。Go爲聲明匿名函數提供了簡單的語法。像許多動態語言同樣,這些函數在它們被定義的範圍內建立了詞法閉包。

考慮以下的程序:

func makeAdder(x int) (func(int) int) {
return func(y int) int { return x + y }
}

func main() {
add5 := makeAdder(5)
add36 := makeAdder(36)
fmt.Println("The answer:", add5(add36(1))) //=> The answer: 42
}

基本類型

像C語言同樣,Go提供了一系列的基本類型,常見的布爾,整數和浮點數類型都具有。它有一個Unicode的字符串類型和數組類型。同時該語言還引入了兩 種新的類型:slice map

數組和切片

Go語言當中的數組不是像C語言那樣動態的。它們的大小是類型的一部分,在編譯時就決定了。數組的索引仍是使用的熟悉的C語法(如 a[i]),而且與C同樣,索引是由0開始的。編譯器提供了內建的功能在編譯時求得一個數組的長度 (如len(a))。若是試圖超過數組界限寫入,會產生一個運行時錯誤。

Go還提供了切片(slices),做爲數組的變形。一個切片(slice)表示一個數組內的連續分段,支持程序員指定底層存儲的明確部分。構建一個切片 的語法與訪問一個數組元素相似:

/* Construct a slice on ary that starts at s and is len elements long */
s1 := ary[s:len]

/* Omit the length to create a slice to the end of ary */
s2 := ary[s:]

/* Slices behave just like arrays */
s[0] == ary[s] //=> true

// Changing the value in a slice changes it in the array
ary[s] = 1
s[0] = 42
ary[s] == 42 //=> true

該切片所引用的數組分段能夠經過將新的切片賦值給同一變量來更改:

/* Move the start of the slice forward by one, but do not move the end */
s2 = s2[1:]

/* Slices can only move forward */
s2 = s2[-1:] // this is a compile error

切片的長度能夠更改,只要不超出切片的容量。切片s的容量是數組從s[0]到數組尾端的大小,並由內建的cap()函數返回。一個切片的長度永遠不能超出它的容量。

這裏有一個展現長度和容量交互的例子:

a := [...]int{1,2,3,4,5} // The ... means "whatever length the initializer has"
len(a) //=> 5

/* Slice from the middle */
s := a[2:4] //=> [3 4]
len(s), cap(s) //=> 2, 3

/* Grow the slice */
s = s[0:3] //=> [3 4 5]
len(s), cap(s) //=> 3, 3

/* Cannot grow it past its capacity */
s = s[0:4] // this is a compile error

一般,一個切片就是一個程序所須要的所有了,在這種狀況下,程序員根本用不着一個數組,Go有兩種方式直接建立切片而不用引用底層存儲:

/* literal */
s1 := []int{1,2,3,4,5}

/* empty (all zero values) */
s2 := make([]int, 10) // cap(s2) == len(s2) == 10

Map類型

幾乎每一個如今流行的動態語言都有的數據類型,但在C中不具有的,就是dictionary。Go提供了一個基本的dictionary類型叫作map。下 面的例子展現瞭如何建立和使用Go map:

m := make(map[string] int) // A mapping of strings to ints

/* Store some values */
m["foo"] = 42
m["bar"] = 30

/* Read, and exit program with a runtime error if key is not present. */
x := m["foo"]

/* Read, with comma-ok check; ok will be false if key was not present. */
x, ok := m["bar"]

/* Check for presence of key, _ means "I don't care about this value." */
_, ok := m["baz"] // ok == false

/* Assign zero as a valid value */
m["foo"] = 0;
_, ok := m["foo"] // ok == true

/* Delete a key */
m["bar"] = 0, false
_, ok := m["bar"] // ok == false

面向對象

Go語言支持相似於C語言中使用的面向對象風格。數據被組織成structs,而後定義操做這些structs的函數。相似於Python,Go語言提供 了定義函數並調用它們的方式,所以語法並不會笨拙。

Struct類型

定義一個新的struct類型很簡單:

type Point struct {
x, y float64
}

如今這一類型的值能夠經過內建的函數new來分配,這將返回一個指針,指向一塊內存單元,其所佔內存槽初始化爲零。

var p *Point = new(Point)
p.x = 3
p.y = 4

這顯得很冗長,而Go語言的一個目標是儘量的簡明扼要。因此提供了一個同時分配和初始化struct的語法:

var p1 Point = Point{3,4}  // Value
var p2 *Point = &Point{3,4} // Pointer

方法

一旦聲明瞭類型,就能夠將該類型顯式的做爲第一個參數來聲明函數:

func (self Point) Length() float {
return math.Sqrt(self.x*self.x + self.y*self.y);
}

這些函數以後可做爲struct的方法而被調用:

p := Point{3,4}
d := p.Length() //=> 5

方法實際上既能夠聲明爲值也能夠聲明爲指針類型。Go將會適當的處理引用或解引用對象,因此既能夠對類型T,也能夠對類型*T聲明方式,併合理地使用它們。

讓咱們爲Point擴展一個變換器:

/* Note the receiver is *Point */
func (self *Point) Scale(factor float64) {
self.x = self.x * factor
self.y = self.y * factor
}

而後咱們能夠像這樣調用:

p.Scale(2);
d = p.Length() //=> 10

很重要的一點是理解傳遞給MoveToXYself和其它的參數同樣,而且是傳遞,而不是引用傳遞。若是它被聲明爲Point,那麼在方法內修改的struct就再也不跟調用方的同樣??值在它們傳遞給方法的時候被 拷貝,並在調用結束後被丟棄。

 

查看英文原文Google Go: A Primer 閱讀全文
類別: Golang  查看評論
相關文章
相關標籤/搜索