Go语言是近年来最热门的编程语言,是Google开发的一种静态强类型、编译型、并发型,并具备垃圾回收功能的编程语言。Go语言同时具有开发效率高和执行效率高两大特色,被誉为云计算时代的C语言。本文做者经过一篇文章带你学会Go语言。git
Go 语言是一门开源语言,可以轻松的构建简单,可靠,高效的软件。
—— Golang
在不少语言中,解决给定的问题一般有多种方式。工程师须要花费大量的时间思考什么才是解决问题的最优解法。而在Golang中,问题的解法一般只有一种。github
这一特性大大节约了工程师的时间,并且使得维护大型代码库变得更容易。在Golang中没有maps和filter这样"高消耗"的特性。golang
语言的特性带来更好的表现力也带来代价。
——Rob Pike
Golang由包组成。Golang编译器将main包编译为可执行文件,而非共享库。main包是应用的入口,一般被定义以下:web
package main
下面看一个hello world 的例子,在Golang 的工做空间建立main.go文件。编程
在Go语言中,工做空间由环境变量GOPATH定义。全部编写的代码须要在工做空间中。Go语言会在GOPATH和GOROOT的路径中搜索包。GoROOT是在安装的时候肯定的安装路径。json
下面来设置GOPATH,咱们将~/workspace 加入工做空间。数组
# export env export GOPATH=~/workspace # go inside the workspace cd ~/workspace
咱们在刚才的工做空间下建立main.go 文件,代码以下:服务器
package main import ( "fmt" ) func main(){ fmt.Println("Hello World!") }
上面的例子中,fmt是Go内置的格式化I/O函数。多线程
咱们在Go语言中使用import关键字导入包,func main 是入口函数。Println是fmt包中函数,用于打印 "Hello World!"。架构
让咱们开始运行该文件。众所周知Go是编译型语言,咱们在运行以前先进行编译。
go build main.go
这将会建立一个二进制运行文件main,咱们如今来运行它:
./main // Hello World!
另外一种简单的方式是使用go run 命令:
go run main.go // Hello World!
Go中的变量类型是显式指定的。Go语言是强类型语言,这意味着在变量声明的时候会检查变量类型。
变量定义以下所示:
var a int 在这个例子中,a的初始值被设置为0。用下面的方式能够定义并初始化变量。 var a = 1 这里的变量被编译器推断为int。更简单的变量定义以下所示: message := "hello world" 咱们也能够在同一行声明多个变量: var b, c int = 2, 3
int 的类型有 int, int8, int16, int32, int64, unit, unit8, unit16, unit 32, unit64, unitptr...
[u开头表示无符号;uintptr 是一种无符号的整数类型,没有指定具体的bit大小可是足以容纳指针。 uintptr类型只有在底层编程是才须要,特别是Go语言和C语言函数库或操做系统接口相交互的地方。]
String类型使用byte序列存储数据,用关键字string来声明变量。
bool 关键字表示布尔类型。
Golang 也支持复数,用conplex64和complex128表示。
var a bool = true vat b int = 1 var c string = "hello world" var d float32 = 1.222 var x complex128 = cmplx.Sqrt(-5 +12i)
Array 是同类型元素的数组。Array在声明的时候会指定长度且不能改变。一个数组的定义以下:
var a[5] int 也有多维数组,定义以下 var multiD [2][3]int Slices 是能随时扩容的同类型元素的序列 。Slice的声明方式以下: var b []int 这将会建立一个容量为0,长度为0的Slice。Slice也能够定义容量和长度,格式以下: numbers := make([]int, 5, 10) 这个Slice初始长度为5,容量为10。
Slice是数组的封装,其内部实现是数组,slice有三个元素,容量,长度和指向内部数组的指针。
Slice的容量能够经过append 或者 copy函数增长。Append函数也能在数组的末尾添加元素,在容量不足的状况下会对slice扩容。
numbers = append(numbers, 1, 2, 3, 4) 另外一种增长slice容量的方式是使用copy函数。Copy函数的原理是建立一个新的大容量的slice,并把原有的slice拷贝到新的slice中。 // 建立新的slice number2 := make([]int, 15) // 复制原有的slice到新的slice copy(number2, number)
咱们也能够建立slice的子slice。例子以下:
package main import ( "fmt" ) func main() { // 初始化slice number2 := []int{1, 2, 3, 4} fmt.Println(number2) // -> [1 2 3 4] // 建立子slice slice1 := number2[2:] fmt.Println(slice1) // -> [3 4] slice2 := number2[:3] fmt.Println(slice2) // -> [1 2 3] slice3 := number2[1:4] fmt.Println(slice3) // -> [2 3 4] }
Go语言中的Map是键值对,定义以下:
var m map[string]int
m是定义的变量名,键的类型是string,值的类型是integers。Map中添加键值对的例子以下:
package main import ( "fmt" ) func main() { m := make(map[string]int) // 添加键值对 m["clearity"] = 2 m["simplicity"] = 3 // 打印值 fmt.Println(m["clearity"]) // -> 2 fmt.Println(m["simplicity"]) // -> 3 }
使用类型转换可以改变数据类型,例子以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { a := 1.1 b := int(a) fmt.Println(b) //-> 1 }
If else 的例子以下,须要注意的是花括号和条件表达式位于同一行。
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { if num := 9; num < 0 { fmt.Println(num, "is negative") } else if num < 10 { fmt.Println(num, "has 1 digit") } else { fmt.Println(num, "has multiple digits") } }
Switch case 能组织多条件表达式,例子以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 2 switch i { case 1: fmt.Println("one") case 2: fmt.Println("two") default: fmt.Println("none") } }
Golang中只有一个循环表达的关键字,不一样形式的循环表达式以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 0 sum := 0 for i < 10 { sum += 1 i++ } fmt.Println(sum) }
上面的例子和C语言中的while循环相似,更为正式的循环表达形式以下:
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
Go 语言中的死循环定义以下:
for { }
Go 语言可使用指针,指针存储变量的地址,指针用*来定义。指针的定义和所指数据的类型相关:
var ap *int 这里的ap是指向整型数据的指针,&用于获取所变量的地址。 a :=12 ap = &a * 用于获取指针所指的地址的值。 fmt.Println(*ap) // => 12 指针一般用于将结构体作为参数传递。
传值一般意味着拷贝,意味着须要更多的内存。
使用指针传递时,在函数中改变的变量会传递给调用的方法或函数。
package main import ( "fmt" ) func increment(i *int) { *i++ } func main() { i := 10 increment(&i) fmt.Println(i) //=> 11 }
main 包中main函数是golang 程序的入口。咱们能够定义多个函数并调用。例如:
package main import ( "fmt" ) func add(a int, b int) int { c := a + b return c } func main() { fmt.Println(add(2, 1)) //=> 3 }
从上面的例子中咱们能够看出,Golang 中的函数用func关键字加上函数名, 后面是附带数据类型的参数,最后是函数的返回类型。
函数的返回值能够被预先定义,例子以下:
package main import ( "fmt" ) func add(a int, b int) (c int) { c = a + b return } func main() { fmt.Println(add(2, 1)) //=> 3 }
这里c定义为返回值,所以变量c将会被自动返回,无需在函数最后的return中声明。
你也能够定义一个多个返回值的函数,使用,进行分割。
package main import ( "fmt" ) func add(a int, b int) (int, string) { c := a + b return c, "successfully added" } func main() { sum, message := add(2, 1) fmt.Println(message) //=> successfully added fmt.Println(sum) //=> 3 }
Golang 不是彻底的面向对象语言,可是支持不少面向对象的特性,例若有结构体,接口,方法等。
结构体是有类型,不一样变量的集合。例如咱们想定义Person类型,其中包含姓名,年龄,性别。例如:
type person struct { name String age int gender string }
定义好了person结构体后,咱们如今来使用它:
//方式 1: 指定属性和值 p = person{name: "Bob", age: 42, gender: "Male"} //方式 2: 只指定值 person{"Bob", 42, "Male"} 咱们可使用.符号访问这些属性: p.name //=> Bob p.age //=> 42 p.gender //=> Male
你也但是使用指针访问结构体的属性:
pp = &person{name: "Bob", age: 42, gender: "Male"} pp.name //=> Bob
方法是一种带有接受器的特殊函数。接收器能够是值或者指针。例子以下:
package main import "fmt" // 定义结构体 type person struct { name string age int gender string } // 定义方法 func (p *person) describe() { fmt.Printf("%v is %v years old.", p.name, p.age) } func (p *person) setAge(age int) { p.age = age } func (p person) setName(name string) { p.name = name } func main() { pp := &person{name: "Bob", age: 42, gender: "Male"} pp.describe() // => Bob is 42 years old pp.setAge(45) fmt.Println(pp.age) //=> 45 pp.setName("Hari") fmt.Println(pp.name) //=> Bob }
从上面的例子咱们能够看出,使用.操做符调用方法,例如pp.describe。须要注意的是,接收器是指针的话,咱们传递的是值的引用,这意味着咱们在方法作修改将会反映到变量pp上。该不会建立对象的拷贝,将会节省内存。
从上面的例子咱们能够看出,age的值被改变了,而name的值并无改变。这是由于方法setName的接受器不是指针。
Golang中的接口是方法的集合,接口有助于将同类型的属性组合起来,让咱们一块儿来看一个anminal的接口。
type animal interface { description() string }
这里的animal是接口类型,咱们来建立两种类型的animal并实现接口。
package main import ( "fmt" ) type animal interface { description() string } type cat struct { Type string Sound string } type snake struct { Type string Poisonous bool } func (s snake) description() string { return fmt.Sprintf("Poisonous: %v", s.Poisonous) } func (c cat) description() string { return fmt.Sprintf("Sound: %v", c.Sound) } func main() { var a animal a = snake{Poisonous: true} fmt.Println(a.description()) a = cat{Sound: "Meow!!!"} fmt.Println(a.description()) } //=> Poisonous: true //=> Sound: Meow!!!
在main函数中,咱们建立可一个animal类型的变量a。咱们把 snake和cat类型赋值给animal,使用Println 输出a.description。
咱们在cat和snake中使用不一样的方式实现了describe方法,咱们获得了不一样类型的输出。
在Golang中,咱们的代码在某个包下。main包是程序执行的入口。在Go中有不少内置的包,例如咱们以前用过的fmt包。
Go 的包机制是大型软件的基础,可以将大型的工程分解成小部分。
—— Robert Griesemer
go get
// 例子 go get github.com/satori/go.uuid
安装的包保存在GOPATH的环境中,你能够在 $GOPATH/pkg 路径下看到安装的包。
首先建立一个文件夹 custom_package:
mkdir custom_package cd custom_package
建立自定义包的第一步是建立一个和包名相同的文件夹。咱们要建立person包,所以咱们在custom_package文件下建立person文件夹:
mkdir person cd person
在该路径下建立一个文件person.go:
package person func Description(name string) string { return "The person name is: " + name } func secretName(name string) string { return "Do not share" }
如今咱们来安装这个包,这样咱们就能够导入和使用它了:
go install
接下来咱们返回custom_package 文件夹中,建立一个main.go:
package main import ( "custom_package/person" "fmt" ) func main() { p := person.Description("Milap") fmt.Println(p) } // => The person name is: Milap
在这里,咱们能够导入以前建立的包person,须要注意的是在person包中函数secretName不能被访问,这是由于Go中小写字母开头的函数是私有函数。
Golang中有内置的功能支持包文档。运行下面的命令将生成文档:
godoc person Description
这将会为Description 函数生成文档,想要在web服务器上查看文档须要运行下面的命令:
godoc -http=":8080"
如今打开连接 http://localhost:8080/pkg/ 将会看到咱们刚才看到的文档。
9.4.1 fmt
fmt包实现可标准的I/O函数,咱们在以前的包中用过其中的打印输出函数。
9.4.2 json
Golang中另外一个内置的重要包的是json,它可以对JSON进行编解码。
编码
package main import ( "encoding/json" "fmt" ) func main() { mapA := map[string]int{"apple": 5, "lettuce": 7} mapB, _ := json.Marshal(mapA) fmt.Println(string(mapB)) }
解码
package main import ( "encoding/json" "fmt" ) type response struct { PageNumber int json:"page" Fruits []string json:"fruits" } func main() { str := {"page": 1, "fruits": ["apple", "peach"]} res := response{} json.Unmarshal([]byte(str), &res) fmt.Println(res.PageNumber) } //=> 1
解码的时候使用Unmarshal方法,第一个参数是json字节,第二个参数是要映射的结构体的地址。须要注意的是json中的“page”对应的是结构体中的PageNumber。
错误是程序中不该该出现的结果。假设咱们编写一个API调用外部的服务。这个API可能成功也可能失败。当存在错误是,Golang程序可以识别:
resp, err := http.Get("http://example.com/")
对API的 调用可能成功也可能失败,咱们能够经过检查错误是否为空来选择处理方式。
package main import ( "fmt" "net/http" ) func main() { resp, err := http.Get("http://example.com/") if err != nil { fmt.Println(err) return } fmt.Println(resp) }
当咱们在自定义函数是,某些状况下会产生错误。咱们可使用error 对象返回这些错误:
package main import ( "errors" "fmt" ) func Increment(n int) (int, error) { if n < 0 { // return error object return 0, errors.New("math: cannot process negative number") } return (n + 1), nil } func main() { num := 5 if inc, err := Increment(num); err != nil { fmt.Printf("Failed Number: %v, error message: %v", num, err) } else { fmt.Printf("Incremented Number: %v", inc) } } // => The person name is: Milap
Go 内置的包,外部的包都有处理错误的机制。所以咱们调用的函数都有可能产生错误。这些错误不该该忽略而是应该向上面的例子那样被优雅的处理。
Panic是程序运行中忽然产生未经处理的异常。在Go中,panic不是合理处理异常的方式,推荐使用error对象代替。当Panic产生时,程序将会暂停运行。当panic被defer以后,程序才能继续运行。
//Go package main import "fmt" func main() { f() fmt.Println("Returned normally from f.") } func f() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fmt.Println("Calling g.") g(0) fmt.Println("Returned normally from g.") } func g(i int) { if i > 3 { fmt.Println("Panicking!") panic(fmt.Sprintf("%v", i)) } defer fmt.Println("Defer in g", i) fmt.Println("Printing in g", i) g(i + 1) }
Defer 在函数结尾必定会执行。在上面的函数中,使用panic() 暂停程序的运行,defer语句使程序执行结束时使用改行。Defer也能够用做咱们想要在函数的结尾执行的语句,例如关闭文件。
Golong使用轻量级线程Go routies支持并发。
Go routine 是可以并行运行的函数。建立Go routine 很是简单,只须要在函数前添加关键字go,这样函数就可以并行运行了。Go routines 是轻量级的,咱们可以建立上千个Go routines。例如:
package main import ( "fmt" "time" ) func main() { go c() fmt.Println("I am main") time.Sleep(time.Second * 2) } func c() { time.Sleep(time.Second * 2) fmt.Println("I am concurrent") } //=> I am main //=> I am concurrent
上面的例子中,函数c是Go routine,可以并行运行。咱们想要在多线程中共享资源,可是Golang并不支持。由于这会致使死锁和资源等待。Go 提供了另外一种共享资源的方式:channel。
咱们可使用Channel在两个Go routine之间传递数据。建立channel以前须要制定接受的数据类型。例如咱们建立了一个接受string类型的channel。
c := make(chan string)
有了这个channel以后,咱们能够经过这个channel发送和接收string类型的数据。
package main import "fmt" func main() { c := make(chan string) go func() { c <- "hello" }() msg := <-c fmt.Println(msg) //=>"hello" }
接收的channel一直等待发送的channel发送数据。
有些状况下,咱们但愿Go routine 经过channel接收数据,但不发送数据,反之亦然。这时候咱们能够建立一个one-way channel。例如:
package main import ( "fmt" ) func main() { ch := make(chan string) go sc(ch) fmt.Println(<-ch) } func sc(ch chan<- string) { ch <- "hello" }
上面例子中,sc是一个Go routine只能给channel发送数据而不能接受数据。
有这样一种状况,一个函数等待多个channel,这时候咱们可使用select语句。例如:
package main import ( "fmt" "time" ) func main() { c1 := make(chan string) c2 := make(chan string) go speed1(c1) go speed2(c2) fmt.Println("The first to arrive is:") select { case s1 := <-c1: fmt.Println(s1) case s2 := <-c2: fmt.Println(s2) } } func speed1(ch chan string) { time.Sleep(2 * time.Second) ch <- "speed 1" } func speed2(ch chan string) { time.Sleep(1 * time.Second) ch <- "speed 2" }
上面例子中,main函数等待两个channel,c1和c2。使用select语句,先从channel中收到的数据会被打印出来。
在Golang中能够建立buffered channel,当buffer满的时候,发送数据给channel将会被阻塞。例如:
package main import "fmt" func main() { ch := make(chan string, 2) ch <- "hello" ch <- "world" ch <- "!" // extra message in buffer fmt.Println(<-ch) } // => fatal error: all goroutines are asleep - deadlock!
Golang为何如此成功?
简单。。。
—— Rob-pike
咱们学习Golang以 下的主要模块和特性:
恭喜你,你已经对Go有了很好的理解。
One of my most productive days was throwing away 1,000 lines of code.
—— Ken Thompson
不要停下脚步,继续前进。思考一个小应用程序并开始动手。
原文连接:
原创: Milap Neupane 高可用架构
https://mp.weixin.qq.com/s?__...
https://milapneupane.com.np/2...
本文做者Milap Neupane,由何朋朋翻译。转载本文请注明出处,欢迎更多小伙伴加入翻译及投稿文章的行列,详情请戳公众号菜单「联系咱们」。