源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Go语言中的复合类型详细介绍

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

扫一扫进微信版
返回顶部