“Go是一种开源编程语言,能够轻松构建简单,可靠,高效的软件”。 ——GoLang程序员
在许多语言中,有许多方法能够解决给定的问题。程序员能够花不少时间思考解决问题的最佳方法。另外一方面,Golang坚持精简的功能 - 只有一种正确的方法能够解决问题。github
这节省了开发人员的时间,并使大型代码库易于维护。Golang中没有像地图和过滤器这样的“富有表现力”的功能。golang
“若是你有增长表现力的功能,一般会增长费用” ——罗伯派克编程
Golang由包组成。包main告诉Golang编译器该程序被编译为可执行文件,而不是共享库。它是应用程序的入口点。主包定义为:json
package main
复制代码
让咱们经过main.go 在Go lang工做区中建立一个文件来编写一个简单的hello world示例数组
Go中的工做空间由环境变量GOPATH定义。bash
您编写的任何代码都将写在工做区内。Go将搜索GOPATH目录中的全部的包,或者GOROOT在安装Go时默认设置的目录。 GOROOT是安装go的路径。服务器
设置 GOPATH 为所需的目录。如今,让咱们将 ~/workspace设置为GOPATH。数据结构
# export env
export GOPATH=~/workspace
# go inside the workspace directory
cd ~/workspace
复制代码
在main.go 咱们刚刚建立的工做空间文件夹中使用如下代码建立文件。
HELLO WORLD!
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello World!")
}
复制代码
在上面的示例中,fmt
是Go中的内置包实现了格式化I / O的函数。
咱们使用import
关键字在Go中导入包。func main
是代码执行的主要入口点。Println
是包内的一个函数,fmt
它为咱们打印“hello world”。
让咱们看一下运行这个文件。咱们能够经过两种方式运行Go命令。咱们知道,Go是一种编译语言,因此咱们首先须要在执行以前编译它。
> go build main.go
复制代码
这将建立一个二进制可执行文件 main ,如今咱们能够运行
> ./main
# Hello World!
复制代码
注意:要尝试运行本文提到的代码,您可使用 play.golang.org
Go中的变量是明确声明的。Go是一种静态类型语言。这意味着在变量声明时检查变量类型。变量能够声明为:
var a int
复制代码
在这种状况下,该值将设置为0.使用如下语法声明和初始化具备不一样值的变量:
var a = 1
复制代码
这里变量自动指定为int。咱们可使用变量声明的简写定义:
message := "hello world"
复制代码
咱们还能够在同一行中声明多个变量:
var b,c int = 2,3
复制代码
与任何其余编程语言同样,Golang支持各类不一样的数据结构。让咱们探讨一下:
一些INT类型的有int,int8,int16,int32,int64, uint,uint8,uint16,uint32,uint64,uintptr ......
字符串类型存储一系列字节。它用关键字string
表示和声明。
使用关键字bool
存储布尔类型。
Golang还支持复杂的数字类型数据类型,能够用complex64
和complex128
。
var a bool = true
var b int = 1
var c string = "hello world"
var d float32 = 1.222
var x complex128 = cmplx.Sqrt(-5 + 12i)
复制代码
数组是相同数据类型的元素序列。数组具备在声明中定义的固定长度,所以不能进行扩展。数组声明为:
var a [5] int
复制代码
数组也能够是多维的。咱们可使用如下格式建立它们:
var multiD [2] [3] int
复制代码
当数组的值在运行时更改时,数组限制了这种状况。数组也不提供获取子数组的能力。为此,Golang有一个名为slices的数据类型。
切片存储一系列元素,能够随时扩展。切片声明相似于数组声明 - 没有定义容量:
var b [] int
复制代码
这将建立一个零容量和零长度的切片。切片也能够定义容量和长度。咱们可使用如下语法:
numbers:= make([] int,5,10)
复制代码
这里,切片的初始长度为5,容量为10。
切片是数组的抽象。切片使用数组做为底层结构。切片包含三个组件:容量,长度和指向底层数组的指针,以下图所示:
经过使用append或copy函数能够增长切片的容量。append函数能够为数组的末尾增长值,并在须要时增长容量。
numbers = append(numbers, 1, 2, 3, 4)
复制代码
增长切片容量的另外一种方法是使用复制功能。只需建立另外一个具备更大容量的切片,并将原始切片复制到新建立的切片:
// create a new slice
number2 := make([]int, 15)
// copy the original slice to new slice
copy(number2, number)
复制代码
咱们能够建立切片的子切片。这可使用如下命令完成:
package main
import (
"fmt"
)
func main() {
// initialize a slice with 4 len and values
number2 := []int{1, 2, 3, 4}
fmt.Println(number2) // -> [1 2 3 4]
// create sub slices
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]
}
复制代码
Map
是Go中的数据类型,它将键映射到值。咱们可使用如下命令定义映射:
var m map[string]int
复制代码
这 m 是新的map变量,其键是string
,值是integers
。咱们能够轻松地将键和值添加到地map中:
package main
import (
"fmt"
)
func main() {
m := make(map[string]int)
// adding key/value
m["clearity"] = 2
m["simplicity"] = 3
// printing the values
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 cases
有助于组织多个条件语句. 如下示例显示了一个简单的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有一个循环关键字。单个for循环命令有助于实现不一样类型的循环:
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循环。对于for循环,可使用相同的for语句:
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 {
}
复制代码
Golang提供了指针。指针是保存值的地址的地方。指针由*定义。根据数据类型定义指针。例:
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包中,并在须要时导入fmt或其余包,如上面第一个main.go示例所示。
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
复制代码
正如咱们在上面的例子中所看到的,使用func
关键字后跟函数名来定义Golang函数。函数所需的参数须要根据其数据类型定义,最后是返回的数据类型。
函数的返回值也能够在函数中预约义:
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)
fmt.Println(sum)
}
//=> successfully added
//=> 3
复制代码
Golang不是一个彻底面向对象的语言,可是有结构体,接口和方法,来支持面向对象的开发。
结构体是不一样字段的类型集合。结构体用于将数据分组在一块儿。例如,若是咱们想要对Person类型的数据进行分组,咱们会定义一我的的属性,其中可能包括姓名,年龄,性别。可使用如下语法定义结构:
type person struct {
名字串
年龄int
性别串
}
复制代码
在定义了人类型结构体的状况下,如今让咱们建立一我的:
//way 1: specifying attribute and value
p = person{name: "Bob", age: 42, gender: "Male"}
//way 2: specifying only value
person{"Bob", 42, "Male"}
复制代码
咱们能够用点(.)轻松访问这些数据
p.name
//=> Bob
p.age
//=> 42
p.gender
//=> Male
复制代码
您还可使用其指针直接访问结构的属性:
pp = &person{name: "Bob", age: 42, gender: "Male"}
pp.name
//=> Bob
复制代码
方法是具备接收器的特殊类型的函数。接收器既能够是值,也能够是指针。让咱们建立一个名为describe的方法,它是咱们上面的例子中person
结构体的一个方法:
package main
import "fmt"
// struct defination
type person struct {
name string
age int
gender string
}
// method defination
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属于接收器类型,而setAge属于指针类型。
Golang接口是方法的集合。接口有助于将类型的属性组合在一块儿。咱们以动物接口为例:
type animal interface {
description() string
}
复制代码
这里的animal是接口类型。如今让咱们建立两种不一样类型的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函数中,咱们建立了一个a
类型为animal
的变量。咱们为animal分配snake和cat类型,并使用Println打印a.description。因为咱们以不一样的方式实现了两种类型(猫和蛇)中描述的方法,咱们获得了不一样animal的描述。
咱们在一个包中写入Golang中的全部代码。该主包是程序执行的入口点。Go中有不少内置包。咱们一直使用的最多的是fmt包。
“Go packages in the main mechanism for programming in the large that go provides and they make possible to divvy up a large project into smaller pieces.”
— Robert Griesemer
go get
// example
go get github.com/satori/go.uuid
复制代码
咱们安装的软件包保存在GOPATH中,这是咱们的工做目录。您能够经过咱们的工做目录中的pkg文件夹进入包 cd $GOPATH/pkg
。
让咱们从建立一个文件夹custom_package开始:
> mkdir custom_package
> cd custom_package
复制代码
要建立自定义包,咱们须要首先使用咱们须要的包名建立一个文件夹。假设咱们正在构建一个包 person
。为此,咱们建立一个名为person
的文件夹在custom_package
文件夹中:
> 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
包,并使用函数Description。请注意,secretName 咱们在包中建立的函数将没法访问。在Go中,以小写字母开头的方法名称将是私有的。
Golang内置了对包文档的支持。运行如下命令以生成文档:
godoc person Description
复制代码
这将为咱们的包开发人员生成Description函数的文档。要查看文档,请使用如下命令运行Web服务器:
godoc -http =“:8080”
复制代码
如今打开URLhttp//localhost8080/pkg/
并查看咱们刚建立的包的文档。
该包实现了格式化的I / O功能。咱们已经使用该包打印到stdout。
Golang中另外一个有用的包是json包。这有助于编码/解码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字节,第二个参数是咱们但愿json映射到的响应类型struct的地址。请注意, json:”page” 映射页面键是结构体中的PageNumber键。
错误是程序的不但愿的和意外的结果。假设咱们正在对外部服务进行API调用。此API调用可能成功或可能失败。当存在错误类型时,能够识别Golang程序中的错误。咱们来看看这个例子:
resp, err := http.Get("http://example.com/")
复制代码
这里的err表示API调用可能会经过或可能失败。咱们能够检查错误是否为nil
,并相应地处理响应:
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)
}
复制代码
当咱们编写本身的函数时,有些状况下咱们会遇到错误。能够经过errors
来返回这些错误:
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不是处理程序中异常的理想方式。建议使用错误对象。发生panic时,程序执行中止。panic以后执行的事情就是defer。
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,例如关闭文件。
Golang在构建时考虑了并发性。Golang中的并发能够经过轻量级线程的Go例程来实现。
Goroutine是能够与另外一个函数并行或同时运行的函数。建立Goroutine很是简单。只需在函数前面添加关键字Go,咱们就可使它并行执行。Go协程很是轻量级,所以咱们能够建立数千个例程。让咱们看一个简单的例子:
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是一个Goroutine,它与主Go线程并行执行。有时咱们但愿在多个线程之间共享资源。Golang不喜欢将一个线程的变量与另外一个线程共享,由于这会增长死锁和资源等待的可能性。还有另外一种在Goroutine之间共享资源的方法:channels。
咱们可使用通道在两个Goroutine之间传递数据。在创通道时,必须指定通道接收的数据类型。让咱们建立一个字符串类型的简单通道,以下所示:
c := make(chan string)
复制代码
使用这个通道,咱们能够发送字符串类型数据。咱们均可以在此通道中发送和接收数据:
package main
import "fmt"
func main() {
c := make(chan string)
go func() { c <- "hello" }()
msg := <-c
fmt.Println(msg)
}
//=>"hello"接收方通道等待发送方向通道发送数据。
复制代码
在某些状况下,咱们但愿Goroutine经过通道接收数据但不发送数据,反之亦然。为此,咱们还能够建立单向通道。让咱们看一个简单的例子:
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go sc(ch)
fmt.Println(<-ch)
}
func sc(ch chan<- string) {
ch <- "hello"
}
复制代码
在上面的示例中,sc
是一个Goroutine,它只能向通道发送消息但不能接收消息。
在一个函数中可能有多个通道正在等待。为此,咱们可使用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正在等待两个通道c1和c2。使用select case语句打印主函数,消息从通道中发送,不管它先收到哪一个。
您能够在Golang中建立缓冲通道。使用缓冲通道,若是缓冲区已满,则将阻止发送到通道的消息。咱们来看看这个例子:
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!
复制代码
Simplicity… — Rob-pike
咱们了解了Golang的一些主要组件和功能。
恭喜你,你如今对Go有了不错的认识。
One of my most productive days was throwing away 1,000 lines of code. --Ken Thompson
不要停在这里。继续前进。考虑一个小应用程序并开始构建。