Go語言主要用做服務器端開發,其定位是用來開發「大型軟件」的,適合於不少程序員一塊兒開發大型軟件,而且開發週期長,支持雲計算的網絡服務。Go語言可以讓程序員快速開發,而且在軟件不斷的增加過程當中,它能讓程序員更容易地進行維護和修改。它融合了傳統編譯型語言的高效性和腳本語言的易用性和富於表達性。前端
Go語言做爲服務器編程語言,很適合處理日誌、數據打包、虛擬機處理、文件系統、分佈式系統、數據庫代理等;網絡編程方面,Go語言普遍應用於Web應用、API應用、下載應用等;除此以外,Go語言還可用於內存數據庫和雲平臺領域,目前國外不少雲平臺都是採用Go開發。java
Go語言成功案例python
Nsq:Nsq 是由Go語言開發的高性能、高可用消息隊列系統,性能很是高,天天能處理數十億條的消息;linux
Docker:基於lxc的一個虛擬打包工具,可以實現PAAS平臺的組建。程序員
Packer:用來生成不一樣平臺的鏡像文件,例如VM、vbox、AWS等,做者是vagrant的做者golang
Skynet:分佈式調度框架redis
Doozer:分佈式同步工具,相似ZooKeeper數據庫
Heka:mazila開源的日誌處理系統編程
Cbfs:couchbase開源的分佈式文件系統vim
Tsuru:開源的PAAS平臺,和SAE實現的功能如出一轍
Groupcache:memcahe做者寫的用於Google下載系統的緩存系統
God:相似redis的緩存系統,可是支持分佈式和擴展性
Gor:網絡流量抓包和重放工具
Go語言做爲一門大型項目開發語言,在不少大公司相繼使用,甚至徹底轉向Go開發,其中表明有Google、Facebook、騰訊、百度、阿里巴巴、京東、小米以及360、美團、滴滴以及新浪等,所以,Go語言的開發前景仍是很不錯的!
在學習Go語言編程以前,咱們須要安裝和配置好Go語言的開發環境。能夠選擇線上的編譯器:http://tour.golang.org/welcome/1 來直接執行代碼。也能夠在您本身的計算機上安裝開發編譯環境。
Go本地環境設置
若是您願意在本地環境安裝和配置Go編程語言,則須要在計算機上提供如下兩個軟件:
文本編輯器
這是用於編寫您的程序代碼。常見的幾個編輯器包括Windows記事本,OS編輯命令,Brief,Epsilon,EMACS和vim(或vi)。
文本編輯器的名稱和版本可能因不一樣的操做系統而異。例如,記事本只能在Windows上使用,vim(或vi)能夠在Windows以及Linux或UNIX上使用。
使用編輯器建立的文件稱爲源文件,源文件中包含程序的源代碼。Go程序的源文件一般使用擴展名「.go」來命名。
在開始編程以前,確保您安裝好並熟練使用一個文本編輯器,而且有足夠的經驗來編寫計算機程序代碼,將代碼保存在文件中,編譯並最終執行它。
Go編譯器
在源文件中編寫的源代碼是人類可讀的源程序。 它須要「編譯」變成機器語言,以便CPU能夠根據給出的指令實際執行程序。
這個Go編程語言編譯器用於將源代碼編譯成可執行程序。這裏假設您知道或瞭解編程語言編譯器的基本知識。
Go發行版本是FreeBSD(版本8及更高版本),Linux,Mac OS X(Snow Leopard及更高版本)和具備32位(386)和64位(amd64)x86處理器架構的Windows操做系統的二進制安裝版本 。
如下部分將演示如何在各類操做系統上安裝Go語言環境的二進制分發包。
下載Go存檔文件
從連接【Go下載】中下載最新版本的Go可安裝的歸檔文件。在寫本教程的時候,選擇的是go1.7.4.windows-amd64.msi並將下載到桌面上。
注:寫本教程的時,使用的電腦是:Windows 10 64bit 系統
若是操做系統不同,可選擇對應版本下載安裝。
操做系統 |
存檔名稱 |
Windows |
go1.7.windows-amd64.msi |
Linux |
go1.7.linux-amd64.tar.gz |
Mac |
go1.7.4.darwin-amd64.pkg |
FreeBSD |
go1.7.freebsd-amd64.tar.gz |
在UNIX/Linux/Mac OS X和FreeBSD上安裝
將下載歸檔文件解壓縮到/usr/local目錄中,在/usr/local/go目錄建立一個Go樹。 例如:
tar -C /usr/local -xzf go1.7.4.linux-amd64.tar.gz
Bash
將/usr/local/go/bin添加到PATH環境變量。
操做系統 |
輸出 |
Linux |
export PATH=$PATH:/usr/local/go/bin |
Mac |
export PATH=$PATH:/usr/local/go/bin |
FreeBSD |
export PATH=$PATH:/usr/local/go/bin |
在Windows上安裝
使用MSI文件並按照提示安裝Go工具。 默認狀況下,安裝程序使用C:\Go目錄。安裝程序應該在窗口的PATH環境變量中設置C:\Go\bin目錄。從新啓動後,打開的命令提示驗證更改是否生效。
驗證安裝結果
在F:\worksp\golang中建立一個test.go的go文件。編寫並保存如下代碼到 test.go 文件中。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
如今運行test.go查看結果並驗證輸出結果以下:
F:\worksp\golang>go run test.go
Hello, World!
包的使用
每一個 Go 程序都是由包組成的。
程序運行的入口是包 main 。
這個程序使用並導入了包 「fmt「 和 「math/rand「 。
按照慣例,包名與導入路徑的最後一個目錄一致。例如,」math/rand「 包由 package rand 語句開始。
注意:這個程序的運行環境是肯定性的,所以 rand.Intn 每次都會返回相同的數字。
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
導入包
這個代碼用圓括號組合了導入,這是「打包」導入語句。
一樣能夠編寫多個導入語句,例如:
import "fmt"
import "math"
不過使用打包的導入語句是更好的形式。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Printf("Now you have %g problems.", math.Sqrt(7))
}
導出名稱
在 Go 中,首字母大寫的名稱是被導出的。
在導入包以後,只能訪問包所導出的名字,任何未導出的名字是不能被包外的代碼訪問的。
Foo 和 FOO 都是被導出的名稱。名稱 foo 是不會被導出的。
執行代碼,注意編譯器報的錯誤。
而後將 math.pi 更名爲 math.Pi 再試着執行一下。
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.pi)
}
函數
函數能夠沒有參數或接受多個參數。
在這個例子中, add 接受兩個 int 類型的參數。
注意類型在變量名以後 。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
當兩個或多個連續的函數命名參數是同一類型,則除了最後一個類型以外,其餘均可以省略。
在這個例子中 ,
x int, y int
可縮寫爲:
x, y int
函數多值返回
函數能夠返回任意數量的返回值。
swap 函數返回了兩個字符串。
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
函數中命名返回值
Go 的返回值能夠被命名,而且就像在函數體開頭聲明的變量那樣使用。
返回值的名稱應當具備必定的意義,能夠做爲文檔使用。
沒有參數的 return 語句返回各個返回變量的當前值。這種用法被稱做「裸」返回。
直接返回語句僅應當用在像下面這樣的短函數中。在長的函數中它們會影響代碼的可讀性。
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
變量
var 語句定義了一個變量的列表;跟函數的參數列表同樣,類型在後面。就像在這個例子中看到的同樣, var 語句能夠定義在包或函數級別。
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
初始化變量
變量定義能夠包含初始值,每一個變量對應一個。若是初始化是使用表達式,則能夠省略類型;變量從初始值中得到類型。
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
短聲明變量
在函數中, := 簡潔賦值語句在明確類型的地方,能夠用於替代 var 定義。
函數外的每一個語句都必須以關鍵字開始( var 、 func 、等等), :=結構不能使用在函數外。
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
基本數據類型
Go 的基本類型有:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的別名
rune // int32 的別名
// 表明一個Unicode碼
float32 float64
complex64 complex128
這個例子演示了具備不一樣類型的變量。 同時與導入語句同樣,變量的定義「打包」在一個語法塊中。
int,uint 和 uintptr 類型在32位的系統上通常是32位,而在64位系統上是64位。當你須要使用一個整數類型時,應該首選 int,僅當有特別的理由才使用定長整數類型或者無符號整數類型。
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
const f = "%T(%v)\n"
fmt.Printf(f, ToBe, ToBe)
fmt.Printf(f, MaxInt, MaxInt)
fmt.Printf(f, z, z)
}
零值
變量在定義時沒有明確的初始化時會賦值爲 零值 。
零值是:
數值類型爲 0 ,
布爾類型爲 false ,
字符串爲 「」 (空字符串)。
package main
import "fmt"
func main() {
var i int
var f float64
var b bool
var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
類型轉換
表達式T(v) 將值 v 轉換爲類型 T 。
一些關於數值的轉換:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加簡單的形式:
i := 42
f := float64(i)
u := uint(f)
與 C 不一樣的是 Go 的在不一樣類型之間的項目賦值時須要顯式轉換。 試着移除例子中 float64 或 int 的轉換看看會發生什麼。
package main
import (
"fmt"
"math"
)
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z uint = uint(f)
fmt.Println(x, y, z)
}
類型推導
類型推導
在定義一個變量卻並不顯式指定其類型時(使用 := 語法或者 var =表達式語法), 變量的類型由(等號)右側的值推導得出。
當右值定義了類型時,新變量的類型與其相同:
var i int
j := i // j 也是一個 int
可是當右邊包含了未指名類型的數字常量時,新的變量就多是 int 、 float64 或 complex128 。 這取決於常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
嘗試修改演示代碼中 v 的初始值,並觀察這是如何影響其類型的。
package main
import "fmt"
func main() {
v := 42 // change me!
fmt.Printf("v is of type %T\n", v)
}
常量
常量的定義與變量相似,只不過使用 const 關鍵字。
常量能夠是字符、字符串、布爾或數字類型的值。
常量不能使用 := 語法定義。
package main
import "fmt"
const Pi = 3.14
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
數值常量
數值常量是高精度的值 。
一個未指定類型的常量由上下文來決定其類型。
也嘗試一下輸出 needInt(Big) 吧。
(int 能夠存放最大64位的整數,根據平臺不一樣有時會更少。)
package main
import "fmt"
const (
Big = 1 << 100
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
控制流
for
Go 只有一種循環結構 —— for 循環。
基本的 for 循環包含三個由分號分開的組成部分:
初始化語句:在第一次循環執行前被執行
循環條件表達式:每輪迭代開始前被求值
後置語句:每輪迭代後被執行
初始化語句通常是一個短變量聲明,這裏聲明的變量僅在整個 for 循環語句可見。
若是條件表達式的值變爲 false,那麼迭代將終止。
注意:不像 C,Java,或者 Javascript 等其餘語言,for 語句的三個組成部分 並不須要用括號括起來,但循環體必須用{ }括起來。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
循環初始化語句和後置語句都是可選的,以下示例代碼所示 -
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
for 是 Go 的 「while」
基於此能夠省略分號:C 的 while 在 Go 中叫作 for 。
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
死循環
若是省略了循環條件,循環就不會結束,所以能夠用更簡潔地形式表達死循環。
package main
func main() {
for {// 無退出條件,變成死循環
}
}
if
就像 for 循環同樣,Go 的 if 語句也不要求用( ) 將條件括起來,同時,{ }仍是必須有的。
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
if 的便捷語句
跟 for 語句同樣, if 語句能夠在條件以前執行一個簡單語句。
由這個語句定義的變量的做用域僅在 if 範圍以內。
(在最後的 return 語句處使用 v 看看。)
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
if 和 else
在 if 的便捷語句定義的變量一樣能夠在任何對應的 else 塊中使用。
(提示:兩個 pow 調用都在 main 調用 fmt.Println 前執行完畢了。)
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 這裏開始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
switch語句
你可能已經知道 switch 語句會長什麼樣了。
除非以 fallthrough 語句結束,不然分支會自動終止。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}
switch 的執行順序
switch 的條件從上到下的執行,當匹配成功的時候中止。
(例如,
switch i {
case 0:
case f():
}
當 i==0 時不會調用 f 。)
注意:Go playground 中的時間老是從 2009-11-10 23:00:00 UTC 開始, 如何校驗這個值做爲一個練習留給讀者完成。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
沒有條件的 switch
沒有條件的 switch 同 switch true 同樣。
這一構造使得能夠用更清晰的形式來編寫長的 if-then-else 鏈。
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
defer語句
defer 語句會延遲函數的執行直到上層函數返回。
延遲調用的參數會馬上生成,可是在上層函數返回前函數都不會被調用。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
defer 棧
延遲的函數調用被壓入一個棧中。當函數返回時, 會按照後進先出的順序調用被延遲的函數調用。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
指針
Go 具備指針。 指針保存了變量的內存地址。
類型 *T 是指向類型 T的值的指針。其零值是 nil 。
var p *int
& 符號會生成一個指向其做用對象的指針。
i := 42
p = &i
*符號表示指針指向的底層的值。
fmt.Println(*p) // 經過指針 p 讀取 i
*p = 21 // 經過指針 p 設置 i
這也就是一般所說的「間接引用」或「非直接引用」。
與 C 不一樣,Go 沒有指針運算。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
結構體
一個結構體( struct )就是一個字段的集合。(而 type 的含義跟其字面意思相符。)
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
結構體字段
結構體字段使用點號來訪問。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
結構體指針
結構體字段能夠經過結構體指針來訪問。
經過指針間接的訪問是透明的。
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
結構體符文
結構體符文表示經過結構體字段的值做爲列表來新分配一個結構體。
使用 Name: 語法能夠僅列出部分字段。(字段名的順序無關。)
特殊的前綴 & 返回一個指向結構體的指針。
package main
import "fmt"
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 類型爲 Vertex
v2 = Vertex{X: 1} // Y:0 被省略
v3 = Vertex{} // X:0 和 Y:0
p = &Vertex{1, 2} // 類型爲 *Vertex
)
func main() {
fmt.Println(v1, p, v2, v3)
}
數組
類型 [n]T 是一個有 n 個類型爲 T 的值的數組。
表達式
var a [10]int
定義變量 a 是一個有十個整數的數組。
數組的長度是其類型的一部分,所以數組不能改變大小。這看起來是一個制約,可是請不要擔憂; Go 提供了更加便利的方式來使用數組。
切片(slice)
一個 slice 會指向一個序列的值,而且包含了長度信息。
[]T 是一個元素類型爲 T 的 切片(slice)。
len(s)返回 slice s的長度。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println("s ==", s)
for i := 0; i < len(s); i++ {
fmt.Printf("s[%d] == %d\n", i, s[i])
}
}
切片(slice)的切片
切片(slice)能夠包含任意的類型,包括另外一個 slice。
package main
import (
"fmt"
"strings"
)
func main() {
// Create a tic-tac-toe board.
game := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// The players take turns.
game[0][0] = "X"
game[2][2] = "O"
game[2][0] = "X"
game[1][0] = "O"
game[0][2] = "X"
printBoard(game)
}
func printBoard(s [][]string) {
for i := 0; i < len(s); i++ {
fmt.Printf("%s\n", strings.Join(s[i], " "))
}
}
對 slice 切片
slice 能夠從新切片,建立一個新的 slice 值指向相同的數組。
表達式
s[lo:hi]
表示從 lo 到 hi-1 的 slice 元素,含前端,不包含後端。所以
s[lo:lo]
是空的,而
s[lo:lo+1]
有一個元素。
package main
import "fmt"
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println("s ==", s)
fmt.Println("s[1:4] ==", s[1:4])
// 省略下標表明從 0 開始
fmt.Println("s[:3] ==", s[:3])
// 省略上標表明到 len(s) 結束
fmt.Println("s[4:] ==", s[4:])
}
構造 slice
slice 由函數 make 建立。這會分配一個全是零值的數組而且返回一個 slice 指向這個數組:
a := make([]int, 5) // len(a)=5
爲了指定容量,可傳遞第三個參數到 make:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
參考如下示例代碼 -
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
nil slice
slice 的零值是 nil 。
一個 nil 的 slice 的長度和容量是 0。
package main
import "fmt"
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
}
向 slice 添加元素
向 slice 的末尾添加元素是一種常見的操做,所以 Go 提供了一個內建函數 append 。 內建函數的文檔對 append 有詳細介紹。
func append(s []T, vs ...T) []T
append 的第一個參數 s 是一個元素類型爲 T 的 slice ,其他類型爲 T 的值將會附加到該 slice 的末尾。
append 的結果是一個包含原 slice 全部元素加上新添加的元素的 slice。
若是 s 的底層數組過小,而不能容納全部值時,會分配一個更大的數組。 返回的 slice 會指向這個新分配的數組。
package main
import "fmt"
func main() {
var a []int
printSlice("a", a)
// append works on nil slices.
a = append(a, 0)
printSlice("a", a)
// the slice grows as needed.
a = append(a, 1)
printSlice("a", a)
// we can add more than one element at a time.
a = append(a, 2, 3, 4)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
範圍(range)
for 循環的 range 格式能夠對 slice 或者 map 進行迭代循環。
當使用 for 循環遍歷一個 slice 時,每次迭代 range 將返回兩個值。 第一個是當前下標(序號),第二個是該下標所對應元素的一個拷貝。
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
能夠經過賦值給 _ 來忽略序號和值。
若是隻須要索引值,去掉 「 , value 」 的部分便可。
package main
import "fmt"
func main() {
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}