复合数据类型由基础数据类型组合构成。
需要明确:数组和结构体都是聚合类型,值由内存中一组变量组成;
4.1数组
长度固定拥有0或多个相同数据类型的元素序列。由内置函数len()
返回长度。
var a [3]int
fmt.Println(a[0])
fmt.Println(a[len(a)-1]) // 即 a[2]
数组的初始值是一组零值;也可以使用数组字面量来初始化。
var q [3]int = [3]int{1, 2, 3}
f := [...]int{1, 2, 3}
f
和q
都是长度为3的数组;后者[...]
表示长度由初始化数组元素个数确定。不过直接用[]不也能行吗?怎么这里没强调。懂了,这样直接变成slice了。
数组的长度必须是常量表达式,即长度必须在编译时确定。
数组的长度是数组的一部分,这也表明[3]int
和[4]int
是两种类型。
q := [4]int{1, 2, 3, 4}
q = [3]int{1, 2, 3} // cannot use [3]int{…} (value of type [3]int) as [4]int value in assignment
说到这就不得不提一下C/C++:本来他俩也是必须固定长度;但是因为一些编译器的支持,像是:
int n;
std::cin>>n;
int a[n];
这种骚操作也能用;不过不对就是了。
symb := [...]string{7: "it"}
中给出的7是引索,不过引索可以乱序给出。没有被指定的元素为零值。本例中len(symb)
为8.
数组可比较(假设元素类型可以比较),比较两侧的值是否相同。
上一章中说到类型相同才可比较,如[3]int和[4]int根本无法比较,因为编译错误。
需要注意:调用函数并传入参数时会创建一个副本然后复制给对应变量。也就是说不像Java那样存在隐式引用传递。
func test(ptr *[3]int) {
for _, i := range ptr {
println(i)
}
}
这就是一个指针典例。需要注意:range
关键字产生引索和引索对应的值,而不是类型的引索和第引索个值。
4.2 slice
slice是长度可变的相同元素集。常为[]T
形式。
可以通过[]int{1,2,3}
初始化,这样同时创建底层数组和指向它的slice。
slice存在底层数组,拥有指针、长度、容量三个属性。这导致slice可以随意切片。同时一个底层数组可以对应多个slice。
其中指针指向第一个可以从slice访问的元素(不是底层数组的),长度是slice元素个数,容量是slice的起始元素到底层数组末尾。
同时因为包含指针,所以可以函数内更改内容。
依靠len()
和cap()
返回长度和容量。
依靠s[i:j]
的形式创建slice,“引用”i到j-1个。
引用slice可以超过被引用slice的长度,但是不能超过容量。前者只会增长,后者会宕机。
对string子串和[]byte的slice格式相同,同时在底层上他们也相同。
但是区别在于返回结果分别是字符串和字节slice。这是本质上的区别。
不同于数组,slice不能用==
比较(除了[]byte有标准库),要自己实现比较(但是都可以和nil比较)。
slice的零值是nil,这时slice没有底层数组。如果想查询slice是否为空就用len() == 0
而不是== nil
。
make([]T, len, cap)
创建一个cap容量长的无名数组并slice引用0~len+1。
4.2.1 append函数
func append(slice []Type, elems ...Type) []Type
为slice追加。
通过slice的结构和append()
和move()
等函数可以实现多种数据结构。
比如栈stack等。
4.3 map
map是散列表的引用,表示为map[Key]Value
,其中Key必须是可比较类型。用map[string]int{}
或make(map[string]int)
创建。
可以使用func delete(m map[Type]Type1, key Type)
删除一个key。
map的元素顺序不固定,你可以认为每一次range
都是随机顺序。(想按照特定顺序遍历可以单独存储一个key序列)
map的元素不是一个变量,不能用&获取地址。
原因之一是map的元素可能会被散列到其他位置,这时地址无效。
通过下标方式访问map一定会有值:
- key在map:得到key对应值。
- key不在map:得到map类型的零值。
可以通过if item, ok := items["isit?"]; !ok { /* "isit?"不是键,此时ok为fales */ }
区分元素不存在和存在但为0的情况。
map的零值是nil,这时没有引用任何散列表;向nil的map里存储数据会报错。
和slice一样,map不能比较。 只能与nil比较。
这导致你要是过程中可能传递某个slice,因为它不能比较,这就要另辟蹊径。
《The Go Programming Language》给出的是做一个slice->string的func。
4.4 结构体
type Srut struct {
Key string // 可导出
info string // 不可导出
}
显然,struct成员遵守首字母可见性原则。
如果一个variable是一个struct本身,可以使用.
访问,如果一个指针指向struct,可以先解引用让然后访问。
插一句题外话,如果比较go/c/c++,他们对于指针指向struct/object这件事有不同
- go:指针解引用然后访问。
- c/c++:可以解引用
( *指针 ) . 成员
,也可以直接指针 -> 成员
访问。
上例中 type 是起一个别名(具体参照变量那一章),所以Srut和struct{ … }是一个意思。
也就是说struct{}
是一个空结构的类型名,那么struct{}{}
就是空结构初始化。
4.4.1 && 4.4.2 结构体字面量和比较
结构体值可以使用结构体字面量设置。
g1 := gif.GIF{LoopCount: 10} // 可以指定初始化
s1 := srut{"isam", "are?"} // 也可以不指定,但是会严格按照顺序初始化。
如果结构体所有成员都可以比较,那么这个结构体可以比较。
其中==
就是按照顺序比较。
4.4.3 结构体嵌套与匿名
结构体中可以包含结构体,但是这样会存在多级.
访问的情况。
我们可以在结构体中包含匿名结构体,这样可以一个.
访问。
type Point struct {
x, y float64
}
type Circle struct {
Point
Radius float64
}
S1 := Circle{Point{1.0, 2.0}, 3.0}
S1.x = 1.0
不过很遗憾,上面的初始化变量是唯一一种方式,毕竟也是嵌套结构不是。
4.5 JSON
JSON包含数字、布尔、字符串。用""
引的是Unicode码点序列,使用\
转义。都写成name:vale
格式。
只有可导出成员可以转为json。
Year int `json:"released"`
Color bool `json:"color,omitempty"`
结构体的成员Tag可以是任意的字符串面值,但是通常是一系列用空格分隔的key:"value"键值对序列;
因为值中含有双引号字符,因此成员Tag一般用原生字符串面值的形式书写。
json开头键名对应的值用于控制encoding/json包的编码和解码的行为,并且encoding/…下面其它的包也遵循这个约定。
成员Tag中json对应值的第一部分用于指定JSON对象的名字,比如将Go语言中的TotalCount成员对应到JSON中的total_count对象。
Color成员的Tag还带了一个额外的omitempty选项,表示当Go语言结构体成员为空或零值时不生成该JSON对象(这里false为零值)。
json包中的marshal()
和unmarshal()
进行序列化和反序列化。
反序列化如果过长,超出部分丢弃。不过在反序列化过成中:json字段到go成员是不需要注意大小写的(但是结构体必须全大写);因此需要Go语言结构体成员Tag来指定对应的JSON名字。
4.6 文本和HTML模板
非常抽象内容,使我无法学习。
有时需要格式和代码分离出来以便更安全地修改。
这些功能是由 text/template 和 html/template 等模板包提供的,它们提供了一个将变量值填充到一个文本或HTML格式的模板的机制。
一个模板是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的{{...}}
对象,这称为操作。
一个操作可以:打印值、选择结构体的成员、调用函数或方法、表达式控制流(if-else
或range
)、实例化模板等。