学习笔记
8小时转职Golang工程师(如果你想低成本学习Go语言)

常用操作

1
2
3
echo $GOPATH #显示GOPATH
go run <filename.go> #编译+运行
go build <filename.go> # 编译生成可执行程序

Go的格式化占位符

以下是Go语言中的全部格式化占位符:

  • 通用占位符:
    • %v:根据值的类型选择合适的格式化方式。
    • %+v:与%v类似,但在结构体中包含字段名。
    • %#v:使用Go语法表示值。
    • %T:打印值的类型。
  • 整数:
    • %b:二进制表示。
    • %c:对应的Unicode字符。
    • %d:十进制表示。
    • %o:八进制表示。
    • %O:带0o前缀的八进制表示。
    • %q:带引号的字符。
    • %x:小写字母的十六进制表示。
    • %X:大写字母的十六进制表示。
  • 浮点数与复数:
    • %b:无小数部分的二进制指数表示。
    • %e:科学计数法表示的小写字母’e’。
    • %E:科学计数法表示的大写字母’E’。
    • %f:默认的浮点数格式。
    • %F:等同于%f
    • %g:根据实际情况选择%e%f格式化浮点数。
    • %G:根据实际情况选择%E%f格式化浮点数。
  • 字符串与字节切片:
    • %s:直接输出字符串或字节切片。
    • %q:带引号的字符串或字节切片。
    • %x:十六进制,小写字母,两个字符表示一个字节。
    • %X:十六进制,大写字母,两个字符表示一个字节。
  • 指针:
    • %p:十六进制表示的指针值。
  • 布尔值:
    • %ttruefalse
  • 宽度与精度:
    • %b:宽度为二进制位数。
    • %d:宽度为十进制数位数。
    • %o:宽度为八进制数位数。
    • %x:宽度为十六进制数位数。
    • %f:精度为默认的小数点后6位。
    • %e:精度为默认的小数点后6位。
    • %g:精度为默认的小数点后6位。
    • %s:宽度为输出的字符数。
  • 其他:
    • %%:打印百分号。

这些格式化占位符涵盖了Go语言中常用的数据类型和格式化需求。根据具体情况,选择适合的占位符来格式化输出。

Golang的优势

简单的部署方式

可以直接编译成机器码
不依赖其他库
直接运行即可部署

静态类型语言

编译的时候可以检查出来隐藏的大多数问题

语言层面的并发

天生支持并发
充分利用多核

强大的标准库

runtime系统调度机制
高效的GC垃圾回收
丰富的标准库

Golang适合用来做什么项目

云计算基础设施领域

Docker Kubernetes etcd consul cloudflare CDN 七牛云存储

代表作

docker k8s

Golang的劣势

包管理不完善

所有的Excepition都用Error来处理,没有try catch语法

配置环境变量

1
2
3
4
# GoLang configeration
export GOROOT="/usr/local/go"
export GOPATH="/Users/sentient3326/GOPATH"
export PATH="$PATH:$GOROOT/bin:GOPATH/bin"

Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main //程序的包名
import "fmt" //导入单个包
import (//导入多个包
"fmt"
"time"
"math"
)

func main() { //go在语法层面要求,左花括号必须与函数名在同一行

//Golang中的表达式加不加;都可以,建议不加
fmt.Println("hello 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
/*
Golang的四种变量声明方式
*/
package main

import "fmt"

func main() {
//方法一:声明一个变量 默认的值是0
var a int
fmt.Println("a =", a)
//方法二:声明一个变量,初始化一个值
var b int = 100
fmt.Println("b = ", b)
//方法三:在初始化时可以省去数据类型,通过值自动匹配
var c = 1000
fmt.Println("c = ", c)
fmt.Printf("type of c = %T\n", c) //打印数据类型
//方法四:省去var直接自动匹配 :=只能在函数体内声明变量 无法用于声明全局变量
e := 100
fmt.Printf("type of e = %T\n", e)

//声明多个变量
var x, y int = 100, 200 //数据类型相同
fmt.Println("x =", x, "y =", y)
var X, Y = "yihaX", 9.9 //数据类型不同
fmt.Println("X =", X, "Y =", Y)

//多行声明多个变量
var (
v1 int = 100
v2 = "wuhu"
)
fmt.Println("v1 = ", v1, "v2 = ", v2)

}

Const和iota

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
package main

import "fmt"

const (
//iota只能出现在const()中
//可以在const()添加一个关键字iota,每行的iota都会累加1,第一行iota的默认值=0
int1 = iota + 1
int2
int3
int4
)

const (
a, b = iota + 1, iota + 2
c, d
e, f
g, h = iota * 2, iota * 3
i, k
)

func main() {
//只读
const length = 10
fmt.Println("length =", length, "int1 =", int1, "int2 =", int2, "int3 =", int3, "int4 =", int4)
fmt.Println("a = ", a, "b = ", b)
fmt.Println("c = ", c, "d = ", d)
fmt.Println("e = ", e, "f = ", f)
fmt.Println("g = ", g, "h = ", h)
fmt.Println("i = ", i, "k = ", k)

}

函数的多个返回值

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
/*
函数有多个返回值的写法
*/
package main

import "fmt"

func func1(a string, b int) int { //单个返回值
fmt.Println("a =", a)
fmt.Println("b =", b)
c := 100
return c
}

func func2() (int, string) { //匿名返回多个值
return 1, "yiha"
}

func func3() (ret1 int, ret2 string) { //有形参名
return ret1, ret2
}

func func4() (ret1, ret2 int) { //返回值类型相同可以仅做一次声明
return ret1, ret2
}

func main() {
fmt.Println(func1("yiha", 10))
fmt.Println(func2())
fmt.Println(func3())
fmt.Println(func4())
}

init函数与import导包

image

1
2
3
4
5
6
7
8
9
10
11
12
// main函数
package main

import (
"GolangStudy/05init/lib1"
"fmt"
)

func main() {
fmt.Println("this is main")
lib1.Silent()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// package lib1
package lib1

import (
"GolangStudy/05init/lib2"
"fmt"
)

func Silent() {//函数名大写相当于开放的接口,小写就只能在包内部访问

}

func init() {
fmt.Println("running lib1")
lib2.Silent()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// package lib2
package lib2

import "fmt"

func Silent() {
}

func init() {
fmt.Println("running lib2")
}
//目录结构
── 05init
├── lib1
│ └── lib1.go
├── lib2
│ └── lib2.go
└── main.go

匿名导包,别名导包,dot导包

1
2
3
4
5
6
7
import (
"GolangStudy/05init/lib1"
_ "GolangStudy/05init/lib3" //下划线表示匿名
AnotherName "GolangStudy/05init/lib4" //起别名
. "GolangStudy/05init/lib5" //should not use dot imports (ST1001)go-staticcheck 不建议使用这种导包方式
"fmt"
)

指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

func main() {
a := 10
b := 20

swap(&a, &b)
fmt.Println("a =", a, "b = ", b)

c := 99
pc := &c
var ppc **int = &pc
fmt.Println("&c =", &c, "pc =", pc, "&pc =", &pc, "ppc =", ppc, "&ppc =", &ppc)
fmt.Println("**ppc = ", **ppc)

}
func swap(pa, pb *int) {
t := 0
t = *pa
*pa = *pb
*pb = t
}

pointer

defer

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
package main

import "fmt"

func main() {
// defer fmt.Println("main end 1") //defer类似于final会在函数结束时执行
// defer fmt.Println("main end 2") //defer可以有多个,以压栈的形式执行 所以输出结果为 this is main,end1,end2
// fmt.Println("this is main")
deferAndReturn()
}

func deferFunc() {
fmt.Println("deferFunc")
}

func returnFunc() int {
fmt.Println("returnFunc")
return 0
}

func deferAndReturn() int { //defer会在return后执行
defer deferFunc()
return returnFunc()
}

动态数组

数组

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
package main

import "fmt"

func printArr(arr []int) {
//引用传递,参数传递的是数组首个元素的地址
fmt.Println("in func address =", &arr[0])
for _, value := range arr {
fmt.Println("value =", value)
}
}

func main() {
// 定长数组
var Array1 [10]int
for i := 0; i < len(Array1); i++ {
fmt.Println(Array1[i])
}

Array2 := [10]int{1, 2, 3, 4}
for i, j := range Array2 {
fmt.Println("index =", i, "value =", j)
}

//动态数组
sliceArr := []int{1, 2, 3, 4}
for i, j := range sliceArr {
fmt.Println("index =", i, "value =", j)
}

printArr(sliceArr)
fmt.Println("in main address =", &sliceArr[0]) //用于对比main和printArr中数组的地址

}

slice切片的追加与截取

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
/*
切片容量的 追加与截取
*/
package main

import "fmt"

func sliceAppend() {
number := make([]int, 3, 5)
fmt.Printf("len = %d,cap = %d,slice = %v\n", len(number), cap(number), number)

//向number追加一个元素9此时number长度为4
number = append(number, 9)
fmt.Printf("len = %d,cap = %d,slice = %v\n", len(number), cap(number), number)

//继续追加元素,当切片容量已满时继续使用append追加元素时,系统会为切片重新开辟一个长度为cap的空间备用
number = append(number, 9, 9)
fmt.Printf("len = %d,cap = %d,slice = %v\n", len(number), cap(number), number)

//当没有指定cap时cap等于len
slice := make([]int, 1)
fmt.Printf("len = %d,cap = %d,slice = %v\n", len(slice), cap(slice), slice)
}

func sliceCut() {
slice := []int{1, 2, 3}
s1 := slice[0:2] //[0,2)包含下标0不包含下标2
fmt.Println(s1)

//copy可以将底层数组的slice进行拷贝

s2 := make([]int, 3)

//将slice的值拷贝到s2
copy(s2, slice)
fmt.Println(s2)

}

func main() {
sliceAppend()
sliceCut()
}

map

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
package main

import "fmt"

func map1() {
//第一种声明方式(必须要有var)
//map[key类型]value类型,在这个例子中key和value的类型都是string
var map1 map[string]string
map1 = make(map[string]string, 3)
map1["first"] = "1"
map1["second"] = "2"
map1["third"] = "3"
fmt.Println(map1)

//第二种声明方式
//与slice类似当添加的元素超出当前容量时map也会自动扩容
map2 := make(map[string]string)
fmt.Println(map2)

//第三种声明方式
map3 := map[string]string{
"key1": "value1",
"key2": "value2",
"key3": "value3",
}
fmt.Println(map3)

}

func printMap(map1 map[string]string) {
//map作为参数是一个引用传递
for key, value := range map1 {
fmt.Println("key =", key, "value =", value)
}
}

func useMap() {

//声明
map1 := map[string]string{
"key1": "value1",
"key2": "value2",
"key3": "value3",
}

//添加
map1["key4"] = "value4"

//遍历
printMap(map1)

//删除
delete(map1, "key1")

}

func main() {
useMap()
map1()
}

结构体与面向对象

封装与表示

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
/*
Go中面向对象的封装与表示
*/
package main

import "fmt"

// type用于声明数据类型
type Book struct {
title string
auth string
}

func func1() {
var b1 Book
b1.title = "Golang"
b1.auth = "zuozhe"
fmt.Println(b1)
}

// 首字母大写表示公有
type Person struct {
name string
weight int
height float32
}

func (p Person) getName() {
fmt.Println("Name =", p.name)
}
func (p *Person) setName(name string) {
//p是一份拷贝,进行的是值传递而非引用传递,要想实现set方法需要将p设置为指针
p.name = name
}

func main() {
var p1 Person
p1.name = "Makima"
p1.weight = 55
p1.height = 168
p1.setName("MakimaBiss")
p1.getName()
}

继承

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
/*
Go语言中的继承
*/
package main

import "fmt"

type Human struct {
name string
gender string
}

func (p Human) getName() string {
return p.name
}

func (p *Human) setName(name string) {
p.name = name
}

type SuperMan struct {
Human
level int
skill string
}

// 添加子类的新方法
func (s1 SuperMan) Show() {
fmt.Println("name =", s1.name, "gender =", s1.gender, "level =", s1.level, "skill = ", s1.skill)
}

// 重定义父类方法
func (s1 SuperMan) getName() {
fmt.Println("this is a rewrite method")
}

func main() {
var h1 Human
h1.name = "makima"
h1.gender = "F"
h1.name = h1.getName()
h1.setName("makima")
fmt.Println(h1)

var s1 SuperMan
s1.name = "superman"
s1.gender = "M"
s1.level = 10
s1.skill = "wuhu"
s1.Show()
s1.getName()
s1.setName("Dyan")
s1.Show()

}

Go语言中多态的实现

多态的基本要素:1.有一个父类(接口)2.有子类(实现了父类的全部接口方法) 3.父类类型的变量(指针)指向子类的具体数据变量 对外暴露父类方法,实际调用子类方法
接口的实现

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
package main

import "fmt"

// interface 本质是一个指针
type Animal interface {
Sleep()
GetColor() string
GetType() string
}

type Cat struct {
color string
}

func (cat Cat) Sleep() {
fmt.Println("Cat is Sleep")
}

func (cat Cat) GetColor() string {
return cat.color
}

func (cat Cat) GetType() string {
return "Cat"
}

type Dog struct {
color string
}

func (dog Dog) Sleep() {
fmt.Println("Dog is Sleep")
}

func (dog Dog) GetColor() string {
return dog.color
}

func (dog Dog) GetType() string {
return "Dog"
}

func main() {
//声明一个父类接口,其本质是一个指针,同时将指针指向猫类
var animal Animal = &Cat{"White"}
//此时调用的是猫的睡觉方法
animal.Sleep()
//将指针指向狗类
animal = &Dog{"black"}
//此时调用狗的睡觉方法
animal.Sleep()
}

万能类型 空接口interface{}和“断言机制”

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

/*
通用万能类型 空接口 interface{}
*/
package main

import "fmt"

type Cat struct {
name string
}

// arg可以接受任意数据类型
func Func1(arg interface{}) {
fmt.Printf("type of arg is %T", arg)
//interface{}如何区分此时引用的底层数据类型

//给interface{}提供“类型断言”机制
_, flag := arg.(string)
if flag {
fmt.Println("arg is string type ")
} else {
fmt.Println("arg is not string type ")
}
}

func main() {
cat := Cat{"mimi"}
Func1(cat)

}


反射

变量内置pair结构

reflect

反射机制的用法

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
package main

import (
"fmt"
"reflect"
)

type User struct {
id int
name string
salary float32
}

func (user User) Call() {
fmt.Println("User is Called")
fmt.Printf("%v\n", user)
}

func reflectNum(arg interface{}) {
fmt.Println("type = ", reflect.TypeOf(arg))
fmt.Println("value = ", reflect.ValueOf(arg))
}

func reflectStruct(arg interface{}) {

//获取结构体的字段和值
argType := reflect.TypeOf(arg)
argValue := reflect.ValueOf(arg)

for i := 0; i < argType.NumField(); i++ {
field := argType.Field(i)
value := argValue.Field(i)
fmt.Printf("type of %s is %v value = %v\n", field.Name, field.Type, value)
}

//通过type获取结构体的方法
for i := 0; i < argType.NumMethod(); i++ {
m := argType.Method(i)
fmt.Printf("%s:%v", m.Name, m.Type)
}
}

func main() {
var num float32 = 1.2345
reflectNum(num)

user := User{1, "makima", 0.0005}
reflectStruct(user)

}

结构体标签

使用反射解析结构体标签

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
package main

import (
"fmt"
"reflect"
)

type resume struct {
//结构体标签需要以键值对的形式存在
Name string `info:"name"`
//可以绑定多个结构体标签
Age int `info:"age" extra:"tag"`
}

func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i := 0; i < t.NumField(); i++ {
properties := t.Field(i).Name
infoTag := t.Field(i).Tag.Get("info")
extraTag := t.Field(i).Tag.Get("extra")
fmt.Println("properties:", properties, "info:", infoTag, "extraTag:", extraTag)
}
}

func main() {
var r resume
findTag(&r)

}

结构体标签在json中的使用

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
package main

import (
"encoding/json"
"fmt"
)

type Movie struct {
//结构体标签中的键会作为json中的键
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"rmb"`
Actors []string `json:"actors"`
}

func main() {
movie := Movie{"电影名", 2000, 20, []string{"yiha", "makima"}}
//encoding
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error")
return
}
fmt.Printf("jsonStr = %s\n", jsonStr)

//decoding
movieDecode := Movie{}
err = json.Unmarshal(jsonStr, &movieDecode)

if err != nil {
fmt.Println("json unmarshal error")
return
}
fmt.Printf("Decode result is %v\n", movieDecode)
}

groutine基本模型和调度设计策略