数组
Go语言的数组是一种值类型,尽管数组的元素能够被批改,然而数组自身的赋值和函数传参都是以整体复制的形式解决的。
定义形式
var a [3]int // 定义长度为3的int型数组, 元素全副为0var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
第一种形式是定义一个数组变量的最根本的形式,数组的长度明确指定,数组中的每个元素都以零值初始化。
第二种形式定义数组,能够在定义的时候程序指定全副元素的初始化值,数组的长度依据初始化元素的数目主动计算。
第三种形式是以索引的形式来初始化数组的元素,因而元素的初始化值呈现程序比拟随便。这种初始化形式和map[int]Type类型的初始化语法相似。数组的长度以呈现的最大的索引为准,没有明确初始化的元素仍然用0值初始化。
第四种形式是混合了第二种和第三种的初始化形式,后面两个元素采纳程序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最初一个元素跟在后面的第五个元素之后采纳程序初始化。
咱们还能够定义一个空的数组:
var d [0]int // 定义一个长度为0的数组var e = [0]int{} // 定义一个长度为0的数组var f = [...]int{} // 定义一个长度为0的数组
长度为0的数组在内存中并不占用空间。空数组尽管很少间接应用,然而能够用于强调某种特有类型的操作时防止调配额定的内存空间,比方用于管道的同步操作:
c1 := make(chan [0]int)go func() { fmt.Println("c1") c1 <- [0]int{}}()<-c1
在这里,咱们并不关怀管道中传输数据的实在类型,其中管道接管和发送操作只是用于音讯的同步。对于这种场景,咱们用空数组来作为管道类型能够缩小管道元素赋值时的开销。当然个别更偏向于用无类型的匿名构造体代替:
c2 := make(chan struct{})go func() { fmt.Println("c2") c2 <- struct{}{} // struct{}局部是类型, {}示意对应的构造体值}()<-c2
数组的遍历
咱们能够用for循环来迭代数组。上面常见的几种形式都能够用来遍历数组:
//索引遍历for i := range a { fmt.Printf("a[%d]: %d\n", i, a[i])}//索引+值for i, v := range b { fmt.Printf("b[%d]: %d\n", i, v)}//一般的for循环for i := 0; i < len(c); i++ { fmt.Printf("c[%d]: %d\n", i, c[i])}
用for range形式迭代的性能可能会更好一些,因为这种迭代能够保障不会呈现数组越界的情景,每轮迭代对数组元素的拜访时能够省去对下标越界的判断。
数组的内存构造
Go语言中数组是值语义。一个数组变量即示意整个数组,它并不是隐式的指向第一个元素的指针(比方C语言的数组),而是一个残缺的值。
当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了防止复制数组带来的开销,能够传递一个指向数组的指针,然而数组指针并不是数组。(尽管它简直能够间接当成数组应用)
var a = [...]int{1, 2, 3} // a 是一个数组var b = &a // b 是指向数组的指针fmt.Println(a[0], a[1]) // 打印数组的前2个元素fmt.Println(b[0], b[1]) // 通过数组指针拜访数组元素的形式和数组相似for i, v := range b { // 通过数组指针迭代数组的元素 fmt.Println(i, v)}
其中b是指向a数组的指针,然而通过b拜访数组中元素的写法和a相似的。
其余数组
数组不仅仅能够用于数值类型,还能够定义字符串数组、构造体数组、函数数组、接口数组、管道数组等等:// 字符串数组var s1 = [2]string{"hello", "world"}var s2 = [...]string{"你好", "世界"}var s3 = [...]string{1: "世界", 0: "你好", }// 构造体数组var line1 [2]image.Pointvar line2 = [...]image.Point{image.Point{X: 0, Y: 0}, image.Point{X: 1, Y: 1}}var line3 = [...]image.Point{{0, 0}, {1, 1}}// 图像解码器数组var decoder1 [2]func(io.Reader) (image.Image, error)var decoder2 = [...]func(io.Reader) (image.Image, error){ png.Decode, jpeg.Decode,}// 接口数组var unknown1 [2]interface{}var unknown2 = [...]interface{}{123, "你好"}// 管道数组var chanList = [2]chan int{}
简而言之,所有雷同的数据结构都能够放在一起汇合为一个数组。
字符串
字符串通常是用来蕴含人类可读的文本数据。在go语言中,字符串的元素是不可批改的,实质上是一个只读的字节数组.
Go语言的源代码要求是UTF8编码,导致Go源代码中呈现的字符串面值常量个别也是UTF8编码的。源代码中的文本字符串通常被解释为采纳UTF8编码的Unicode码点(rune)序列。
咱们也能够用字符串示意GBK等非UTF8编码的数据,不过这种时候将字符串看作是一个只读的二进制数组更精确,因为for range等语法并不能反对非UTF8编码的字符串的遍历。
底层构造
Go语言字符串的底层构造在reflect.StringHeader中定义:
type StringHeader struct { Data uintptr Len int}
字符串构造由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个构造体,因而字符串的赋值操作也就是reflect.StringHeader构造体的复制过程,并不会波及底层字节数组的复制。能够将字符串数组看作一个构造体数组。