环境搭建

第一步肯定是要先有个环境可以上手了,我们选择使用WSL(Ubuntu)+VSCode进行

安装参考:https://go.dev/doc/install

WSL(Ubuntu)

# 卸载旧版本
$ sudo apt remove golang
$ sudo apt autoremove

# 安装新版本
# 这里我们使用最新的稳定版
# 以root或sudo运行安装命令
$ sudo rm -rf /usr/local/go
$ sudo tar -C /usr/local -xzf go1.17.6.linux-amd64.tar.gz

# 添加/usr/local/go/bin/到PATH环境变量
$ export PATH=$PATH:/usr/local/go/bin

# 配置代理,国内环境嘛
$ go env -w GO111MODULE=on
$ go env -w GOPROXY=https://goproxy.cn,direct

# 配置GOPATH工作目录
$ go env -w GOPATH=~/developer/gowork
$ go env -w GOMODCACHE=~/developer/gowork/pkg/mod

# 将$GOPATH/bin也添加到PATH变量,方便我们安装的程序可以直接使用
$ export PATH=$PATH:/usr/local/go/bin:~/developer/gowork/bin
# 将上述导入变量也添加到~/.profile中
$ source ~/.profile

# 安装完成,查看一下版本信息
$ go version
go version go1.17.6 linux/amd64

配置文档

配置一份速查文档能提高我们的学习效率,养成一份先查文档再搜索的习惯。毕竟一个内置库的使用也去“百度”搜索,难免不被笑话,哈哈

godoc

官方提供的一种离线文档查看方式

$ go install golang.org/x/tools/cmd/godoc@latest
# 运行godoc
$ godoc
# 或指定访问地址
# 浏览器访问localhost:6060查看文档
$ godoc -http localhost:6060

DevDocs

这也是我更喜欢的方式

  1. 使用Chrome浏览器访问 https://devdocs.io/
  2. 将应用创建为桌面打开的应用
  3. Preferences 添加自己需要的语言,这里当然选Go
  4. Offline Data 安装离线数据

开发工具

开发工具选用VSCode,主要原因是和WSL的联动相当好,且精简。另一个好用的IDE是GoLand

  1. 下载打开VSCode
  2. Extensions 搜索安装Go扩展
  3. Ctrl+Shift+P 调出命令面板,搜索 go: install tools,安装一些必须的go包
  4. Remote Explorer 打开WSL中我们创建的学习目录(也可以直接在WSL中输入code PATH快速打开)

基础语法

环境和文档已配置完成,第一天的学习正式开始

$ mkdir day1
$ code day1

优势

  • 开源,大量的标准库
  • 编译性语言,编译为单文件并支持交叉编译
  • 自动的内存管理和垃圾回收机制
  • 语言简单,更易的并发支持
  • 风格优雅统一
  • 向后兼容

运行方式

新建一个main.go,尝试写入以下代码

// package 软件包的名称,main为入口软件包
package main

// 导入的软件包
import "fmt"

// 入口函数
func main() {
	// 包中的方法调用
	fmt.Println("Hello World!")
}

第一个程序我们就写好了,下面运行一下

# 编译并执行应用程序,不生产二进制文件
$ go run main.go
Hello World!

# 生成二进制可执行文件
$ go build main.go
$ ./main
# 完美输出我们向世界的问好
Hello World!

变量

// 函数外我们使用var关键字进行声明
var firstName string
var age int

// 声明多个相同类型的变量
var firstName, lastName string

// 使用块进行变量声明
var (
    firstName, lastName string
    age int
)

// 声明时进行赋值操作 
var (
    firstName string = "John"
    lastName  string = "Doe"
    age       int    = 32
)

// 支持类型推断的方式进行赋值
var (
    firstName = "John"
    lastName  = "Doe"
    age       = 32
)
var (
    firstName, lastName, age = "John", "Doe", 32
)

// 函数内声明
func main() {
    // 函数外必须使用var关键字
    // 函数内使用:=进行初始类型推断
    firstName, lastName := "John", "Doe"
    age := 32
    fmt.Println(firstName, lastName, age)
}

常量

// 常量使用const关键字,可以使用但不适用
const A = 1

// 可以使用[iota](https://github.com/golang/go/wiki/Iota)关键字标识进行递增操作
const (
    // 0
    A = iota
    // 1
    B
    // 2
    C
)

// 使用_进行值忽略
const (
	// 忽略第一个0值
	_ = iota
    A = iota * 2
    // 4
    B
    // 6
    C
)

数据类型

整数

// 提供int8,int16,int32,int64和uint8,uint16,uint32,uint64
// 还有一些类型别名
type rune = int32

// 取值返回可以查看[builtin.go](https://go.dev/src/builtin/builtin.go)
var i1 int8 = 127
var i16 int16
var i32 int32
var i64 int64

// 默认值为0
fmt.Println(i16, i32, i64)

// 还可以用于表示 Unicode 字符码
var i32a int32 = 'G'
var i32b rune = '你'

浮点数

// 最大值可以通过 math.MaxFloat32 查看
var f32 float32 = math.MaxFloat32
var f64 float64

布尔值

var b1 bool
// 默认值为false
fmt.Println(b1)

字符串

var s1 string = "Hello"
var s2 string
// 默认值空
fmt.Println(s2)

类型转换

内置方法

var i16 int16 = 127
var i32 int32 = 127
fmt.Println(int32(i16) + i32)

strconv

// strconv包提供了类型转换的一些方法
import "strconv"

// _用于忽略的变量,不会使用
i, _ := strconv.Atoi("-42")
s := strconv.Itoa(-42)

// 提供了一些字符串转换方法
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)
i, err := strconv.ParseInt("-42", 10, 64)
u, err := strconv.ParseUint("42", 10, 64)

// 转换为ASCII字符串
q := strconv.QuoteToASCII("Hello, 世界")

函数

// 语法
func name(parameters) (results) {
    body-content
}

// 常规写法
func sum(n1 string, n2 string) int {
	int1, _ := strconv.Atoi(n1)
	int2, _ := strconv.Atoi(n2)
	return int1 + int2
}

// 定义返回的值
func sum(n1 string, n2 string) (result int) {
    result = 10
    // 无返回会将result返回为当前值
    // => 10
    return
    // 返回为return值
    // => 20
    return 20
}

// 多个返回值
func sum(n1 string, n2 string) (result1 int, result2 int) {
    return 10, 20
}

指针

// 按值传递
// & 运算符使用其后对象的地址。
// * 运算符取消引用指针。
func main() {
	firstName := "John"
	updateName(&firstName)
	fmt.Println(firstName)
}

func updateName(name *string) {
	*name = "David"
}

数组

相同元素组成的集合,数组在初始化后无法改变大小

// 定义
var a [3]int
a[0] = 1

// 初始化
a := [5]int{1, 2, 3}

// [1 2 3 0 0] 剩余元素为类型默认值
fmt.Println(a)

// 省略数量的初始化
a := [...]int{1, 2, 3}

// 省略数量,但指定某个位置的初始化
a := [...]int{10: 3, 20: 5}

// len(a) = 21
a := [...]string{10: "A", 20: "C"}

// 多维数组
var a1 [3][3][3]int
var a1 [3][3]int
a1 := [3][3]int{{1}, {2}}

切片

切片可以认为是动态数组

// 切片和数组声明方式类似,但切片可以动态追加元素
a := []int{1, 2, 3, 4, 5, 6, 7}

// s[i:p]
fmt.Println(a[:])
fmt.Println(a[2:3])
fmt.Println(a[:3])

// 追加
// func append(slice []Type, elems ...Type) []Type
a = append(a, 8)
a = append(a, 8, 9)
b := []int{0, 0, 0}

// 追加其他切片
a = append(a, b...)

// 使用len查看长度,使用cap查看容量
// 切片容量不足时会自动翻倍递增
fmt.Println(len(a), cap(a))

// 删除项
// 没有提供删除的方法,但可以通过切片追加进行实现
a = append(a[:3], a[3+1:]...)

// 数组元素为引用类型,更改切片元素会影响基础数组
a := []int{1, 2, 3, 4, 5, 6, 7}
slice1 := a[0:2]
slice2 := a[1:3]
slice1[1] = 9
// 所有关联值都改为了相同值
fmt.Println(a, slice1, slice2)

// 使用make分配了指定容量的切片
// func make(t Type, size ...IntegerType) Type
slice2 := make([]int, 3)

// 使用存在指定容量的切片
slice2 := []int{0, 0, 0}

// 使用内置copy创建副本
// func copy(dst, src []Type) int
copy(slice2, a[1:3])

map

items := map[int]string{1: "A", 2: "B", 3: "C"}
fmt.Println(items)

// 使用make创建一个动态映射
items := make(map[int]string)
items := map[int]string{}

// 方法外使用var创建
var items = make(map[int]string)
items[3] = "C"
// false
fmt.Println(items == nil)

// 以下会创建一个nil的映射关系,无法动态添加,但可以查询和删除
var items = map[int]string
// true
fmt.Println(items == nil)
// panic: assignment to entry in nil map
items[1] = "A"

// 使用delete删除项
// func delete(m map[Type]Type1, key Type)
delete(items, 1)
delete(items, "A")

if

// 简单判断
x := 10
if x-10 == 0 {
}

// 复合判断
if x := 10; x-10 == 0 {
}

// else
if num := somenumber(); num < 0 {
    fmt.Println(num)
} else if num < 10 {
    fmt.Println(num)
} else {
    fmt.Println(num)
}

// 结构中创建的变量在外部无法使用
// undefined: num
fmt.Println(num)

switch

switch i {
case 0:
    fmt.Print("zero...")
// 多个case值
case 1, 2:
    fmt.Print("one two...")
default:
    fmt.Print("no match...")
}

// 可以条件中创建变量
switch x := 11; {
case x > 20:
    fmt.Println("x>20")
case x > 10:
    fmt.Println("x>10")
    // 正常匹配到退出
    // 使用fallthrough关键词转入下一个case
    fallthrough
case x > 5:
    fmt.Println("x>5")
default:
    fmt.Println(x)
}

for

// 简单循环
sum := 0
for i := 1; i <= 100; i++ {
    sum += i
}

// while循环
var num int64
for num != 5 {
    num = rand.Int63n(15)
    fmt.Println(num)
}

// 死循环
var num int32
for {
    fmt.Print("Writting inside the loop...")
    if num = rand.Int31n(10); num == 5 {
        fmt.Println("finish!")
        // break 关键词跳出循环
        break
    } else if num > 9 {
        fmt.Println("num > 9")
        // continue 跳出当前迭代
        continue
    }
    fmt.Println(num)
}

// 遍历
for key := range items {
    fmt.Println(key)
}
for key, value := range items {
    fmt.Println(key, value)
}

参考