Go语言上手 |青训营笔记

102 阅读5分钟

概述

本节课程主要分为四个方面:

  1. Go 语言简介
  2. Go 语言开发入门,包括开发环境配置、基础语法、标准库
  3. Go 实战,包括三个实战项目

课前部分主要罗列课程中涉及到的概念。对于不熟悉的概念,同学们可以提前查询预习;课中部分主要罗列每一部分的关键思路,帮助同学们跟上课程的进度;课后部分是一些问题,帮助同学们在课后梳理本课程的重点。

课前 (必须)

安装 Go 语言

  1. 访问 go.dev/ ,点击 Download ,下载对应平台安装包,安装即可
  2. 如果无法访问上述网址,可以改为访问 studygolang.com/dl 下载安装
  3. 如果访问 github 速度比较慢,建议配置 go mod proxy,参考 goproxy.cn/ 里面的描述配置,下载第三方依赖包的速度可以大大加快

配置 Go 语言开发环境

可以选择安装 VS Code , 或者 Goland ,对于 VS Code,需要安装 Go 插件

下载课程示例代码

  1. Windows 平台建议安装 git,其它系统自带,安装教程
  2. 打开 github.com/wangkechun/… 克隆课程示例项目
  3. 进入课程示例项目代码目录,运行 go run example/01-hello/main.go 如果正确输出 hello world,则说明环境配置正确

【可选】 学习 Go 语言基础语法

空余时间阅读 Go语言圣经(中文版)

课堂内容

Go语言特性

  1. 高性能、高并发
  2. 丰富的标准库
  3. 完善的工具链
  4. 内置单元测试,辅助高效开发
  5. 静态链接:只需要包含一个编译后的可执行文件即可进行部署使用,c++需要链接动态链接库文件(.so文件),java则需要庞大的jar包。
  6. 快速编译:增量编译,速度快;支持交叉编译
  7. 跨平台
  8. 垃圾回收

Go语法和C++的不同

switch语法

package day01_go_programming_basic

import (
	"fmt"
	"time"
)

func main() {
	a := 2
	switch a {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	default:
		fmt.Println("other")
	}

	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("It's before noon!")
	default:
		fmt.Println("It's after noon!")
	}
}

c++中的case语句如果不加break是会继续运行下方的case语句的。但是go中的case语句不需要加break语句,一个case运行结束后是不会继续向下运行的。

切片

在go中数组实际上没有切片常用。切片的原理是:它存储了一个长度、一个容量和一个指向数组的指针,当append进去值后如果超过了容量,就会发生扩容,因此append方法有返回值。

与python不同的是go语言中的切片操作不支持负数索引。

// 切片
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get : ", s[2])
fmt.Println("len : ", len(s))

s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)

c := make([]string, len(s))
copy(c, s)
fmt.Println(c)

fmt.Println(s[2:5])
fmt.Println(s[:5])
fmt.Println(s[2:])

good := []string{"gddd", "o", "o", "d"}
fmt.Println(good)

map

golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是顺序输出。

// map
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m)           // map[one:1, two:2]
fmt.Println(len(m))      // 2
fmt.Println(m["one"])    // 1
fmt.Println(m["unknow"]) // 0

r, ok := m["unknow"]
fmt.Println(r, ok)

delete(m, "one")

m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)

range

// range
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
	sum += num
	if num == 2 {
		fmt.Println("index: ", i, "num: ", num)
	}
}
fmt.Println(sum)

mm := map[string]string{"a": "A", "b": "B"}
for k, v := range mm {
	fmt.Println(k, v)
}
for k := range mm {
	fmt.Println("key", k)
}

函数

golang的变量类型是后置的。并且,golang里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都有两个值,第一个是真正的返回值,第二个值是一个错误信息。

func exists(m map[string]string, k string) (v string, ok bool) {
	v, ok = m[k]
	return v, ok
}

func main() {
	// function
	v, ok := exists(map[string]string{"a": "A"}, "a")
	fmt.Println(v, ok)
}

指针

go里面也支持指针。相比于c和c++的指针,支持的操作很有限。指针的一个主要作用就是对传入的参数进行修改。

func add2(n int) {
	n += 2
}

func add2ptr(n *int) {
	*n += 2
}

func main() {
	n := 5
	add2(n)
	fmt.Println(n)
	add2ptr(&n)
	fmt.Print(n)
}

结构体

我们可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以使用键值对的方式去指定初始值。

作为函数参数传入的结构体也可使用指针,这样可以避免大结构体传入的开销,也可以在函数中实现对结构体内容的修改。

type user struct {
	name     string
	password string
}

func checkPassword(u user, password string) bool {
	return u.password == password
}

func checkPassword2(u *user, password string) bool {
	return u.password == password
}

int main() {
	// struct
	stra := user{name: "wang", password: "1024"}
	strb := user{"wang", "1024"}
	strc := user{name: "wang"}
	strc.password = "1024"
	var d user
	d.name = "wang"
	d.password = "1024"

	fmt.Println(stra, strb, strc, d)
	fmt.Println(checkPassword(stra, "haha"))
	fmt.Println(checkPassword2(&stra, "haha"))
}

结构体方法

golang中,可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。比如,我们把上面一个例子的checkPassword的实现,从一个普通函数,改成结构体方法。这样用户可以像a.checkPassword(”xx”)这样去调用。具体代码修改,就是把第一个参数,加上括号,写到函数名称前面。

在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。这个他们的区别的话是说如果你带指针的话,那么你可以对这个结构体进行修改;如果你不带指针的话,那你实际上操作的是一个拷贝,你就无法对结构体进行修改。

func (u user) checkPassword(password string) bool {
	return u.password == password
}

func (u *user) resetPassword(password string) {
	u.password = password
}

int main() {
	usera := user{name: "wang", password: "1024"}
	usera.resetPassword("2048")
	fmt.Println(usera.checkPassword("2048"))
}

错误处理

只要在函数的参数列表中传入一个err类型的参数,即可说明这个函数有可能返回错误。

那么在函数实现的时候,return需要同时return两个值,要么就是如果出现错误的话,那么可以return nil和一个error。如果没有的话,那么返回原本的结果和nil。

func findUser(users []user, name string) (v *user, err error) {
	for _, u := range users {
		if u.name == name {
			return &u, nil
		}
	}
	return nil, errors.New("not found")
}

int main() {
	u, err := findUser([]user{{"wang", "1024"}}, "wang")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(u.name)

	if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
		fmt.Println(err)
		return
	} else {
		fmt.Println(u.name)
	}
}