go语言学习路线图bytedance.feishu.cn/docs/doccn3…
Go语言基础
什么是go语言
go语言具有以下特性:
- 高性能、高开发:go语言有与c++,java媲美的性能,还引进了对高变化的支持。不需要像其他语言一样去寻找经过高度性能优化的第三方库来开发应用,只需要使用标准库或者任意基于标准库的第三方库即可开发高并发应用程序。
- 语法简单、学习曲线平缓:类似C语言,去掉了不需要的表达式括号,循环也只有for循环一种
举例:goland实现http静态服务器
package main
import (
"fmt"
"net/http"
)func main() {
http.Handle("/", http.FileServer(http.Dir("."))) //http.Dir:将字符串路径转换成文件系统
// http.FileServer:如果访问路径是目录,则列出目录内容,如果是文件则使用 serveContent() 方法输出文件内容。serveContent() 方法则是个读取文件内容并输出的方法
//把根目录也就是“/”目录映射出来
e := http.ListenAndServe(":8080", nil) // 监听 TCP 端口8080并提供路由服务
fmt.Println(e)
}
- 丰富的标准库:标准库有很高的稳定性和兼容性保障
- 完善的工具链:无论是编译,代码格式化,错误检查,帮助包管理,代码补充提示,都有对应的工具。go语言还内置了完整的单元测试框架,能够支持单元测试,性能测试,代码覆盖率,数据间帧检测,性能优化。这些都是保障代码能够正确和稳定运行的必备利器
- 静态链接:所有的编译结构默认都是静态链接,只需要拷贝编译之后的唯一一个可执行文件,不需要附加任何东西就可以部署运行。在现下的容器环境下运行,体积可以控制的非常小
- 快速编译:耗时短
- 跨平台:能在Windows,linux,Mac os操作系统运行,也能开发安卓ros软件。还能在路由器、树莓派运行。go语言有很方便的交叉编译特性,能够轻易的在笔记本上编译出二进制文件拷贝到路由器上运行
- 垃圾回收:可专注于业务逻辑,无需关心垃圾代码回收
字节跳动选择go语言
go入门
下载go语言,配置集成开发环境
验证:win+R输入cmd 输入go env验证成功
基础语法-helloworld
编译执行可以使用go run
编译成二进制可以使用go build
编译完成后 ./main.exe就可以运行
基础语法-变量
go语言是一门强类型语言,每一个变量都有它自己的变量类型
常见的变量类型包括 字符串 整数 浮点型、布尔型等
go 语言的字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串
在go语言里面,大部分运算符的使用和优先级都和 C 或者 C++ 类似,这里就不再概述
go语言变量声明方式有两种:
- var name=value 系统会自动推导变量的类型 eg:var a="initial"
或者显式写出变量类型 eg:var b int=1; var b,c int=1,2;var e float64; 2. 变量名 := 值 eg:f := float32(e) 3. 对于常量把var改成const即可。在goland里的常量没有确定的类型,根据上下文自动确定类型
基础语法-if else
与c不同点在于if后边没有括号,如果写了保存时候编译器会自动去掉括号;还有if条件后紧跟大括号,不能写在同一行
基础语法-for循环
for循环里什么都不写表示死循环,同样条件也没有括号;continue继续循环,break跳出循环
基础语法-switch分支结构
不同点在于c如果case不加break,会默认继续往下跑完所有的case分支;而goland不需要加break即可实现break功能;同时goland的switch功能更加强大,可以使用任意变量类型比如字符串、结构体,甚至可以用来取代任意的ifelse语句(switch不加任意变量)
基础语法-数组
数组就是一个具有编号且长度固定的元素序列。
对于一个数组,可以很方便地取特定索引的值或者往特定索引取存储值,然后也能够接去打印一个数组。不过,在真实业务代码里面,我们很少直接使用数组,因为它长度是固定的,我们用的更多的是切片存储数据。
var a [5]int
a[4] = 100
fmt.Println(a[4], len(a))arr := [5]int{1, 2, 3, 4, 5}
fmt.Println(arr) //[1 2 3 4 5]var two [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
two[i][j] = i + j
}
}
fmt.Println(two) //[[0 1 2] [1 2 3]]
基础语法-切片
切片不同于数组 切片可以任意更改长度,然后也有更多丰富的操作。比如说我们可以用make 来创建一个切片,可以像数组一样去取值,使用 append 来追加元素
注意 append 的用法的话,你必须把 append 的结果赋值为原数组。因为 slice片 的原理实际上是它存储了一个长度和一个容量,加一个指向一个数组的指针,在你执行 append 操作的时候,如果容量不够的话,会扩容并且返回新的slice.所以必须要赋值回去
slice make初始化的时候也可以指定长度,还可以copy片元素copy(c,a)把a片的元素复制给c
slice 拥有像 python 一样的切片操作,比如s[2:5]这个代表取出第二个到第五个位置的元素不包括第五个元素。不过不同于python,这里不支持负数索引
//切片
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)co := make([]string, len(s))
copy(co, s) //a,b,c,d,e,f
fmt.Println(s[2:5]) //c,d,e
fmt.Println(s[:5]) //a,b,c,d,e
fmt.Println(s[2:]) //c,d,e,f
基础语法-map
map,在其他编程语言里面,它可能可以叫做哈希或者字典。
map 是实际使用过程中最频繁用到的数据结构
make创建空map,需要两个类型,一个代表key,一个代表value map[string]int
写完后可以通过方括号语法写入和读取kv对我们可以从里面去存储或者取出键值对。可以用 delete 从里面除键值对。delete(map,key)
goland的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) //map[one:1 two:2]
fmt.Println(m["sss"]) //0//从map里读取KV对的时候,可以加个ok来判断键值对是否存在
r, ok := m["sss"]
fmt.Println(r, ok) //0 false
ri, ok := m["one"]
fmt.Println(ri, ok) //1 true//delete(m, "one")
//map是乱序的
for s2 := range m {
fmt.Println(s2) //two one
}m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
//map[one:1 two:2] map[one:1 two:2]
fmt.Println(m2, m3)
基础语法-range
对于一个 slice 或者一个 map 的话,我们可以用 range 来快速遍历,这样代码能够更加简洁。
range 遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。 如果我们不需要索引的话,我们可以用下划线来忽略。
//range遍历
nums := []int{1, 2, 3}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "nums:", num)
}
}
基础语法-函数
函数方法形式 func 方法名(参数 参数类型) 方法返回类型{ 方法体 } Golang 和其他很多语言不一样的是,变量类型是后置的。 Golang 里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。
fmt.Println(add(1, 2))
fmt.Println(exists(map[string]string{"a": "A"}, "a"))
}// int变量类型后置
func add(a int, b int) int {
return a + b
}// 第一个值返回真正的值,第二个返回错误信息提示是否存在
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k] //这里不用冒号 定义和赋值不一样
return v, ok
}
//3
//A true
基础语法-指针
go里面也支持指针。当然,相比 C 和 C++ 里面的指针,支持的操作很有限。
指针的主要用途就是对于传入参数进行修改。
这个函数试图把一个变量+2。但是单纯像上面这种写法其实是无的。因为传入函数的参数实际上是一个拷贝,那也说这个+2,是对那个拷贝进行了+2,并不起作用。
如果我们需要起作用的话,那么我们需要把那个类型写成指针类型,那么为了类型匹配,调用的时候会加一个 & 符号。
对一个变量自增add的函数如下:
n := 5
add2(&n)
fmt.Println(n) //7
}
func add2(n *int) {
*n += 2
}
基础语法-结构体
结构体的话是带类型的字段的集合
比如这里 user 结构包含了两个字段,name 和 password。我们可以用结构体的名称初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用这种键值的方式去指定初始值,这样可以只对一部分字段进行初始化。
结构体也能作为参数,同样的,结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体拷贝的开销。
hh := user{name: "sss", pwd: "sss"}
hhh := user{name: "ss"}
hhh.pwd = "sss"
var hhhh user
hhhh.name = "s"
hhhh.pwd = "ss"
fmt.Println(hh)
基础语法-结构体方法
在 Golang 里面可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。这样用户可以像 a.checkPassword(“xx”) 这样去调用。具体的代码修改,就是把第一个参数,加上括号,写到函数名称前面。
func (结构体) 方法名(参数 参数类型) 方法返回类型{ 方法体 }
例如:func (u user) check(pwd string) bool{ return u.pwd==pwd } //修改密码 func (u *user) check(pwd string){ u.pwd==pwd }
在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。这个它们的区别是如果你带指针的话,就可以对这个结构体去做修改。如果不带指针的话,实际上操作的是一个拷贝,就无法对结构体进行修改。
基础语法-错误处理
错误处理在 go 语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息
不同于 Java 使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。在函数里面,我们可以在函数的返回值类型里面,后面加一个 error, 就代表这个函数可能会返回错误。那么在函数实现的时候,return 需要同时 return 两个值,如果出现错误的话,那么可以* return nil 和一个 error*。如果没有的话,那么返回原本的结果和 nil。
func main() {
v, err := findUser([]user{{"sss", "111"}}, "sss")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(&v)//0x9c0a158
fmt.Println(v.name)//
//另一种方式处理错误
if v, err := findUser([]user{{"sss", "111"}}, "li"); err != nil {
fmt.Println(err)//找不到user
return
} else {
fmt.Println(&v)
}
}
fmt.Println(v) //&{sss 111}
基础语法-字符串操作
在标准库 strings 包里面有很多常用的字符串工具函数,比如 contains 判断一个字符串里面是否有包含另一个字符串, count字符串计数, index 查找某个字符串的位置。 join 连接多个字符串 repeat 重复多个字符串 replace 替换字符串。
一个中文对应3个字符
基础语法-字符串格式化
在标准库的 FMT 包里面有很多的字符串格式相关的方法,比如 printf 类似于 C 语言里面的 printf 函数,不同的是,在go语言里面的话,你可以很轻松地%v 来打印任意类型的变量,而不需要区分数字字符串。
你也可以用 %+v 打印详细结果.%#v 则更详细。
type point struct {
x, y int
}
p := point{1, 2}
fmt.Printf("s=%v\n", p)
fmt.Printf("p=%+v\n", p)
fmt.Printf("p=%#v\n", p)
fmt.Printf("%.2f\n", 3.1415926)
//s={1 2}
//p={x:1 y:2}
//p=main.point{x:1, y:2}
//3.14
基础语法-json处理
go语言里面的JSON 操作非常简单,对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用JSON.marshaler 去序列化,变成一个JSON 的字符串(需要转成string类型,不然得到的是十六进制编码)
序列化之后的字符串也能够用JSON.unmarshaler 去反序列化到一个空的变量里面
这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。
我们可以在后面用json tag 等语法来去修改输出JSON 结果里面的字段名
type userinfo struct {
Name string
Age intjson:"age"
Hobby []string
}a := userinfo{Name: "ww", Age: 5, Hobby: []string{"ss", "sss"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
/* fmt.Println(buf)
fmt.Println(string(buf))*/
//[123 34 78 97 109 101 34 58 34 119 119 34 44 34 97 103 101 34 58 53 44 34 72 111 98 98 121 34 58 91 34 115 115 34 44 34 115 115 115 34 93 125]
//{"Name":"ww","age":5,"Hobby":["ss","sss"]} 注意:age变小写了 因为使用了json的tagbuf, err = json.MarshalIndent(a, "", "\t") //该方法为json格式化输出
if err != nil {
panic(err)
}
fmt.Println(string(buf))
// {
// "Name": "ww",
// "age": 5,
// "Hobby": [
// "ss",
// "sss"
// ]
//}
var b userinfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b)
//main.userinfo{Name:"ww", Age:5, Hobby:[]string{"ss", "sss"}} 大写字母开头
基础语法-时间处理
在go语言里面最常用的就是 time.now来获取当前时间,也可以用 time.date 去构造一个带时区的时间,构造完的时间。上面有很多方法来获取这个时间点的年月日小时分钟秒,然后也能用t2.sub(t) 去对两个时间进行减法(t2-t),得到一个时间段。时间段又可以去得到它有多少小时,多少分钟、多少秒。
t.format用来格式化,此时不再是“YYYY”这些,而是一个固定的值2006-。。。 time.parse(固定的时间字符串,字符串)把字符串解析成时间
在和某些系统交互的时候,我们经常会用到时间戳。可以用.UNIX 来获取时间戳
基础语法-数字解析
在 go 语言当中,关于字符串和数字类型之间的转换都在 strconv 这个包下,这个包是 string convert 这两个单词的缩写。我们可以用 parselnt 或者 parseFloat 来解析一个字符串。 parseint 参数我们可以用 Atoi 把一个十进制字符串快速转成数字。可以用itoA 把数字转成字符串
ParseInt("111",10,64) 10进制(还有16进制,传0代表自动推测)64位精度的整数
如果声明字符串不为数字,会报错:声明不合法
基础语法-进程信息
在 go 里面,我们能够用 os.args 来得到程序执行的时候的指定的命令行参数
go run .../main.go a b c d 比如我们编译的一个二进制文件,command。 后面接 abcd 来启动,os.args输出就是会是一个长度为 5的 slice ,第一个成员代表二进制自身的名字我们可以用 so.getenv来读取环境变量exec