Go 语言入门与实战课程笔记 | 青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第 1 天
基础入门
基础环境配置
安装
go语言的sdk 可以在官网下载,Get Started - The Go Programming Language (google.cn)。下载最新版即可,当前为1.19.5
go语言 开发环境可选vscode,Golang.后者为jetrain的付费IDE,功能更加完善。
环境配置
常见变量:(明后补全)
- GOROOT:
- GOPATH:
- GOPROXY
基础语法学习
基础数据类型
go的数据类型有如下几种:
- 整型:
包括
int8,int16,int32,int64,uint8,uint16,unint32,unint64 - 浮点型: 包含
float32和float64float64:64位浮点数 相当于Java中的doublefloat32: 32位浮点数 相当于普通的float
- 字符串:
string - 布尔值:
布尔型的值只可以是常量 true 或者 false。一个简单的例子:
var b bool = true。 - 若干派生类型
变量
Go语言的变量声明有三种常用的方法:
// 常见的创建变量格式
var identifier type
// var 可直接定义变量
var a = "initial"
// 后置类型可省略,var初始化时可以自动推导类型
var num int = 4
// 相同类型的变量可以在同一行声明并初始化
var b, c int = 1, 2
var d = true
// 如果未为一个变量初始化,则必须显式指定变量类型,此时,变量会被以初始值自动初始化:
var e float64
// 海象运算符:不需要var,可自动推导类型并赋值
// 等价于 var f = 3.2
f := float32(e)
常量
常量用const修饰,并且不需要声明类型,可由上下文自动推导
const variable = value
流程控制
条件语句
与其他语言的主要区别:
- golang中的IF语句不需要括号,
- IF语句后必须接花括号,即使是对于单行语句块,也必须添加括号,而不能像其他语言那样直接省略。
循环
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
由上述代码,go中的循环相比其他语言简化了许多。while,do全部统一为for,且for语句内不需要括号,
switch
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
相比 C 或者 C++ , go语言里面的 switch 功能更强大。可以使用任意的变量类型,甚至可以用来取代任意的 if else 语句。你可以在 switch 后面不加任何的变量,然后在 case 里面写条件分支。这样代码相比你用多个 if else 代码逻辑会更为清晰。
需要注意的是,与其他语言恰好相反,switch 语句中每个 case 的 break 是隐式存在的,也就是说,每个 case 的逻辑会在执行完毕后立刻退出,而不是跳转到下一个 case。
要想跳转到下一个 case,则应该使用 fallthrough 关键字
数组
数组就是一个具有编号且长度固定的元素序列。 比如这里的话是一个可以存放 5 个int元素的数组 A 。
var a [5]int
数组初始化,此处创建了有5个元素,内部元素分别为12345的数组。
b := [5]int{1, 2, 3, 4, 5}
对于一个数组,可以很方便地取特定索引的值或者往特定索引取存储值,然后也能够直接去打印一个数组。不过,在真实业务代码里面,我们很少直接使用数组,因为它长度是固定的,我们用的更多的是切片。
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
fmt.Println(b)
var twoD [2][3]int
切片
切片可以粗浅理解为更灵活的动态数组。比如说我们可以用 make 来创建一个切片,可以像数组一样去取值,使用 append 来追加元素。
s := make([]string, 3)
上述就创建了一个长度为3,容量为3的字符串slice。这其中slice的长度和容量与Java中ArrayList的size与capacity概念比较类似。
注意 append 的用法的话,你必须把 append 的结果赋值为原数组。因为 slice 的原理实际上是存储了长度和容量,加指向数组的指针,在append时,如果容量不够的话,会扩容并且返回新的 slice。
slice 初始化的时候也可以指定长度。
slice 拥有像 python 一样的切片操作,索引区间是左闭右开,如果左或者右不写则表示到最远端。不过不同于python,不支持负数索引
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
// append 向尾部添加元素,返回值必须赋值给原切片
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
//可以使用 copy 方法将一个切片内的元素复制到另一个切片中
copy(c, s)
fmt.Println(c) // [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]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
map
map 中文译作映射,是无序的键值对集合。
m := make(map[string]int) 声明了一个键(key)为 string 类型,值(value)为 int 类型的 Map。
map元素的访问与赋值如下:
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"])
map[key]可以返回value和bool类型的变量两个值,这也是golang的特殊之处,函数天生支持多个返回值。
如果map[key]中的key在map中不存在,那么访问会返回默认值,如下代码,
v, err := m["hello"]
fmt.Println(v, err)
// 执行结果为0 false
map可定义时提前初始化,基本逻辑与其他语言类似,类型后加花括号,
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
删除map中的元素,基本格式为delete(map,key)
delete(m, "one")
range
range的概念是为了更方便地遍历数组array和map。
range函数用在数组中将返回两个参数,分别是索引与对应位置的值。如果索引不需要,则可以用下划线替代。(golang中如返回参数的err不需要,可以统一用下划线接收,相当于垃圾桶)
range 处理map则会循环输出key和value
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
函数,结构体,指针
函数
golang中的函数与其他语言主要区别在于 类型后置,且可以返回多个参数。返回值写在参数括号后面,func关键字则相当于void。
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
指针
go的指针特性有限,主要作用是对传入参数进行修改。一般情况下传参默认传值,
基本操作:使用 *type 声明一个指针变量,使用 * 对一个变量进行解引用,使用 & 获取一个变量的指针(引用)。
默认情况下,Go 的方法传参均为传值,而不是传引用,如果不传入指针而直接传入一个值的话,则方法实参会被复制一份再传入。
结构体
go中结构体与c类似,可以用 type className struct{}来定义结构体。
构造时需要传入每个字段的初始值,也可以采用键值对的形式初始化一部分字段。
在传入函数作为参数时可使用结构体的指针,这样可以实现修改结构体参数。
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
结构体方法
结构体方法类似其他语言中的类成员函数,对应的类及参数名写在func后面,同时参数可以选是否带指针,带指针的话则可以修改结构体参数内部的信息,否则只是拷贝参数。
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
错误处理
不同于 Java 自家家使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。
在函数里面,我们可以在那个函数的返回值类型里面,后面加一个 error, 就代表这个函数可能会返回错误。
那么在函数实现的时候, return 需要同时返回两个值,要么就是如果出现错误的话,那么可以 return nil 和一个 error。如果没有的话,那么返回原本的结果和 nil。
字符串操作
string字符串相关操作,需要导入string包。
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
字符串格式化
fmt.Printf("s=%v\n", s)
格式化输出类似C语言中的printf函数,但是不需要格式控制符 比如%s来控制输出字符串或者数字。只需要%v即可,%+v可以打印详细结果。