这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
1.1 什么是go语言
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
1.2 哪些公司在使用Go语言
1.3 字节跳动为什么会全面拥抱Go语言
- 最初使用的 Python,由于性能问题换成了 Go
- C++ 不太适合在线 Web 业务
- 早期团队非 Java 背景
- 性能比较好
- 部署简单,学习成本低
- 内部 RPC 和 HTTP 框架的推广
2.1 环境安装
安装 Golang
配置集成开发环境
可以使用 Vscode 安装 go 插件或者使用 Goland
2.2 基础语法
1. Hello World
package main表示此文件属于main包,其也是程序的入口import下面是main函数里面调用了fmt.Println输出hello,world
2. 变量
- Go是强类型语言,每个变量都有自己的类型。常见的类型有 字符串型 整数型 浮点型 布尔型,
- 其中字符串属于内置类型可以通过 “+” 来拼接 如上图中的
go := a + "foo" - 变量声明有两种方式:
- 通过
var var_name type = value其中 type 可以省略 go 会根据上下文推断类型 - 通过
var_name := value
-
常量声明就是把
var编程const,根据上下文自动判断类型
3.if else
- 和其他语言有区别的地方是
if后面的判断条件无需加()例如:if 7%2 == 0 {} - 与C和C++不同的是
if后面必须使用大括号{}
4.循环
Go语言没有while、do while循环,只有for循环
//这种for里面什么都不写称为死循环
for {
fmt.Println("loop")
break
}
//也可以使用经典的 C 循环
for j := 7; j < 9; j++ {
fmt.Println(j)
}
//可以使用continue继续循环,也可以使用break跳出循环
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
5.switch
- golang里的分支结构看起来和
C、C++类似,同样的switch后面的变量也无需括号() - 有个很大不同点在
c++中switch case不显式加break的话会继续跑完剩下的case语句;而在golang里面是不需要加的,就像上图中的执行完第15行会直接跳到24行 - golang的switch更加强大,case后面可以是:
变量、结构体甚至可以取代任意的if语句
t := time.Now()
//switch后面不加变量
switch {
//在每个case后面写条件分支,相比多个if else 嵌套跟家通俗易懂
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
6.数组
- 数组是个具有
编号且长度固定的元素序列比如 :var a [5]int是一个可以存放5个int类型的元素数组a,[5]表示。数组的长度索引值是从0开始的 - 数组的创建方式:
//1.先声明再写入值
var a [5]int
a[4] = 100
//2.直接写入
b := [5]int{1, 2, 3, 4, 5}
- 二维数组的创建方式:
var towD [2][3]int其本质是一个包含多个一维数组的一维数组 - 数组可以很方便的存取或读取特定索引上的值,不过在真实的业务代码里面,我们很少直接使用数组,因为它长度固定,更多用的是
切片。
7.切片
切片不同于数组可以任意更改长度,然后也有更多丰富的操作。
- 比如说我们可以用
make来创建一个切片s := make([]string, 3)也可以直接赋值good := []string{"g", "o", "o", "d"} - 可以像数组一样去存取和读取
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:",s[2]) //c
fmt.Println("len:",len(s)) //3
- 使用
append来追加元素,这里注意append的用法,必须把append的结果赋值给原数组。因为slice的原理实际上是它有一个它存储了一个长度和一个容量,加一个指向一个数组的指针,在你执行append操作的时候,如果容量不够的话,会扩容并返回新的slice。
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
- 在创建slice的时候也可以初始化指定他的长度 例如:
c:=make([]string,3) - 使用
copy来拷贝另一个slice 例如:copy(c, s) - slice拥有像
python一样的切片操作,比如fmt.Println(s[2:5])代表取出第二个到第五个位置的元素,不包括第五个元素,不同于python的是这里不支持负数索引
8.map
- 在其它语言里它可以叫做哈希或者字典。
map是实际使用过程中最频繁用到的数据结构。 - 我们可以使用
make来创建一个空map,m : = make(map[string]int)其中有两个类型,第一个是key的类型string,另一个是key的value的类型即int - 可以从里面存储或者取出键值对。可以用
delete删除键值对
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["unknown"]) // 0
delete(m, "one") //删除键值对
- 在从map里读取键值对的时候可以在后面接一个ok用来获取这个map里是否有这个key存在
r, ok := m["unknown"]
fmt.Println(r,ok) //0 false
- golang的map是完全无序的,遍历的时候不会按照
字母顺序,也不会按照插入顺序输出,而是随机顺序。
9.range
- 对于一个
slice或者一个map的话,我们可以用range来快速遍历,这样代码更加简洁。range遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值,如果我们不需要索引的话,可以用下划线来忽略。
for k, v := range m {
fmt.Println(k, v) // a A; b B
}
for _, v := range m {
fmt.Println("value", v) // value A; value B
}
10.函数
- 这是一个golang里面最简单的实现两个变量相加的函数。不同与其他语言,变量类型是
后置的。
func add(a int, b int) int {
return a + b
}
- golang里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok //v返回结果,ok返回的信息
}
11.指针
- go里面也支持指针,相比于C和C++里面的指针,支持的操作有限。指针的一个主要用途就是对
传入的参数进行修改。比如下面这个:试图把一个变量n +2.但是这种写法是无效的。因为传入函数的参数实际上是一个拷贝,这个+2是对那个拷贝进行了+2,并不起作用。
func add2(n int) {
n += 2
fmt.Println(n) //7
}
- 如果我们需要起作用的话需要把那个类型写成
指针类型,那么为了类型匹配,调用的时候会加一个&符号
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) //5
add2ptr(&n)
fmt.Println(n) //7
}
12.结构体
- 结构体是带
类型的字段的集合。比如下面user结构包含了两个字段,name和password。
type user struct {
name string
password string
}
- 我们可以用
结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。
a := user{name: "wang", password: "1024"}
- 也可以用
键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。
b := user{"wang", "1024"}
c := user{name: "wang"}
同样的结构体我们也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销
//无指针
func checkPassword(u user, password string) bool {
return u.password == password
}
//指针
func checkPassword2(u *user, password string) bool {
return u.password == password
}
func main() {
//传入的a为上图示代码的a
fmt.Println(checkPassword(a, "haha"))//false
fmt.Println(checkPassword2(&a, "haha"))//false
}
13.结构体方法
- 在golang里面可以为结构体去定义一些方法,类似于其他语言里的类成员方法/函数。
- 比如上图所示,我们把上面一个例子的
checkPassword的实现从一个普通函数,改成了结构体方法,这样用户可以像a.checkPassword("xx")这样去调用。具体的语法如下(就是把第一个参数,加上括号,写到函数名称前面):
//结构体作为传入函数参数
func checkPassword(u user, password string) bool {
return u.password == password
}
//将结构体放到func 后 方法名前 就构成了结构体方法
func (u user) checkPassword(password string) bool {
return u.password == password
}
- 在实现结构体的方法的时候也有两种写法,一种是
带指针,一种是不带指针。
//不带指针
func (u user) checkPassword(password string) bool {
return u.password == password
}
//带指针
func (u *user) checkPassword(password string) bool {
return u.password == password
}
- 两者区别:
- 带指针:就可以对这个结构体去做修改
- 不带指针:实际上操作的是一个拷贝,无法对结构体进行修改。
14.错误处理
- 错误处理在go语言里符合语言习惯的做法就是使用一个
单独的返回值来传递错误信息。不同于Java的异常处理,go语言的处理方式,能够清楚地知道哪个函数返回了错误,并且用简单的if else来处理错误。(go的错误处理的优于其它语言的地方) - 在函数里面,我们可以在函数的返回值类型里面,末尾加一个
error,就代表这个函数可能返回错误。同时函数也应多一个返回值
- 如果出现错误,
return nil, error - 如果没出错误,
return 结果, 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")
}
- 在调用这些返回错误的函数时,接收需要写两个变量比如下图中的
u和err - 调用完之后需要判断
err是否存在,如果存在需要做一些处理,比如打印异常信息然后退出返回函数,只有没有err才能得到真正的返回值,否则会出现空指针错误
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
15.字符串操作
- go语言里面的字符串操作,在标准库
strings包里有很多常用的字符串工具函数比如:
contains判断一个字符串里面是否有包含另一个字符串count字符串计数index查找某个字符串的位置join连接多个字符串repeat重复多个字符串replace替换字符串len()内置函数获取字符串的长度(一个字可能对应多个字符)
16.字符串格式化
- 字符串格式化,在标准的FMT包里面有很多的字符串格式相关的方法,比如
printf这个类似于C语言里面的printf函数。不同的是,在go语言里面,可以轻松使用%v来打印任意类型的变量,而不需要区分数字字符串。用%+v打印详细结果,%#v则更详细。使用%.2f来保留两位小数的浮点数
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
17.JSON处理
- go语言里面的JSON操作非常简单,对于一个已有的结构体,可以什么都不做,只要保证每个字段的第一个字符是
大写,也就是golang里的公开字段。
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
- 那么这个结构体就能用
json.Marshal去序列化,变成一个JSON的字符串。输出的时候需要string(xx)强制类型转换成字符串否则输出16进制的编码。
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
序列化之后的字符串也能够用json.unmarshal去反序列化到一个空的变量里面。这样默认序列化出来的字符串的话,它的风格是大写字符开头,而不是下划线,我们可以在后面用json:tag等语法去修改输出json结果里面的字段名,使其小写。
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
json.marshalIndent()简单的来说对json多了一些格式处理
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
//consle输出的结果如下
{
"Name": "wang",
"age": 18,
"Hobby": [
"Golang",
"TypeScript"
]
}
18.时间处理
- 在go语言里最常用的就是
time.now()来获取当前时间,然后你也可以用time.Date去构造一个带时区的时间,构造完的时间。 time.Format("2006-01-02 15:04:05")可以格式一个时间到字符串,括号里格式是规定好的time.parse("2006-01-02 15:04:05")与time.Format相反将一个字符串时间解析成时间
now := time.Now()
fmt.Println(now) //2023-01-15 20:25:46.9077674 +0800 CST m=+0.003648501
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t)// 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05"))// 2022-03-27 01:25:36
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
- 上面有很多方法来获取这个时间的年月日小时分钟秒,然后也能用
.Sub去对两个时间进行减法,得到一个时间段,时间段又可以去得到他有多少个小时,多少分钟,多少秒。
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds())//65 3900
- 在和某些系统交互的时候,我们经常会聊到时间戳。可以用
.UNIX来获取时间戳。
fmt.Println(now.Unix()) // 1648738080
19.数字解析
- 下面我们来学习一下字符串和数字之间的转换。在go语言当中,关于字符串和数字类型之间的转换都在
STR conv(string convert的缩写)这个包下。 - 我们可以用
parselnt或者parseFloat来解析一个字parseInt参数
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
- 可以用
Atoi把一个十进制字符串转成数字。 - 可以用
itoA把数字转成字符串。如果输入不合法,那么这些函数都会返回error
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
20.进程信息
- 在go里面,我们能够用
os.Argv来得到程序执行的时候的指定的命令行参数。比如我们编译一个二进制文件,command。后面接abcd来启动,输出的就是一个长度为5的slice,第一个成员代表二进制自身的名字。
// go run example/20-env/main.go a b c d
fmt.Println(os.Args)// [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
- 我们可以用
so.getenv来读取环境变量。so.setenv来写入环境变量
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
- 同过exec.command来快速启动子进程并获取其输入输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
总结
一、本堂课重点内容:
- 本堂课的知识要点有哪些?
switch、切片slice、map、函数、指针、结构体方法、进程信息
二、详细知识点介绍:
- 本堂课介绍了哪些知识点? 变量、ifelse、循环、switch、数组、切片、map、range、函数、指针、结构体以及方法、错误处理、字符串操作、字符串格式化、json处理、时间处理、数字解析、进程信息
三、实践练习例子:
- 有什么实践举例帮助理解知识点?
比如13-struct-method、11-point
四、课后个人总结:
- 本章有什么知识点不容易掌握?
结构体以及方法、进程信息、函数知识点 - 什么地方容易与其他内容混淆?
go的if不用括号而Java是需要的、go的传参是值拷贝要改变其值需要传入指针
五、引用参考:
- 我参考了哪些外部博客/笔记/文章?
- 文章中有什么地方是我参考引用了外部博客/笔记/文章的?
blog.csdn.net/judgejames/…