Golang 的常用容器

本篇主要是讲述了数组和切片、Map 的初始化方式与基本使用、重点阐述了如何使用 Map 实现 Set、用 Map 实现工厂模式,以及字符串的使用,字符串常用 API、Unicode 与 UTF8 的关系!

mark

数组和切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func TestArray01(t *testing.T)  {
// 声明并初始化为默认值
var a [3]int
a [0] = 1

b := [3] int {11, 22, 33}
c := [2][2] int {{11, 22}, {33, 44}}

for _,data := range b{
t.Log (data)
}
for _,data := range c{
t.Log (data)
}
}

func TestArraySection(t *testing.T){
arr := [...]int{11, 22, 33, 44}
arr_sec := arr [:3] // 取前三个元素
t.Log (arr_sec)
arr_sec = arr [3:] // 取下标为 3 的后面的所有元素
t.Log (arr_sec)
}

mark

其实切片的内部结构和 Buffer 的结构是差不多的,切片的使用:

make 函数相当于申请空间、并且可以控制多少空间初始化!

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

import "testing"

func TestSlice(t *testing.T) {
var s0 []int // 切片的声明
t.Log (len(s0), cap(s0)) //0 0
s0 = append(s0, 1)
t.Log (len(s0), cap(s0)) //1 1

s1 := []int{1, 2, 3, 4}
t.Log (len(s1), cap(s1)) //4 4

s2 := make([]int, 3, 5)
t.Log (len(s2), cap(s2)) //4 4

s2 = append(s2, 1)
t.Log (len(s2), cap(s2)) //4 4
t.Log (s2 [0], s2 [1], s2 [2], s2 [3])
}

切片如何扩容呢?

1
2
3
4
5
6
7
func TestSliceGrowing(t *testing.T)  {
s := []int{}
for i:=0; i<10; i++{
s = append(s, i)
t.Log (len(s), cap(s))
}
}

mark

可以看出来,每次增长二倍的关系,所以就解释了为什么每次 append 之后都要接收 append 的返回值,因为 go 的内部实现就是内存区域 Copy 的形式,所以随时可能返回新的内存地址,所以需要接收!

mark

1
2
3
4
5
6
7
8
9
10
11
func TestSliceShareMemory(t *testing.T){
year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}

Q2 := year [3:6]
t.Log (Q2, len(Q2), cap(Q2)) //[Apr May Jun] 3 9
summer := year [5:8]
t.Log (summer, len(summer), cap(summer)) //[Jun Jul Aug] 3 7
summer [0] = "UnKnow"
t.Log (Q2, len(Q2), cap(Q2)) //[Apr May UnKnow] 3 9
}

数组和切片的比较:数组不可伸缩、切片可以伸缩;相同维数且相同长度的数组才是可以比较的,切片是不可以比较的,下面的代码会编译不通过:

1
2
3
4
5
6
7
func TestSliceCompare(t *testing.T){
a := []int{1, 2 , 3, 4}
b := []int{1, 2 , 3, 4}
if a == b{ //error
t.Log ("equal")
}
}

Map 的基本使用

mark

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

import "testing"

func TestInitMap(t *testing.T) {
m1 := map[string]int{"one":1, "two":2, "three":3}
t.Log (m1, len(m1)) //map [one:1 three:3 two:2] 3

m2 := map[int]int{}
m2 [4] = 16
t.Log (m2, len(m2)) //map [4:16] 1

m3 := make(map[int]int, 10)
t.Log (m3, len(m3)) //map [] 0
}

mark

Map 的遍历

1
2
3
4
5
6
func TestForEachMap(t *testing.T)  {
m1 := map[string]int{"one":1, "two":2, "three":3}
for k, v := range m1{
t.Log (k, "=", v)
}
}

Map 与工厂模式

Map 的 Value 可以是一个方法,因为在 Golang 中,方法也是一种类型,可以当做参数传递的,与 Dock type 接口方式一起,可以方便的实现单一方法对象的工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func TestMapWithFunValue(t *testing.T)  {
//Key 是整型、Value 是方法类型
m := map[int] func(op int) int {}

m [1] = func(op int) int {
return op
}

m [2] = func(op int) int {
return op * op
}

m [3] = func(op int) int {
return op * op * op
}

t.Log (m [1](2 ), m [2](2), m [3](2)) //2 4 8
}

Map 实现 Set

Go 内置集合没有 Set 实现,可以 map [type] bool

1、元素的唯一性

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
29
func TestMapForSet(t *testing.T)  {
mySet := map[int]bool{}
//1、添加元素
mySet [1] = true

//n := 3 //3 is not existing
n := 1 //1 is existing

//2、判断一个元素是否存在
if mySet [n]{
t.Logf ("% d is existing", n) //1 is existing
}else{
t.Logf ("% d is not existing", n)
}

//3、获取 Set 的长度
mySet [2] = true
mySet [3] = true
t.Log ("len =", len(mySet)) //len = 3

//4、从 Set 删除元素
delete(mySet, 1)
n = 1
if mySet [n]{
t.Logf ("% d is existing", n)
}else{
t.Logf ("% d is not existing", n) //1 is not existing
}
}

字符串

与其他主要编程语言的差异:

string 是数据类型,不是引用或指针类型

string 是只读的 byte slice, len 函数可以它所包含的 byte 数

string 的 byte 数组可以存放任何数据

string 实际上就是一个不可变的切片

1
2
3
4
5
6
7
8
9
10
11
12
func TestString(t *testing.T)  {
var s string
t.Log (s) // 初始化默认值 & quot;"
s = "hello"
t.Log (len(s))

//s [3] = 'c' error string 不可变
s = "\xE4\xB8\xA5" // 可以存储任意二进制数据
t.Log (s, len(s)) // 严
s = " 中国 & quot;
t.Log (len(s)) //len 求的是字节数
}

Unicode 与 UTF8

1、Unicode 是一种字符集(code point)

2、UTF8 是 Unicode 的存储实现(转为字节序列的规则)

Unicode 为世界上所有字符都分配了一个唯一的数字编号,这个编号范围从 0x000000 到 0x10FFFF (十六进制),有 110 多万,每个字符都有一个唯一的 Unicode 编号,这个编号一般写成 16 进制, Unicode 就相当于一张表,建立了字符与编号之间的联系,而 UTF8 是具体实现, 在 UTF8 中编号小的使用的字节就少,编号大的使用的字节就多。使用的字节个数从 1 到 4 个不等。

rune 函数

在 Go 当中 string 底层是用 byte 数组存的,并且是不可以改变的。fmt.Println (len (“Go 编程”)) 输出结果应该是 8 因为中文字符是用 3 个字节存的, len (string (rune (‘ 编 ‘))) 的结果是 3 ,如果想要获得我们想要的情况的话,需要先转换为 rune 切片再使用内置的 len 函数: fmt.Println (len ([] rune (s))) 结果就是 4 了。 所以用 string 存储 unicode 的话,如果有中文,按下标是访问不到的,因为你只能得到一个 byte。 要想访问中文的话,还是要用 rune 切片,这样就能按下标访问。

1
2
3
4
5
6
7
8
9
func TestString(t *testing.T)  {
s := " 中 & quot;
t.Log (len(s)) //3

c := []rune(s)

t.Logf (" 中 unicode % x", c [0]) // 中 unicode 4e2d
t.Logf (" 中 UTF8 % x", s) // 中 UTF8 e4b8ad
}

mark

常用的字符串处理函数:
strings 包:
https://golang.org/pkg/strings

strconv 包
https://golang.org/pkg/strconv

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func TestStringFunc(t *testing.T)  {
// 切割字符串
s := "A,B,C"
split := strings.Split (s, ",")
for _, ch := range split{
t.Log (ch)
}

// 连接字符串数组的元素
t.Log (strings.Join (split, "-"))

// 字符串与数字的转换
s = strconv.Itoa (250)
t.Logf ("s = % s", s)

num := "100"
// 返回两个值,err 代表是否发生错误
if i, err := strconv.Atoi (num); err == nil{
t.Log (100 + i)
}
}