本章主要介绍组合类型(composite type)中的array、slice、map、struct

Slice

slice的组成

A slice has three components: a pointer, a length, and a capacity.
slice由三个部分组成:指针,长度,容量

slice之间比较

Unlike arrays, slices are not comparable, so we cannot use == to test whether two slices contain the same elements.
与数组不同的是,slice并不能使用==来比较两个slice是否包含相同的元素。

标准库中有bytes.Equal来比较[]byte, 其他的slice类型比较需要我们自己实现。

package bytes// Equal reports whether a and b
// are the same length and contain the same bytes.
// A nil argument is equivalent to an empty slice.
func Equal(a, b []byte) bool {// Neither cmd/compile nor gccgo allocates for these string conversions.return string(a) == string(b)
}

比如我们自己实现两个[]string的比较,先比较深度,再逐一比较各个元素:

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为什么不能比较

为什么slice不采用上面这种先深度相等判断然后逐一比较元素的方法,来进行两个slice的比较呢?
有两个方面原因:
1:与array不同, slice的元素是间接(indirect)的,因此一个slice可能会包含自身

s := []interface{}{1, nil}
s[1] = s
fmt.Println(s)  // fatal error: stack overflow

这样的s执行fmt.Println(s)时会报错stack overflow,其原因是Println函数是递归打印的,s的元素类型是interface{},打印时要获取其动态类型和动态值。
而s[1]的动态类型是[]interface{},其动态值是指向s底层数组的指针。
就是说s[1]又指向它自己,又要获取其动态类型和动态值?这个过程一直持续,导致最后栈溢出。
【这就涉及到了将具体类型赋值给空接口时,接口里保存的是什么?比如array、slice、string、int、map、chan、interface】

s包含了它自身,只要不用fmt.Println(s)导致迭代栈溢出,其元素是可以打印的:

s2 := s[1].([]interface{})
fmt.Println(s2[0])          // 1
s3 := s2[1].([]interface{})
fmt.Println(s3[0])          // 1

相对应的array,不能包含它自己:

s := [2]interface{}{1, nil}
s[1] = s
fmt.Println(s) //  [1 [1 <nil>]]

可以看到在赋值s[1] = s时,是将当前s的每个元素组成array赋给了s[1]这个interface{},是值复制。

2: 因为slice element是indirect, 因此一个固定的slice value在不同时刻可能会有不同的元素
因为slice value仅有三个部分(指针、长度、容量),当底层数组改变时,slice的元素也会改变。
而hash table如go中的map只是对它的key进行shallow copy浅复制,它要求在hash table的声明周期内
每个key保持不变。因此slice不能成为map的key。

而对于引用类型如ptr和chan而言,等号 == 操作符test reference identity只是判断是否引用相同的对象。
而如果slice也采用这种 语义,它也就可以解决map的key问题,但它就和同为容器类的array的等号操作符含义不同了,会令人感到困惑,因此干脆禁用slice的比较操作。

总结:slice为什么不能比较?

  • 如果采用类似array的逐个元素比较的这种”deep“ equality,由于slice可能会包含它自己,
    导致这种比较会比较复杂也不易于理解。
  • 如果采用了”deep“ equality方式而使得两个slice可以比较,那按道理他们就可以作为map的key。
    但map的key只是被浅复制,要求在声明周期内不可变,那么”deep“ equality就会使得即使两个slice可比较,
    也不能作为map的key。而如果采用了类似其他引用方式的shallow queqlity方式,即比较引用对应的对象是否是同一个,这会使得slice与array的==操作符产生不同的含义。

因此,slice不能比较,也不能作为map的key值
slice的零值为nil,代表没有底层数组,它的len和cap也都为0.
同时也有非nil的slice,但len和cap也为0,例如[]int{}或者make([]int,3)[3:].
和其他有nil值的类型一样,一个nil类型的slice可以简写为[]int(nil)。
一般情况下,除非特别说明,go函数对待0-length的slice应该是相同的行为,而不管它是不是nil或非nil。

slice扩容

append函数为slice增加元素,根据slice的cap来确定是否扩容,如果发生扩容,则返回的新slice和旧slice指向的不是同一个地址。通常我们返回的结果需要赋值给原来的slice,这样原slice的地址就不再使用了。

slice作为函数参数

当slice作为函数参数时,什么时候传slice,什么时候传slice的指针?
go的函数参数传递都是值转递,只是由于slice本身是引用类型,因此函数内部拷贝的参数和形参指向的是同一个地址。
但如果函数函数对slice进行了扩容,则可能导致内部slice指向新地址,则函数外的slice并不能感知到。
通俗的讲,函数内部slice的len和cap的变化,都不能改变函数外slice的len和cap,如果要同步改变,则需要用slice指针。

func mytest(s []int) {s = append(s, 3)fmt.Println(s)
}
func main() {s := make([]int, 3, 5)s[0] = 1s[1] = 2fmt.Println(s)  // [1 2 0]mytest(s)   // [1 2 0 3]fmt.Println("after func mytest")fmt.Println(s)  // [1 2 0] 这里已经将s[3]改为3,但原s看不到s = s[:4]fmt.Println(s)  // [1 2 0 3]
}

Map

一个无序的字典,key必须是能使用==操作符的可比较的类型,如整数、字符串等。

  • map的零值为nil,代表没有hash表
  • map的元素不是一个变量,不能取地址(不能取地址的一个原因是,map的增长将会导致已存储的元素rehash到另外的位置,因此地址会发生变化)。
  • map不能直接比较,需要自己实现比较方法
  • 如果想用slice作为map的key,可以利用一个函数将slice转为string,再作为key,这种方法可以试用于所有不可比较的类型。
  • 可以利用map的key值不重复的特点,构建一个set,且将其value用bool或struct{}表示
_ = &ages["bob"] // compile error
func mapEqual(x, y map[string]int) bool {if len(x) != len(y) {return false}for k, xv := range x {if yv, ok := y[k]; !ok || yv != xv {return false}}return true
}

Struct

  • 结构体的每个元素都是一个变量,因此可以取地址。
  • 成员的顺序很重要,不同的顺序定义出的结构体不同

A named struct type S can’t declare a field of the same type S: an aggregate value cannot contain itself。(An analogous restriction applies to arrays.)
一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。
(该限制同样适用于数组。)
But S may declare a field of the pointer type S, which lets us create recursive data structures like linked lists and trees.
但是S类型的结构体可以包含
S指针类型的成员,这可以让我们创建递归的数据结构

// 一个排序算法
func Sort(values []int) {var root *treefor _, v := range values {root = add(root, v)}appendValues(values[:0], root)
}
func appendValues(values []int, t *tree) []int {if t != nil {values = appendValues(values, t.left)values = append(values, t.value)values = appendValues(values, t.right)}return values
}func add(t *tree, value int) *tree {if t == nil {t = new(tree)t.value = value}if value < t.value {t.left = add(t.left, value)} else {t.right = add(t.right, value)}return t
}

如果结构体没有任何成员的话就是空结构体,写作struct{}。它的大小为0,也不包含任何信息,但是有时候依然是有价值的。
有些Go语言程序员用map来模拟set数据结构时,用它来代替map中布尔类型的value,
只是强调key的重要性,但是因为节约的空间有限,而且语法比较复杂,所以我们通常会避免这样的用法。

函数返回一个结构体,则不能使用.形式调用结构体的元素,返回结构体指针则可以。

如果结构体的所有成员都是可比较的,则结构体是可比较的。比较时按顺序比较各个字段
如果结构体可比较,则可以作为map的key值。
匿名成员: 当成员只有类型,不指定名字时为匿名成员。使用点号操作符可以直接抵达最底层的叶子名称。

总结

类型 零值 是否可比较 赋值 作为函数参数 操作
slice 引用 nil 不可比较 共享底层元素,
但一个切片改变len和cap,另一个不会感知到
行参、实参共享底层元素,
但如果函数内对slice的len和cap做更改,则外部感知不到,
此时要使用slice的指针
复制:make + copy形式
map 引用 nil 不可比较 共享底层元素,
修改、增加、删除都会同步体现
行参、实参共享底层元素,
内部修改、增加、删除元素外都可感知到

其他:

  • go的可变参数通过slice来实现,在定义时用三个点形式,在传递时可传任意数量的元素,或者是slice后跟三个点的形式
func printSlice(elems ...string) {fmt.Println(elems)   // 函数内部,elems是一个slice
}printSlice("America", "Cuba")
str_slice := []string{"China","Russia",}
printSlice(str_slice...)
  • 一些其他的切片操作
// 切片复制 建议使用make + copy
slice1 := []string{"A", "B", "C"}
slice2 := make([]string, len(slice1))
copy(slice2, slice1)
// 或者
slice3 := append(slice1[:0:0], slice1...)
slice4 := append([]string(nil), slice1...)// 删除一段切片,从from删除到to
s = append(s[:from], s[to:]...)    // 删除并保持顺序不变
s = s[:from + copy(s[:from], s[to:])]// 插入
s = append(s[:i], append(elements, s[i:]...)...) // 插入中间
s = append(s, elements...)                       // 插入结尾
s = append(elements, s...)                      // 插入开头

go程序设计语言第四章-组合类型相关推荐

  1. C 程序设计语言——第四章练习题

    C 程序设计语言第二版--第四章练习题 1. Write the function strindex(s,t) which returns the position of the rightmost ...

  2. c语言textout字体大小,《WINDOWS程序设计》第四章关于TEXTOUT的小问题

    <WINDOWS程序设计>第四章关于TEXTOUT的小问题 文章原文是这样说的: 您会发现常常需要显示格式化的数字跟简单的字符串.我在第二章讲到过,您不能使惯用的工具(可 爱的printf ...

  3. if语句写阶跃函数C语言,C语言第四章分支语句.ppt

    C语言第四章分支语句.ppt 例: 输入三个实数,按从小到大的顺序输出. main( ) {float a,b,c,t; scanf("%f,%f,%f ",&a,& ...

  4. 【深度之眼Python基础+数据科学入门训练营】第四章 组合数据类型

    第四章 组合数据类型 4.1 列表 4.1.1 列表的表达 序列类型:内部元素有位置关系,能通过位置序号访问其中元素 列表是一个可以使用多种类型元素,支持元素的增.删.查.改操作的序列类型 ls = ...

  5. 深度之眼 - Python学习笔记——第四章 组合数据类型

    第四章 组合数据类型 4.1 列表 列表是可变的! 4.1.1 列表的表达 序列类型:内部元素有位置关系,能通过位置序号访问其中元素 列表是一个可以使用多种类型元素,支持元素的增.删.查.改操作的序列 ...

  6. c语言结构类型ppt,C语言 第10章 结构类型与其他构造类型.ppt

    C语言 第10章 结构类型与其他构造类型 第7章 结构类型与其它构造类型 本章的主要内容 1.三种新的复杂数据类型:结构体型.共用体型.枚举型的概念与作用. 2.结构体的定义方法,结构体型变量.数组. ...

  7. ASP.NET自定义控件组件开发 第四章 组合控件开发CompositeControl

    第四章 组合控件开发CompositeControl 大家好,今天我们来实现一个自定义的控件,之前我们已经知道了,要开发自定义的控件一般继承三个基 类:Control,WebControl,还有一个就 ...

  8. c语言第四章作业,大学C语言第四章作业答案

    大学C语言第四章作业答案,期末考试的题库,二级C语言的练习 第四章课后习题参考程序 三.编程 1.输入一个正整数,判断该数为奇数还是偶数. 参考程序:(1) #include int main() { ...

  9. 《NAO机器人程序设计》---第四章 运动控制

    <NAO机器人程序设计>-第四章 运动控制 Nao机器人-Choregraphe 关节名 机器人边走边说话 moveInit():运动进程的初始化,检查机器人的当前状态,并选择一个正确的姿 ...

最新文章

  1. 1041 Be Unique
  2. JDK5.0环境下配置PKCS#11
  3. 计算机d盘无法格式化,电脑D盘无法格式化怎么办 D盘无法格式化问题解决办法...
  4. 轻量级锁的加锁和解锁逻辑
  5. knn算法实现电影分类
  6. 面试题 01.06. 字符串压缩
  7. 替换 Nginx 使用 Caddy 作为博客静态服务器
  8. vc++ cfile 文件操作
  9. RabbitMQ-镜像队列配置相关
  10. ORACLE之常用FAQ V1.0二(构架系统) (1)
  11. C++ for_each函数
  12. 用java完成身高预测
  13. 卡券、直充订单列表(post 表单提交)接口
  14. 2017-10-19 远光软件Java开发面试+达达京东到家笔试总结
  15. matlab做二元garch m,多元garch模型的matlab程序如何运行?能否举例说明下啊,希望高手指点...
  16. 17岁电竞少年追梦之旅,多次试训无果黯然返乡,沉淀一年,少年杨帆终圆梦~
  17. [转]Windows 下常用盗版软件的替代免费软件列表
  18. RabbitMQ与PHP应用
  19. vscode中前端vue项目详解_vs code 第一次创建前端项目 vuejs 从零开始
  20. Unity初级教程2048附带源码及插件(400行代码1个脚本UI实现)

热门文章

  1. 偏差与方差、梯度下降、交叉验证与归一化标准化
  2. SocketTools库版,资源重定向的处理
  3. Linux查看每个进程的Openfiles数量
  4. MATLAB函数var、std浅析
  5. 使用DevExpress Reports和PDF Viewer创建AcroForm Designer
  6. 阿里云栖大会首日:成立芯片公司“平头哥”,发布城市大脑2.0
  7. PLC编程软件在线调试程序的方法
  8. rsync 匹配通配符 * 失败 link_stat failed: No such file or directory
  9. DesktopLayer.exe专杀
  10. stem课程教学的设计