1. 程式人生 > >GO語言語法入門

GO語言語法入門

ptr 足夠 提示 同時 $path state 兩個 work 虛擬

引言

Go


Go語言是谷歌2009發布的編程語言,它是一種並發的、帶垃圾回收的、快速編譯的語言。

它結合了解釋型語言的遊刃有余,動態類型語言的開發效率,以及靜態類型的安全性。它也打算成為現代的,支持網絡與多核計算的語言。要滿足這些目標,需要解決一些語言上的問題:一個富有表達能力但輕量級的類型系統,並發與垃圾回收機制,嚴格的依賴規範等等。這些無法通過庫或工具解決好,因此Go也就應運而生了。

技術分享

優勢

  1. 語法簡單,上手快;
  2. 性能高,編譯快,開發效率也不低;
  3. 豐富的標準庫;
  4. 原生支持並發,協程模型是非常優秀的服務端模型,同時也適合網絡調用;
  5. 部署方便,編譯包小,除 glibc 外沒有其他外部依賴;
  6. 自帶完善的工具鏈, 大大提高了團隊協作效率和一致性。 比如 gofmt ,gofix,,govet 等工具。

Docker等很多 Go 產品的流行,更證明了 Go 語言的優秀。

適用場景

- 服務器編程,如:處理日誌、數據打包、虛擬機處理、文件系統等;

- 分布式系統,數據庫代理器等;

- 內存數據庫,google開發的groupcache,couchbase的部分組建;

- 雲平臺,目前國外很多雲平臺在采用Go開發,如:CloudFoundy(VMware推出的業界第一個開源PaaS雲平臺)的部分組建。

缺點

1. Go的import包不支持版本,有時候升級容易導致項目不可運行,需要自己控制相應的版本信息;

2. Go的goroutine一旦啟動之後,不同的goroutine之間切換不是受程序控制,需要嚴謹的邏輯;

3. 沒什麽太多應用場景非要 Golang 才能做的

3.1 開發 web 沒有 php ruby 成熟、快速

3.2 開發 server 沒有 java 現成解決方案多


GO指南

環境搭建

安裝Golang的SDK


(1) http://www.golangtc.com/download
(2) 安裝完成之後,打開終端,輸入go、或者go version查看安裝版本

配置Go環境變量

配置Go環境變量GOPATH和GOBIN

  (1)打開終端,cd ~

  (2)查看是否有.bash_profile文件:

     ls -all

  (3)有則跳過此步,沒有則:

    1)創建:touch .bash_profile

    2)編輯:open -e .bash_profile

    3)自定義GOPATH和GOBIN位置:

GOPATH:日常開發的根目錄。GOBIN:是GOPATH下的bin目錄。

export GOPATH=/Users/yuan/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

  (4)編譯:source .bash_profile

  (5)*查看Go環境變量:go env


開發工具配置


sublime text


一定要先配置好Go環境變量GOPATH和GOBIN,再安裝此插件,要不插件會提示找不到GOPATH和GOBIN;

選用 sublime text 安裝 gosublime 插件進行開發( golang 語法高亮提示)

  (1)安裝 package controll(若已安裝,請跳過)
  使用Ctrl+`快捷鍵或者通過View->Show Console菜單打開命令行,粘貼如下代碼:

import urllib.request,os; pf = ‘Package Control.sublime-package‘; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), ‘wb‘).write(urllib.request.urlopen( ‘http://sublime.wbond.net/‘ + pf.replace(‘ ‘,‘%20‘)).read()) 2


  (2)install go sublime
Command+shift+p 輸入並選擇packageControl: install package
然後輸入並選擇goSublime
安裝完成就OK啦~~


Gogland


選擇Gogland, 下載安裝即可,3個月
https://www.jetbrains.com/go/download/

LiteIDE


國產IDE
http://golangtc.com/download/liteide


小試牛刀


在你的gopath目錄下,新建main.go文件即可以進行編碼了。

package main
import (
	"fmt"
)
func main() {
	fmt.Println("hello gopher~")
}



代碼編寫完成之後,使用command+b打開sublime text終端

(一)編譯+執行
使用go build main.go對其進行編譯,編譯通過的結果信息如下:

[ `go build main.go` | done: 420.495985ms ]
提示編譯成功之後,再執行shell命令,執行剛剛編譯之後的文件./main即可看到運行結果:

[ `./main` | done: 10.532868ms ]
hello go

(二)直接執行
如果僅僅是只需要看到運行的結果,而不產生可執行文件(文件名和項目名一樣)則在sublime text終端中直接使用go run xxx.go即可:

[ `go run main.go` | done: 314.476988ms ]
hello go


基礎

包、變量、函數

package main
import "fmt"

func main() {
	var hello string = "Hello"
	who := "gopher"
	var s = hello+"," + who
	fmt.Println(s)
}


1. 每個 Go 程序都是由包組成的。
2. 程序運行的入口是包 main 。
3. 按照慣例,包名與導入路徑的最後一個目錄一致。例如,"math/rand" 包由 package rand 語句開始。

變量

  • 變量聲明使用關鍵字var
  • 初始值存在時可省略類型聲明
  • 短賦值語句:= 可以用於替代 var 的隱式類型聲明(:=結構不能使用在函數外,函數外的每個語法塊都必須以關鍵字開始)

var name1 string //聲明變量
name1 = "tom" //給變量賦值
var name2 string = "tom" //聲明變量+賦值
var name3 = "tom" // 聲明時同時賦值,可以省略變量類型
name4 := "tom" //短賦值語句
// 多個變量
var x, y, z int
var c, python, java bool
var x, y, z int = 1, 2, 3
var c, python, java = true, false, "no!"
c, python, java := true, false, "no!"


函數

  • 可以返回任意數量的返回值
  • 類型聲明在變量名之後
  • 同一類型的多個參數,最後一個參數需聲明類型
func swap(x, y string) (string, string) {
	return y, x
}



  • 命名返回值的參數
func split(sum int) (x, y int) {
	x = sum * 4/9
	y = sum - x
	return
}



基本類型

bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的別名
rune // int32 的別名
// 代表一個Unicode碼位

float32 float64
complex64 complex128

常量

const Pi = 3.14
const World = "世界"
const Truth = true


運算符


http://www.yiibai.com/go/go_operators.html

註意:

  • 沒有++i,--i,只有i++、i--
  • 不能使用i++、i--對變量直接賦值


流程控制


for


Go 只有一種循環結構—— for 循環

for i := 0; i < 10; i++ {
	//do something
}
i := 0
for ; i < 1000; {
	//do something
}
for i < 1000 {
	//do something
} 
for {
	//死循環
}

if else

if x < 0 {
	return x
}
if v := 0; v < 5 {
	return v
}
return 9

switch


case 語句匹配後會自動終止(無需break),除非用 fallthrough 語句作為結尾,則會強制執行下一條case的語句或者default語句,而不判斷expression。

	x := 2
	switch x {
	case 1:
		fmt.Println(1)
	case 2:
		fmt.Println(2)
		fallthrough
	case 3:
		fmt.Println(x > 1)
	default:
		fmt.Println("default")
	}

// 結果
// 2
// true



defer


延遲(defer)處理
Defer用於確保在稍後的程序中,執行函數調用。
defer語句在封裝函數(main)結束時執行。

package main
import "fmt"
import "os"
func main() {
	f := createFile("defer-test.txt")
	defer closeFile(f)
	writeFile(f)
}
func createFile(p string) *os.File {
	fmt.Println("creating")
	f, err := os.Create(p)
	if err != nil {
		panic(err)
	}
	return f
}
func writeFile(f *os.File) {
	fmt.Println("writing")
	fmt.Fprintln(f, "data")
}
func closeFile(f *os.File) {
	fmt.Println("closing")
	f.Close()
}

// output
creating
writing
closing

復雜類型


struct


要定義結構,必須使用type和struct語句。struct語句定義了一個新的數據類型,在程序中有多個成員。type語句在例子中綁定一個類型為struct的名字。 struct語句的格式如下:

type person struct {
	name string
	age int
}


訪問結構體成員使 .

package main
import "fmt"
type person struct {
	name string
	age  int
}
func main() {
	fmt.Println(person{"Bob", 20})
	fmt.Println(person{name: "Alice", age: 30})
	fmt.Println(person{name: "Fred"})
	fmt.Println(&person{name: "Ann", age: 40})
	s := person{name: "Sean", age: 50}
	fmt.Println(s.name)
	sp := &s
	fmt.Println(sp.age)
	sp.age = 51
	fmt.Println(sp.age)
}


#output
{Bob 20}
{Alice 30}
{Fred 0}
&{Ann 40}
Sean
50
51

slice


因為切片(Slice)是數組上的抽象。 它實際上使用數組作為底層結構體.len()函數返回切片中存在的元素數量,其中cap()函數返回切片(Slice)的容量(大小),即可容納多少個元素。

package main
import "fmt"
func main() {
	var numbers = make([]int, 3, 5)
	printSlice(numbers)
}
func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}


//output
len=3 cap=5 slice=[0 0 0]

map

var map_variable map[key_data_type]value_data_type
map_variable = make(map[key_data_type]value_data_type)

map_variable := make(map[key_data_type]value_data_type)


delete(map_variable, key)

package main
import "fmt"
func main() {
	var countryCapitalMap map[string]string = make(map[string]string)
	/* create a map*/
	// countryCapitalMap = make(map[string]string)
	/* insert key-value pairs in the map*/
	countryCapitalMap["France"] = "Paris"
	countryCapitalMap["Italy"] = "Rome"
	countryCapitalMap["Japan"] = "Tokyo"
	countryCapitalMap["India"] = "New Delhi"
	/* print map using keys*/
	for country := range countryCapitalMap {
		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
	}
	/* test if entry is present in the map or not*/
	capital, ok := countryCapitalMap["United States"]
	/* if ok is true, entry is present otherwise entry is absent*/
	if ok {
		fmt.Println("Capital of United States is", capital)
	} else {
		fmt.Println("Capital of United States is not present")
	}
	/* delete an entry */
	delete(countryCapitalMap, "France")
	fmt.Println("Entry for France is deleted")
	fmt.Println("Updated map")
	/* print map */
	for country := range countryCapitalMap {
		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
	}
}

range

range函數可以用來遍歷array,slice和map。

當用於遍歷array和slice時,range函數返回索引和元素;

當用於遍歷map的時候,range函數返回key和value。

package main
import "fmt"
func main() {
	nums := []int{2, 3, 4}
	sum := 0
	for _, num := range nums {
		sum += num
	}
	fmt.Println("sum:", sum)
	for i, num := range nums {
		if num == 3 {
			fmt.Println("index:", i)
		}
	}
	kvs := map[string]string{"a": "apple", "b": "banana"}
	for k, v := range kvs {
		fmt.Printf("%s -> %s\n", k, v)
	}
	for k := range kvs {
		fmt.Println("key:", k)
	}
}


方法和接口

方法

Go中沒有類,但是可以為結構體定義方法,方法就是一類帶有特殊的接受者參數的函數。

方法接受者位於func關鍵字和方法名之間。
可以為非結構體類型聲明方法,但不能為其它包內定義的類型的接收者聲明方法,且不能為內建類型聲明方法。

package main
import "fmt"
func add(x int, y int) int {
	return x + y
}
func main() {
	fmt.Println(add(42, 13))
}

package main
import "fmt"
type rect struct {
	width, height int
}
func (r *rect) area() int {
	return r.width * r.height
}
func (r rect) perim() int {
	return 2*r.width + 2*r.height
}
func main() {
	r := rect{width: 10, height: 5}
	fmt.Println("area: ", r.area())
	fmt.Println("perim:", r.perim())
	rp := &r
	fmt.Println("area: ", rp.area())
	fmt.Println("perim:", rp.perim())
}



// output
  area:  50
  perim: 30
  area:  50
  perim: 30

函數是完全閉包的

package main
import "fmt"
// 函數 adder 返回一個閉包。每個閉包被綁定到自己的 sum 變量上
func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}
func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

接口

接口類型是由一組方法簽名的集合。
接口類型的值可以保存任何實現了接口方法的變量。
類型通過實現了一個接口的所有方法來實現這個接口,而不需要專門的顯示聲明也就是”implements”關鍵字來聲明。

package main

import "fmt"
import "math"

type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}

func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}

// output
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793


error


Go編程提供了一個非常簡單的錯誤處理框架,以及內置的錯誤接口類型,如下聲明:

type error interface {
	Error() string
}


Go函數通常返回錯誤作為最後一個返回值。 可使用errors.New來構造一個基本的錯誤消息

package main
import "errors"
import "fmt"
import "math"
func Sqrt(value float64) (float64, error) {
	if value < 0 {
		return 0, errors.New("Math: negative number passed to Sqrt")
	}
	return math.Sqrt(value), nil
}
func main() {
	result, err := Sqrt(-1)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}
	result, err = Sqrt(9)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println(result)
	}
}

並發

goroutine

goroutine 是由 Go 運行時環境管理的輕量級線程。
使用 go f(x, y, z) 開啟一個新的 goroutine 執行。

package main
import "fmt"
func f(from string) {
	for i := 0; i < 3; i++ {
		fmt.Println(from, ":", i)
	}
}
func main() {
	f("direct")
	go f("goroutine")
	go func(msg string) {
		fmt.Println(msg)
	}("going")
	var input string
	fmt.Scanln(&input)
	fmt.Println("done")
}


// output

$ go run goroutines.go
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
<enter>
done

channel

channel 是有類型的管道,可以用 channel 操作符 <- 對其發送或者接收值。

ch <- v // 將 v 送入 channel ch。
v := <-ch // 從 ch 接收,並且賦值給 v。

默認情況下,在另一端準備好之前,發送和接收都會阻塞。這使得 goroutine 可以在沒有明確的鎖或競態變量的情況下進行同步。

package main
import "fmt"
func main() {
	messages := make(chan string)
	go func() { messages <- "ping" }()
	msg := <-messages
	fmt.Println(msg)
}

channel 可以是帶緩沖的。為 make 提供第二個參數作為緩沖長度來初始化一個緩沖 channel:

ch := make(chan int, 100)
向緩沖 channel 發送數據的時候,只有在緩沖區滿的時候才會阻塞。當緩沖區清空的時候接受阻塞。

package main
import "fmt"
func main() {
	c := make(chan int, 2)
	c <- 1
	c <- 2
	fmt.Println(<-c)
	fmt.Println(<-c)
}

close

發送者可以 close 一個 channel 來表示再沒有值會被發送了。接收者可以通過賦值語句的第二參數來測試 channel 是否被關閉:當沒有值可以接收並且 channel 已經被關閉,那麽

v, ok := <-ch
ok 會被設置為 false。

註意: 只有發送者才能關閉 channel,而不是接收者。向一個已經關閉的 channel 發送數據會引起 panic。

package main
import "fmt"
func main() {
	jobs := make(chan int, 5)
	done := make(chan bool)
	go func() {
		for {
			j, more := <-jobs
			if more {
				fmt.Println("received job", j)
			} else {
				fmt.Println("received all jobs")
				done <- true
				return
			}
		}
	}()
	for j := 1; j <= 3; j++ {
		jobs <- j
		fmt.Println("sent job", j)
	}
	close(jobs)
	fmt.Println("sent all jobs")
	<-done
}

select


Go語言的選擇(select)可等待多個通道操作。將goroutine和channel與select結合是Go語言的一個強大功能。

select 會阻塞,直到條件分支中的某個可以繼續執行,這時就會執行那個條件分支。如果有多個都準備好的時候,會隨機選一個。

package main
import "time"
import "fmt"
func main() {
	c1 := make(chan string)
	c2 := make(chan string)
	c3 := make(chan string)
	t1 := time.Now().UnixNano()
	go func() {
		time.Sleep(time.Second * 1)
		c1 <- "one"
	}()
	go func() {
		time.Sleep(time.Second * 2)
		c2 <- "two"
	}()
	go func() {
		time.Sleep(time.Second * 2)
		c3 <- "three"
	}()
	for i := 0; i < 3; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("received", msg1)
		case msg2 := <-c2:
			fmt.Println("received", msg2)
		case msg3 := <-c3:
			fmt.Println("received", msg3)
		}
	}
	t2 := time.Now().UnixNano()
	dt := (t2 - t1) / 1e6
	fmt.Println(dt)
}

Goroutine 調度

結構——M, P, S Go的調度器內部有三個重要的結構:M,P,S 技術分享
M: 內核OS線程
G: 一個goroutine,它有自己的棧,instruction pointer和其他信息(正在等待的channel等等),用於調度。
P: 代表調度的上下文,可以把它看做一個局部的調度器,使go代碼在一個線程上跑,它是實現從N:1到N:M映射的關鍵。 P的數量可以通過GOMAXPROCS()來設置,它其實也就代表了真正的並發度,即有多少個goroutine可以同時運行。 技術分享 線程阻塞——投奔其他線程
  • 圖中看到,當一個OS線程M0陷入阻塞時,P轉而在OS線程M1上運行。調度器保證有足夠的線程來運行所以的context P。
  • 當MO返回時,它必須嘗試取得一個context P來運行goroutine,一般情況下,它會從其他的OS線程那裏steal偷一個context過來,
如果沒有偷到的話,它就把goroutine放在一個global runqueue裏,然後自己就去睡大覺了(放入線程池裏)。Contexts們也會周期性的檢查global runqueue。 技術分享

分配不均——steal work

  1. global runqueue

  2. 其他的P
技術分享 常見開發陷阱
  • 開大括號不能放在單獨的一行
  • 未使用的變量
  • 未使用的Imports
  • 不支持前置版本的自增和自減,也無法在表達式中使用這兩個操作符。

。。。

查看更多陷阱,請點擊↓
http://studygolang.com/articles/8382

GO語言語法入門