青

一言

Go 语言学习笔记

Go 语言学习笔记

Go 笔记

基础

常量、变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

// 全局常量
const pi = 3.1415
const version = 0.1
const n10 = 42
const n8 = 0600 // 八进制
const n16 = 0x80 // 十六进制
const ff = 07.1 // 十进制(浮点数)
const ne = 1e4 // 科学计数法

const (
ok = 1
msg = "OK"
)

const (
running = false
finished
success
)

// 未初始化则等于上一行声明

const (
number0 = iota
number1
_ = "Hello"
number2 = iota
) // iota为常量声明计数器,每次从 0 计数,n 分别为 0,1,3

const (
a, b, c = iota, iota, iota
) // a = b = c = 0

// 全局变量
var (
gba int = 1
gbb float64 = 2.1
gbc string = "OK"
)

func main() {
// 局部常量
const loa = 1
const b = "1"

// 局部变量
var c int = 2

// 局部变量声明后必须使用,否则编译报错
println(c)

var _ string = "_匿名变量不会被处理,也不占用内存"

}

基本数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

func main() {
var (
// 局部常量
a int = 1 // int 长度视操作系统而定
b int32 = 2 // 指定位数
c float64 = 3.14
d uint = 5 // 无符号整数
e string = "Hello\""
e2 rune = 'B' //实质是 int32
f bool = false
g byte = 65
h complex64 = 2 + 3i // 复数
)

fmt.Println(a, b, c, d, e, e2, f, g, h)
}
/*
1 2 3.14 5 Hello" 66 false 65 (2+3i)
*/

注:

  • 字符串是常量,可以用索引访问,但不可修改,基于字符串创建的切片同样不可修改
  • 字符串转 []bytes 切片慎用
  • 字符串结尾不包含空字符

复合数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
a := 0
var b *int = &a // 指针
var c [2]int = [2]int{1, 2} // 数组
var d []int = []int{1, 2, 3} // 切片
var e map[int]string // 字典
var f chan int // 通道
g := struct {
id int
name string
}{1,"张三"}// 结构体
var h interface {
func1(int) string
func2(string) (int, string)
} // 接口

fmt.Println(a, b, c, d, e, f, g, h)
}

/*
0 0xc0000a2058 [1 2] [1 2 3] map[] <nil> {0 } <nil>
*/
  • 结构体访问字段使用 . 标识符,没有 ->标识符
  • 不支持指针运算
  • 函数允许返回局部变量地址以实现“栈逃逸”

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数组声明
var a [2]int // 默认元素均为 0,不常用

// 数组初始化
b := [...]int{1, 2, 3, 4, 5} // [...] 后跟初始化列表
c := [3]int{2, 3, 4}

d := [10]int{1: 4, 5: 8} // 指定总长度,通过索引初始化,没有指定索引的为 0
e := [...]int{1: 5, 5: 6} // 通过索引初始化,长度由最后一个索引确定

// 数组操作
_ = a[0]
for index, value := range e {
println(index, ":", value)
}

_ = len(e) // 数组长度
  • 数据不可追加元素
  • 数组赋值或作为参数均为值的拷贝
  • 不同数组长度是不同数据类型

切片(变长数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 切片创建
// 通过数组创建
a := []int{1, 2, 3, 4, 5, 6, 7}
b := a[0:4]

// 通过 make 创建
c := make([]int, 5)
d := make([]int, 10, 20) // 长度10,容量20

// 切片使用
_ = a[0]
_ = len(a) // 长度
_ = append(a, 3) // 追加元素,若容量不足,cap 会被扩展
_ = cap(a) // 容量
e := make([]int, 10)
copy(e, b) // 复制,不会超过容量

// 字符串切片
s := "Hello,World!"
s2 := []byte(s)
s3 := []rune(s)

字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 创建字典
// 直接创建
a := map[int]string{1: "张三", 4: "李四"}

// 使用 make 创建
b := make(map[string]int, 10) // 容量为 10
b["A"] = 1

// 字典操作
_ = a[1] // 读
a[3] = "王五" // 新增
delete(a, 3) // 删除字典项,参数为字典的键
_ = len(a) // 字典长度

// 字典修改
// 修改值必须整体修改,不能通过引用修改
type User struct {
name string
age int
}
c := make(map[int]User)
c[1] = User{name: "张三", age: 20}
// 错误的修改:c[1].age = 21
c[1] = User{name: "张三", age: 21}
println(c[1].age)

a[3] = "1"
// 字典遍历(不保证顺序)
for i, v := range a {
println(i, ":", v)
}

结构体

结构体中可存放任意类型,结构体在存储空间上是连续的,按声明的顺序存放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义结构体
type Student struct {
id int
name string
done bool
}

// 初始化
a := Student{
id: 1,
name: "张三",
done: true,
}

a.name = "李四"
println(a.name)

判断

if 语句进行判断,if 后的条件不需要小括号,{必须和 if 或 if else 放在一行

1
2
3
4
5
6
7
if h < 12 {
println("早上好")
} else if h < 16 {
println("中午好")
} else {
println("晚上好")
}

Switch

循环

for 初始化;循环判断条件;每次循环执行{}

1
2
for key,value := range map{}
for index,value := range arry{}

跳转

goto 只能在函数内部跳转,不能跳转到内部作用域

break 用于跳出 for swtch 和 select 语句,默认跳出最内层,可配合标签使用跳出多层

continue 用于跳出当前 for 循环并开始步进执行和下一次循环,可配合标签使用

return 用于函数和方法退出,也会触发跳转

Stdin/out/err

可用操作文件的方式对基本输入输出进行读写

1
2
3
4
5
6
func main() {
os.Stdout.Write([]byte("Hello,World!"))
bf := make([]byte, 10)
os.Stdin.Read(bf)
println(bf)
}

函数

特点:

  • 支持多值返回
  • 支持闭包
  • 支持可变参数
  • 不支持默认参数
  • 不支持重载
  • 不支持命名函数嵌套定义

函数定义

函数名首字母大写可供其他包访问,小写仅能在相同包内访问(共有,私有函数)

func 函数名(输入)(返回值){
    函数体
}

当返回值非命名且只有一个是可以省略返回值的括号

若命名返回值则 return 可不带返回参数

函数的调用是值的拷贝,意味若不使用指针则函数不会对输入进行修改

1
2
3
4
5
6
7
8
func main() {
println(add(1, 2))
}

func add(a, b int) (sum int) {
sum = a + b
return
}

不定参数

所有不定参数的类型必须相同,不定参必须是最后一个参数,在函数体内相当于切片(若将切片传入函数,则需要在切片名后面加...,如add(a...)

1
2
3
4
5
6
7
8
9
10
func main() {
println(add(1, 2, 3, 4, 5))
}

func add(vs ...int) (sum int) {
for _, v := range vs {
sum += v
}
return
}

函数签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Calc func(int, int) int

func add(a, b int) int {
return a + b
}

func sub(a, b int) int {
return a - b
}

func doCalc(f Calc, a, b int) int {
return f(a, b)
}

func main() {
println(doCalc(add, 1, 2))
println(doCalc(sub, 19, 2))
}
/*
3
17
*/

有名函数和匿名函数

1
2
3
4
5
6
7
8
9
// 有名函数
func add(a, b int) int {
return a + b
}

func main() {
f := add
println(f(1,2))
}

匿名函数:返回函数的函数所返回的函数为匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
// 匿名函数
func add() func(int, int) int {
f := func(a, b int) int {
return a + b
}
return f
}

func main() {
f := add()
println(f(1, 2))
}

延迟调用

1
2
3
4
5
6
7
8
func main() {
defer println("Hello")
println("World")
}
/*
World
Hello
*/

defer 后必须是函数或者方法的调用,不能是语句,注册时使用的是值的拷贝,意味着若不使用指针,则执行时输入的值为调用是的值的拷贝,不会受到后续影响,也不会影响输入内容

位于 return 后的 defer 不会被执行

非必要应避免在循环中使用 defer

当主动调用 os.Exit 时,已注册的 defer 不会被执行

函数闭包

闭包最初的目的是减少全局变量,但隐式的共享变量不清晰,一般不建议使用闭包

异常处理

方法

方法和函数类似,但又特殊,可为除接口外的命名类增加方法。

方法的定义必须和类型的定义在同一个包中(所以不能为 int 增加方法),可见范围取决于方法名第一位大小写,使用 type 定义的新类型不能继承原类型除底层类型支持的运算外的方法

1
2
3
4
5
6
7
8
9
10
type sint int

func (a sint) next() sint {
return a + 1
}

func main() {
var a sint = 1
println(a.next())
}

接口

接口是自定义类型,是对其他类型行为的抽象,不包含数据

接口的定义、赋值、使用、断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package main

type Student interface {
Login(uname, password string) (res bool, token string)
Logout(token string)
}

// 带方法的结构体

// StudentInfo 学生信息
type StudentInfo struct {
id int
name string
password string
token string
}

func (st *StudentInfo) Login(uname, password string) (suc bool, token string) {
return true, "123456"
}

func (st *StudentInfo) Logout(token string) {
return
}

func (st *StudentInfo) UpdateName(n string) {
st.name = n
return
}

func main() {
var st Student
admin := &StudentInfo{
id: 0,
name: "admin",
password: "123456",
token: "",
}

// 接口赋值,当结构体内有所有接口定义的方法签名相同则可以直接赋值
st = admin

// 不能通过接口对象访问原结构体的属性,不可访问结构体的其他方法
// st.token 不可行,st.UpdateName 不可被调用,如需调用方法需要使用
// 断言,由接口到结构体反向转换
println(st.(*StudentInfo).name)

_, token := st.Login("admin", "123456")
// do something
st.Logout(token)
}

空接口

1
2
3
4
5
6
7
8
9
10
func main() {
var ei interface{}
ei = 1
ei = true
ei = "Hello"
ei = [3]int{1, 2, 3}
ei = map[int]string{1: "a"}
obj, _ := ei.(map[int]string)
println(obj[1])
}

反射

反射,即在程序运行时动态创建、访问、修改任意类型的结构和成员

测试

testing

单元测试

通过 func TestGetLevel(t *testing.T) {}定义的测试方法对功能进行测试,Xxx 不能以小写字母开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.go
package main

func GetLevel(score int) string {
switch {
case score >= 90:
return "优秀"
case score >= 80:
return "良好"
case score >= 60:
return "及格"
default:
return "不及格"
}
}

func main() {

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main_test.go
package main

import "testing"

func TestGetLevel(t *testing.T) {
if GetLevel(99) != "优秀" {
t.Error("99 未正确返回 \"优秀\"")
}
if GetLevel(87) != "良好" {
t.Error("87 未正确返回 \"良好\"")
}
if GetLevel(60) != "及格" {
t.Error("60 未正确返回 \"及格\"")
}
}

运行测试

go test p1
# ok      p1      0.209s
1
2
3
4
# 查看测试覆盖率
go test -coverprofile='test.log'
# html 显示测试覆盖
go tool cover -html .\test.log

打开的网页通过不同颜色显示是否被测试覆盖
image

基准测试

通过 func BenchmarkFact(b *testing.B) {} 进行基准测试,若测试过程中需要重置计时器,可使用 b.ResetTimer()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// main.go
package main

// Fact 计算阶乘
func Fact(n int) int {
if n > 1 {
return n * Fact(n-1)
}
return n
}

func main() {

}
1
2
3
4
5
6
7
8
9
10
11
12
// main_test.go
package main

import (
"math/rand"
"testing"
)

func BenchmarkFact(b *testing.B) {
Fact(rand.Intn(100))
}

运行测试

1
2
3
4
5
6
7
8
go test p1 -bench . -benchmem

# goos: windows
# goarch: amd64
# pkg: p1
# BenchmarkFact-12 1000000000 0 B/op 0 allocs/op
# PASS
# ok p1 0.032s

文件、bufio 、ioutil

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 写文件
func Write2File(tpath, wdata string) {
//f, err := os.Create(tpath) // 创建文件
f, err := os.OpenFile(tpath, os.O_APPEND, os.ModePerm) // 追加写
if err != nil {
fmt.Printf("%s\n", err)
return
} else {
defer func(f *os.File) {
_ = f.Close()
}(f)
}
f.WriteString(wdata)

}

// 读文件
func ReadFromFile(tpath string) {
f, err := os.Open(tpath)
if err != nil {
os.Create(tpath)
f, _ = os.Open(tpath)
} else {
defer func(f *os.File) {
_ = f.Close()
}(f)
}
var buf = make([]byte, 200)
for {
l, err := f.Read(buf)
if l == 0 || err == io.EOF {
println("\n文件读取完毕\n")
break
} else {
if err != nil {
fmt.Println(err)
} else {
println(string(buf[:l]))
}
}
}

// 文件指针操作
f.Seek(-1,1) // 偏移, 位置(0 开头 1 当前 2 末尾)
l, err := f.Read(buf)
println(string(buf[:l]))

}

func main() {
// 文件日志输出
f, err := os.OpenFile("test.log", os.O_APPEND, os.ModePerm) // 追加写
defer f.Close()
if err == nil {
log.SetOutput(f)
log.SetFlags(log.Flags() | log.Lshortfile)
}
log.Println("日志文件测试")
/*
2022/03/29 13:12:49 main.go:18: 日志文件测试
*/
}

ioutil

1
2
3
4
5
6
7
8
9
10
11
func main() {
f, _ := os.OpenFile("test.log", os.O_APPEND, os.ModePerm) // 追加写
defer f.Close()
d, _ := ioutil.ReadAll(f)
println(string(d))

ioutil.WriteFile("test.txt", []byte("Hello,World"), os.ModePerm)

d2, _ := ioutil.ReadFile("test.txt")
println(string(d2))
}

bufio

1
2
3
4
5
6
7
8
9
10
func main() {
// 逐行读取
f, _ := os.OpenFile("test.log", os.O_RDONLY, os.ModePerm)
defer f.Close()

sc := bufio.NewScanner(f)
for sc.Scan() {
println(sc.Text())
}
}

删除、重命名、信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
// 文件重命名
os.Rename("test.log", "test2.log")
// 删除文件
os.Remove("test.log")
// 创建文件夹
os.Mkdir("a", 0644)
// -p
os.MkdirAll("b/c/d", 0644)
// 删除目录
os.RemoveAll("b")
// 文件信息读取
f, _ := os.Stat("test.txt")
fmt.Printf("%#v\n", f)
}

常用库

命令行解析

1
2
3
4
5
6
7
8
9
10
var a int
var help bool
flag.IntVar(&a, "n", 1, "input number")
flag.BoolVar(&help, "h", false, "Help")
flag.Parse()
if help {
flag.Usage() // 默认的帮助信息
return
}
println(Fact(a))

时间、延时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

func main() {
// 当前时间
now := time.Now()
// fmt.Printf("%v", now)

// 时间输出
println(now.Format("2006 年 01 月 02 日 03 时 04 分 05 秒"))

// 时间解析
t, err := time.Parse("20060102 030405", "20080808 100100")
if err == nil {
fmt.Println(t)
} else {
println(err)
}

// 时间戳
fmt.Println(t.Unix())
fmt.Println(t.UnixNano())
t = time.Unix(1618189660, 0)
fmt.Println(t)

// 时间差
d := now.Sub(t)
println(d.Seconds())

// Sleep(延时) 2 秒
time.Sleep(2 * time.Second)

// 时间计算
now = now.Add(10 * time.Hour)
dd, err := time.ParseDuration("4h3m2s")
now = now.Add(dd)
fmt.Println(now)
}

/*
2022 年 03 月 24 日 02 时 36 分 18 秒
2008-08-08 10:01:00 +0000 UTC
1218189660
1218189660000000000
2021-04-12 09:07:40 +0800 CST
+2.991412e+007
2022-03-25 04:39:20.2951004 +0800 CST m=+50582.003003201
*/

加密、哈希

MD5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
// 计算 MD5
a := md5.Sum([]byte("Hello,World!"))
m := fmt.Sprintf("% 2x", a)

b := md5.New()
b.Write([]byte("Hello,"))
b.Write([]byte("World!"))
m2 := hex.EncodeToString(b.Sum(nil)[:])

fmt.Printf(m)
print("\n")
fmt.Printf(m2)
}
/*
98 f9 7a 79 1e f1 45 75 79 a5 b7 e8 8a 49 50 63
98f97a791ef1457579a5b7e88a495063
*/

Base64

1
2
3
4
5
6
7
8
9
10
11
12
13

func main() {
// Base64
a := base64.StdEncoding.EncodeToString([]byte("Hello,World!"))
println(a)
b, _ := base64.StdEncoding.DecodeString(a)
print(string(b))
}

/*
98 f9 7a 79 1e f1 45 75 79 a5 b7 e8 8a 49 50 63
98f97a791ef1457579a5b7e88a495063
*/

本文作者:
本文链接:https://tdh6.top/%E7%BC%96%E7%A8%8B/note-go-01/
版权声明:本站文章采用 CC BY-NC-SA 3.0 CN 协议进行许可,翻译文章遵循原文协议。
图片来源:本站部分图像来源于网络,前往查看 相关说明。