Go 极速上手

Sep 03, 2020 • 预计阅读时间 7 分钟

30 分钟极速上手 Go

Go 开发环境

安装 Go (mac)

> brew install go
> go env GOROOT
> export GOPATH=$HOME/workspace/go
> mkdir -p $GOPATH/{src,bin}

设置好环境变量 GOROOTGOPATH

# .zshrc or .bashrc
# Go
export GOPATH=$HOME/workspace/go
export GOROOT=/usr/local/opt/go/libexec

GOROOT 乃 Go 安装的本地目录

Go 依照“everything under one roof”原则,GOPATH 乃 Go 工作区(workspace),可以是一个或多个目录(通常建议是一个),存放项目有关的一切:源代码,第三方库,编译中间文件,等等,GOPATH 目录下,约定有三个子目录:

  • src:存放源代码
  • pkg:存放编译时,生成的中间文件
  • bin:存放编译后生成的可执行文件 (通常会将 $GOPATH\bin 加入环境变量 PATH 中,以方便执行编译后的程序)

设置方式:

  • 一个工作区,全局多个项目共享,简单,但隔离不够
  • 每个项目使用单独工作区,每次要单独指定 GOPATH
  • 项目/业务和第三方库分开放置,配置两个 GOPATH

💡 GOPATH 简单,但难以处理版本依赖和隔离,Go1.14 推荐在生产上使用 Go Module(类似 NPM,Go 语言的依赖解决方案),取代 GOPATH

GOBIN 是编译之后,可执行文件的安装目录。如果设置了 GOBIN,编译后的可执行文件将不会安装到 GOPATH 下的 bin 目录。如果 GOPATH 包含了多个目录,则必须设置 GOBIN。

VS Code

安装 Go 插件:

vs code go

Goland

设置好 GOROOT 和 GOPATH 即可。

Hello World

在$GOPATH/src/example 下创建一个 hello.go 文件:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World!")
}
// 创建example项目
workspace/go/src via 🐹 v1.15
[I] ➜ mkcd example

// 创建hello.go
go/src/example via 🐹 v1.15
[I] ➜ code hello.go

go/src/example via 🐹 v1.15
[I] ➜ ls
hello.go

// 直接运行
go/src/example via 🐹 v1.15
[I] ➜ go run hello.go
Hello World!

// 编译成可执行二进制
go/src/example via 🐹 v1.15
[I] ➜ go build hello.go

go/src/example via 🐹 v1.15
[I] ➜ l
Permissions Size User     Date Modified Name
.rwxr-xr-x  2.1M fastzhong  3 Sep 17:04  hello*
.rw-r--r--    83 fastzhong  3 Sep 16:24  hello.go

go/src/example via 🐹 v1.15
[I] ➜ ./hello
Hello World!

// 清理
go/src/example via 🐹 v1.15
[I] ➜ go clean

go/src/example via 🐹 v1.15
[I] ➜ l
Permissions Size User     Date Modified Name
.rw-r--r--    83 fastzhong  3 Sep 16:24  hello.go

程序结构

从上面的 hello.go 可以看出:

✦ 源文件以 .go 为后缀名存储

✦ package(包)
Go 使用 package(包)来管理代码,package 特性:

  • 一个目录下的同级文件属于一个 package
  • 包名与目录名可以不同,但是通常会保持一致
  • package 是一个或多个 Go 源码文件的集合(内容包含了变量,struct,类型,接口,函数),Go 的包更像 Python,而非 Java(内容以 class 为基本单位,万事皆 class),每个程序可以直接使用自身的包或者引用其它的包:
    • 声明:在源文件的第一行声明明该文件属于哪一个包,如:package myservice,注意包名没有层级概念
    • 引用:需要加入引用路径(目录名),如 “import xxx.com/a/b/myservice“ 就对应着 src 目录下的 xxx.com/a/b/myservice,这点和 Java 不同,Go 的声明和引用路径是分开的
  • 程序的入口是 main 包的 main 函数,如果没有 main 包,则不会生成可执行文件,main 包不可被引用
  • Go 语言也有类似 Java Public 和 Private 的概念,如果类型/接口/方法/函数/字段的首字母大写(例如 fmt.Print),则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见

✦ Go 编译生成的是一个静态可执行文件,除了 glibc 外没有其他外部依赖

数据结构

✦ 基础类型 布尔类型:bool
整型:byte、int、int8、int16、uint、uintptr
浮点类型:float32、float64
复数类型:complex64、complex128
字符串:string
字符类型:rune
错误类型:error

error Go 内建错误类型,其实是一个接口类型(interface)

✦ 复合类型
指针:pointer
数组:array
切片:slice
字典:map
通道:chan
结构体:struct
接口:interface

💡struct & pointer 和 C/C++很类似 😅,理解指针的关键点是内存模型(变量/数据在内存中的样子)。

func add(num int) {
	num += 1
}

func realAdd(num *int) {
	*num += 1
}

func main() {
	num := 100
	add(num)
	fmt.Println(num)  // 100,num 没有变化

	realAdd(&num)
	fmt.Println(num)  // 101,指针传递,num 被修改
}

slice 相当于动态数组,其用法和 Python 类似:

a := [...]int{1, 2, 3, 4, 5}
s1 := a[2:4]   // 3,4
s2 := a[1:5]   // 2,3,4,5
s3 := a[:]     // a
s4 := a[:4]    // 1,2,3,4
s5 := s2[:]    // 1,2,3,4,5
s6 := a[2:4:5] // 3

逻辑结构

✦ if/els & switch

if x > 0 {
    return "positive"
} else {
    return "negative"
}

switch t := i.(type) {
case bool:
    return "I'm a bool"
case int:
    return "I'm an int"
default:
    return "Don't know type %T", t
}

✦ for

-   for init; condition; post { } // 类似 C
-   for condition { } // 相当于 while
-   for { } // 无限循环
for i := 0; i < 10; i++ {
    sum += i
}

循环体内可以有 breakcontinue

✦ goto

if skipped {
  goto doLast
}
// 跳过上面的代码块
doLast:
// 立即执行的代码块

✦ range

和 Python 类似:

list := []string{"a", "b", "c", "d", "e", "f"}
for i, v := range list {
    // i is the element index
    // v is the element value
}

内置函数

✦ main() & init()

和 Python 类似,init 的作用在于初始化:

-   init 函数先于 main 函数自动执行,不能被其他函数调用;
-   init 函数没有输入参数、返回值;
-   每个包可以有多个 init 函数,每个源文件也可以有多个 init 函数;
-   同一个包的 init 执行顺序,golang 没有明确定义,编程时要注意程序不要依赖这个执行顺序;
-   不同包的 init 函数按照包导入的依赖关系决定执行顺序;

✦ defer

defer 修饰的代码,不会马上执行,而是在当前函数返回或退出之前执行,常用于关闭/释放资源,如文件描述符,数据库链接,锁等:

func main() {
	for i := 0; i < 5; i++ {
		defer fmt.Println(i)
	}
}

$ go run main.go
4
3
2
1

✦ panic() & recover()

panic 抛出异常,recover 用来俘获,并抛向上一层:

func action() {
    defer fmt.Println("action completed")
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
            // do something to recover
        }
    }()

    panic("something goes wrong !!!")
}
// something goes wrong !!!
// action completed

✦ new() & make()

  • make 和 new 都是 golang 用来分配内存的內建函数,且在堆上分配内存,make 即分配内存,也初始化内存。new 只是将内存清零,并没有初始化内存。
  • make 返回的还是引用类型本身;而 new 返回的是指向类型的指针。
  • make 只能用来分配及初始化类型为 slice,map,channel 的数据;new 可以分配任意类型的数据。

还有其它的: close,delete,len,cap,copy,append,print,println,complex,real,imag

变量

// 单个变量
var i int = 0
var i = 0
i := 0

// 多个变量
var i, j, k int

// 常量:
const (
    a = iota    // a=0
    b           // 和 a 一样,不用重复 b = iota,iota会自增,b=1
    c           // c=2
    d = 1
    f           // 和 d 一样,f=1
)

函数

在 Go 语言中函数是一等公民,它作为一个变量、类型、参数、返回值,甚至可以去实现一个接口,但是 Go 语言中函数不支持重载、嵌套和默认参数。

✦ 函数定义:

func function_name( [parameter list] ) [return_types] {
// 函数体
}

✦ 不定长度变参:

func test(num ...int){
    fmt.Println(num) // [1 2 3 4]
}

test(1,2,3,4)

✦ 多返回值:

func test() (string,int,bool){
    return "Hello World", 100, true
}

v1, v2, v3 := test()

fmt.Println(v1, v2, v3) // Hello World 100 true

✦ 命名返回值:

func test() (a string, b bool, c int) {
    a = "Golang"
    b = false
    c = 200
    return
}

v1, v2, v3 := test()

fmt.Println(v1, v2, v3) // Golang false 200

✦ 作为变量:

func test(){
    // 函数体
}

funcTest := test
fmt.Println(funcTest())

✦ 匿名函数:

test := func(){
    // 函数体
}

✦ 作为类型:

package main

import "fmt"

type iAdder func(int, int) int

func main(){
    var adder iAdder = func(a int, b int) int {
        return a + b
    }

    fmt.Println(adder(1,2)) // 3
}

✦ 闭包:

package main

import "fmt"

// 使用 闭包实现 斐波那契数列
func fibonacci() func() int {
    a, b := 0, 1

    return func() int {
        a, b = b, a +b
        return a
    }
}

func main() {
    f := fibonacci()

    fmt.Println(f()) // 1
    fmt.Println(f()) // 1
    fmt.Println(f()) // 2
    fmt.Println(f()) // 3
    fmt.Println(f()) // 5
}

💡 理解闭包的关键点是作用域

结构体(struct) 和方法(methods)

与 C 语言中的 struct 或其他面向对象编程语言中的 class 类似,

type Student struct {
	name string
	age  int
}

func (stu *Student) hello(person string) string {
	return fmt.Sprintf("hello %s, I am %s", person, stu.name)
}

func main() {
	stu := &Student{
		name: "Tom",
	}
	msg := stu.hello("Jack")
	fmt.Println(msg) // hello Jack, I am Tom
}

接口(interfaces)

和 Java 类似,接口避免多继承(multi-inheritance),而采用组合方式(composition)。接口定义了一组抽象行为 - 方法(method):

type Person interface {
    getName() string
    getAge() int
}

type Student struct {
	name string
	age  int
}

func (stu *Student) getName() string {
	return stu.name
}

func (stu *Student) getAge() int {
	return stu.age
}

func main() {
	var p Person = &Student{
		name: "Tom",
		age:  18,
	}

	fmt.Println(p.getName()) // Tom
}
  • 接口不能被实例化,一个类型可以实现多个接口
  • 不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可
  • 方法和函数不同,不可直接调用,必须通过实现接口的实例调用

   小结:如果有 Java,Javascript,C++,Python 编程经验,上手 Go 确实很快,Go 借鉴了它们的特点。

㊫ Why Go or Go Why

㊫ 更深入的教程

编程go

Kubernetes 101

Docker 实操(Mac)

comments powered by Disqus