Golang学习笔记

你的 Go 安装目录($GOROOT)的文件夹结构应该如下所示:

1
2
3
4
5
6
7
/bin:包含可执行文件,如:编译器,Go 工具
/doc:包含示例程序,代码工具,本地文档等
/lib:包含文档模版
/misc:包含与支持 Go 编辑器有关的配置文件以及 cgo 的示例
/os_arch:包含标准库的包的对象文件(.a)
/src:包含源代码构建脚本和标准库的包的完整源代码(Go 是一门开源语言)
/src/cmd:包含 Go 和 C 的编译器和命令行脚本

数据类型

1
2
3
4
5
这些浮点数类型的取值范围可以从很微小到很巨大。浮点数取值范围的极限值可以在 math 包中找到:
常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度,通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。

变量

1
2
3
4
5
6
7
8
9
10
1. 变量如果只声明没有赋值,则默认值为0,bool型是false,字符型是""
2. =: (1)不能在函数体外面复制,这样会报错。(2)=:声明的变量不能用var声明,而且这个变量是没有被声明过的
3. 变量被声明了之后必须被使用,不然就会报错
4. 下划线也被称为空白标识符,例如:var _ , a=1,2中的1会被抛弃,空白标识符经常在函数返回值中使用
5. 同时声明多个变量,var a,b,c=1,2,3 //其中不用标明变量类型,这一点和python很像
6. 交换两个变量的值时候要注意类型要相同,a,b=b,a
7. 变量还可以使用枚举的方式声明 var(
name="qiyou"
age=10
)

常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
2. 声明格式:const identifier [type] = value //你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
3. 常量声明之后不使用也可以
4. 常量还可以使用枚举的方式声明 const(
NAME="qiyou"
AGE=10
)
5. 同时声明多个常量:const NAME,AGE="QIYOU",1
6. 在定义常量组时,如果不提供初始值,则表示将使用上行的表达式(只适用于枚举的情况下)。
const(
a=1
b
)
fmt.Println(a,b) //输出1 1

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
33
34
35
36
37
38
39
40
41
42
	const(
a=iota //a=0
b //b=1
c //c=2
d //d=3
)

如果想忽略中间一个量则可以使用
const(
a=iota //a=0
b //b=1
c //c=2
d //d=3
_
e //e=5
)

乘法实例
const(
a=2*iota //2*0
b //2*1
c //2*2
d //2*3
e //2*4
f="test" //注意这里iota变成了5
g=2*iota //2*6
)
fmt.Println(a,b,c,d,e,g)

iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
const (
i = iota
j = iota
x = iota
)
const xx = iota
const yy = iota
func main(){
println(i, j, x, xx, yy)
}

// 输出是 0 1 2 0 0

运算符

1
2
3
4
5
6
7
8
9
10
11
+	相加	A + B 输出结果 30
- 相减 A - B 输出结果 -10
* 相乘 A * B 输出结果 200
/ 相除 B / A 输出结果 2
% 求余 B % A 输出结果 0
++ 自增 A++ 输出结果 11
-- 自减 A-- 输出结果 9

&& 逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。 (A && B) 为 False
|| 逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。 (A || B) 为 True
! 逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。 !(A && B) 为 True

条件

case

1
2
3
4
1. 注意多个case是不能出现相同的值的,否则会报错
2. 可以在一个 case 中包含多个表达式,每个表达式用逗号分隔。
3. 没有表达式的 switch,则相当于 switch true,这种情况下会将每一个 case 的表达式的求值结果与 true 做比较,如果相等,则执行相应的代码。
4. fallthrough语句用于标明执行完当前 case 语句之后按顺序执行下一个case 语句。

类型转换

1
2
3
4
5
1. 在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明

2. bool型不能转换位int型,相反int型也不能转换成bool型

3.

指针

1
2
3
1. new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。

2. %p表示输出指针地址

Go语言type关键字(类型别名)

1
2
type NewType = Type //定义别名
type NewType Type //定义一个类型

结构体

实例化的方法

  1. 用var实例化

    1
    1. 结构体本身也是一种类型,可以用var实例化:var name T
  2. 创建指针类型的结构体

1
2
3
4
语法:ins := new(T)

T 为类型,可以是结构体、整型、字符串等。
ins:T 类型被实例化后保存到 ins 变量中,ins 的类型为 *T,属于指针。
  1. 取结构体的地址实例化
    1
    2
    在Go语言中,对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作,取地址格式为:ins := &T{}
    带传值的:ins:=&person{name: name,age: age,nickname: nickname}

成员赋值

  1. 结构体实例化后字段的默认值是字段类型的默认值,例如 ,数值为 0、字符串为 “”(空字符串)、布尔为 false、指针为 nil 等。

  2. 使用多个值的列表初始化结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    必须初始化结构体的所有字段。
    每一个初始值的填充顺序必须与字段在结构体中的声明顺序一致。
    键值对与值列表的初始化形式不能混用。

    type Address struct {
    Province string
    City string
    ZipCode int
    PhoneNumber string
    }
    addr := Address{
    "四川",
    "成都",
    610000,
    "0",
    }
    fmt.Println(addr)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//指针结构体,成员可以直接使用.号访问

type person struct{
name string
age int
weigth int
nickname string
}

func main(){
var lisi person
lisi.name="lisi"
changename(&lisi)
fmt.Println(lisi.name)

}

func changename(pname *person){
pname.name="qiyou"
}

接收器

接收器的格式如下:

1
2
3
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
函数体
}

指针类型的接收器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type person struct{
name string
age uint
nickname string
}


func main(){
lisi:=&person{}
lisi.setValue("lisi")
fmt.Println(lisi.getValue())
}


func (p *person)setValue(v string){
p.name=v
}

func (p *person)getValue() string{
return p.name
}

Go语言类型内嵌和结构体内嵌

  1. 在一个结构体中对于每一种数据类型只能有一个匿名字段。
  2. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    func main()  {
    type inn struct{
    name string
    }
    type test struct{
    nickname string
    name string
    int
    inn
    }
    abc:=&test{}
    abc.nickname="zhangsan"
    abc.inn.name="lisi" //另外一种访问方式:abc.name="lisi",前提test这个结构体没有name这个成员
    abc.int=1 //直接使用abc.int访问匿名变量,不过每一种类型的匿名变量只能有一个
    fmt.Println(abc.inn.name) //也可以abc.name,前提test这个结构体没有name这个成员

    }

结构体内嵌模拟类的继承

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
type Walkable struct{}
type Fly struct{}

func (f *Fly) IsFly(){
fmt.Println("I can fly!")
}

func (w * Walkable)IsWalk(){
fmt.Println("I can Walk!")
}

type bird struct{
Walkable
Fly
}

type human struct{
Walkable
}

func main() {
b:=new(bird)
b.IsFly() //或者 b.Fly.IsFly()
b.IsWalk() //或者 b.Walkable.IsWalk()
h:=new(human)
h.IsWalk()
}

初始化内嵌结构体

例子一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type computer struct{
cpu string
RAM uint
ROM uint
}


func main() {
lianxiang:=computer{
cpu:"Intel",
RAM:8,
ROM:1000, //注意最后要有逗号,不然就会报错
}
fmt.Println(lianxiang)
}

例子二:
有时考虑编写代码的便利性,会将结构体直接定义在嵌入的结构体中。也就是说,结构体的定义不会被外部引用到。在初始化这个被嵌入的结构体时,就需要再次声明结构才能赋予数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type computer struct{
cpu string
RAM uint
ROM struct{
size uint
brand string
}
}


func main() {
lianxiang:=computer{
cpu: "Intel",
RAM: 8,
ROM: struct{
size uint
brand string
}{
size: 1000,
brand: "Kingston",
}, //注意这里要有逗号,不然就会报错
}
fmt.Println(lianxiang)
}

map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type

/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

声明: var person map[string]string //map为nil,可以通过这种形式来添加键值对:person = map[string]string{"name":"qiyou"}
var person =map[string]string{} //注意后面有一个{},map不是nil
var person = make(map[string]string)

如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对

2. delete函数用于删除一个集合中的元素:
delete(person,"name")

3. 一个可以对应多个值我们可以用切片来解决:
m1:=make(map[string][]int)
m1["name"]=[]int {1,1,2,1}
fmt.Println(m1["name"])
4. map的遍历我们可以用for range

5. go没有提供清空map的函数,所以清空map的方法是重新make一个map

sync.Map

Go中的 map 在并发情况下,只读是线程安全的,同时读写是线程不安全的。所以就有了sync.Map

1
2
3
4
5
6
1. sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
2. sync.Map 有以下特性:
无须初始化,直接声明即可。
sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用,Store 表示存储,Load表示获取,Delete 表示删除。
使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值,Range参数中回调函数的返回值在需要继续迭代遍历时,返回 true;终止迭代遍历时,返回 false。
3. sync.Map的键类型和值类型不一定必须相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import (
"fmt"
"sync"
)
func main() {
var scene sync.Map
// 将键值对保存到sync.Map
scene.Store("greece", 97)
scene.Store("london", 100)
scene.Store("egypt", 200)
// 从sync.Map中根据键取值
fmt.Println(scene.Load("london"))
// 根据键删除对应的键值对
scene.Delete("london")
// 遍历所有sync.Map中的键值对
scene.Range(func(k, v interface{}) bool {
fmt.Println("iterate:", k, v)
return true
})
}

数组

如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:

1
a:=[...]int {1,1,1,1} //最终长度为4

切片

切片并不存储任何元素而只是对现有数组的引用。

1
2
3
4
5
6
7
8
9
10
11
12
从数组或切片生成新的切片拥有如下特性:
取出的元素数量为:结束位置 - 开始位置;
取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
当缺省开始位置时,表示从连续区域开头到结束位置;
当缺省结束位置时,表示从开始位置到整个连续区域末尾;
两者同时缺省时,与切片本身等效;//a[:]
两者同时为 0 时,等效于空切片,一般用于切片复位。 //a[0:0]

切片和数组的区别就是:切片是引用,而数组是值传递

切片的长度和容量:
切片的长度是指切片中元素的个数。切片的容量是指从切片的起始元素开始到其底层数组中的最后一个元素的个数。

append

1
2
3
append只能用于操作切片,不能用于操作数组
当新元素通过调用 append 函数追加到切片末尾时,如果超出了容量,append 内部会创建一个新的数组。并将原有数组的元素被拷贝给这个新的数组,最后返回建立在这个新数组上的切片。这个新切片的容量是旧切片的二倍
append的返回值是添加后的切片

1
2
3
4
5
6
7
var a= []int{1,2,3}
var b =a[1:2]
for x,_:=range b{
b[x]++
}
fmt.Println(a)
//注意切片是对数组的引用,而不是传递值,所有a输出的是[1 3 3]

list

不是go内置的,要导入包container/list

1
2
3
4
5
6
1. list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的。
1) 通过 container/list 包的 New() 函数初始化 list
变量名 := list.New()

2) 通过 var 关键字声明初始化 list
var 变量名 list.List

函数

1
2
3
4
5
1. mylist.Len() 返回链表的长度
2. mylist.init() 初始化链表,即清空链表
3. PushBack在列表mylist的后面插入一个值为v的新元素e并返回e。
4. PushFront在列表mylist的前面插入一个值为v的新元素e并返回e。
5. Next返回下一个list元素或nil

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
l := list.New()
// 尾部添加
l.PushBack("canon")
// 头部添加
l.PushFront(67)
// 尾部添加后保存元素句柄
element := l.PushBack("fist")
// 在fist之后添加high
l.InsertAfter("high", element)
// 在fist之前添加noon
l.InsertBefore("noon", element)
// 使用
l.Remove(element) //删除fist这个元素

遍历所有链表

1
2
3
for i:=mylist.Front();i!=nil;i=i.Next(){
fmt.Println(i.Value)
}

nil

nil 是 map、slice、pointer、channel、func、interface 的零值

if

  1. if左边的括号必须要和if在同一行

if 还有一种特殊的写法,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,代码如下:

1
2
3
4
if err := Connect(); err != nil {
fmt.Println(err)
return
}

for

break退出指定循环,一个标签只能对应一个循环

1
2
3
4
5
6
7
8
out:
for i:=1;;i++{
if i>9{
break out
}else{
fmt.Println("test")
}
}

continue 语句可以结束当前循环,开始下一次的循环迭代过程,仅限在 for 循环内使用,在 continue 语句后添加标签时,表示开始标签对应的循环

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

OuterLoop:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
switch j {
case 2:
fmt.Println(i, j)
continue OuterLoop //表示结束当前循环,开启下一次的外层循环,而不是第 10 行的循环。
}
}
}

输出:0 2
1 2

函数

  1. 在函数中,实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参,但是,如果实参包括引用类型,如指针、slice(切片)、map、function、channel 等类型,实参可能会由于函数的间接引用被修改。

  2. 在Go语言中,函数也是一种类型,可以和其他类型一样保存在变量中,下面的代码定义了一个函数变量 f,并将一个函数名为 echo() 的函数赋给函数变量 f,这样调用函数变量 f 时,实际调用的就是 echo() 函数,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func main(){
    var f func(string)
    f=echo
    f("test")
    }

    func echo(s string) {
    a:=s
    fmt.Println(a)
    }
  3. 宕机

发生宕机时后面的代码都不会执行,注意一点就是defer如果在发生宕机前面的话,还是正常执行的

1
2
3
4
5
6
7
8
9
10
func main(){
defer fmt.Println("a")
defer fmt.Println("b")
panic("Done!")
}

输出:
b
a
panic: Done!

  1. 宕机恢复recover

一旦发生宕机,其后的代码是不会执行的,但是会调用位于panic代码所在的哪一行之前的defer延迟函数,所以说这个特性就决定recover应该用在defer函数中,否则一旦发生宕机,除了defer延迟函数中的语句还能执行外,其他的语句都是不能执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
fmt.Println("Begin!")
defer func() {
if err := recover(); err != nil {
fmt.Println("error: ", err)
}
}()
var a *int
*a = 1 //空指针的引用,会发生宕机
fmt.Println("Not end!")
}

输出:
Begin!
error: runtime error: invalid memory address or nil pointer dereference

接口

每个接口类型由数个方法组成。接口的形式代码如下:

1
2
3
4
5
6
接口声明的格式
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}

对各个部分的说明:

  1. 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加 er,如有写操作的接口叫 Writer,有字符串功能的接口叫 Stringer,有关闭功能的接口叫 Closer 等。
  2. 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
    参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如:
    1
    2
    3
    type writer interface{
    Write([]byte) error
    }

package语句

  1. 要想使用自定义的包中的函数、变量、结构体,则首字母必须要大写(即public),如果首字母是小写则外部包访问(即private)
导入自定义包
  1. import后面是文件夹名称
  2. 文件夹名称和package名称不一定要相同
  3. 调用自定义包中的函数使用packeage.func()来调用
  4. 自定义包的调用和文件名没有关系,只与文件中的package语句有关,go会自动查找import文件夹里面的所有文件,然后查找所有package,不同的go文件可以有相同的package名称

import语句

导入匿名包

如果只希望导入包,而不使用任何包内的结构和类型,也不调用包内的任何函数时,可以使用匿名导入包

使用下划线导入一个匿名包格式如下:

1
import _ "path/to/package"

注意:匿名包也会和其它包一样,也会被编译到可执行文件中,同时匿名包中的init函数也会被执行

io

ReadByte()方法

ReadByte() 方法的功能是读取并返回一个字节。如果没有字节可读,则返回错误信息。该方法原型声明如下:

1
func (b *Reader) ReadByte() (c byte,err error)
1
2
3
4
5
6
7
8
9
10
11
12
13
func main()  {
arr:=[]byte("test")
rd := bytes.NewReader(arr)
r := bufio.NewReader(rd)
nn,err:=r.ReadByte()
fmt.Println(nn,err)
n,error_1:=r.ReadByte() //读完一个字节之后,将指针指向下一个字节
fmt.Println(n,error_1)
}

output:
116 <nil>
101 <nil>

ReadBytes() 方法

ReadBytes() 方法的功能是 ReadBytes 读取数据直到遇到第一个分隔符“delim”,并返回读取的字节序列(包括“delim”)。如果 ReadBytes 在读到第一个“delim”之前出错,它返回已读取的数据和那个错误(通常是 io.EOF)。只有当返回的数据不以“delim”结尾时,返回的 err 才不为空值。该方法原型声明如下:

1
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
1
2
3
4
5
6
7
8
9
10
11
    arr:=[]byte("Hey,guy")
rd := bytes.NewReader(arr)
r := bufio.NewReader(rd)
n,err:=r.ReadBytes(',')
fmt.Println(string(n),err)
n,err=r.ReadBytes(',')
fmt.Println(string(n),err)

output:
Hey, <nil>
guy EOF

接口

接口被实现的条件一:接口的方法与实现接口的类型方法格式一致

在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致,那么接口的这个方法就不会被实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Print interface{
Printdata(data interface{}) error
}

type Data struct{
}

func (d *Data) Printdata(data interface{}) error {
fmt.Println(data)
return nil
}

func main(){
f:=&Data{}
var toPrint Print
toPrint = f //虽然f和toPrint变量类型不一致,但是toPrint是一个接口,而且toPrint中的方法已经被Printdata全部实现,所以是可以直接赋值
toPrint.Printdata("Hello World!")
}

如果结构体中没有实现接口中的所有方法,那么就会报错。

接口被实现的条件二:接口中所有方法均被实现

当一个接口中有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用。

如果我们在接口中新添加一个方法IsPrint(),如果结构体中没有实现,那么也会编译不通过

多个类型可以实现相同的接口

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
package main
import (
"fmt"
)

type Service interface{
Start()
Logger(string)
}

type Showlog struct{
}

type MyService struct{
Showlog
}

func (S *Showlog) Logger(log string){
fmt.Println(log,"is running")
}

func (M *MyService) Start(){
fmt.Println("The service begin run")
}

func main(){
var s Service = &MyService{}
s.Start()
s.Logger("localhost")
}

goland的常见标准库

strings

HasPrefix判断字符串s是否以prefix开头:
函数签名

1
strings.HasPrefix(s, prefix string) bool

HasSuffix判断字符串 s 是否以 suffix 结尾:

函数签名

1
strings.HasSuffix(s, suffix string) bool

1
2
3
4
5
6
7
8
func main()  {
fmt.Println(strings.HasPrefix("this is strings","th"))
fmt.Println(strings.HasSuffix("this is strings","ings"))
}

output:
true
true

Contains字符串包含关系

功能:字符串s中是否包含substr,返回bool值

1
func Contains(s, substr string) bool
1
2
3
4
fmt.Println(strings.Contains("this my world!","this"))

output:
true

Join(拼接slice到字符串,不能是数组)

Join 用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串

函数签名

1
strings.Join(sl []string, sep string) string

例子

1
2
3
4
5
6
7
arr:=[]string {"abc","def"}
fmt.Println(strings.Join(arr,""))
fmt.Println(strings.Join(arr,","))

output:
abcdef
abc,def

Index

在字符串s中查找sep所在的位置,返回位置值,找不到返回-1

语法:

1
strings.Index(s, str string) int

1
2
3
fmt.Println(strings.Index("this is my world","is"))

output: 2

LastIndex

LastIndex 返回字符串 str 在字符串 s 中最后出现位置的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:

1
strings.LastIndex(s, str string) int
1
2
3
fmt.Println(strings.LastIndex("this is my world","is"))

output: 5

如果需要查询非 ASCII 编码的字符在父字符串中的位置,建议使用以下函数来对字符进行定位:

1
strings.IndexRune(s string, r rune) int

1
2
3
4
5
6
7
str:="this is my world"
fmt.Println(strings.IndexRune(str,rune('a')))
fmt.Println(strings.IndexRune(str,111))

output:
-1
12

Count

Count 用于计算字符串 str 在字符串 s 中出现的非重叠次数:

1
strings.Count(s, str string) int

例子

1
2
3
4
5
str:="this is my world"
fmt.Println(strings.Count(str,"is"))

output:
2

Repeat

重复s字符串count次,并返回一个新的字符串

1
func Repeat(s string, count int) string
1
2
3
4
5
str:="this is my world"
fmt.Println(strings.Repeat(str,3))

output:
this is my worldthis is my worldthis is my world

Replace (字符串替换)

在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换

注意:替换的次数必须有,不然就会报错

1
func Replace(s, old, new string, n int) string

例子

1
2
str:="this is my world"
fmt.Println(strings.Replace(str,"is","are",2))

time

时间戳

1
2
now:=time.Now()
fmt.Println(now.Unix())

延时

1
2
3
time.Sleep(3 *time.Second) //延时3秒
time.Sleep(3 *time.Minute) //延时3分钟
time.Sleep(3 *time.Hour) //延时3小时

fmt

Printf

整数型

1
2
3
4
5
6
7
8
%b    表示为二进制
%c 该值对应的unicode码值
%d 表示为十进制
%o 表示为八进制
%q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x 表示为十六进制,使用a-f
%X 表示为十六进制,使用A-F
%U 表示为Unicode格式:U+1234,等价于"U+%04X"

math/big

主要用于大数运算

1
2
3
4
5
6
7
8
9
10
11
12
13
//设置一个大于Int64的10进制整数
bigint := new(big.Int)
mybigint, _ := bigint.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10)

//输出可以用%v或者用String()方法
fmt.Printf("%v\n",a)
fmt.Println(a.String())

//大数相加

c:=big.NewInt(1)
mybigint.Add(mybigint,c) //Add的返回值是一个big.Int对象,即mybigint+c的结果
fmt.Println(mybigint.String())
1
2
3
4
5
6
7
8
9
10
11
12
13
bigint := new(big.Int)
mybigint, _ := bigint.SetString("115792089237316195423570985008687907853269984665640564039457584007913129639936", 10)
c:=big.NewInt(1)
f:=big.NewInt(1)
d:=f.Add(mybigint,c)
fmt.Println(mybigint.String()) //此时mybigint的值没有发生变化
fmt.Println(f.String()) //此时f的值发生了变化
fmt.Println(d.String()) //此时d的值发生了变化

output:
115792089237316195423570985008687907853269984665640564039457584007913129639936
115792089237316195423570985008687907853269984665640564039457584007913129639937
115792089237316195423570985008687907853269984665640564039457584007913129639937

os包

1
2
3
4
5
6
7
8
9
fmt.Println(os.Getegid()) //
fmt.Println(os.Chdir("../")) //改变工作目录
fmt.Println(os.Getwd()) //获取当前工作目录
fmt.Println(os.Getenv("tmp")) //返回指定环境变量
fmt.Println(os.Environ()) //返回所有环境变量
//os.Exit(0) //系统退出,并返回 code,其中 0 表示执行成功并退出,非 0 表示错误并退出
fmt.Println(os.Getpid()) //获取进程id
fmt.Println(os.Getppid()) //获取父进程id
fmt.Println(os.Hostname()) //获取主机名

os/exec

执行系统命令

1
2
3
4
5
6
7
8
9
func main() {
c:=exec.Command("ipconfig")
cmd,err:=c.CombinedOutput()
if err!=nil {
fmt.Printf("%v",err)
}else{
fmt.Printf("%s",string(cmd))
}
}

log

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"log"
)
func main() {
log.Println("Gooood")
log.Println("Loooog")
}

output:
2019/12/03 23:05:40 Gooood
2019/12/03 23:05:40 Loooog

自定义Logger类型,log.Logger提供了一个New方法用来创建对象:

1
func New(out io.Writer, prefix string, flag int) *Logger

该函数一共有三个参数:

  1. 输出位置out,是一个io.Writer对象,该对象可以是一个文件也可以是实现了该接口的对象。通常我们可以用这个来指定日志输出到哪个文件。
  2. prefix 我们在前面已经看到,就是在日志内容前面的东西。我们可以将其置为 “[Info]” 、 “[Warning]”等来帮助区分日志级别。
  3. flags 是一个选项,显示日志开头的东西,可选的值有:
    1
    2
    3
    4
    5
    6
    Ldate         = 1 << iota     // 形如 2009/01/23 的日期
    Ltime // 形如 01:23:23 的时间
    Lmicroseconds // 形如 01:23:23.123123 的时间
    Llongfile // 全路径文件名和行号: /a/b/c/d.go:23
    Lshortfile // 文件名和行号: d.go:23
    LstdFlags = Ldate | Ltime // 日期和时间

设置前缀、输出格式

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"log"
"os"
)
func main() {
// var out io.Writer
debugger:=log.New(os.Stdout,"[info]\t",log.Llongfile)
debugger.Println("Gooood")
debugger.Println("Loooog")
}

regexp

Golang的正则提供16种正则匹配方法

1
Find(All)?(String)?(Submatch)?(Index)?

MustCompileCompile区别

1
2
3
MustCompile 的作用和 Compile 一样
不同的是,当正则表达式 str 不合法时,MustCompile 会抛出异常
而 Compile 仅返回一个 error 值

CompilePOSIX和 Compile的区别

1
2
3
4
5
CompilePOSIX 的作用和 Compile 一样
不同的是,CompilePOSIX 使用 POSIX 语法,
同时,它采用最左最长方式搜索,
而 Compile 采用最左最短方式搜索
POSIX 语法不支持 Perl 的语法格式:\d、\D、\s、\S、\w、\W

Match

func Match(pattern string, b []byte) (matched bool, err error)

1
2
3
4
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.Match([]byte(search)))

MatchReader

func MatchReader(pattern string, r io.RuneReader) (matched bool, err error),r:要在其中进行查找的 RuneReader 接口

1
2
3
4
search := bytes.NewReader([]byte("baidu:https://www.baidu.com google:https://www.google.com"))
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.MatchReader(search))

MatchString

返回是否匹配到结果,true或者false

1
2
3
4
5
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
ok:=re.MatchString(search)
fmt.Println(ok)

Find

func (re *Regexp) Find(b []byte) []byte,查找byte数组,并返回第一个匹配的内容

1
2
3
4
5
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
match:=re.Find([]byte(search))
fmt.Println(match)

FindAll

func (re *Regexp) FindAll(b []byte, n int) [][]byte,查找前n个匹配项,如果n<0,则查找所有匹配项

1
2
3
4
5
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
match:=re.FindAll([]byte(search),-1)
fmt.Println(match)

FindString

func (re *Regexp) FindString(s string) string,查找字符串,并返回第一个找到的结果

1
2
3
4
5
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
match:=re.FindString(search)
fmt.Println(match)

FindAllString

func (re *Regexp) FindAllString(s string, n int) []string,查找前n个匹配项,如果n<0,则查找所有匹配项

1
2
3
4
5
6
7
func main() {
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern) //只有如果返回值,如果放回nil则说明没有匹配的结果
fmt.Println(re.FindAllString(search, -1))

}

FindAllStringIndex

func (re *Regexp) FindAllStringIndex(s string, n int) [][]int,返回匹配到的字符串的位置,[[起始位置, 结束位置], [起始位置, 结束位置], …]

1
2
3
4
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.FindAllStringIndex(search,-1))

FindStringSubmatch

func (re *Regexp) FindStringSubmatch(s string) []string,匹配子组的内容,第一个返回的内容是匹配到的整一个字符串。{完整匹配项, 子匹配项, 子匹配项, …}

1
2
3
4
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.FindStringSubmatch(search))

ReplaceAllString

1
2
3
4
5
6
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.ReplaceAllString(search,"${1}http://www.test.com"))

baidu:http://www.test.com google:http://www.test.com

ReplaceAllLiteralString

func (re *Regexp) ReplaceAllLiteralString(src, repl string) string, 如果 repl 中有”分组引用符”($1、$name),则将“分组引用符”当普通字符处理

1
2
3
4
5
6
7
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.ReplaceAllLiteralString(search,"${1}http://www.test.com"))

output:
baidu:${1}http://www.test.com google:${1}http://www.test.com

Split

func (re *Regexp) Split(s string, n int) []string 最多分割出 n 个子串,第 n 个子串不再进行分割 如果 n < 0,则分割所有子串 返回分割后的子串列表

1
2
3
4
5
6
7
search := "baidu:https://www.baidu.com google:https://www.google.com"
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.Split(search,-1))

output:
[baidu: google: ]

String

返回 re 中的“正则表达式”字符串

1
2
3
pattern := `(http|https)?://[a-zA-Z0-9\.]+\.(com|net|cn|gov|edu)+`
re := regexp.MustCompile(pattern)
fmt.Println(re.String())

encoding

encoding/hex

hex

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
name:=[]byte("test")
//字符串转化为16进制
fmt.Println(hex.EncodeToString(name))
//字符串转化为16进制
endst:=make([]byte,hex.EncodedLen(len(name)))
hex.Encode(endst,[]byte(name))
fmt.Println(string(endst))
//16转字符串
dst:=make([]byte,hex.DecodedLen(len(("74657374"))))
hex.Decode(dst,[]byte("74657374"))
fmt.Println(string(dst))
//16转字符串
hex2str,_:=hex.DecodeString("74657374")
fmt.Println(string(hex2str))
//hexdump
hexdump:=[]byte("A strong man will struggle with the storms of fate. -- Thomas Addison")
fmt.Println(hex.Dump(hexdump))

output:
74657374
74657374
test
test
00000000 41 20 73 74 72 6f 6e 67 20 6d 61 6e 20 77 69 6c |A strong man wil|
00000010 6c 20 73 74 72 75 67 67 6c 65 20 77 69 74 68 20 |l struggle with |
00000020 74 68 65 20 73 74 6f 72 6d 73 20 6f 66 20 66 61 |the storms of fa|
00000030 74 65 2e 20 2d 2d 20 54 68 6f 6d 61 73 20 41 64 |te. -- Thomas Ad|
00000040 64 69 73 6f 6e |dison|

encoding/base64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//base64编码
b64:=[]byte("test")
str2b64:=base64.StdEncoding.EncodeToString(b64)
fmt.Println(str2b64)
//base64编码
dst:=make([]byte,base64.StdEncoding.EncodedLen(len(b64)))
base64.StdEncoding.Encode(dst,b64)
fmt.Println(string(dst))
//base64解码
data:="dGVzdA=="
b642str,_:=base64.StdEncoding.DecodeString(data)
fmt.Println(string(b642str))
//base64解码
data_1:=[]byte(data)
dst=make([]byte,base64.StdEncoding.DecodedLen(len(data_1)))
base64.StdEncoding.Decode(dst,data_1)
fmt.Println(string(dst))

output:
dGVzdA==
dGVzdA==
test
test

crypto

crypto/md5

1
2
3
4
5
6
7
8
9
//MD5
data := []byte("test")
mymd5:=fmt.Sprintf("%x",md5.Sum(data)) //注意md5.Sum(data)返回是一个数组而不是切片
fmt.Println(mymd5)

//sha1
data := []byte("test")
mymd5:=fmt.Sprintf("%x",sha1.Sum(data))
fmt.Println(mymd5)

crypto/sha256

1
2
3
4
5
data := []byte("test")
mysha224:=fmt.Sprintf("%x",sha256.Sum224(data)) //sha224
mysha256:=fmt.Sprintf("%x",sha256.Sum256(data)) //sha256
fmt.Println(mysha224)
fmt.Println(mysha256)

crypto/sha512

1
2
3
4
5
data := []byte("test")
mysha384:=fmt.Sprintf("%x",sha512.Sum384(data)) //sha384
mysha512:=fmt.Sprintf("%x",sha512.Sum512(data)) //sha512
fmt.Println(mysha384)
fmt.Println(mysha512)

crypto/rand

rand.Reader

ReadFull从rand.Reader精确地读取len(b)字节数据填充进b,rand.Reader是一个全局、共享的密码用强随机数生成器

1
2
3
4
5
b:=make([]byte,64)
io.ReadFull(rand.Reader,b)
fmt.Println(b)
output:
[132 20 249 35 161 31 131 62 200 97 167 216 145 88 146 242 249 70 7 62 249 47 240 60 73 89 148 232 61 187 77 241 179 12 24 84 118 164 101 244 240 4 55 11 59 255 140 206 10 81 170 107 57 195 27 122 3 81 207 123 53 14 241 91]

Prime

Prime(rand io.Reader, bits int) (p *big.Int, err error),生成一个n bit的素数,如果n<2,则会返回一个error

1
2
3
p,_:=rand.Prime(rand.Reader,2048) //生成一个2048bit的素数
fmt.Printf("%T\n",p)
fmt.Println(p)

rand.Int

Int(rand io.Reader, max *big.Int) (n *big.Int, err error),取一个[0,max)的整数

1
2
3
max:=big.NewInt(math.MaxInt64)
r,_:=rand.Int(rand.Reader,max)
fmt.Println(r)

crypto/aes

golang实现aes加密解密

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

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)

func padding(data []byte, BlackSize int) []byte {
PadLen := BlackSize - len(data)%BlackSize
PadData := bytes.Repeat([]byte{byte(PadLen)}, PadLen)
return append(data, PadData...)
}

func unpadding(data []byte) []byte {
n := len(data)
UnpadLen := int(data[n-1])
UnpadData := data[:n-UnpadLen]
return UnpadData
}

func DecryptoAES(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
Decrypter := cipher.NewCBCDecrypter(block, key)
// var DecryptData []byte
DecryptData := make([]byte, len(data))
Decrypter.CryptBlocks(DecryptData, data)
DecryptData = unpadding(DecryptData)
return DecryptData, nil
}

func EncryptoAES(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddata := padding(data, block.BlockSize())
Encrypter := cipher.NewCBCEncrypter(block, key)
EncryptData := make([]byte, len(paddata))
Encrypter.CryptBlocks(EncryptData, paddata)
return EncryptData, nil
}

func main() {
data := []byte("Why not go far without dreams")
key := make([]byte, 16)
io.ReadFull(rand.Reader, key)
fmt.Println(key)
endata, _ := EncryptoAES(data, key)
fmt.Println(string(endata))
dedata, _ := DecryptoAES(endata, key)
fmt.Println(string(dedata))
}

crypto/des

crypto/des包中实现了des加密和三重des加密
des加密

和aes差不多,就改一下方法就行了

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

import (
"bytes"
"crypto/des"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)

func padding(data []byte, BlackSize int) []byte {
PadLen := BlackSize - len(data)%BlackSize
PadData := bytes.Repeat([]byte{byte(PadLen)}, PadLen)
return append(data, PadData...)
}

func unpadding(data []byte) []byte {
n := len(data)
UnpadLen := int(data[n-1])
UnpadData := data[:n-UnpadLen]
return UnpadData
}

func DecryptoDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
Decrypter := cipher.NewCBCDecrypter(block, key)
DecryptData := make([]byte, len(data))
Decrypter.CryptBlocks(DecryptData, data)
DecryptData = unpadding(DecryptData)
return DecryptData, nil
}

func EncryptoDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
paddata := padding(data, block.BlockSize())
Encrypter := cipher.NewCBCEncrypter(block, key)
EncryptData := make([]byte, len(paddata))
Encrypter.CryptBlocks(EncryptData, paddata)
return EncryptData, nil
}

func main() {
data := []byte("Why not go far without dreams")
key := make([]byte, 8) // key的长度必须为8
io.ReadFull(rand.Reader, key)
fmt.Println(key)
endata, _ := EncryptoDES(data, key)
fmt.Println(string(endata))
dedata, _ := DecryptoDES(endata, key)
fmt.Println(string(dedata))
}

3des

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

import (
"bytes"
"crypto/des"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)

func padding(data []byte, BlackSize int) []byte {
PadLen := BlackSize - len(data)%BlackSize
PadData := bytes.Repeat([]byte{byte(PadLen)}, PadLen)
return append(data, PadData...)
}

func unpadding(data []byte) []byte {
n := len(data)
UnpadLen := int(data[n-1])
UnpadData := data[:n-UnpadLen]
return UnpadData
}

func DecryptoTripleDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
Decrypter := cipher.NewCBCDecrypter(block, key[:8])
DecryptData := make([]byte, len(data))
Decrypter.CryptBlocks(DecryptData, data)
DecryptData = unpadding(DecryptData)
return DecryptData, nil
}

func EncryptoTripleDES(data []byte, key []byte) ([]byte, error) {
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
paddata := padding(data, block.BlockSize())
Encrypter := cipher.NewCBCEncrypter(block, key[:8]) //vi的值必须为8,不然就会panic
EncryptData := make([]byte, len(paddata))
Encrypter.CryptBlocks(EncryptData, paddata)
return EncryptData, nil
}

func main() {
data := []byte("Why not go far without dreams")
key := make([]byte, 24) // key的长度必须为24
io.ReadFull(rand.Reader, key)
fmt.Println(key)
endata, _ := EncryptoTripleDES(data, key)
fmt.Println(string(endata))
dedata, _ := DecryptoTripleDES(endata, key)
fmt.Println(string(dedata))
}

crypto/hmac

HMAC是使用密钥对消息进行签名的加密散列

1
2
3
4
5
6
7
8
9
10
11
12
13
//用hmac对hash值进行签名
key:=[]byte("salted")
message:=[]byte("Why not go far without dreams")
enhash:=hmac.New(md5.New,key)
enhash.Write(message)
fmt.Printf("%x\n",enhash.Sum(nil))
//校验
hash:=[]byte {28 ,171 ,78 ,222 ,246 ,68 ,186 ,131 ,28 ,11 ,246 ,196 ,249 ,129 ,172 ,228}
fmt.Println(hmac.Equal(enhash.Sum(nil),hash))

output:
1cab4edef644ba831c0bf6c4f981ace4
true

hash

hash/crc32

1
2
data:=crc32.ChecksumIEEE([]byte("crc32")) //压缩包的是采用CRC-32-IEEE 802.3标准
fmt.Println(data)

reflect

注意:go语言里面struct里面变量如果大写开头则是public,如果是小写开头则是private的,private的时候通过反射不能获取其值,否则会出现如下panic

1
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

从实例到 Value

通过实例获取 Value 对象,直接使用 reflect.ValueOf() 函数。例如:

1
func ValueOf(i interface {}) Value

从实例到 Type

通过实例获取反射对象的 Type,直接使用 reflect.TypeOf() 函数。例如:

1
func TypeOf(i interface{}) Type

从Value到实例

1
2
3
4
5
6
7
8
9
//该方法最通用,用来将 Value 转换为空接口,该空接口内部存放具体类型实例
//可以使用接口类型查询去还原为具体的类型
func (v Value) Interface()i interface{})

//Value 自身也提供丰富的方法,直接将 Value 转换为简单类型实例,如果类型不匹配,则会返回"<" + v.Type().String() + " Value>"
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64

从 Value 到 Type

从反射对象 Value 到 Type 可以直接调用 Type 的方法,因为 Value 内部存放着到 Type 类型的指针。例如:

1
func (v Value) Type() Type

从 Value 的指针到值

1
2
3
4
5
从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。
//如果 v 类型是接口,则 Elem() 返回接口绑定的实例的 Value,如采 v 类型是指针,则返回指针值的 Value,否则引起 panic
func (v Value) Elem() Value
//如果 v 是指针,则返回指针值的 Value,否则返回 v 自身,该函数不会引起 panic
func Indirect(v Value) Value

Value 值的可修改性

Value 值的修改涉及如下两个方法:
//通过 CanSet 判断是否能修改

1
func (v Value ) CanSet() bool

//通过 Set 进行修改

1
func (v Value ) Set(x Value)

实例

实例1

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

import (
"fmt"
"reflect"
)

func main() {
var f float64 = 3.1
v := reflect.ValueOf(&f)
fmt.Println("Canset: ",v.CanSet())
fv:=v.Elem()
fmt.Println("Canset: ",fv.CanSet())
fv.SetFloat(3.14)
fmt.Println(f)
}

实例2

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

import (
"fmt"
"reflect"
)

type person struct {
Name string
Age uint16
}

func main() {
zhangsan := person{"zhangsan", 20}
v := reflect.ValueOf(&zhangsan).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
tt := t.Field(i)
fmt.Println(tt.Name, f.Interface(), tt.Type)
}
}

实例3,通过反射修改结构体的值:

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

import (
"fmt"
"reflect"
)

type person struct {
Name string
Age uint16
}

func main() {
zhangsan := person{"zhangsan", 20}
v := reflect.ValueOf(&zhangsan).Elem()
name:="qiyou"
n:=reflect.ValueOf(name)
v.FieldByName("Name").Set(n)
fmt.Println(v.Field(0).Interface())
}

Name和Kind的区别

Go 程序中的类型(Type)指的是系统原生数据类型,如 int、string、bool、float32 等类型,以及使用 type 关键字定义的类型,这些类型的名称就是其类型本身的名称(Name)。例如使用 type A struct{} 定义结构体时,A 就是 struct{} 的类型。

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"
"reflect"
)

type myint int

type person struct {
}

func main() {
var i myint
zhangsan:=person{}
t1:=reflect.TypeOf(i)
t2:=reflect.TypeOf(zhangsan)
fmt.Println(t1.Name(),t1.Kind())
fmt.Println(t2.Name(),t2.Kind())
}

output:
myint int
person struct

通过反射获取指针指向的元素类型

Go语言程序中对指针获取反射对象时,可以通过reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

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

import (
"fmt"
"reflect"
)

type person struct {
}

func main() {
zhangsan:=person{}
t:=reflect.TypeOf(&zhangsan)
fmt.Println(t.Name(),t.Kind())
et:=t.Elem()
fmt.Println(et.Name(),et.Kind())
}

output:
ptr //Go 语言的反射中对所有指针变量的种类都是 Ptr,但注意,指针变量的类型名称是空,不是*person
person struct

结构体标签

结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。

从结构体标签中获取值
StructTag 拥有一些方法,可以进行 Tag 信息的解析和提取,如下所示:

1
func(tag StructTag)Get(key string)string

根据 Tag 中的键获取对应的值,例如key1:"value1"key2:"value2" 的 Tag 中,可以传入“key1”获得“value1”。

1
func(tag StructTag)Lookup(key string)(value string,ok bool)

根据 Tag 中的键,查询值是否存在

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"
"reflect"
)

type person struct {
Name string `name:"qiyou" Sex:"man"`
}

func main() {
zhangsan := person{}
t := reflect.TypeOf(zhangsan)

if name, ok := t.FieldByName("Name"); ok {
fmt.Println(name.Tag.Get("name"))
fmt.Println(name.Tag.Get("Sex"))
}
}

output:
qiyou
man

注意一点就是:key和value之间不能有任何空格,即上面例子中name:”qiyou”之间不能有任何空格,如果有空格则会输出空,不会报错

通过反射获取值信息

Go语言中,使用reflect.ValueOf()函数获得值的反射值对象(reflect.Value)。书写格式如下:

1
value := reflect.ValueOf(rawValue)

reflect.ValueOf 返回reflect.Value类型,包含有 rawValue的值信息。reflect.Value 与原值间可以通过值包装和值获取互相转化。reflect.Value 是一些反射操作的重要类型,如反射调用函数。

从反射值对象(reflect.Value)中获取值的方法
可以通过下面几种方法从反射值对象 reflect.Value 中获取原值,如下表所示。

反射值获取原始值的方法

1
2
3
4
5
6
7
Interface() interface {}	将值以 interface{} 类型返回,可以通过类型断言转换为指定类型
Int() int64 将值以 int 类型返回,所有有符号整型均可以此方式返回
Uint() uint64 将值以 uint 类型返回,所有无符号整型均可以此方式返回
Float() float64 将值以双精度(float64)类型返回,所有浮点数(float32float64)均可以此方式返回
Bool() bool 将值以 bool 类型返回
Bytes() []bytes 将值以字节数组 []bytes 类型返回
String() string 将值以字符串类型返回

例子:

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

import (
"fmt"
"reflect"
)

func main() {
var i int=12
t:=reflect.ValueOf(i)
fmt.Println(t.Interface())
fmt.Println(t.Int())
}

通过反射访问结构体成员的值

反射值对象(reflect.Value)提供对结构体访问的方法,通过这些方法可以完成对结构体任意值的访问,如下表所示。

反射值对象的成员访问方法

1
2
3
4
5
Field(i int) Value	根据索引,返回索引对应的结构体成员字段的反射值对象。当值不是结构体或索引超界时发生宕机
NumField() int 返回结构体成员字段数量。当值不是结构体或索引超界时发生宕机
FieldByName(name string) Value 根据给定字符串返回字符串对应的结构体字段。没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByIndex(index []int) Value 多层成员访问时,根据 []int 提供的每个结构体的字段索引,返回字段的值。 没有找到时返回零值,当值不是结构体或索引超界时发生宕机
FieldByNameFunc(match func(string) bool) Value 根据匹配函数匹配需要的字段。找到时返回零值,当值不是结构体或索引超界时发生宕机

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

import (
"fmt"
"reflect"

)

type say struct{
int
string
}

type person struct {
Name string
Age uint
test func()
bool
method say
}

func main() {
zhangsan := person{Name: "zhangsan", method: say{1,"lisi"}}
t := reflect.ValueOf(zhangsan)
fmt.Println("Len: ", t.NumField()) //字段数
fmt.Println("Value: ", t.Field(1), "Type: ", t.Field(1).Type()) //输出字段的值和类型
fmt.Println(t.FieldByName("Name")) //根据名字查找字段
fmt.Println(t.FieldByIndex([]int{0}))
fmt.Println("Value: ",t.FieldByIndex([]int{4,1}), "Type: ",t.FieldByIndex([]int{4,1}).Type()) //[]int{4,1}中的四就是索引为4的元素,即method;1为method中索引为1的元素
}

判断反射值的空和有效性

IsNil()和IsValid()

通过反射修改变量的值

1
2
3
4
5
6
使用 reflect.Value 取元素、取地址及修改值的属性方法请参考下表。
反射值对象的判定及获取元素的方法
Elem() Value 取值指向的元素值,类似于语言层*操作。当值类型不是指针或接口时发生宕 机,空指针时返回 nil 的 Value
Addr() Value 对可寻址的值返回其地址,类似于语言层&操作。当值不可寻址时发生宕机
CanAddr() bool 表示值是否可寻址
CanSet() bool 返回值能否被修改。要求值可寻址且是导出的字段

我们可以通过调用 reflect.ValueOf(&x).Elem(),来获取任意变量x对应的可取地址的 Value。

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

import (
"fmt"
"reflect"

)

func main() {
t:=1
fmt.Println(reflect.ValueOf(t).CanAddr()) //false
fmt.Println(reflect.ValueOf(&t).CanAddr()) //false
fmt.Println(reflect.ValueOf(&t).Elem().CanAddr()) //true
}

值修改相关方法
使用 reflect.Value修改值的相关方法如下表所示。
反射值对象修改值的方法

1
2
3
4
5
6
7
8
Set(x Value)	将值设置为传入的反射值对象的值
Setlnt(x int64) 使用 int64 设置值。当值的类型不是 int、int8、int16、 int32、int64 时会发生宕机
SetUint(x uint64) 使用 uint64 设置值。当值的类型不是 uint、uint8、uint16、uint32、uint64 时会发生宕机
SetFloat(x float64) 使用 float64 设置值。当值的类型不是 float32、float64 时会发生宕机
SetBool(x bool) 使用 bool 设置值。当值的类型不是 bod 时会发生宕机
SetBytes(x []byte) 设置字节数组 []bytes值。当值的类型不是 []byte 时会发生宕机
SetString(x string) 设置字符串值。当值的类型不是 string 时会发生宕机
以上方法,在 reflect.Value 的 CanSet 返回 false 仍然修改值时会发生宕机。

通过反射调用函数

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

import(
"fmt"
"reflect"
)

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

func main() {
funcAdd:=reflect.ValueOf(add)
param:=[]reflect.Value{reflect.ValueOf(1),reflect.ValueOf(2)}
result:=funcAdd.Call(param)
fmt.Println(result[0].Interface())
}

并发

  1. 当main函数返回时,所有的goroutine都会退出,然后程序就退出
  2. main函数是不会等待goroutine执行完的,比如如下代码没有输出A,因为还没有执行到匿名函数的goroutinemain函数就已经结束了

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

    import(
    "fmt"
    "time"
    )

    func main() {
    go func(){
    fmt.Println("A")
    }()
    fmt.Println("B")
    fmt.Println("Done")
    }
  3. 不同的goroutine是不会相会影响的,不如如下代码,第一个匿名函数中的sleep是不会影响第二匿名函数的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
    package main

    import(
    "fmt"
    "time"
    )

    func main() {
    go func(){
    for i:=0;i<=10;i++{
    fmt.Println("A")
    time.Sleep(200 *time.Millisecond)
    }
    }()

    go func(){
    for i:=0;i<=10;i++{
    fmt.Println("C")
    }
    }()

    fmt.Println("B")
    time.Sleep(1 *time.Second)
    fmt.Println("Done")
    }
  4. 要注意的一点是,如果主goroutine一直阻塞的话,会报错,但是其它goroutine是没有影响的,比如如下代码,最后是没有输入Die的,说明这个goroutine被一直阻塞着,但是对整个程序来说没有影响,反过来,如果是主goroutine阻塞了,没有接收或者发送给其它goroitine,那么就会报错:fatal error: all goroutines are asleep - deadlock!,主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
    package main

    import (
    "fmt"
    "time"
    )

    var ch chan int = make(chan int)

    func test() {
    for data := range ch {
    fmt.Println(data)
    }
    fmt.Println("Die")
    }

    func main() {
    go test()
    for i := 0; i <= 9; i++ {
    ch <- i
    }
    time.Sleep(1 * time.Second)
    data := 10
    if data == 10 {
    fmt.Println("Done")
    }
    }

带缓冲通道的阻塞条件
带缓冲通道在很多特性上和无缓冲通道是类似的。无缓冲通道可以看作是长度永远为 0 的带缓冲通道。因此根据这个特性,带缓冲通道在下面列举的情况下依然会发生阻塞:

1
2
带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
带缓冲通道为空时,尝试接收数据时发生阻塞。

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

import "fmt"

func main() {
Sendch := make(chan int, 3)
Sendch <-1
Sendch <-2
Sendch <-3
fmt.Println("len:",len(Sendch))
fmt.Println(<-Sendch)
//Sendch <-4 这个时候如果再往通道里面放数据,就会阻塞
for data:=range Sendch{
fmt.Println(data)
if data==3{
break
}
}
fmt.Println("len:",len(Sendch))
}

通道的超时机制

配合select机制

如果有一个或多个IO操作可以完成,则Go运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行.

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"
import "time"

func main() {
ch:=make(chan interface{})
timeout:=make(chan bool,1)
go func(){
time.Sleep(3 *time.Second)
timeout<-true
}()
select{
case <-ch:
fmt.Println("No timeout")
case <-timeout:
fmt.Println("timeout")
default:
fmt.Println("default")
}
}

output:

default
因为前面的ch和timeout的通道都没有接收到数据,所以默认会执行default

1). 如果我们把上面的default注释掉的话,会怎么样呢,会输出timeout,因为没有了default语句,如果其它通道一直接收到数据的话就会一直阻塞,直到有其它goroutine给它发送数据,上面的匿名goroutinesleep 3秒之后就会给通道timeout发送数据,所以就会输出timeout

2). 那如果我们把上面代码改为如下代码会输出什么呢

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"
// import "time"

func main() {
ch:=make(chan interface{})
timeout:=make(chan bool,1)
go func(){
timeout<-true
ch<-1
}()
fmt.Println("main goroutine begin")
select{
case <-ch:
fmt.Println("No timeout")
case <-timeout:
fmt.Println("timeout")
default:
fmt.Println("default")
}
}

output
会随机输出timeout、No timeout和default,因为所有通道都满足case,则go运行的时候会随机选择一个case执行

简单模拟一下客户端服务端通信

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

import (
"errors"
"fmt"
"time"
)

func Client(ch chan interface{}, content string) (interface{}, error) {
ch <- content
select {
case resp := <-ch:
return resp, nil
case <-time.After(2 * time.Second):
return "", errors.New("time out")
}
}

func Server(ch chan interface{}, content string){
for{
data:=<-ch
fmt.Println("received:",data)
// time.Sleep(3 *time.Second) 如果想模拟超时可以加上这条代码
ch<-content
}
}

func main() {
ch:=make(chan interface{})

go Server(ch,"Send data")
recv,err:=Client(ch,"hello")
if err!=nil{
fmt.Println(err)
}else{
fmt.Println(recv)
}
}

通道响应计时器

1). 延时回调

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"
"time"
)



func main() {
IsExit:=make(chan bool)
fmt.Println("Start")
time.AfterFunc(2 * time.Second,func(){
fmt.Println("Exec")
IsExit<-true
})
<-IsExit
fmt.Println("Exit")
}

output:
Start
Exec
Exit

2). 定点计时

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

import (
"fmt"
"time"
)



func main() {
ticker := time.NewTicker(200 * time.Millisecond) //创建一个打点器,每200毫秒触发一次
breaker:= time.NewTimer(3 * time.Second) //创建一个计时器,3秒后触发一次
var stop bool = false
var count int
for{
select{
case <-ticker.C:
count++
case <-breaker.C:
fmt.Println("Time out break")
stop=true
}
if stop{
break //如果breaker通道接收到数据则退出
}
}
fmt.Println(count)
}

从已经关闭中的通道获取数据

  1. 关闭的通道依然可以被访问,访问被关闭的通道将会发生一些问题。
  2. 被关闭的通道不会被置为 nil
  3. 如果尝试对已经关闭的通道进行发送,将会触发宕机

从已经关闭的通道接收数据或者正在接收数据时,将会接收到通道类型的零值,然后停止阻塞并返回,如下代码

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

import (
"fmt"
)



func main() {
ch:=make(chan int,2)
ch<-1
ch<-2
close(ch)
for i:=0;i<=cap(ch);i++{
data,ok:=<-ch
fmt.Println(data,ok)
}
}

output:
1 true
2 true
0 false //false 表示没有获取成功,因为此时通道已经空了

活锁、死锁、饥饿

死锁

死锁:会使得所有并发程序在等待,如果没有外界干预,程序不能恢复

1
2


出现死锁的几个必要条件,也被称为Coffman条件

Coffman 条件如下:

  1. 相互排斥:井发进程同时拥有资源的独占权。
  2. 等待条件:并发进程必须同时拥有一个资源,并等待额外的资源。
  3. 没有抢占:并发进程拥有的资掘只能被该进程释放,即可满足这个条件。
  4. 循环等待:一个并发进程(P1)必须等待一系列其他井发进程(P2),这些并发进程同时也在等待进程(P2),这样便满足了这个最终条件。

一些杂项

生成二维码

获取包:go get github.com/skip2/go-qrcode

生成一个跳转到百度的二维码

1
2
3
4
5
package main
import "github.com/skip2/go-qrcode"
func main() {
qrcode.WriteFile("http://www.baidu.com/",qrcode.Medium,256,"./qrcode.png")
}