《Go语言圣经》学习笔记:4.复合数据类型

4. 复合数据类型

4.1 数组

没有手动分配值初始化,编译器会给数组进行零初始化

如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。

q := [...]int{1, 2, 3}
fmt.Printf("%T\n", q) // "[3]int"

数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。所以长度不同但底层类型相同的数组不能进行比较(==, !=等都算非法)

a := [2]int{1, 2}
b := [...]int{1, 2}
c := [2]int{1, 3}
fmt.Println(a == b, a == c, b == c) // "true false false"
d := [3]int{1, 2}
fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int

可以在初始化的时候指定索引(位置)进行初始化,生成的数组大小为最后一个索引的值+1。在最后一个索引之前的索引的位置没有手动初始化赋值,那么会进行零初始化。因此除非必要数组依然很少用作函数参数;相反,我们一般使用slice来替代数组。

a := [...]int{2:10, 4:20}
fmt.Println(a)		// "[0, 0, 10, 0, 20]"
fmt.Printf("%T", a) // "[5]int"

由于数组长度只要不一样,就属于不同的类型了,在一些情况处理起来会很麻烦。比如一个函数声明的时候有个参数是16大小的数组,那么在传参的时候就不能使用大小为8或者32的数组了。

4.2 切片

一个slice由三个部分构成:指针、长度和容量。

有点类似于:

type IntSlice struct {
    ptr      *int
    len, cap int
}

多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。由于共享底层数据,所以一个切片的改动会影响另外一个切片的数据:

func main() {
	arr := [...]int{1:10, 5:7, 9:10}
	s1 := arr[:7]
	s2 := arr[5:]
	fmt.Println(arr, s1, s2)
	//[0 10 0 0 0 7 0 0 0 10] [0 10 0 0 0 7 0] [7 0 0 0 10]
	s1[6] = 66
	fmt.Println(arr, s1, s2)
	//[0 10 0 0 0 7 66 0 0 10] [0 10 0 0 0 7 66] [7 66 0 0 10]
}

和数组不同的是,slice之间不能比较,哪怕长度一样。slice唯一合法的比较操作是和nil比较。可以bytes.Equal函数来判断两个字节型slice是否相等。其他的类型还是要自己动手写一个出来。

func equal(x, y []string) bool {
    if len(x) != len(y) {
        return false
    }
    for i := range x {
        if x[i] != y[i] {
            return false
        }
    }
    return true
}

如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断。因为有不为nil的切片其长度也为空的。

可以利用append函数在切片中插入新的元素。append对于底层数组内存扩展采取的是翻倍,一旦容量不够,就扩充原理一倍的容量。但是通常我们并不知道append调用是否导致了内存的重新分配,因此我们也不能确认新的slice和原始的slice是否引用的是相同的底层数组空间。比如以下代码

func main() {
	s1 := make([]int, 0, 5)
	s1 = append(s1, 1, 2, 3, 4, 5)
	s2 := append(s1, 6, 7, 8, 9, 10, 11)
	s1[0] = 20
	fmt.Println(s1, s2)
}

结果如下,可以看到s1和s2指向的底层数据不一样了

[20 2 3 4 5] [1 2 3 4 5 6 7 8 9 10 11]

4.3 map

一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。

但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:

_ = &ages["bob"] // compile error: cannot take address of map element

禁止对map元素取址的原因是map可能随着元素数量的增长而重新分配更大的内存空间,从而可能导致之前的地址无效。

Map的迭代顺序是不确定的。这是故意的,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。

map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但是向一个nil值的map存入元素将导致一个panic异常

ages["carol"] = 21 // panic: assignment to entry in nil map

4.4 结构体

和map不同,结构体可以对成员取地址,然后通过指针访问。点操作符也可以和指向结构体的指针一起工作:

var employeeOfTheMonth *Employee = &dilbert
employeeOfTheMonth.Position += " (proactive team player)"
// 相当于
(*employeeOfTheMonth).Position += " (proactive team player)"

如果结构体成员名字是以大写字母开头的,那么该成员就是导出的:

package test

type T struct{ a, b int } // a and b are not exported

type G struct{ A, B int } // a and b are not exported

package main
import (
	"studygo/day12/test"
)


func main() {
	t := test.T{a:10, b:20} //compile error
	t := test.T{10, 20}		//compile error
	g := test.G{A:10, B:20}
}

如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0。空结构题会有一些特殊作用:Go语言–空结构体struct{}解析

如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的。
可比较的结构体类型和其他可比较的类型一样,可以用于map的key类型。注意,一定要是可比较的结构体

type address struct {
    hostname string
    port     int
}

hits := make(map[address]int)
hits[address{"golang.org", 443}]++

嵌套结构体以及初始化

type Point struct {
	X int
	Y int
}

type Circle struct {
	P Point
	Radius int
}

type Wheel struct {
	Circle // 匿名嵌套
	Spokes int
}

func main()  {
	var w1 Wheel
	fmt.Printf("%v\n", w1)   	// {{{0 0} 0} 0} 自动进行零值初始化

	//w2 := Wheel{10, 20, 30, 40, 50 }  // 这种声明初试化的方式不行

	// 比较简洁的初试化方式
	w2 := Wheel{Circle{Point{1, 2}, 3}, 4}
	fmt.Printf("%v\n", w2)		{{{1 2} 3} 4}

	// 比较详细的初始化方式
	w3 := Wheel{
		Circle:      Circle{  //匿名嵌套使用的是该类型的名字
			P:Point{X:1, Y:2},   // 非匿名的用声明的那个名
			Radius:3,
		},
		Spokes: 4,
	}
	fmt.Printf("%v\n", w3)		//{{{1 2} 3} 4}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
个人学习golang笔记,从各种教程中总结而来,作为入门参考。目录如下 目录 1. 入门 1 1.1. Hello world 1 1.2. 命令行参数 2 2. 程序结构 3 2.1. 类型 4 2.1.1. 命名类型(named type)与未命名类型(unamed type) 4 2.1.2. 基础类型(underlying type) 4 2.1.3. 可赋值性 5 2.1.4. 类型方法集 6 2.1.5. 类型声明 6 2.2. 变量 8 2.2.1. 变量声明 8 2.2.2. 类型零值 12 2.2.3. 指针 13 2.3. 赋值 17 2.4. 包和文件 17 2.5. 作用域 18 2.6. 语句 19 2.7. 比较运算符 20 2.8. 类型转换 21 2.9. 控制流 23 2.9.1. If 23 2.9.2. Goto 24 2.9.3. For 25 2.9.4. Switch 25 2.9.5. break语句 31 2.9.6. Continue语句 31 3. 基础数据类型 31 3.1. golang类型 31 3.2. Numeric types 32 3.3. 字符串 33 3.3.1. 什么是字符串 33 3.3.2. 字符串底层概念 35 3.3.3. 获取每个字节 38 3.3.4. Rune 39 3.3.5. 字符串的 for range 循环 40 3.3.6. 用字节切片构造字符串 41 3.3.7. 用rune切片构造字符串 42 3.3.8. 字符串的长度 42 3.3.9. 字符串是不可变的 42 3.3.10. UTF8(go圣经) 43 3.4. 常量 45 3.4.1. 常量定义 45 3.4.2. 常量类型 46 3.4.3. Iota 46 4. 组合数据类型 47 4.1. 数组 47 4.1.1. 数组概述 47 4.1.2. 数组的声明 49 4.1.3. 数组的长度 50 4.1.4. 遍历数组 50 4.1.5. 多维数组 51 4.2. 切片 52 4.2.1. 什么是切片 52 4.2.2. 切片概述 55 4.2.3. 创建一个切片 55 4.2.4. 切片遍历 57 4.2.5. 切片的修改 58 4.2.6. 切片的长度和容量 60 4.2.7. 追加切片元素 62 4.2.8. 切片的函数传递 65 4.2.9. 多维切片 66 4.2.10. 内存优化 67 4.2.11. nil slice和empty slice 69 4.2.12. For range 70 4.3. 结构 71 4.3.1. 什么是结构体? 71 4.3.2. 结构体声明 73 4.3.3. 结构体初始化 77 4.3.4. 嵌套结构体(Nested Structs) 81 4.3.5. 匿名字段 82 4.3.6. 导出结构体和字段 84 4.3.7. 结构体相等性(Structs Equality) 85 4.4. 指针类型 86 4.5. 函数 87 4.6. map 87 4.6.1. 什么是map 87 4.6.2. 声明、初始化和make 89 4.6.3. 给 map 添加元素 91 4.6.4. 获取 map 中的元素 91 4.6.5. 删除 map 中的元素 92 4.6.6. 获取 map 的长度 92 4.6.7. Map 的相等性 92 4.6.8. map的排序 92 4.7. 接口 93 4.7.1. 什么是接口? 93 4.7.2. 接口的声明与实现 96 4.7.3. 接口的实际用途 97 4.7.4. 接口的内部表示 99 4.7.5. 空接口 102 4.7.6. 类型断言 105 4.7.7. 类型选择(Type Switch) 109 4.7.8. 实现接口:指针接受者与值接受者 112 4.7.9. 实现多个接口 114 4.7.10. 接口的嵌套 116 4.7.11. 接口的零值 119 4.8. Channel 120 4.9. 类型转换 120 5. 函数 120 5.1. 函数的声明 121 5.2. 一个递归函数的例子( recursive functions) 121 5.3. 多返回值 121 5.4. 命名返回值 121 5.5. 可变函数参数 122 5.6. Defer 123 5.6.1. Defer语句介绍 123 5.6.2. Defer使用场景 128 5.7. 什么是头等(第一类)函数? 130 5.8. 匿名函数 130 5.9. 用户自定义的函数类型 132 5.10. 高阶函数(装饰器?) 133 5.10.1. 把函数作为参数,传递给其它函数 134 5.10.2. 在其它函数中返回函数 134 5.11. 闭包 135 5.12. 头等函数的实际用途 137 6. 微服务创建 140 6.1. 使用net/http创建简单的web server 140 6.2. 读写JSON 144 6.2.1. Marshal go结构到JSON 144 6.2.2. Unmarshalling JSON 到Go结构 146 7. 方法 146 7.1. 什么是方法? 146 7.2. 方法示例 146 7.3. 函数和方法区别 148 7.4. 指针接收器与值接收器 153 7.5. 那么什么时候使用指针接收器,什么时候使用值接收器? 155 7.6. 匿名字段的方法 156 7.7. 在方法中使用值接收器 与 在函数中使用值参数 157 7.8. 在方法中使用指针接收器 与 在函数中使用指针参数 159 7.9. 在非结构体上的方法 161 8. 并发入门 162 8.1. 并发是什么? 162 8.2. 并行是什么? 162 8.3. 从技术上看并发和并行 163 8.4. Go 对并发的支持 164 9. Go 协程 164 9.1. Go 协程是什么? 164 9.2. Go 协程相比于线程的优势 164 9.3. 如何启动一个 Go 协程? 165 9.4. 启动多个 Go 协程 167 10. 信道channel 169 10.1. 什么是信道? 169 10.2. 信道的声明 169 10.3. 通过信道进行发送和接收 169 10.4. 发送与接收默认是阻塞的 170 10.5. 信道的代码示例 170 10.6. 信道的另一个示例 173 10.7. 死锁 174 10.8. 单向信道 175 10.9. 关闭信道和使用 for range 遍历信道 176 11. 缓冲信道和工作池(Buffered Channels and Worker Pools) 179 11.1. 什么是缓冲信道? 179 11.2. 死锁 182 11.3. 长度 vs 容量 183 11.4. WaitGroup 184 11.5. 工作池的实现 186 12. Select 188 12.1. 什么是 select? 188 12.2. 示例 189 12.3. select 的应用 190 12.4. 默认情况 190 12.5. 死锁与默认情况 191 12.6. 随机选取 191 12.7. 这下我懂了:空 select 191 13. 文件读写 191 13.1. GoLang几种读文件方式的比较 197 14. 个人 197 14.1. ++,-- 198 14.2. 逗号 198 14.3. 未使用的变量 199 14.4. Effective go 199 14.4.1. 指针 vs. 值 199 14.5. 可寻址性-map和slice的区别 201 14.5.1. slice 201 14.5.2. map 202 14.6. golang库 203 14.6.1. unicode/utf8包 203 14.6.2. time包 205 14.6.3. Strings包 205 14.6.4. 输入输出 212 14.6.5. 正则处理 224 14.6.6. Golang内建函数 226

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值