Go语言中的复合类型详细介绍
golang复合类型包括:结构体、数组、切片、Maps。
1、数组
数组
golang中的数组与C语言中的数组差异很大,倒更类似Pascal中的数组。(Slice,下个话题,有些像C语言中的数组)
varar[3]int
声明ar为一个拥有三个整型数的数组,所有元素初始化为0。
大小是类型的一个组成部分。
内置的函数len可以用于获取数组大小:
len(ar)=3
数组是值类型
golang中的数组是值,而非C语言中的隐式指针。你可以获得数组的地址,并生成一个指向数组的指针(例如,将其高效地传递给函数):
funcf(a[3]int){fmt.Println(a)}
funcfp(a*[3]int){fmt.Println(a)}
funcmain(){
varar[3]int
f(ar)//传递一个ar的拷贝
fp(&ar)//传递一个指向ar的指针
}
输出结果:
&[000] 数组字面值 所有的符合类型都有相同的值创建语法。以数组为例,其语法如下: 3个整数的数组: 10个整数的数组,前三个元素不是0: 不想数?使用…代表长度: 不想初始化所有值?使用key:value对: 指向数组字面值的指针 你可以获取数组字面值的地址,这样可以得到一个指向新建数组实例的指针: 输出结果: 2、切片(Slice) 切片 切片是对数组中某一段的引用。 切片比普通数组应用得更多也更广泛。 切片使用的代价很低。 一个切片类型很像一个没有大小的数组类型: 内置的len(a)可以返回切片中元素的个数。 通过对数组或切片进行"切片",我们可以创建一个新切片: a(上面例子中的a)的有效下标值是0和1;len(a)==2。 切片速记 当对数组进行切片时,第一个下标值默认是0: ar[:n]等价于a[0:n]。 第二个下标值默认为len(array/slice): ar[n:]等价于ar[n:len(ar)]。 因此由数组创建切片时: ar[:]等价于ar[0:len(ar)]。 切片引用数组 概念上: 数组: 切片: 创建切片 切片字面值看起来像没有指定大小的数组字面值: 上面代码创建了一个长度为5的数组并创建一个切片用于引用这个数组。 我们可以使用内置的make函数分配一个切片(底层实际是个数组): 为何用make而不是用new?因为我们需要创建切片,而不仅仅是为了分配内存。注意make([]int,10)返回[]int,而new([]int)返回*[]int。 使用make创建切片、map以及channel。 切片容量 切片是对底层数组的一个引用。因此存在一些在数组里但却没在切片引用的范围内的元素。 内置的函数cap(capacity)用于报告切片可能增长到多长。 len(a)=2,cap(a)=5,现在我们可以重新切片: len(a)现在是4,而cap(a)依旧是5。 调整切片大小 切片可被当作可增长的数组用。使用make分配一个切片,并指定其长度和容量。当要增长时,我们可以做重新切片: 因此,sl的长度总是元素的个数,但其容量可根据需要增加。 这种手法代价很小,并且是Go语言中的惯用法。 切片使用的代价很小 你可以根据需要自由地分配和调整切片大小。它们的传递仅需要很小的代价;不必分配。 记住它们是引用,因此下层的存储可以被修改。 例如,I/O使用切片,而不是计数: 拆分一个Buffer: 字符串也可以被切片,而且效率相似。 3、Maps maps Map是另外一种引用类型。它们是这样声明的: 对于给定mapm,len(m)返回key的数量。 map的创建 和创建一个切片一样,一个map变量是一个空引用;在可以使用它之前,应先要向里面放入一些内容。 三种方式: 1)字面值:逗号分隔的key:value对列表 map索引 (接下来的几个例子全都使用: 测试存在性 要测试一个map中是否存在某个key,我们可以使用一个多项赋值的"comma,om"形式: varvaluefloat64 value,present=m[x] 或者按惯例: 如果map中存在x这个key,布尔变量会被设置为true;value会被赋值为map中key对应的值。相反,布尔变量会被设置为false,value被设置为相应值类型的零值。 删除 使用多元赋值可以删除map中的一个值: varkeepbool m[x]=v,keep 如果keep的值为true,则将v赋值到map中;如果keep为false,则删除map中的keyx。因此删除一个key: 译注:Go1中上述的删除方式已被取消,取而代之的是delete(m,x)。 for和range 对于数组、切片和map(以及我们在第三部分将要看到的更多类型),for循环提供了一种特殊的语法用于迭代访问其中的元素。 forkey,value:=rangem{ 只用一个变量,我们可以获得key: 变量可以用:=赋值或声明。 对于数组和切片来说,通过这种方式我们可以获得元素的下标以及元素值。 将range用于字符串 将forrange用于字符串时,实际迭代的元素是Unicode码点(codepoint),而不是字节(对字节,可使用[]byte或使用标准的for语句)。我们假设字符串包 含使用UTF-8编码的字符。 下面循环: 输出:0:"["1:"ÿ"3:"界"6:"]" 如果遇到了错误的UTF-8码点,这个字符将被设置为U+FFFD,下标向后移动一个字节。 4、Structs structs 对于Go中的struct,你应该感觉十分熟悉:简单的数据字段声明。 更常用的是: typePointstruct{ struct允许程序员定义内存布局。 struct是值类型 struct是值类型,new(StructType)返回一个指向零值的指针(分配的内存都被置0)。 对于结构体指针,没有->符号可用。Go提供了间接的方式。 创建结构体 结构体是值类型,因此你可只通过声明就可以创建一个全0的结构体变量。 你也可以使用new创建一个结构体。 结构体字面值语法也不出所料: 和数组一样,得到了结构体字面值的地址,就得到了新建结构体的地址。 这些例子都是构造器。 导出类型和字段 只有当结构体的字段(和方法,即将讲解)名字的首字母大写时,它才能被包外可见。 私有类型和字段: 匿名字段 在一个结构体内,你可以声明不带名字的字段,比如另外一个结构体类型。这些字段被称为匿名字段。它们看起来就像里层的结构体简单插入或“嵌入”到 外层结构体似的。 这个简单的机制为从其他类型继承已有的实现提供了一种方法。 下面是一个例子。 一个匿名结构体字段: typeBstruct{ B看起来像有四个字段ax、ay、bx和by。B可看成{ax,ayint;bx,byfloat64}。 然后B的字面值必须提供细节: 输出1234 匿名字段以类型作为名字 匿名字段不仅仅是简单插入这些字段这么简单,其含义更为丰富:B还拥有字段A。匿名字段看起来就像名字为其类型名的字段。 输出:{12}。如果A来自于另外一个包,这个字段依旧被称为A。 任意类型的匿名字段 任何具名类型或指向具名类型的指针都可以用作匿名字段。它们可以出现在结构体中的任意位置。 输出:3.57hello 冲突和遮蔽 如果有两个字段具有相同的名字(可能是一个继承类型的名字),代码将遵循下面规则: 1)外层的名字遮蔽内层的名字。这提供了一个重写字段/方法的方式。 二义性是没有规则能解决的,必须被修正。 冲突的例子 使用c.a将会出现错误。它到底是c.A.a还是c.B.a呢? 使用d.b没有问题:它是float64类型变量,不是d.B.b。要获得内层的b,可用d.B.b。
[000]
[3]int{1,2,3}
[10]int{1,2,3}
[...]int{1,2,3}
[10]int{2:1,3:1,5:1,7:1}
funcfp(a*[3]int){fmt.Println(a)}
funcmain(){
fori:=0;i<3;i++{
fp(&[3]int{i,i*i,i*i*i})
}
}
&[000]
&[111]
&[248]
vara[]int
a=ar[7:9]
typeSlicestruct{
base*elemType//指向0th元素的指针
lenint//切片中元素的数量
capint//切片可以容纳元素的数量
}
ar:715438721153
a=ar[7:9]:base=&ar[7](指向ar中的2)len=2cap=4
varslice=[]int{1,2,3,4,5}
vars100=make([]int,100)//slice:100ints
varar=[10]int{0,1,2,3,4,5,6,7,8,9}
vara=ar[5:7]//引用子数组{5,6}
a=a[0:4]//引用子数组{5,6,7,8}
varsl=make([]int,0,100)//长度0,容量100
funcappendToSlice(iint,sl[]int)[]int{
iflen(sl)==cap(sl){error(…)}
n:=len(sl)
sl=sl[0:n+1]//长度增加1
sl[n]=i
returnsl
}
funcRead(fdint,b[]byte)int
varbuffer[100]byte
fori:=0;i<100;i++{
//每次向Buffer中填充一个字节
Read(fd,buffer[i:i+1])//noallocationhere
}
header,data:=buf[:n],buf[n:]
varmmap[string]float64
这里声明了一个map,索引key的类型为string,值类型为float64。这类似于C++中的类型*map<string,float64>。
m=map[string]float64{"1":1,"pi":3.1415}
2)创建
m=make(map[string]float64)//makenotnew
3)赋值
varm1map[string]float64
m1=m//m1和m现在引用相同的map
m=map[string]float64{"1":1,"pi":3.1415})
访问一个元素;如果该元素不存在,则得到对应mapvalue类型的零值:
one:=m["1"]
zero:=m["notpresent"]//zero被置为0.0.
设置一个元素的值(两次设置将更新为最新值)
m["2"]=2
m["2"]=3//思维混乱
m=map[string]float64{"1":1,"pi":3.1415}
varpresentbool
value,ok:=m[x]//"commaok"形式
m=map[string]float64{"1":1.0,"pi":3.1415}
varvaluefloat64
varxstring=f()
m[x]=0,false//从map中删除x
m:=map[string]float64{"1":1.0,"pi":3.1415}
fmt.Printf("key%s,value%g\n",key,value)
}
forkey=rangem{
fmt.Printf("key%s\n",key)
}
s:="[\u00ff\u754c]"
fori,c:=ranges{
fmt.Printf("%d:%q",i,c)//%qfor"quoted"
}
varpstruct{
x,yfloat64
}
x,yfloat64
}
varpPoint
typePointstruct{
x,yfloat64
}
varpPoint
p.x=7
p.y=23.4
varpp*Point=new(Point)
*pp=p
pp.x=Pi//(*pp).x的语法糖
varpPoint//零值
pp:=new(Point)//惯用法
p=Point{7.2,8.4}
p=Point{y:8.4,x:7.2}
pp=&Point{7.2,8.4}//惯用法
pp=&Point{}//也是惯用法,==new(Point)
typepointstruct{x,yfloat64}
导出类型和字段:
typePointstruct{X,Yfloat64}
导出类型和私有类型混合字段:
typePointstruct{
X,Yfloat64//exported
namestring//notexported
}
你甚至可以创建一个带有导出字段的私有类型。(练习:何时能派上用场呢?)
typeAstruct{
ax,ayint
}
A
bx,byfloat64
}
b:=B{A{1,2},3.0,4.0}
fmt.Println(b.ax,b.ay,b.bx,b.by)
b:=B{A{1,2},3.0,4.0}
fmt.Println(b.A)
import"pkg"
typeCstruct{pkg.A}
…
c:=C{pkg.A{1,2}}
fmt.Println(c.A)//不是c.pkg.A
typeCstruct{
xfloat64
int
string
}
c:=C{3.5,7,"hello"}
fmt.Println(c.x,c.int,c.string)
2)如果在同一层次上出现了相同的名字,如果名字被使用,那么将是一个错误。(如果没有使用,不会出现错误)
typeAstruct{aint}
typeBstruct{a,bint}
typeCstruct{A;B}
varcC
typeDstruct{B;bfloat64}
vardD
相关文章