上一章我们深入学习了基本数据类型,它们是构建复杂数据类型的基础,是组成Go语言世界的原子。本章,我们将学习复合数据类型:通过不同的方式将基本类型组合起来。主要有四种复合类型--数组,切片(slice),map,结构体(struct),在本章末尾,我们将展示如何通过struct来进行JSON编解码,同时配合template模板来生成HTML页面。

数组和结构体都是聚合类型:数组是由元素组成,结构体由字段组成,无论是元素还是字段,在内存中都是连续排列的(这个极大的增加了内存的连续访问性,也是Go的一个重要优点,内存排列很紧密)。数组是同构类型--每个元素的类型都是相同的;结构体是异构类型--每个字段的类型都可以不同。数组和结构体的内存大小在初始化后都是固定的,相比之下,切片和map则是动态数据结构,它们的内存会按需增长。

数组是同一类型元素组成的序列,长度是固定的,一个数组可以由零个或多个元素组成。因为数组长度是固定的,所以Go语言中很少直接使用数组,更多使用的是slice(切片)。slice是长度可变的元素序列,使用起来非常灵活,但是要理解切片,首先要理解数组(slice是底层数组的引用)。

数组元素可以通过下标来访问,下标的取值范围是0到数组长度减1,内置len函数可以获取数组的长度(元素个数):

var a [3]int             // 整形数组,包含3个元素
fmt.Println(a[0])        // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素, a[2]// 打印索引和元素
for i, v := range a {fmt.Printf("%d %d\n", i, v)
}// 只打印元素
for _, v := range a {fmt.Printf("%d\n", v)
}

如果没有显式初始化数组,那么默认情况下,数组中的元素会初始化为相应元素类型的零值,对于int类型来说就是0。也可以使用这种数组字面值语法来初始化:

var q [3]int = [3]int{1, 2, 3}
var r [3]int = [3]int{1, 2}
fmt.Println(r[2]) // "0"

如果用...来指定数组的长度,那么数组的长度就是初始化元素的个数。因此,上面q数组的初始化可以简化为:

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

数组长度也是数组类型的一部分,所以[3]int和[4]int是不同的数组类型。数组长度是在编译期确定的,因此必须是常量:

q := [3]int{1, 2, 3}
q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int

学到后面大家就会发现,数组、切片、map及结构体的初始化写法是很像的。上面的初始化是直接通过值序列来完成的,也可以通过索引:值这种键值对列表来实现:

type Currency intconst (USD Currency = iota // 美元EUR                 // 欧元GBP                 // 英镑RMB                 // 人民币
)symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}fmt.Println(RMB, symbol[RMB]) // "3 ¥"

上面的初始化中,索引的顺序是不重要的,甚至可以省略一些索引,还记得前面的内容吗:未指定初始值的元素将用零值进行默认初始化,例如:

r := [...]int{99: -1}

上条语句定义一个包含了100个元素的数组r,其中最后一个元素被初始化为-1,其它元素都是0。

如果数组元素的类型是可比较的,那么数组类型也是可比较的,可以使用==或者!=来比较两个数组。只有当两个数组的所有元素都相等时,数组才相等:

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,类型不同,不能比较

再看一个真实的例子,对于一条任意长度的消息(byte slice类型),crypto/sha256包中的Sum256函数会对其进行摘要或者hash。摘要是256bit长度,因此它的类型是[32]byte数组(32字节 * 8 = 256bit)。如果两条消息摘要相同,那么消息就可以认为是相同的(实际上,两条不同的消息也可以有相同的摘要,但是很少见,摘要攻击就是利用了不同的密码可能生成同一个摘要,直接进行摘要破解,而不是进行密码破解,这样可以减少很多种组合情况);如果消息摘要不同,那消息也必然不同。下面的例子中,用SHA256算法分别生成"x"和"X"的摘要:

import "crypto/sha256"func main() {c1 := sha256.Sum256([]byte("x"))c2 := sha256.Sum256([]byte("X"))fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1)// Output:// 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881// 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015// false// [32]uint8
}

上面的例子中,"x"和"X"只有一个bit位的差异,但是生成的摘要几乎有一半的bit位是不同的。这里Printf中的%x参数,指定了以16进制格式打印数组或slice中全部元素,%t参数用语打印布尔值,%T参数用来显示变量的数据类型。

进行函数调用时,传递给函数参数的值实际上是变量的拷贝,所以函数参数接收的是一个副本,并不是原始的变量,这种就是值传递,Go语言的函数调用默认就是值传递。这种机制在传递较大的数组值时,效率是很低的(数组中的所有元素都会重新拷贝一次),并且对数组的修改实际上是修改拷贝值,而不是原始数组。Go语言的这种机制和其它很多语言是不同的,其它编程语言在函数调用时,可能会隐式地将数组作为引用或者指针进行参数传递(C语言)。

当然,我们可以显示传递一个数组指针,这样函数对数组的修改就会直接修改原始数组。下面的函数将[32]byte类型的数组进行清零:

func zero(ptr *[32]byte) {for i := range ptr {ptr[i] = 0}
}

上面的函数可以修改的更简洁,因为[32]byte{}可以生成一个所有元素都是0的数组:

func zero(ptr *[32]byte) {*ptr = [32]byte{}
}

尽管通过指针来传递、修改数组是很高效的,但是数组依然不具有可伸缩性,因为数组的长度是固定的。上面的zero函数就无法处理*[16]byte类型的数组指针,而且也没有任何添加、删除数组元素的办法。因此,除了类似SHA256这种需要固定大小数组的场景,其它时候,我们一般都用slice。

练习 4.1: 编写一个函数,计算两个SHA256哈希码中不同bit的数目。

练习 4.2: 编写一个程序,默认使用SHA256对标准输入进行摘要,同时也可以通过命令行参数指定使用SHA384或SHA512算法。

文章所有权:Golang隐修会 联系人:孙飞,CTO@188.com!

Go语言核心之美 3.1-数组相关推荐

  1. Go语言核心之美-必读

    Go语言核心之美开篇了!,无论你是新手还是一代高人,在这个系列文章中,总能找到你想要的! 博主是计算机领域资深专家并且是英语专8水平,翻译标准只有三个:精确.专业.不晦涩,为此每篇文章可能都要耗费数个 ...

  2. Go语言核心之美 3.4-Struct结构体

    struct(结构体)也是一种聚合的数据类型,struct可以包含多个任意类型的值,这些值被称为struct的字段.用来演示struct的一个经典案例就是雇员信息,每条雇员信息包含:员工编号,姓名,住 ...

  3. Go语言核心之美 2.6-常量

    在Go语言中,常量表达式是在编译期求值的,因此在程序运行时是没有性能损耗的.常量的底层类型是前面提过的基本类型:布尔值,字符串,数值变量. 常量的声明方式和变量很相似,但是常量的值是不可变的,因此在运 ...

  4. Go语言核心之美 3.3-Map

    哈希表是一种非常好用.适用面很广的数据结构,是key-value对的无序集合.它的key是唯一的,通过key可以在常数复杂度时间内进行查询.更新或删除,无论哈希表有多大. Go语言的map类型就是对哈 ...

  5. Go语言核心之美 1.2-变量及声明篇

    变量 1.声明变量 使用var关键字可以创建一个指定类型的变量: var i int = 0 var i = 0 var i int 以上三个表达式均是合法的,第三个表达式会将i初始化为int类型的零 ...

  6. Go语言核心之美 2.1-整数

    第二章 序 在计算机底层,一切都是比特位.然而计算机一般操作的都是固定大小的值,称之为字(word).字会被解释为整数.浮点数.比特位数组.内存地址等,这些字又可以进一步聚合成数据包(packet). ...

  7. Go语言核心之美 2.5-字符串

    字符串是不可变的字节序列,虽然可以包含任意数据,包括0这个字节,不过字符串通常是用来包含可读性较强的文本.文本字符串通常采用UTF-8编码,由Unicode码点(rune)组成. 内置的len函数会返 ...

  8. Go语言核心之美 3.2-slice切片

    Slice(切片)是长度可变的元素序列(与之对应,上一节中的数组是不可变的),每个元素都有相同的类型.slice类型写作[]T,T是元素类型.slice和数组写法很像,区别在于slice没有指定长度. ...

  9. Go语言核心之美 1.5-作用域

    变量的作用域是指程序代码中可以有效使用这个变量的范围.不要将作用域和生命期混在一起.作用域是代码中的一块区域,是一个编译期的属性:生命期是程序运行期间变量存活的时间段,在此时间段内,变量可以被程序的其 ...

最新文章

  1. SpringBoot+MyBatis+Shiro 搭建杂谈
  2. IE6下png图片透明代码
  3. 在Linux和Windows的Docker容器中运行ASP.NET Core
  4. 自然语言处理的一些链接
  5. 11月25号站立会议
  6. IntelliJ IDEA 2016
  7. 通过ssh证书远程登录
  8. Tomcat发生java.lang.OutOfMemoryError: PermGen space的解决方案
  9. [BZOJ4066]简单题
  10. 19款国产手机无一幸免:15分钟破解人脸识别!
  11. 中科院分词系统大致流程
  12. 微信生成公众号带参数二维码(一)
  13. 高并发系统设计——API网关技术选型
  14. [附源码]计算机毕业设计springboot基于微信小程序的网络办公系统
  15. 火影推荐程序连载6-径向模糊简介
  16. java中特殊符号怎么校验_校验中文、空格和特殊符号的方法
  17. Win32设计图标、光标样式和窗口标题(简单易懂)
  18. 北邮信通2022C++大一上学期PTA汇总(含代码)(已完结)
  19. pytesseract 安装错误总结
  20. 计算机flash ram是什么意思,ROM、RAM、CPU、CACHE、FLASH的区别

热门文章

  1. JAVA中的API是什么?
  2. 微信公众账号开发接口实现 - java servlet
  3. 师者,传道授业解惑者也:看现在的培训机构和毕业生就业状况
  4. 【并发编程】线程池及Executor框架
  5. 2D和3D的对比,不用犹豫,3D建模是行业未来必然趋势!
  6. 一篇看懂顺序表!!(刘欣大佬《码农翻身》特别提及)
  7. 解析java当中switch语句的作用和break、continue关键字的用法
  8. mysql mysqli 设置,mysqli函数操作mysql实例
  9. 管家基因 | Human housekeeping genes
  10. 荧光标记β-胡萝卜素/荧光标记甲状腺激素T3/荧光标记甲状腺激素T4