go语言主要有四种复合数据类型,数组、slice、map、结构体。
数组和结构体都是聚合类型,他们的值由内存中的一组变量构成。数组和结构体的长度都是固定的,而slice和map都是动态的数据结构。
数组
var a [3]int //定义 for i,v:=range a{ fmt.Printf("%d %d",i,v)//%x表示输出16进制,%t输出布尔值,%T输出类型 } //输出索引和数值 for _,v:=range a{ fmt.Printf("%d",v) }//仅输出数值 len(a) //返回长度 var a [3]int = [3]int{1,2,3} q:=[...]int{1,2,3} //...表示长度由后面而定 //还可以给出i:v形式定义 a:=[3]int{0:1,1:2,2:3} a:=[...]int{99:-1}//长度100,除了最后一个是-1,其余元素都为0
两个数组可以使用==比较,当且仅当长度和值一样是a==b才是true,如果长度不等会报编译错误。go语言把数组和其他类型都看成值传递,其他语言中数组是隐式的引用传递。(C语言中,数组名是一个指针,因此传入开销很小),当然,go语言也可以显示的传递一个数组的指针给函数,比如 prt *[3]int。go语言中*[3]int和*[4]int是两种不同类型的指针。
实际应用中,很少使用数组,更多的是slice。
Slice
定义:[]T或make([]T,len,cap),相当于创建了一个指向数组的元素,包含了指向数组元素的指针,将slice传给函数的时候,可以改变底层数组的值。(可以理解为slice就是一个指针,C语言中动态数组就是指针来实现的)
slice可以用来访问数组的部分或全部元素,这个数组也被称为slice的底层数组。slice有三个属性:指针,长度和容量。其中容量的大小通常是从slice的起始元素到底层数组最后一个元素的个数。len和cap用来求相应属性值。
s[i:j]创建了一个新的slice,s可以是数组,指向数组的指针,slice。
slice无法用==比较,唯一允许的比较操作是和nil比较。slice类型的零值是nil。如果想要检查slice是否为空,那么使用len(s)==0而不是s==nil,因为s!=nil情况,也可能为空,比如s=[]int{}或make([]int,3)。
append函数
使用:append(x,value1,value2),append(x,x...),追加x中所有元素
append工作原理:如果slice容量足够,那么append会定义一个新的slice(仍然使用原来的底层数组)。如果容量不够,就要开辟一块新的空间,在把原来的东西copy过来。通常我们不清楚一次append调用会不会导致内存分配,所以我们不能假设原始的slice和调用append后的slice指向同一个底层数组,所以应该这样使用:runes = append(runes,r)。
func remove(slice []int,i int)[] int{ copy(slice[i:],slice[i+1:]) return slice[:len(slice)-1] } //移除一个元素
map
ages:=make(map[string]int)//等价于map[string]int{} ages:=map[string]int{"k":1,"v":2} delete(map["k"]) //移除一个元素,即使k不存在 ages["bob"] = a["bob"]+1 //不存在的键值为0
map元素不是一个变量,无法去地址。map中元素的迭代顺序是不固定的,如果需要按照顺序遍历map中的元素,需要显式给键排序。
import "sort" var names []string //names:=make([]string,0,len(args))会更加高效 for name:=range ages{ names = append(names,name) } sort.Strings(names)
通过下标方式访问总会有值,但我们需要判别一个不存在的元素或者恰好这个元素的值是0的时候,可以这样做:
age, ok:=ages["bob"] if !ok{ //bob不是字典中的键 }
if age,ok!=ages["bob"];!ok{} //通常将上面两句合成一句
和slice一样,map不可以比较,唯一合法的就是和nil比较,当需要判断两个map是否含有相同的键和值,必须做一个循环。
func equal(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!=xy{ return false } } return true }
go没有提供集合这个功能,但是既然map的键是唯一的,就可以用map来实现这个功能。
有时候,需要一个map的键是slice,但因为map的键必须是可比较的,所以无法直接实现。我们可以通过把slice映射到一个字符串即可。
var m = make(map[string]int) func k(list []string) string{ return fmt.Sprintf("%q",list) }//把字符串slice转化为一个适合做map键的字符串
map值的类型本身也可以是复合类型。var graph = make(map[string]map[string]int)。
结构体
概念和C语言里的相同。
定义:
type Employee struct{ ID int Name string Address string }//相同类型的可以写在一行,如果相同变量顺序不一样,那么我们就在定义不同的结构体 var tom Employee
用法基本和C语言一样,不同的是,结构体指针访问成员时候依旧使用'.'运算符。
func EmployeeById(id int) *Employee{} fmt.Println(EmployeeById(tom.ID).Position) id:=mary.ID EmployeeById(id).Adress="Hangzhou" //如果函数返回的不是*Employee类型,此条语句编译不能通过。
如果结构体成员变量名称首字母是大写,这个变量是可导出的,这个是go语言最主要的访问控制机制。
结构体类型不支持嵌套自己,也就是一个聚合类型不可以包含它自己(对数组也适用),但是可以包含自身的指针。
结构体字面量
type Point struct{ x,y int } p:=Point{1,2} //按照正常顺序赋值,但是可读性差 p:=Point{x:1,y:2} //通常使用这种方式,如果某个没有指定,则为零值
package p
type T struct{a,b int} //a,b不可导出
package q
import "p"
var _ := p.T{a:1,b:2} //编译错误
var _ := p.T{1,2} //编译错误
和C语言一样,大型结构体通常使用指针形式传递给函数,所以我们通常这样定义结构体:
pp:=&Point{1,2} //等价于 pp:=new(Point) *pp=Point{1,2}
结构体比较
所有成员变量可比较时,结构体可比较,可以作为map键类型。
结构体嵌套和匿名成员
嵌套不在叙述,嵌套带来的好处是程序清晰,但访问成员变得很麻烦。go允许我们定义不带名称的结构体成员,只需要指定类型即可,这种结构体成员称为匿名成员。
type Circle struct{ Point Radius int } var c Circle //有了这种结构体嵌套的功能,我们才能直接访问到我们需要的变量而不是指定一大串中间变量 c.X = 8 //等驾驭c.Point.X=8,因此匿名成员的名字其实就是它的类型名 c.Y = 9 c.Radius = 10
当我们访问最终变量时候,可以省略所有中间的匿名变量。
遗憾的是结构体字面量并没有什么快捷方式初始化这种嵌套的结构体。(和C语言机制不一样)
var c Circle := Circle{Point{1,2},3} //等价于 c = Circle{ Point: Point{x:8,y:8}, Radius: 5, }//尾部逗号是必须的
因为匿名成员有隐式的名字,所以不能在一个结构体里面定义两个相同类型的匿名成员。匿名成员的可导出性由它们的类型名字决定!!!!
结构体于JSON
go语言通过标准库encoding/json、encoding.xml、encoding/asn1盒其他的库对这些格式的编码和解码提供了非常良好的支持。
type Movie struct{ Title string Year int `json:"released"` Color bool `json:"color,omitempty"`//成员标签定义 Actors []string } var movies = []Movie{/*...*/} //这种类型的数据结构体最适合JSON data,err:=json.Marshal(movies) //结构体转json //Marshal生成了一个字节slice,这种方式把结果折叠在一起,很不适合阅读。 data,err:=json.MarshalIndent(movies,""," ") //MarshalIndent可以输出整齐格式化过的结果。中间参数是每行输出的前缀字符串,最后一个是定义缩进的字符串。
只有可导出的成员变量可以转换为JSON字段,这也是为什么我们把结构体变量全定义为大写。上述成员标签定义相当于转成json对象后做了个重命名。omitempty表示如果这个成员的值是零或者空,则不输出到JSON中。
json转Go数据结构通过json.Unmarshal实现,通过合理的定义Go数据结构,我们可以选择将那部分JSON数据解码到结构体对象,哪些数据可以丢弃。
var titles []struct{Title string} json.Unmarshal(data,&titles) //data其他字段丢弃
文本和HTML模版
简单的格式化通过Printf函数足够了,复杂的格式化可以通过text/template包和html/template包实现。具体不在介绍。
参考书目《Go程序设计语言》