go学习笔记(基础篇)

我们一起学猫叫 一起喵喵喵喵喵~

[[TOC]]

常量

常量的定义格式:const identifier [type] = value

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

变量

使用 var 关键字:var identifier type

整数的类型

屏幕快照 2019-03-28 23.39.02.png
屏幕快照 2019-03-28 23.40.52.png

浮点型

屏幕快照 2019-03-28 23.44.11.png

通常情况下应该使用 float64, 因为它比 float32 更精确

字符

golang 中字符串是不可变的

1
2
s1 := "hello killy"
s1[0] = "y" //报错

字符串的表示

  • 双引号
  • 反引号: 以字符串的形式原生输出(python 中的 r'hello kitty')

基本数据类型默认值

屏幕快照 2019-03-29 00.00.00.png

基本数据类型的转换

Go 在不同的类型之间赋值时需要显示的转换. 即 Golang 中数据类型不能自动转换.

1
2
var i int32 = 100
var n1 float32 = float32(i)

数据转换中若结果溢出, 编译时不会报错, 只是转换的结果会按照溢出处理.

1
2
3
var num1 int64 = 9999999
var num2 int8 = int8(num1)
// num2 = 63

命名规范

  • 保持 package 名字和目录一致.
  • 变量名, 函数名, 常量名采用驼峰法命名
  • 若首字母大写, 贼可以被其它的包访问, 若小写则只能在本包中使用.

指针

1
2
3
4
5
6
var intP *int
var i int = 10
var ptr *int = &i
// ptr 是一个指针变量
// ptr 的类型是 *int
// ptr 本身的值是 &i

源码, 反码, 补码

屏幕快照 2019-03-29 00.29.53.png

控制结构

if-else

1
2
3
4
5
6
7
if condition1 {
// do something
} else if condition2 {
// do something else
} else {
// catch-all or default
}

常用例子

判断一个字符串是否为空:

  • if str == "" { ... }
  • if len(str) == 0 {...}

函数 Abs() 用于返回一个整型数字的绝对值:

1
2
3
4
5
6
func Abs(x int) int {
if x < 0 {
return -x
}
return x
}

测试多返回值函数的错误

参考教程

swich

1
2
3
4
5
6
7
8
switch var1 {
case val1:
...
case val2:
...
default:
...
}

for结构

1
for 初始化语句; 条件语句; 修饰语句 {}

Break 与 continue

Break 与 continue

标签与 goto

标签与 goto

函数

传递变长参数

1
func myFunc(a, b, arg ...int) {}

示例函数和调用:

1
2
func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")

在 Greeting 函数中,变量 who 的值为 []string{"Joe", "Anna", "Eileen"}

函数作为参数

1
2
3
4
5
6
7
8
9
10
11
func main() {
callback(1, Add)
}

func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}

使用闭包调试

1
2
3
4
5
6
7
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()

通过内存缓存提升性能

牺牲空间换时间, 示例 fibonacci_memoization.go

数组和切片

声明

一个切片在未初始化之前默认为 nil,长度为 0。

切片的初始化格式是:var slice1 []type = arr1[start:end]

使用 make() 创建去切片

func make([]T, len, cap),其中 cap 是可选参数。

所以下面两种方法可以生成相同的切片:

1
2
make([]int, 50, 100)
new([100]int)[0:50]

new()make() 的区别

  • new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 * T 的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}
  • make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片、map 和 channel(参见第 8 章,第 13 章)。

bytes

For-range 结构

这种构建方法可以应用于数组和切片

1
2
3
for ix, value := range slice1 {
...
}

切片重组(reslice)

改变切片长度的过程称之为切片重组 reslicing,做法如下:slice1 = slice1[0:end],其中 end 是新的末尾索引(即长度)

参考示例

Map

声明、初始化和 make

map 是引用类型

1
2
3
var map1 map[keytype]valuetype

var map1 map[string]int

未初始化的 map 的值是 nil

var map1 = make(map[keytype]valuetype)

不要用 new 构造 Map

测试键值是否存在及删除元素

判断 key 是否存在

1
_, ok := map1[key1] // 如果key1存在则ok == true,否则ok为false

或者和 if 混合使用:

1
2
3
if _, ok := map1[key1]; ok {
// ...
}

删除 key

delete(map1, key1)

key 不存在, 不会产生错误.

For-range

1
2
3
for key, value := range map1 {
...
}

map 类型的切片

想获取一个 map 类型的切片, 必须使用两次 make(), 第一次分配切片, 第二次分配切片中每个 map 元素, 示例

排序

代码示例

键值对调

代码示例

GFW 牛逼 …

有些方法得研究研究

init 函数

init 是一类非常特殊的函数, 不能够被人为调用, 会在包初始化后自动执行, 优先级高于 main.

结构和方法

定义

1
2
3
4
5
type identifier struct {
field1 type1
field2 type2
...
}

new()

t := new(T),变量 t 是一个指向 T的指针,此时结构体字段的值是它们所属类型的零值

工厂方法创建结构体实例

按惯例,工厂的名字以 new 或 New 开头.

1
2
3
4
type File struct {
fd int // 文件描述符
name string // 文件名
}

这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

1
2
3
4
5
6
7
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}

return &File{fd, name}
}

然后这样调用它:

1
f := NewFile(10, "./test.txt")

强制使用工厂方法

1
2
3
4
5
6
7
8
type matrix truct {
...
}

func NewMatrix(params) *matrix {
m := new(matrix) // 初始化 m
return m
}

在其他包里使用工厂方法:

1
2
3
4
5
package main
import "matrix"
...
wrong := new(matrix.matrix) // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式

带标签的结构体

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体

方法

定义方法的一般格式如下:

1
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }

类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法

函数和方法的区别

函数将变量作为参数:Function1(recv)

方法在变量上被调用:recv.Method1()

指针或值作为接收者

pointer_value

方法和未导出字段

提供 getter 和 setter 方法

person2.go

类型的 String() 方法和格式化描述符

如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print()fmt.Println() 也会自动使用 String() 方法。

接口

通过如下格式定义接口:

1
2
3
4
5
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}

上面的 Namer 是一个 接口类型

(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头(像 .NETJava 中那样)

Go 语言中的接口都很简短,通常它们会包含 0 个、最多 3 个方法。

demo

demo1

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

import "fmt"

//声明一个接口
type Usb interface {
//声明两个没有实现的方法
Start()
Stop()
}

type Phone struct {
}

//让 Phone 实现 Usb 接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作")
}

func (p Phone) Stop() {
fmt.Println("手机停止工作")
}

//让 camera 实现 Usb 接口的方法
type Camera struct {
}

func (c Camera) Start() {
fmt.Println("Camera 开始工作")
}

func (c Camera) Stop() {
fmt.Println("Camera 停止工作")
}
//计算机
type Cumputer struct {

}
// 编写一个方法 Working 接收一个 usb 接口类型变量
//只要实现了 Usb 接口 (所谓实现Usb 接口,就是指实现了Usb接口声明所有方法)
func (c Cumputer)Working(usb Usb) {
//通过 Usb接口变量来调用 Start 和 Stop 方法
usb.Start()
usb.Stop()
}
func main() {
//测试
//先声明相关的结构体变量
computer := new(Cumputer)
phone := new(Phone)
camera := new(Camera)
//关键点
computer.Working(phone)
computer.Working(camera)
}
1
2
3
4
手机开始工作
手机停止工作
Camera 开始工作
Camera 停止工作

demo2

使用接口对结构体排序

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

import (
"fmt"
"math/rand"
"sort"
)

//声明 Hero 结构体
type Hero struct {
Name string
Age int
}

//声明一个 Hero 结构体的切片类型
type HeroSlice []Hero

//实现 interface (排序用) 接口
func (hs HeroSlice) Len() int {
return len(hs)
}

//less 方法判断使用什么标准进行排序
//1. 按照年龄从小到达排序
func (hs HeroSlice) Less(i, j int) bool {
return hs[i].Age < hs[j].Age
}
func (hs HeroSlice) Swap(i, j int) {
hs[i], hs[j] = hs[j], hs[j]
//temp := hs[i]
//hs[i] = hs[j]
//hs[j] = temp
}
func main() {
// 先定义一个数组 or 切片
intSlice := []int{0, -1, 10, 7, 90}
//对 intSlice 进行冒泡排序
//使用系统提供的方法
sort.Ints(intSlice)
fmt.Println(intSlice)
//对结构体切片进行排序
var heroes HeroSlice
for i := 0; i < 10; i++ {
hero := Hero{
Name: fmt.Sprintf("英雄_%d", rand.Intn(100)),
Age: rand.Intn(100),
}
//将 hreo append 到 heros 切片中
heroes = append(heroes, hero)
}
//查看排序前的顺序
for _, v := range heroes {
fmt.Println(v)
}
fmt.Println("------ 正在排序 ---------")
//调用 sort.sort 进行排序
sort.Sort(heroes)
//排序后的顺序
for _, v := range heroes {
fmt.Println(v)
}
}

output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[-1 0 7 10 90]
{英雄_81 87}
{英雄_47 59}
{英雄_81 18}
{英雄_25 40}
{英雄_56 0}
{英雄_94 11}
{英雄_62 89}
{英雄_28 74}
{英雄_11 45}
{英雄_37 6}
------ 正在排序 ---------
{英雄_81 87}
{英雄_81 87}
{英雄_81 87}
{英雄_81 87}
{英雄_81 87}
{英雄_81 87}
{英雄_62 89}
{英雄_62 89}
{英雄_62 89}
{英雄_62 89}

类型断言

使用以下形式来进行类型断言:

1
2
3
4
5
if v, ok := varI.(T); ok {  // checked type assertion
Process(v)
return
}
// varI is not of type T

如果转换合法,vvarI 转换到类型 T 的值,ok 会是 true;否则 v 是类型 T 的零值,okfalse,也没有运行时错误发生。

由于接口是一般类型, 不知道具体类型. 如果要转换成具体类型, 就需要使用类型断言.

1
2
3
4
5
6
7
8
9
10
func main () {
var x interface{}
b := 1.1
//var b float32 = 1.1
x = b // 空接口 可以接收任意类型
b_type := reflect.TypeOf(b)
fmt.Println(b_type)
y := x.(float64)
fmt.Printf("y 的类型是 %T, 值是%v", y, y)
}

输出:

1
2
3
4
map[1:1]
[{} {} {}]
float64
y 的类型是 float64, 值是1.1

类型判断:type-switch

1
2
3
4
5
6
7
8
9
10
switch t := areaIntf.(type) {
case *Square:
fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
fmt.Printf("nil value: nothing to check?\n")
default:
fmt.Printf("Unexpected type %T\n", t)
}

测试值是否实现了某个接口

1
2
3
4
5
6
7
type Stringer interface {
String() string
}

if sv, ok := v.(Stringer); ok {
fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}

空接口

空接口或者最小接口 不包含任何方法,它对实现不做任何要求:

1
type Any interface {}

任何其他类型都实现了空接口(它不仅仅像 Java/C#Object 引用类型),anyAny 是空接口一个很好的别名或缩写。

反射包

gorouttine

Go 协程的特点:

- 有独立的栈空间
- 共享程序堆空间
- 调度由用户控制

demo

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

import (
"fmt"
"strconv"
"time"
)

//在主线程中开启一个 goroutine, 隔一秒输出
//主线程每隔一秒输出, 输出十次后, 退出程序
//主线程和协程同时执行

func loop_print(c string) {
for i := 1; i <= 10; i++ {
fmt.Println(c + strconv.Itoa(i))
time.Sleep(time.Second)
}
}

func hello_world() {
loop_print("hello world")
}
func main() {
go hello_world()
loop_print("hello golang")
}

channel

channel 本质是一个队列

线程安全, 多 goroutine 访问时, 不需要加锁.

channel 是有类型的.

声明

var <变量名> chan <数据类型>

ch := make(chan int)

channel 是引用类型, 必须初始化, 即 make 后才能使用.

在没有使用协程的情况下, 如果管道的数据被全部取出, 继续读取数据会死锁, 报 deadlock 错误

创建空接口类型的 channel, 可以存放任意类型.

1
allChan := make(chan interface{}, 10)

使用 channel 进行 goroutine 通信demo

使用通信共享内存, 而不是共享内存来通信

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

import (
"fmt"
)

func writeData(intchan chan int) {
for i := 0; i < 50; i++ {
intchan <- i
fmt.Printf("writeDate =%v\n", i)
}
close(intchan)
}

func readData(intchan chan int, exitchan chan bool) {
for {
v, ok := <-intchan
if ok {
fmt.Printf("readDate =%v\n", v)
} else {
break
}
}
//读取完数据后, 即任务完成
exitchan <- true
close(exitchan)
}

func main() {
//创建两个 channel
intchan := make(chan int, 50)
exitchan := make(chan bool, 1)

go writeData(intchan)
// 如果只向管道写数据, 会出现 dead lock 错误.
go readData(intchan, exitchan)
//time.Sleep(time.Second * 10)
for true {
_, ok := <-exitchan
if !ok {
break
}
}
}


使用 select 读取管道数据

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"

func main() {
//使用 select 解决 channel 阻塞问题
intChan := make(chan int, 10)
for i := 1; i <= 10; i++ {
intChan <- i
}
strChan := make(chan string, 5)
for i := 0; i < 5; i++ {
strChan <- "hello" + fmt.Sprintf("%d", i)
}
//传统的方法在遍历管道时, 如果不关闭会阻塞导致死锁 deadlock
//开发时, 不好确定怎么关闭管道时, 可以使用 select

for {
select {
case v := <-intChan: // 如果管道未关闭也不会一直阻塞, 会匹配下一个 case
fmt.Printf(" read data from intChan, %d\n", v)
case v := <-strChan:
fmt.Printf("read data from strChan, %s\n", v)
default:
fmt.Printf("read data failed, alsdkjfl;kasjdlkfjklajskldfj\n")
return
}
}
}

goroutine 中捕获错误

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

import (
"fmt"
"time"
)

func sayHello() {
for i := 0; i < 10; i++ {
time.Sleep(time.Second)
fmt.Println("hello world")
}
}

func test() {

//=======================================
//捕获 panic
defer func() {
err:=recover()
if err !=nil {
fmt.Println("test() panic, err:", err)
}
//if err := recover(); err != nil {
// fmt.Println("test() panic, err:", err)
//}
}()
//=======================================


//=======================================
//错误使用 map 导致 panic
//panic: assignment to entry in nil map
var mymap map[int]string
mymap[0] = "golang"
//=======================================


}

func main() {
go sayHello()
go test()
for i, _ := range "1234567890" {
fmt.Println("main ok=", i)
time.Sleep(time.Second)
}
}

反射

  • 反射可以在运行时动态的获取变量的各种信息
  • 如果是结构体变量, 还可以获取到结构体本身的信息.
  • 通过反射, 可以修改变量的值, 可以调用相关的方法
  • 使用反射, 需要 import (“reflect”)
  • 变量 interface() 和 reflect.Value 是可以相互转换的, 示意

demo1

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

import (
"fmt"
"reflect"
)

func reflectTest1(b interface{}) {
//获取 reflect.Type()
rType := reflect.TypeOf(b)
fmt.Println("rType: ", rType)
//获取 reflect.Value{}
rValue := reflect.ValueOf(b)
fmt.Printf("rValue: %v,\nrValueType: %T\n", rValue, rValue)
//转换为 int
rValue1 := reflect.ValueOf(b).Int()
fmt.Printf("rValue: %v,\nrValueType: %T\n", rValue1, rValue1)

//将 rValue 转换成 interface()
iV := rValue.Interface()
num2 := iV.(int)
fmt.Println("num2: ", num2)
}

func reflectTest2(b interface{}) {
//获取 reflect.Type()
rType := reflect.TypeOf(b)
fmt.Println("rType: ", rType)
//获取 reflect.Value{}
rValue := reflect.ValueOf(b)
fmt.Printf("rValue: %v,\nrValueType: %T\n", rValue, rValue)
//将 rValue 转换成 interface()
iV := rValue.Interface()
fmt.Printf("iv: %v, type: %T\n", iV, iV)
//通过类型断言转换成需要的类型
stu, ok:=iV.(*Student) // 这里不用指针, 会不 ok, 没弄懂, 是因为我传递的参数是指针me
if ok {
fmt.Printf("stu.Name=%v \n", stu.Name)
}
}

type Student struct {
Name string
Age int
}

//反射的基本操作
func main() {
num := 10
reflectTest1(num)
fmt.Println("\n")
stu := new(Student)
stu.Name = "tom"
stu.Age = 10
reflectTest2(stu)
}

demo2 通过反射修改值

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

import (
"fmt"
"reflect"
)

func reflectTest(b interface{}) {
rVal:=reflect.ValueOf(b)
//修改 b 的值
rVal.Elem().SetInt(2)
}

func main() {
num:=2
reflectTest(&num)
fmt.Println("num: ",num)
}

demo3 通过反射遍历结构体, 获取方法和标签值

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import (
"fmt"
"github.com/bradfitz/iter"
"reflect"
)

type Monster struct {
Name string `json:"name"`
Age int `json:"monster_age"`
Score float64
Sex string
}

//显示 monster 的值
func (s Monster) Print() {
fmt.Println("===== start =====")
fmt.Println(s)
fmt.Println("===== end =====")
}
func (s Monster) GetSum(n1, n2 int) int {
return n1 + n2
}

func (s Monster) Set(name string, age int, score float64, sex string) {
s.Name = name
s.Age = age
s.Score = score
s.Sex = sex
}

func TestStruct(a interface{}) {
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
kd := val.Kind()
if kd != reflect.Struct {
fmt.Println("except struct")
return
}
//获取结构体字段数量
num := val.NumField()
fmt.Printf("struct has %d fields \n", num)
for i, _ := range iter.N(num) {
fmt.Printf("field %d: 值: %v\n ", i, val.Field(i))
//通过 reflect.Type 获取 struct 标签,
tagVal := typ.Field(i).Tag.Get("json")
if tagVal != "" {
fmt.Printf("field %d: tag: %v\n", i, tagVal)
}
}
numOfMethod := val.NumMethod()
fmt.Printf("struct has %d methods\n", numOfMethod)
//方法的排序按照函数名进行排序
//调用结构体的第一个方法 method(0)
val.Method(1).Call(nil)

// todo 使用 make 方法创建 slice
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(40))
//传入的参数是 []reflect.Value
res := val.Method(0).Call(params)
//返回的结果是 []reflect.Value
fmt.Println("red: ", res[0].Int())
}

func main() {
m := new(Monster)
m.Name = "test"
m.Age = 100
m.Score = 40.0
m.Sex = "nan"
TestStruct(*m)
}