Go基础语法入门 | 青训营笔记

179 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

知识要点

  • Go 的特点
  • Go 的基础语法
  • 并发 Goroutine
  • 管道 Channel
  • 上下文机制 context

详细知识点介绍

1. Go 的特点

  1. 高并发、高性能
  2. 上手快
  3. 标准库、工具链丰富
  4. 静态编译
  5. GC
  6. 有指针

2. 变量

Go 是强类型语言,内置数据类型:

  1. 字符串 string
  2. 整数 int
  3. 浮点型 float
  4. 布尔型 bool

2.1 声明变量

// 自动推导数据类型
var a = "foo"

// 显式指定数据类型
var num1, num2 int = 1, 2

// 使用 := 声明(仅限于函数内使用)
num3 := 3

对于常量,使用 const 修饰符替换 var 修饰符。

3. 判断

3.1 if 语句

if condition {
    // code
}

condition 的括号是可选的,但 code 的括号是必须的。

3.2 switch 语句

  1. 不需要 break,执行到匹配的 case 之后自动跳出。
  2. condition 是可选的
switch condition {
    case 1:
        // code
    case 2:
        // code
    case 3, 4: // 多个条件
        // code
    case foo() > 4: // evaluated val
        // code
    default:
       // code
}

4. 循环

只有 for 循环。

for a; b; c {
    // code
}
for {
    // 死循环
}

5. 数组

声明方式:

var a [大小]类型

6. 切片

可变长度。
在初始化时可以指定大小,当追加的元素超过了原来的容量时,内部会创建一个新的数组用来存放数据,这种机制类似于 Java 中的 ArrayList

// 创建
var a = make([]string, 3)

// 追加
a = append(a, "d", "e", "f")

// 求元素个数
var b = make([]str, len(a))

// 拷贝
copy(b, a)

// 切片
a[1:3] // 前开后闭

7. 字典

// 创建 key: string, value: int
var m = make(map[string]int)

// 赋值
m["key1"] = 1

// 删除
delete(m, "key1")

// 读
val, ok := m["key1"]

8. Range

类似于 forEach

for a, b := range nums {
    // code
}

对于数组/切片,a 为索引, b 为值。

对于字典,a 为键,b 为值。

b 是可选的。

9. 函数

func foo(x int, y int) (a int, b int) {

}

10. 结构体

type User struct {
    username string
    password string
}

u := User{username: "123", password: "456"}

可以给类定义成员方法。

func (u user) checkPassword(password string) bool {
    return u.password == password
}
// 如果要修改成员
func (u *user) changePassword(password string) bool {
    return u.password = password
}

11. 错误处理

没有 try...catch... 结构,通常直接返回错误。

return nil, errors.New("Not found") // 错误的类型为 error

实践练习

1. 猜数字游戏

生成一个随机数,然后让用户输入一个数字,告诉他输入的数字是偏大、偏小或者猜对了。

随机数生成

生成随机数使用的是 rand 库,需要注意的是, rand 库在生成随机数之前需要设置随机数种子 rand.Seed(seed)

随机数种子的一种是当前系统时间戳,它的获取方法可以使用 time.Now().UnixNano()

读取用户输入

使用 reader := bufio.NewReader(os.Stdin) 创建一个系统输入流的 Reader

reader.ReadString(terminator) 可以用来读取文本,以 terminator 结尾,读取进来的字符串会包含 terminator

strings.TrimSuffix(suffix) 可以将 terminator 去掉。

strconv.Atoi(str) 可以将字符串转成 int 类型的数字变量。

2. 在线词典

这个例子中使用了两个网站来生成代码,一个是 cURL Converter,可以将 cURL 命令行转为 Go 代码。

还有一个网站是json2go,可以将 JSON 转化为 Go 中的结构体定义。

3. Socks5 代理

这个实例的知识点还是比较多的。

3.1 defer 关键字 - 延迟函数

对于关闭一个流,可以使用

defer stream.close()

defer 关键字表示该行代码会在函数返回时执行。
如果函数中有多个 defer,则它们会自下往上的顺序执行。

3.2 go 关键字 - goroutine

在实现 socks5 的 relay 功能时候,我们需要同时转发客户端和服务器的数据, 这个时候我们可以用 goroutine 来实现这个功能。

go func() {
   // code: src -> dst
}
go func() {
   // code: dst -> src
}

goroutine 的创建方法很简单,只需要在调用的函数前面加上 go 就可以了,所以上文的两个函数其实是 匿名函数

go hello()

3.3 管道 channel

Channel 是用来实现函数与函数之间通信的一种方式,类似于 Linux 中的 pipe
基本用法:

// 声明
var ch1 chan int
// 写数据
ch1 <- 10
// 读数据
x := <- ch1
// 关闭
close(ch1)

可见,Channel 是带有数据类型的。
Channel 有特殊的操作符 <-,它的位置表示了数据移动的方向,类似于 C++ 中的流操作符 <<, 但是 Go 中没有 ->
在读数据时,如果管道中没有内容,将会发送堵塞。

3.4 上下文 context

要实现服务端或客户端任意一方断开连接时,终止整个 relay 过程,需要让两个 goroutine 能够互相通信,这个时候需要借助 context 机制。

ctx, cancel := context.WithCancel(context.Background())

这里的 ctx 是一个 Channel, cancel 是一个函数。
当调用了 cancel() 之后, 上下文将会关闭。
ctx.Done() 这个管道会返回结果。

个人总结

Go 代码的编写思路和 C 很像,由于没有了 try...catch... 机制,
在写代码的时候,对于可能会出现错误的地方,都需要使用 if err != nil 这样的判断来检查这步操作是不是出现了什么问题。

Go 中没有线程、进程、协程,在需要并发的地方,我们只需要用 goroutine

引用参考

  1. 标准库函数 - pkgdoc
  2. Goroutine - TopGoer
  3. 管道 Channel
  4. 上下文 Context