概述
本节课程主要分为四个方面:
- Go 语言简介
- Go 语言开发入门,包括开发环境配置、基础语法、标准库
- Go 实战,包括三个实战项目
课前部分主要罗列课程中涉及到的概念。对于不熟悉的概念,同学们可以提前查询预习;课中部分主要罗列每一部分的关键思路,帮助同学们跟上课程的进度;课后部分是一些问题,帮助同学们在课后梳理本课程的重点。
课前 (必须)
安装 Go 语言
- 访问 go.dev/ ,点击 Download ,下载对应平台安装包,安装即可
- 如果无法访问上述网址,可以改为访问 studygolang.com/dl 下载安装
- 如果访问 github 速度比较慢,建议配置 go mod proxy,参考 goproxy.cn/ 里面的描述配置,下载第三方依赖包的速度可以大大加快
配置 Go 语言开发环境
可以选择安装 VS Code , 或者 Goland ,对于 VS Code,需要安装 Go 插件
下载课程示例代码
- Windows 平台建议安装 git,其它系统自带,安装教程
- 打开 github.com/wangkechun/… 克隆课程示例项目
- 进入课程示例项目代码目录,运行
go run example/01-hello/main.go如果正确输出 hello world,则说明环境配置正确
【可选】 学习 Go 语言基础语法
空余时间阅读 Go语言圣经(中文版)
课堂内容
Go语言特性
- 高性能、高并发
- 丰富的标准库
- 完善的工具链
- 内置单元测试,辅助高效开发
- 静态链接:只需要包含一个编译后的可执行文件即可进行部署使用,c++需要链接动态链接库文件(.so文件),java则需要庞大的jar包。
- 快速编译:增量编译,速度快;支持交叉编译
- 跨平台
- 垃圾回收
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)
}
}