复合数据类型
数组和结
构体都是有固定内存大小的数据结构。相比之下,slice和map则是动态的数据结构,它们将根据需要动态增长。
数组
数组是一个由固定 长度的特定 元素组成的序列
var a [3 ]int var q [3 ]int = [3 ]int {1 , 2 , 3 }
在数组字面值中,如果数组的长度是省略号"...",
则表示数组的长度是根据初始化的值的个数来计算的。
数组的长度是数组类型的一个组成部分,所以 [3]int 和 [4]int
是两种不同的数组类型
也可以直接定义索引
Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型,
一般一个Slice写作 []T
一个slice由三个部分构成:指针、长度和容量
指针指向数组的第一个可以从slice中访问的元素,这个元素不一定是数组的第一个元素
长度是指slice中的元素個數,它不能超过slice的容量
容量的大小通常是从slice的起始元素 到底层数组 的最后一个 元素间元素的个数。
内置函数len
和cap
用来返回slice的长度和容量。
import "fmt" month := []string {1 :"January" , 2 :"February" , 3 :"March" , 4 :"Apil" , 5 :"May" , 6 : "June" , 7 :"July" , 8 :"August" , 9 :"September" , 10 :"October" , 11 :"november" , 12 :"December" } summer := month[6 :9 ] Q2 := month[4 :7 ] fmt.Printf("%v cap: %d len: %d \n" , summer, cap (summer), len (summer)) fmt.Printf("%v cap: %d len: %d \n" , Q2, cap (Q2), len (Q2)) fmt.Println(summer[:5 ])
slice操作符s[i:j](其中0<=i<=j<=cap(s))创建了一个新的slice,这个新的slice引用了序列s中从i到j-1索引位置的所有元素,这里的s既可以是数组或指向数组的指针,也可以是slice。
Slice无法使用==比较,Slice唯一允许的比较操作是和nil比较,例如:
slice类型的零值是nil
内置函数make
可以创建一个具有指定元素类型、长度和容量的slice。其中容量参数可以省略,在这种情况下,slice的长度和容量相等。
make ([]T, len )make ([]T, len , cap )
append函数
内置函数append用来将元素追加到slice的后面
func appendInt (x []int , y int ) []int { var z []int zlen := len (x) + 1 if zlen <= cap (x) { z = x[:zlen] } else { zcap := zlen if zcap < 2 *len (x) { zcap = 2 * len (x) } z = make ([]int , zlen, zcap) } z[len (x)] = y return z }
slice就地修改
在原有内存空间修改slice
比如:
package mainimport "fmt" func NonEmpty (strings []string ) []string { i := 0 for _, s := range strings { if s != "" { strings[i] = s i++ } } return strings[:i] } data := []string {"one" , "" , "three" } fmt.Printf("%q\n" , nonempty(data)) fmt.Printf("%q\n" , data)
array 和 slice 的区别
array固定长度,slice可变
array类型是聚合类型,slice是引用类型
var a = [3 ]int {1 ,2 ,3 }var b = []int {}fmt.Println(reflect.TypeOf(a)) fmt.Println(reflect.Typeof(b)) fmt.Println(reflect.Typeof(a[1 :]))
声明:
array:var a = [3]int
slice: var a = []int{}
Map
一个map就是一个哈希表的引用,map的类型可以写成map[K]V
,
map中所有的key都有相同的类型,所有的value也都有相同的类型,但是key和value不一定是相同的类型。
内置的make函数可以创建一个map:
ages := make (map [string ]int )
也可以使用map字面值的语法创建map:
ages := map [string ]int { "alice" : 21 , "micel" : 34 } empty_map := map [string ]int {}
map的很多操作都是安全的,即使这些值不在map中也没有关系,如果一个查找失败返回的是对应值类型的零值。
ages := map [string ]int {} ages["you" ] = 18 delete (ages, "you" )ages["bob" ] = ages["bob" ] + 1 ages["bob" ] += 1 ages["bob" ]++ age, ok := ages["bob" ] if !ok {}if age, ok := ages["bob" ]; !ok { }
但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作:
遍历map,遍历的时候顺序是随机的:
for k, v := range ages { ... }
map类型的零值是nil,
map的大部分操作都可以安全的在nil值的map上执行,但是向一个nil值的map存入元素将导致一个panic异常
var ages map [string ]int fmt.Println(ages == nil ) fmt.Println(len (ages) == 0 ) ages["bob" ] = 21
结构体
结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体,
每个值称为结构体的成员。
下面两个语句声明了一个叫Employee的命名的结构体类型,并且声明了一个Employee类型的
变量dilbert:
type Employee struct { ID int Name string Address string DoB time.Time Position string Salary int ManagerID int } var dilbert Employee
一个命名为S的结构体类型将不能再包含S类型的成员:因为一个聚合的值不能包含它自身。
(该限制同样适应于数组。)但是S类型的结构体可以包含 *S
指针类型的成员,这可以让我们创建递归的数据结构,比如链表和树结构等。在下面的代码中,我们使用一个二叉树来实现一个插入排序:
type tree struct { value int left, right *tree } func Sort (values []int ) []int { var root *tree for _, v := range values { root = add(root, v) } appendValues(values[:0 ], root) return values } 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 return t } if value < t.value { t.left = add(t.left, value) } else { t.right = add(t.right, value) } return t }
结构体字面值
结构体值也可以用结构体字面值表示,结构体字面值可以指定每个成员的值。
type Point struct {X, Y int }p := Point{1 , 2 } p2 := Point{X: 1 , Y: 2 }
结构体比较
如果结构体的全部成员是可以比较的,则结构体也是可以比较的。
结构体嵌入和匿名成员
type Point struct { X, Y int } type Circle struct { Center Point Radius int } type Wheel struct { Circle Circle Spokes int }
但是因此访问内嵌的成员会变得非常繁琐。
Go语言有一个特性让我们只声明一个成员对应的数据类型而不指名成员的名字;这类成员就
叫匿名成员
type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int }
然后我们可以直接访问叶子属性而不需要给出完整的路径(有点像类的继承)
var w Wheelw.X = 8 w.Y = 8 w.Radius = 5 w.Spokes = 20
不幸的是,结构体字面值并没有简短表示匿名成员的语法,
因此下面的语句都不能编译通过:
w = Wheel{8 , 8 , 5 , 20 } w = Wheel{X: 8 , Y: 8 , Radius: 5 , Spokes: 20 }
结构体字面值声明必须遵循其本来的结构:
w = Wheel{Circle{Point{8 , 8 }, 5 }, 20 } w = Wheel{ Circle: Circle{ Point: Point{X: 8 , Y: 8 }, Radius: 5 , }, Spokes: 20 , }
JSON
type Movie struct { Title string Year int `json:"released"` Color bool `json:"color,omitempty"` Actors []string }
其中Year和Color成员后面的字符串面值是结构体成员Tag
data, err := json.MarshalIndent(movies, "" , " " ) if err != nil { log.Fatalf("JSON marshaling failed: %s" , err) } fmt.Printf("%s\n" , data) [ { "Title" : "Casablanca" , "released" : 1942 , "Actors" : [ "Humphrey Bogart" , "Ingrid Bergman" ] }, { "Title" : "Cool Hand Luke" , "released" : 1967 , "color" : true , "Actors" : [ "Paul Newman" ] }, { "Title" : "Bullitt" , "released" : 1968 , "color" : true , "Actors" : [ "Steve McQueen" , "Jacqueline Bisset" ] } ]
从输出可以看出团体成员Tag影响了JSON的值,
一个构体成员Tag是和在编译阶段关联到该成员的元信息字符串
文本和html模板
练习
练习
4.1 :
编写一个函数,计算两个SHA256哈希码中不同bit的数目。(参考2.6.2节的
PopCount函数。)
练习
4.2 :
编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag
定制,输出SHA384或SHA512哈希算法。
练习4.3:
重写函数reverse, 使用数组指针作为参数而不是slice
练习4.4:
编写一个函数rotate,实现一次遍历就可以完成元素旋转
练习4.5:
编写一个就地处理函数,用于去除[]string slice中相邻的重复字符串元素
练习4.6:
编写一个就地处理函数,
用于将一个UTF-8编码的字节slice中所有相邻的Unicode空白字符(查看unicode.IsSpace)缩减为一个ASCII空白字符
练习4.7:修改函数reverse,来翻转一个UTF-8编码的字符串中的字符串元素,传入参数是该字符串对应的字节slice类型([]byte)。你可以做到不需要重新分配内存就实现该功能吗?
练习
4.8 :
修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等
Unicode中不同的字符类别。
练习
4.10 :
修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、
超过一年。
练习
4.11 :
编写一个工具,允许用户在命令行创建、读取、更新和关闭GitHub上的issue,当
必要的时候自动打开用户默认的编辑器用于输入文本信息。
练习
4.12 :
流行的web漫画服务xkcd也提供了JSON接口。例如,一个
https://xkcd.com/571/info.0.json
请求将返回一个很多人喜爱的571编号的详细描述。下载每
个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打
印和命令行输入的检索词相匹配的漫画的URL。
练习
4.13 :
使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/
上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对
应的海报。
练习
4.14 :
创建一个web服务器,查询一次GitHub,然后生成BUG报告、里程碑和对应的用户信息。