【Go语言学习笔记】数据

目录

    • 字符串
    • 数组
      • 数组初始化
      • 指针
      • 复制
    • 切片
      • 基本操作
      • reslice
      • append
      • copy
    • 字典
      • delete
      • map是引用类型
      • 并发操作

字符串

字符串是不可变字节(byte)序列,其本身是一个复合结构

type stringStruct struct{
	str unsafe.Pointer
	len int
}

头部指针指向字节数组,但没有null结尾,默认以utf-8编码粗出Unicode字符,字面量李允许使用十六进制、八进制和UTF编码格式。默认值是""而不是null。

使用"`"定义不做转移处理的原始字符串,支持跨行

func main() {
	s := `line\r\n,
	line 2`
	println(s)
}
  • 支持!=、==、<、>、+、+=操作符
  • 允许以索引号访问字节数组,但不能获取元素地址
  • 以切片语法返回子串时,其内部依旧指向原字节数组
  • 使用for遍历字符串时,分byte和rune两种方式;
func main() {
	s := "哈喽"

	for i := 0; i < len(s); i++ { // byte
		fmt.Printf("%d: [%c]\n", i, s[i])
	}
	for i, c := range s {  // rune 返回数组索引号 以及unicode字符
		fmt.Printf("%d: [%c]\n", i, c)
	}

	0: [å]
	1: [ ]
	2: [ ]
	3: [å]
	4: [ ]
	5: [½]
	0: []
	3: []

}

数组

定义数组类型时,数组长度必须是非负整形常量表达式,长度是类型组成部分,所以,元素类型相同但是长度不同的数组不属于同一类型

func main() {
	d1 := [3]int{}
	d2 := [2]int{}
	d1 = d2 // cannot use d2 (variable of type [2]int) as type [3]int in assignment
}

数组初始化

	var a [4]int              // 元素自动初始化为0  [0 0 0 0]
	b := [4]int{2, 5}         // 未提供初始值得元素自动初始化为0  [2 5 0 0]
	c := [4]int{5, 3: 100}    // 可指定索引位置初始化  [5 0 0 100]
	d := [...]int{1, 2, 3}    // 编译器按初始化值数量确定数组长度  [1 2 3]
	e := [...]int{10, 3: 100} // 支持索引初始化,但是数组长度与此有关 [10 0 0 100]
	fmt.Println(a, b, c, d, e)

对于结构等复合类型,可省略元素初始化类型标签

	type user struct {
		name string
		age  byte
	}
	d := [...]user{
		{"Tome", 20}, // 省略了类型标签
		{"Mary", 18}}

	fmt.Printf("%#v\n", d)

在定义多维数组时,仅第一维度允许使用“…”

	a := [2][2]int{
		{1, 2},
		{3, 4},
	}
	b := [...][2]int{
		{10, 20},
		{30, 40},
	}
	c := [...][2][2]int{
		{
			{1, 2},
			{3, 4},
		},
		{
			{10, 20},
			{30, 40},
		},
	}
	fmt.Println(a)
	fmt.Println(b)
	fmt.Println(c)
	
	[[1 2] [3 4]]
	[[10 20] [30 40]]
	[[[1 2] [3 4]] [[10 20] [30 40]]]

内置函数len和cap都返回第一维度长度;
如果元素类型支持==、!=操作符,那么数组也支持此操作

指针

要分清楚指针数组和数组指针的区别,指针数组是指元素为指针类型的数组,数组指针是获取数组变量的地址。

	x, y := 10, 20
	a := [...]*int{&x, &y} // 指针数组
	p := &a                // 数组指针

	fmt.Printf("%T,%v\n", a, a) //[2]*int,[0xc000018088 0xc0000180a0]
	fmt.Printf("%T,%v\n", p, p) //*[2]*int,&[0xc000018088 0xc0000180a0]

	// 获取任意元素地址
	println(&a, &a[0], &a[1])

	//数组指针可以直接用来操作元素
	c := [...]int{1, 2}
	d := &c
	d[1] += 10
	println(d[1])

复制

Go数组是值类型,赋值和传参操作都会复制整个数组数据。

func test(a [3]int) {
	println(&a, &a[0], &a[1], &a[2]) // 传参 0xc0000cbf40 0xc0000cbf40 0xc0000cbf48 0xc0000cbf50
}

func main() {
	a := [...]int{1, 2, 3}
	println(&a, &a[0], &a[1], &a[2]) // 0xc0000cbf58 0xc0000cbf58 0xc0000cbf60 0xc0000cbf68
	b := a
	println(&b, &b[0], &b[1], &b[2]) //赋值 0xc0000cbf28 0xc0000cbf28 0xc0000cbf30 0xc0000cbf38
	test(a)

如果需要,可改用指针或者切片,以避免数据复制

func test(a *[3]int) {
	println(&a, &a[0], &a[1], &a[2]) // 指针传参 0xc00007bf50 0xc00007bf38 0xc00007bf40 0xc00007bf48
}

func main() {
	a := [...]int{1, 2, 3}
	println(&a, &a[0], &a[1], &a[2]) // 0xc00007bf38 0xc00007bf38 0xc00007bf40 0xc00007bf48
	b := a[:]
	println(&b, &b[0], &b[1], &b[2]) //切片赋值 0xc00007bf58 0xc00007bf38 0xc00007bf40 0xc00007bf48
	var c = &a		// 指针赋值  0xc00007bf68 0xc00007bf38 0xc00007bf40 0xc00007bf48
	println(&c, &c[0], &c[1], &c[2])

	test(&a)

切片

基本操作

切片本身并非动态数组或数组指针,内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内。其本身是个只读对象,工作机制类似数组指针的一种包装。

	x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	x1 := x[:]
	x2 := x[2:5]
	x3 := x[2:5:7]
	x4 := x[4:]
	x5 := x[:4]
	x6 := x[:4:6]
	fmt.Println(x1, len(x1), cap(x1)) // [0 1 2 3 4 5 6 7 8 9] 10 10
	fmt.Println(x2, len(x2), cap(x2)) // [2 3 4] 3 8
	fmt.Println(x3, len(x3), cap(x3)) // [2 3 4] 3 5
	fmt.Println(x4, len(x4), cap(x4)) // [4 5 6 7 8 9] 6 6
	fmt.Println(x5, len(x5), cap(x5)) // [0 1 2 3] 4 10
	fmt.Println(x6, len(x6), cap(x6)) // [0 1 2 3] 4 6

属性cap表示切片所引用数组片段的真实长度,len用于限定可读的写元素数量。

cap表示切片引用数组的容量,该数组可以插入多少个元素,如果cap不够则需要扩容

	x3 = append(x3, 1000)
	x3 = append(x3, 1001)
	fmt.Println(x3, len(x3), cap(x3)) // [2 3 4 1000 1001] 5 5
	println(&x3)
	x3 = append(x3, 1002)
	fmt.Println(x3, len(x3), cap(x3)) //cap不够,发生扩容,cap变成10 [2 3 4 1000 1001 1002] 6 10
	println(&x3)

可以直接创建切片对象,无须预先准备数组,因为是引用类型,须使用make函数或显

	s1 := make([]int, 3, 5)    // 指定len、cap底层数组初始化为零值
	s2 := make([]int, 3)       // 省略cap 和len 相等
	s3 := []int{10, 20, 5: 30} // 按初始化元素分配底层数组,并设置 len、cap

	fmt.Println(s1, len(s1), cap(s1)) // [0 0 0] 3 5
	fmt.Println(s2, len(s2), cap(s2)) // [0 0 0] 3 3
	fmt.Println(s3, len(s3), cap(s3)) // [10 20 0 0 0 30] 6 6

可以获取元素地址,但是不能向数组那样直接用指针访问元素内容

	s := []int{0, 1, 2, 3, 4}
	p := &s
	p0 := &s[0]
	p1 := &s[1]

	println(p, p0, p1) // 0xc00007bf58 0xc00000a360 0xc00000a368

	(*p)[0] += 100 // *[]int 不支持索引操作 须先返回[]int对象 mismatched types []int and untyped int
	*p1 += 100

	fmt.Println(s) // [100 101 2 3 4]

reslice

对切片再次进行切片,不能超出cap,但是不受len限制。新切片对象依旧指向原底层数组,也就是说修改对所有关联切片可见。

	d := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s1 := d[3:7]
	s2 := s1[:3]
	s2[1] = 100
	println(&s1[0]) // 0xc000014298
	println(&s2[0]) // 0xc000014298
	fmt.Println(s1) // [3 4 100 6]
	fmt.Println(s2) // [3 100 5] 

append

向切片尾部添加数据,返回新的切片对象

	s := make([]int, 0, 5)
	s1 := append(s, 10)
	s2 := append(s1, 20, 30)

	fmt.Println(s, len(s), cap(s))    // [] 0 5
	fmt.Println(s1, len(s1), cap(s1)) // [10] 1 5
	fmt.Println(s2, len(s2), cap(s2)) // [10 20 30] 3 5

数据被追加到原底层数组,如超出cap限制,则为新切片对象重新分配数组。

copy

在两个切片对象间复制数据,允许指向同一底层数组,允许目标区间重叠,最终所复制长度以较短的切片长度为准。


	d := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	s1 := d[5:8]
	n := copy(d[4:], s1) // 在同一底层数组的不同区间复制
	fmt.Println(n, d)    // 3 [0 1 2 3 5 6 7 7 8 9]

	s2 := make([]int, 6)
	n = copy(s2, d)    // 在不同数组间复制
	fmt.Println(n, s2) // 6 [0 1 2 3 5 6]

字典

字典是引用类型,使用make函数或初始化表达语句来创建。

	m := map[string]int{
		"a": 1,
		"b": 2,
	}
	fmt.Println(m)

	m["a"] = 10 // 修改
	m["c"] = 30 // 新增

	if v, ok := m["d"]; ok { // 使用ok-idiom 判断key是否存在
		println(v)
	}
	delete(m, "d") // 删除键值对,不存在时,不会出错

访问不存在的键值对,默认返回零值,不会引发错误,但推荐使用ok-idiom模式,毕竟通过零值无法判断键值是否存在,或许存储的value本身就是零。

因内存访问安全和河西算法等缘故,字典被设计成“not addressable” 故不能直接修改value成员。

正确做法时返回整个value,待修改后再设置字典键值,或直接用指针类型。

	type user struct {
		name string
		age  byte
	}

	m := map[int]user{
		1: {"Tome", 19},
	}

	//m[1].age += 1 // Cannot assign to m[1].age

	u := m[1]
	u.age += 1
	m[1] = u
	fmt.Println(m) // map[1:{Tome 20}]

	m2 := map[int]*user{
		1: &user{"Jack", 20},
	}
	m2[1].age++

delete

在迭代期间删除或新增键值时安全的。

	m := make(map[int]int)

	for i := 0; i < 10; i++ {
		m[i] = i + 10
	}

	for k := range m {
		if k == 5 {
			m[100] = 1000
		}
		delete(m, k)
		fmt.Println(k, m)
	}
	//1 map[0:10 2:12 3:13 4:14 5:15 9:19]
	//3 map[0:10 2:12 4:14 5:15 9:19]
	//4 map[0:10 2:12 5:15 9:19]
	//5 map[0:10 2:12 9:19 100:1000]
	//0 map[2:12 9:19 100:1000]
	//2 map[9:19 100:1000]
	//9 map[100:1000]

不能保证迭代操作会删除新增的键值

Go没有为map提供清空所有元素的函数,清空map唯一的办法是重新make一个新的map。
Go语言的垃圾回收比写一个清空函数更高效

map是引用类型

map与切片相似,都是引用类型。将一个map赋值给一个新的变量时,它们指向同一块内存,因此修改两个变量的内容都能够引起它们所指向的数据发生变化。

	map1 := map[string]string{
		"hello":"123",
		"demo":"321",
	}
	fmt.Println("原始map: ",map1)
	newMap := map1
	newMap["hello"]="456"
	fmt.Println("修改后newMap: ",newMap)
	fmt.Println("修改后map: ",map1)
//	原始map:  map[demo:321 hello:123]
//	修改后newMap:  map[demo:321 hello:456]
//	修改后map:  map[demo:321 hello:456]

并发操作

运行时会对字典并发操作做出检测,如果某个任务正在对字段进行写操作,那么其他任务就不能对该字典并发操作,否则会导致进程崩溃。

	m := make(map[string]int)

	go func() {
		for {
			m["a"] += 1 // 写操作
			time.Sleep(time.Millisecond)
		}
	}()

	go func() {
		for {
			_ = m["b"] // 读操作
			time.Sleep(time.Millisecond)
		}
	}()

	select {} // 阻止进程退出
	
	// fatal error: concurrent map read and map write

你可能感兴趣的:(GoLang,golang,学习,java)