如果一個變量沒有明確地初始化,則必須指定其類型。在這種情況下,它將隱式地初始化為其類型的零值(0,nil 等)。在Go語言中沒有其它某些語言中的未初始化的變量。
type binOp func(int, int) int
var op binOp
add := func(i, j int) int { return i + j }
op = add
n = op(100, 200) // n = 100 + 200</pre>
多重賦值
Go 允許多重賦值。右邊的表達式先被求值然后賦給左邊的操作數。
i, j = j, i // 交換 i 和 j 的值(不用象傳統語言中需要第三個臨時變量).
函數可以有多個返回值,用參數括號后面的一個括號中的列表表示。返回值通過賦予一個變量列表來存儲,如:
func f() (i int, pj *int) { ... }
v1, v2 = f()
空標識符
空標識符用下劃線字符表示,它提供了一種方法來忽略由多值表達式返回的某個值,如:
v1, _ = f() // 忽略f()返回的第二個值.
分號和格式
不需要擔心分號和格式,你可以用 gofmt程序創建一個標準的Go樣式。雖然這個樣式最初看起來或許有點古怪,但它同任何其它樣式一樣的好,而且熟悉以后會感覺越來越舒服。
在實踐中Go代碼使用分號不多。嚴格說,所有的Go語句都是由分號終止。不過,Go在非空白行的末尾隱式插入一個分號,除非該行明顯未結束。這帶來的影響是,在某些情況下Go不允許換行符。例如,你不能這么寫
func g()
{ // INVALID; "{" should be on previous line.
}
g()后面會插入一個分號,使它成為一個函數聲明,而不是函數定義。類似的,你也不能寫
if n == 0 {
}
else { // INVALID; "else {" should be on previous line.
}
}后面else前面會插入一個分號,導致語法錯誤。
條件語句
Go 不使用括號來包裹 if 語句中的條件,和 for 語句中的表達式, 以及 switch 語句中的值。但另一方面,它必須用花括號來包裹 if 或 for 的執行語句體。
if a < b { f() }
if (a < b) { f() } // 不需要括號.
if (a < b) f() // 非法(執行體未用花括號括起來)
for i = 0; i < 10; i++ {}
for (i = 0; i < 10; i++) {} // 非法(表達式不需要括起來)
此外,if 和 switch 接受一個可選的初始化語句,通常用它建立一個局部變量,如: if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
For 語句
Go 不具有 while 語句,也沒有 do-while 語句。可以賦以for語句一個單一條件,使其與while語句等效。完全省略條件會制造一個無限循環。
for語句可能包含一個范圍條件,對strings, arrays, slices, maps, 或 channels進行迭代。除了這么寫
for i := 0; i < len(a); i++ { ... }
要循環遍歷a的元素,我們也可以這么寫
for i, v := range a { ... }
這會將i作為索引,給v賦以一個array, slice, 或 string中的連續元素。對于字符串,i是對單一字節的索引,v是一個符文類型的Unicode代碼點(符文是int32的一個別名)。對maps迭代會產生鍵值對,而對channels則只會產生一個迭代值。
Break和Continue
像Java一樣,Go允許使用break和continue來指定標簽(label),但是這個標簽所引用的必須是一個for、switch或者select語句。
Switch語句
在一個switch語句中,case標簽默認不會往下傳遞(fall through,也就是在沒有break的情況下也不會執行后續case的程序),但是你能夠通過使用一個向下傳遞(fallthrough)語句來使得它們可以向下傳遞。
switch n {
case 0: // empty case body
case 1:
f() // f is not called when n == 0.
}
但是一個case能夠有多個值:
switch n {
case 0, 1:
f() // f is called if n == 0 || n == 1.
}
case后面的值可以是支持相等比較(equality comparison)操作符的任何類型,比如string或者pointer。缺省的switch表達式等效于true表達式:
switch {
case n < 0:
f1()
case n == 0:
f2()
default:
f3()
}
++ 和 -- 語句
++ 和 --只能被用作語句中的后綴操作符,而不能用在表達式中。例如,你不能再這樣寫了: n = i++。
defer 語句
defer語句用來調用一個函數,但將其執行延遲到上一個附近的函數返回之后的時刻。被延遲的函數的執行與附近函數返回采取的路徑無關。然而,當defer語句執行時,被延遲函數的參數已經被計算并保存以供之后使用。
f, err := os.Open("filename")
defer f.Close() // 當上個函數返回后,f 會被關閉.
常量
在 Go 中常量可以是無類型的。這適用于數值常量,只使用無類型常量的表達式,以及沒有給出類型和初始化表達式是無類型的常量聲明。當一個無類型的常量被用于一個需要類型化的值的環境中,它的值會轉換成類型化的。所以即使 Go 沒有隱式的類型轉換,這也能允許常量被相對自由地使用。
var a uint
f(a + 1) // 無類型的數字常量 1 成為 uint 類型.
f(a + 1e3) // 1e3 也作為 uint 類型.
語言在無類型的數值常數的大小上不強加任何限制。 只有當一個常數被用在需要一個有類型的值時才會根據該類型對大小有所限制,如: const huge = 1 << 100
var n int = huge >> 98
如果在一個類型聲明中類型關鍵字缺失,而相關的表達式計算出來是一個非類型(untyped)的數字常量,這個常量就會被分別轉換成rune、int、float64、或者complex128類型,取決于這個值是否是一個字符(character)、整形(integer)、浮點型(float-point)還是復雜型(complex)的常量。
c := '?' // rune (alias for int32)
n := 1 + 2 // int
x := 2.7 // float64
z := 1 + 2i // complex128
Go沒有枚舉(enumerate)類型。取而代之,你可以在一個單獨的常量(const)聲明中使用特殊的名字iota來獲得一系列增長的值。當一個初始化表達簡化成一個常量時,它就會重用前面的表達式。
const (
red = iota // red == 0
blue // blue == 1
green // green == 2
)
結構
結構對應于 Java 中的類,但結構的成員不能是方法,只能是變量。結構指針類似 Java 中的引用變量。與 Java 的類不同,結構也可以被定義為直接值。對于結構和結構指針都可以使用“.”來訪問結構中的成員,如:
type MyStruct struct {
s string
n int64
}
var x MyStruct // x 被初始化為 MyStruct{"", 0}.
var px *MyStruct // 指針 px 初始化為 nil.
px = new(MyStruct) // px 指向新結構體 MyStruct{"", 0}.
x.s = "Foo"
px.s = "Bar"</pre>
在 Go 中,方法可以與任意命名類型有關,不只與結構; 參考方法和接口的討論.
指針
如果你有一個整數或一個結構或一個數組,賦值會復制對象的內容。Go 使用指針來實現 Java 的引用變量的效果。對于任意類型T,有一個相應的指針類型*T,表示指向類型T的值。
要為一個指針變量分配存儲空間,要使用內置函數 new,它接受一個類型并返回一個指向已分配存儲空間的指針。分配的空間將根據類型進行零初始化。 例如,new(int)為一個新int分配存儲空間,初始值為0,并返回它的地址,類型為*int。
在Java代碼 T p = new T()中,T是一個帶有兩個int類型實體變量a和b的類,對應于:
type T struct { a, b int }
var p *T = new(T)
或者更地道的:
p := new(T)
聲明語句 var v T 聲明了一個裝著類型T的值的變量,在Java中沒有相匹配的語句。值也可以使用一種復合語法來創建或者初始化,例如:
v := T{1, 2}
等效于:
var v T
v.a = 1
v.b = 2
對于類型T的一個操作數x,尋址操作符 &x 提供x的地址,它是類型*T的一個值。例如:
p := &T{1, 2} // p has type *T
對于指針類型的一個操作數x,指針指向(pointer indirection)通過x用*x表示所指向的值。指針指向是很少被用到的;而Go像Java一樣,能夠自動獲取到一個變量的地址:
p := new(T)
p.a = 1 // equivalent to (*p).a = 1
Slices
一個slice是一個含有3個域的結構體:一個指向數組的指針,一個長度,一個容量大小。Slices可以用[]來訪問數組元素。內置的len函數返回slice的長度,cap函數返回容量大小。
創建一個新的slice,可以用給定數組或slice a,通過a[i:j]的方式創建。新創建的slice是對a的引用,并且內容用是從a內容的索引的i到索引j。它的長度是j-i。如果i缺省,其slice開始于0,j缺省表示len(a)。新的slice是原來a的引用,如果改變了新slice里元素的值,a也會改變。新slice的容量是a的容量減去i。其數值的容量是原數值的長度。
var s[]int
var a[10]int
s=a[:]//s=a[0:len(a)]的簡寫
如果創建了一個數[100]byte(100bytes的數組,也許用作緩存區),并且想將它傳遞給一個函數,那么可以將函數的參數設置為[]byte類型,這樣就會傳遞一個slice。slice也可以通過make函數來創建(如下有描述)。
和Java中的ArrayList用法一樣,slice也內建append函數。
s0:=[]int{1,2}
s1:=append(s0,3)//添加一個單元素
s2:=append(s1,4,5)//添加多元素
s3:=append(s2,s0...)//添加slice
slice也能用于string,它將返回子字符串。 初始化
Map和channel的值必須用內建的函數make來申請值。例如,用
make(map[string]int)
將得到一個類型為map[string]int類型的值。于用new不同,make將返回對象值而不是地址。這樣就于map和channel為引用類型就保持一致了。 對于map,make函數提供一個隱含可選的第二個參數。對于channel,也有第二個可選參數,它是用來設置channel緩沖區的大小;默認是0(沒有緩沖區)。
make函數也能為slice來申請值。那樣將申請一個隱藏在slice里數組,而返回的是指向它的slice引用。此時用make需要一個slice元素個數的參數。第二個可選參數是slice的容量。
m:=make([]int,10,20)//于new([20]int)[:10]相同
方法和接口
方法
方法像是一個普通的函數定義,除非它有一個接收器(receiver)。接收器類似于Java實體方法中的this引用。
type MyType struct { i int }
func (p *MyType) Get() int {
return p.i
}
var pm = new(MyType)
var n = pm.Get()
這里定義了一個同MyType聯系起來的Get方法。名為p的接收器在函數體之中。
方法被定義在有命名的類型中。如果轉換成不同類型的值,新的值將會擁有新類型的方法,而不是原有類型的那些方法。
你也許會在一個內建的類型中定義方法,通過聲明一個繼承自它的新命名的類型。新的類型同內建的類型是不同的。
type MyInt int
func (p MyInt) Get() int {
return int(p) // The conversion is required.
}
func f(i int) {}
var v MyInt
v = v * v // The operators of the underlying type still apply.
f(int(v)) // int(v) has no defined methods.
f(v) // INVALID
接口
Go的接口同Java的接口類似,但任何提供了用一個Go接口命名的方法的類型,都可以被看做是對那個接口的實現。不需要額外的聲明了。
給定下面這個接口:
type MyInterface interface {
Get() int
Set(i int)
}
由于MyType已經有了一個Get方法,我們能夠讓Mytype滿足這個接口的要求,通過添加: func (p *MyType) Set(i int) {
p.i = i
}
現在任何使用MyInterface作為參數的函數,將可以接受 *MyType類型的變量: func GetAndSet(x MyInterface) {}
func f1() {
var p MyType
GetAndSet(&p)
}
在Java中,為*MyType類型定義Set和Get,就會讓*MyType自動實現了MyInterface。一個類型可以滿足多個接口。這是一種鴨式類型(duck typing)的形式。 當我看到一只鳥兒走起路來像鴨子,游起來也像鴨子,呱呱的叫起來也像鴨子,我就會把這只鳥兒稱作鴨子.
James Whitcomb Riley 匿名域
匿名域和Java中的子類類似.
type MySubType struct {
MyType
j int
}
func (p *MySubType) Get() int {
p.j++
return p.MyType.Get()
}
MySubType是作為MyType的子類。 func f2() {
var p MySubType
GetAndSet(&p)
}
Set方法繼承于MyType,在閉合類型中的匿名域的方法可以提升為閉合類型的方法。也就是說,MyType做為MySubType的匿名域,MyType中的方法MySubType都能用。Get方法是覆蓋方法,Set方法是繼承來的。
匿名域和也不是和Java中的子類完全一樣。當調用匿名域的方法,方法的接收者是匿名域而不是閉合類型。也就是說,匿名域的方法不會動態分發。如果想實現Java中的動態方法looup,就等用interface。
func f3() {
var v MyInterface
v = new(MyType)
v.Get() // Call the Get method for *MyType.
v = new(MySubType)
v.Get() // Call the Get method for *MySubType.
}
類型斷言
用類型斷言可以將變量從一個接口類型轉變為不同的接口類型。這是在運行時動態實現的。與Java不同,不需要對兩個接口之間的關系作任何聲明,如:
type Printer interface {
Print()
}
func f4(x MyInterface) {
x.(Printer).Print() // 類型斷言為 Printer
}
轉換到Printer完全是動態的。 只要x的動態類型(存儲在x中的值的實際類型)定義了一個Print方法,它就會工作。
泛型 Go 沒有泛型類型,但通過結合匿名字段和類型斷言可以實現類似于Java的參數化的類型,如:
type StringStack struct {
Stack
}
func (s *StringStack) Push(n string) { s.Stack.Push(n) }
func (s *StringStack) Pop() string { return s.Stack.Pop().(string) }
StringStack限定Hello stack例子中的泛型Stack,所以它只操作字符串元素——就像Java中的Stack 。注意,Sizemethod繼承于Stack。
錯誤
Java經常使用異常,Go則有兩種機制。大多數程序只有真正不能回收的情況下返回錯誤,比如超出范圍的索引,產生一個運行時異常。
Go的多值返回使得返回一個詳細的錯誤消息和正常的返回值十分容易。按照慣例,這些消息有類型錯誤,一個簡單的內置接口。
type error interface {
Error() string
}
舉個例子,如果打開文件失敗,os.Open函數返回一個非空錯誤值。 func Open(name string) (file *File, err error)
下面的代碼使用os.Open來打開一個文件。如果遇到錯誤就會調用log.Fatal來打印錯誤消息并終止。 f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
錯誤接口只需要一個Error方法,但是特定的錯誤實現往往會有附加的方法,允許調用者檢查詳細的錯誤。
Panic和recover
一個panic是一個運行時刻錯誤,并釋放Go程序的堆棧,同時運行defer程序,最終程序停止。Panic和Java中的異常相似,但它僅僅表明是運行時錯誤,比如空指針或是數組越界。Go用內建錯誤類型來描述如訪問文件結尾等上述錯誤信息。
內建函數recover可用在panic并內恢復Go程序運行,同時recover能停止循環返回參數傳遞給panic。因為在defer函數里只能運行循環代碼,同時revcover只能運行defer函數中。如果Go程序沒有panic,recover將返回nil。 Go協程和信道
壽司制作流程
Go協程
Go中的線程,使用go聲明,執行一個goroutine.并且在不同的,新創建的goroutine中運行該函數.在一個程序中所有的Go協程,共用相同的地址空間.
Go協程是輕量級的,消耗成本只比分配的棧空間多一點, 棧開始時較小并通過堆存儲的分配和釋放來實現其增長。內部的Go協程像協程一樣并存在操作系統的多個線程中。你不必去拘泥于這些細節。
go list.Sort() // Run list.Sort in parallel; don’t wait for it.
Go 擁有函數字面量,可以表現為閉包函數,與go 聲明一起使用的話,功能將更為強大. // 發布后打印文本到標準輸出,直到給定的時間過期。
func Publish(text string, delay time.Duration) {
go func() {
time.Sleep(delay)
fmt.Println(text)
}() // 注意括號,我們必須調用該函數.
}
變量text和delay在外部函數和閉包(函數字面量)之間共享,只存在于在它們可訪問期間。
Channels
channel 通過傳遞特定元素類型的值,提供了一套兩個 goroutines 同步執行及交流的機制。 <- 操作符制定 channel 發送或接收的方向。如果沒有明確方向,則通道為雙向的。
chan Sushi // can be used to send and receive values of type Sushi
chan<- float64 // can only be used to send float64s
<-chan int // can only be used to receive ints
Channel 為引用類型,使用 make 分配。
ic := make(chan int) // unbuffered channel of ints
wc := make(chan *Work, 10) // buffered channel of pointers to Work
要向通道傳遞值,以二進制操作符的方式使用 <- 。要接收數據,則以一元運算符的方式使用它。
ic <- 3 // Send 3 on the channel.
work := <-wc // Receive a pointer to Work from the channel.
如若 channel 無緩沖區,則發送者堵塞,直到接收者接受到傳值。如果 channel 有緩沖區,發送者堵塞,直到接收者開始讀取緩沖區;如果緩沖區滿了,則需要等到某些接收者開始檢索值。接收者堵塞,直到有數據可接收。
close 函數將記錄通道不能再被用來發送數據。當調用 close 函數后,在所有先前發送的值都被接收以后,接收操作將不會堵塞,同時返回 0 值。一個多值的接收操作將能夠獲取到 channel 是否被關閉的指示。
ch := make(chan string)
go func() {
ch <- "Hello!"
close(ch)
}()
fmt.Println(<-ch) // Print "Hello!".
fmt.Println(<-ch) // Print the zero value "" without blocking.
fmt.Println(<-ch) // Once again print "".
v, ok := <-ch // v is "", ok is false.
在下面的例子,我們將使 Publish 函數返回一個通道,它將被用來在文本發表完成后廣播消息
// Publish prints text to stdout after the given time has expired.
// It closes the wait channel when the text has been published.
func Publish(text string, delay time.Duration) (wait <-chan struct{}) {
ch := make(chan struct{})
go func() {
time.Sleep(delay)
fmt.Println(text)
close(ch)
}()
return ch
}
下面就是 Publish 函數的大概用法
wait := Publish("important news", 2 * time.Minute)
// Do some more work.
<-wait // blocks until the text has been published
Select語句
select語句是Go統一工具箱中的最終工具。它選擇哪些通信將被處理。如果任何的通信都能處理,那么就會隨機選其一,與之對應的語句就會執行。另外,如果沒有默認的case,語句就會阻塞直到其中一個通信完成為止。
這里有一個toy的例子,展示了select語句如何用來實現一個隨機數發生器。
rand := make(chan int)
for { // Send random sequence of bits to rand.
select {
case rand <- 0: // note: no statement
case rand <- 1:
}
}
更加現實的是,這里有一個select語句可以用來設置時間限制一個接受操作。 select {
case news := <-AFP:
fmt.Println(news)
case <-time.After(time.Minute):
fmt.Println("Time out: no news in one minute.")
}
time.After函數式標準庫的一部分;它等待一段特定時間后發送當前時間到返回的頻道。
并發(示例)
最后我們通過一個小而全的例子展示如何將若干塊拼湊在一起。它是一個服務器通過一個頻道(channel)接受Work請求(Work request)的草案代碼。每一個請求都使用一個單獨的渠道進行處理。Work將自身構造包含進一個用來返回結果的頻道中。
package server
import "log"
// New creates a new server that accepts Work requests
// through the req channel.
func New() (req chan<- *Work) {
wc := make(chan *Work)
go serve(wc)
return wc
}
type Work struct {
Op func(int, int) int
A, B int
Reply chan int // Server sends result on this channel.
}
func serve(wc <-chan *Work) {
for w := range wc {
go safelyDo(w)
}
}
func safelyDo(w *Work) {
// Regain control of panicking goroutine to avoid
// killing the other executing goroutines.
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(w)
}
func do(w *Work) {
w.Reply <- w.Op(w.A, w.B)
}
而下面這是你如何使用它: package server_test
import (
server "."
"fmt"
"time"
)
func main() {
s := server.New()
divideByZero := &server.Work{
Op: func(a, b int) int { return a / b },
A: 100,
B: 0,
Reply: make(chan int),
}
s <- divideByZero
select {
case res := <-divideByZero.Reply:
fmt.Println(res)
case <-time.After(time.Second):
fmt.Println("No result in one second.")
}
// Output: No result in one second.
}
并發是一個龐大的話題,而Go的方法和Java的方法時相當不同的。有兩篇文章涉及到了這些基礎: 并發編程基礎(Fundamentals of concurrent programming )使用Go編寫的小例子來介紹并發。
通過交流共享內存(Share Memory by Communicating )使用更大幅度的例子進行代碼走讀(codewalk)。
Stefan Nilsson
本文由用戶
jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
sesese色