最近写了不少Go代码,但是写着写着,还是容易忘,尤其是再写点Python代码后。所以找了一篇不错的Golang基础教程,翻译一下,时常看看。

开始

Go是由各种 包 组成的。main包是程序的入口,由它告诉编译器,这是一个可执行程序,而不是共享包。main包定义如下:

package main

工作区

GOPATHGOPATHGOROOTGOROOT
GOPATH
# export 环境变量
export GOPATH=~/workspace
# 进入工作区目录
cd ~/workspace
mian.go
package main

import (
 "fmt"
)

func main(){
  fmt.Println("Hello World!")
}
importfunc main

想要运行Go程序,有两种方法。

方法一

大家都知道,Go是一门编译型语言,所以在执行之前,我们需要先编译它。

> go build main.go

这个命令会生成二进制可执行文件 main,然后我们再运行它。

> ./main 
# Hello World!

方法二

go run
go run main.go
# Hello World!

注意:你可以在执行本文中的代码。

变量

Go中的变量都是显式声明的。Go是静态语言,因此声明变量时,就会去检查变量的类型。

变量声明有以下三种方式。

# 1) a的默认值为0
var a int

# 2) 声明并初始化a,a自动赋值为int
var a = 1

# 3) 简写声明
message := "hello world"

还可以在一行声明多个变量

var b, c int = 2, 3

数据类型

数字,字符串 和 布尔型

intint8int16int32int64uintuint8uint16uint32uint64uintptr
string
bool
complex64complex128
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)

数组, 分片 和 映射Map

数组是包含同一数据类型的元素序列,在声明时确定数组长度,因此不能随意扩展。

数组的声明方式如下:

var a [5]int

多维数组的声明方式如下:

var multiD [2][3]int
slice[分片]

分片用于存储一组元素,允许随时扩展其长度。分片的声明类似数组,只是去掉了长度声明。

var b []int

这行代码会创建一个 0容量、0长度的分片。也可以使用以下代码 设置分片的容量和长度。

// 初始化一个长度为5,容量为10的分片
numbers := make([]int,5,10)

实际上,分片是对数组的抽象。分片使用数组作为底层结构。一个分片由三部分组成:容量、长度和指向底层数组的指针。


appendcopyappend
numbers = append(numbers, 1, 2, 3, 4)
copy
// 创建一个更大容量的分片
number2 := make([]int, 15)
// 把原分片复制到新分片
copy(number2, number)

如何创建一个分片的子分片呢?参考以下代码。

// 创建一个长度为4的分片
number2 = []int{1,2,3,4}
fmt.Println(numbers) // -> [1 2 3 4]
// 创建子分片
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的一种数据类型,用于记录键值间的映射关系。使用以下代码创建一个map。

var m map[string]int

// 新增 键/值
m['clearity'] = 2
m['simplicity'] = 3
// 打印值
fmt.Println(m['clearity']) // -> 2
fmt.Println(m['simplicity']) // -> 3

这里,m是一个键为string,值为int的map变量。

类型转换

接下来看一下如何进行简单的类型转换。

a := 1.1
b := int(a)
fmt.Println(b)
//-> 1

并非所有的数据类型都能转换成其他类型。注意:确保数据类型与转换类型相互兼容。

条件语句

if else

参考以下代码中的if-else语句进行条件判断。注意:花括号与条件语句要在同一行。

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

switch-case用于组织多个条件语句,详看以下代码

i := 2
switch i {
case 1:
 fmt.Println("one")
case 2:
 fmt.Println("two")
default:
 fmt.Println("none")
}

循环

for
i := 0
sum := 0
for i < 10 {
 sum += 1
  i++
}
fmt.Println(sum)
while
sum := 0
for i := 0; i < 10; i++ {
  sum += i
}
fmt.Println(sum)

Go中的死循环

for {
}

指针

*
var ap *int
&*
a := 12
ap = &a

fmt.Println(*ap)
// => 12

以下两种情况,通常优先选用指针。

  • 把结构体作为参数传递时。因为值传递会耗费更多内存。
  • 声明某类型的方法时。传递指针后,方法/函数可以直接修改指针所指向的值。

比如:

func increment(i *int) {
  *i++
}
func main() {
  i := 10
  increment(&i)
  fmt.Println(i)
}
//=> 11

函数

mainmain
func add(a int, b int) int {
 c := a + b
 return c
}
func main() {
 fmt.Println(add(2, 1))
}
//=> 3
func

函数的返回值也可以在函数中提前定义:

func add(a int, b int) (c int) {
  c = a + b
  return
}
func main() {
  fmt.Println(add(2, 1))
}
//=> 3
return

你也可以一次返回多个变量:

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)
}

方法、结构体和接口

Go 不是完全面向对象的语言,但是有了 方法、结构体和接口,它也可以达到面向对象的效果。

Struct 结构体

结构体包含不同类型的字段,可用来对数据进行分组。例如,如果我们要对Person类型的数据进行分组,那么可以定义一个人的各种属性,包括姓名,年龄,性别等。

type person struct {
  name string
  age int
  gender string
}

有了Person类型后,现在来创建一个 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

方法

方法是一种带有接收器的函数。接收器可以是一个值或指针。我们可以把刚刚创建的Person类型作为接收器来创建方法:

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
agenamesetAge

接口

在Go中,接口是方法的集合。接口可以对一个类型的属性进行分组,比如:

type animal interface {
  description() string
}
animalanimal
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!!!
a.description

mainfmt
“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 <package-url-github>
// 举个栗子
go get github.com/satori/go.uuid
GOPATHcd $GOPATH/pkg

自定义包

custom_package
> mkdir custom_package
> cd custom_package
personcustom_packageperson
> 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
GO111MODULE
custom_packagemain.go
package main
import(
  "custom_package/person"
  "fmt"
)
func main(){ 
  p := person.Description("Milap")
  fmt.Println(p)
}
// => The person name is: Milap
DescriptionsecretName

包的文档

Go内置了对包文档的支持。运行以下命令生成文档:

go doc person Description
personDescription
godoc -http=":8080"

打开这个链接,就能看到文档了。

Go中的一些内置包

fmt

fmt

json

jsonJson
// 编码
package main

import (
  "fmt"
  "encoding/json"
)

func main(){
  mapA := map[string]int{"apple": 5, "lettuce": 7}
  mapB, _ := json.Marshal(mapA)
  fmt.Println(string(mapB))
}
// 解码
package main

import (
  "fmt"
  "encoding/json"
)

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
Unmarshaljson:"page"pagePageNumber

错误处理

APIAPI
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
func Increment(n int) (int, error) {
  if n < 0 {
    // return error object
    return nil, 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)
  }
}

大部分的内置包或者外部包,都有自己的报错处理机制。因此我们使用的任何函数可能报错,这些报错都不应该被忽略,应该像上面示例中,在调用函数的地方,优雅地处理报错。

Panic

panicerrorpanicpanicdefer
//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

Defer
panicdeferDefer

并发

Go routines

Go routine

Go routineGo routinegoroutine
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
Go routinego channel

Channels

channelroutinechannel
c := make(chan string)
channelstring
package main

import "fmt"

func main(){
  c := make(chan string)
  go func(){ c <- "hello" }()
  msg := <-c
  fmt.Println(msg)
}
//=>"hello"
channelchannel

单向channel

Go routinechannel
package main

import (
 "fmt"
)

func main() {
 ch := make(chan string)
 
 go sc(ch)
 fmt.Println(<-ch)
}

// sc函数:只能发送数据给 channel,不能接收数据
func sc(ch chan<- string) {
 ch <- "hello"
}
selectGo routinechannel
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"
}
// => The first to arrive is:
// => speed 2

Buffered channel

channelchannel
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
Simplicity… — Rob-pike
因为简单...

好了,本文终于结束了!你从菜鸟变成大佬了吗?开个玩笑,希望看完能有所收获。

欢迎关注:测试开发Guide,持续更新测试开发干货、面试资料。