go中的类型与c的类似,经常使用类型有一个特例:byte类型,即字节类型,长度为,默认值是0;git
1 bytes = [5]btye{'h', 'e', 'l', 'l', 'o'}
变量bytes的类型是[5]byte,一个由5个字节组成的数组。它的内存表示就是连起来的5个字节,就像C的数组。数组
字符串在Go语言内存模型中用一个2字长(64位,32位内存布局方式下)的数据结构表示。它包含一个指向字符串数据存储地方的指针,和一个字符串长度数据以下图:安全
s是一个string类型的字符串,由于string类型不可变,对于多字符串共享同一个存储数据是安全的。切分操做str[i:j]
会获得一个新的2字长结构t,一个可能不一样的但仍指向同一个字节序列(即上文说的存储数据)的指针和长度数据。因此字符串切分不涉及内存分配或复制操做,其效率等同于传递下标。数据结构
数组类型定义了长度和元素类型。如, [4]int
类型表示一个四个整数的数组,其长度是固定的,长度是数组类型的一部分( [4]int
和 [5]int
是彻底不一样的类型)。 数组能够以常规的索引方式访问,表达式 s[n]
访问数组的第 n 个元素。数组不须要显式的初始化;数组的零值是能够直接使用的,数组元素会自动初始化为其对应类型的零值。app
1 var a [4]int 2 a[0] = 1 3 i := a[0] 4 // i == 1 5 // a[2] == 0, int 类型的零值
Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。 (为了不复制数组,你能够传递一个指向数组的指针,可是数组指针并非数组。) 能够将数组看做一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。函数
b := [2]string{"Penn", "Teller"} b := [...]string{"Penn", "Teller"}
这两种写法, b
都是对应 [2]string
类型。布局
切片类型的写法是[]T
,T
是切片元素的类型。和数组不一样的是,切片类型并无给定固定的长度。切片的字面值和数组字面值很像,不过切片没有指定元素个数:学习
1 letters := []string{"a", "b", "c", "d"}
2 s := letters [:] //a slice referencing the storage of x 3 func make([]T, len, cap) []T //使用内置函数 make 建立
一个slice是一个数组某个部分的引用。在内存中它是一个包含三个域的结构体:指向slice中第一个元素的指针ptr,slice的长度数据len,以及slice的容量cap。长度是下标操做的上界,如x[i]中i必须小于长度。容量是分割操做的上界,如x[i:j]中j不能大于容量。slice在Go的运行时库中就是一个C语言动态数组的实现,在$GOROOT/src/pkg/runtime/runtime.h中定义:ui
struct Slice { // must not move anything byte* array; // actual data uintgo len; // number of elements uintgo cap; // allocated number of elements };
数组的slice会建立一份新的数据结构,包含一个指针,一个指针和一个容量数据。如同分割一个字符串,分割数组也不涉及复制操做,它只是新建了一个结构放置三个数据。以下图:spa
示例中,对[]int{2,3,5,7,11}
求值操做会建立一个包含五个值的数组,并设置x的属性来描述这个数组。分割表达式x[1:3]
不从新分配内存数据,只写了一个新的slice结构属性来引用相同的存储数据。上例中,长度为2--只有y[0]和y[1]是有效的索引,可是容量为4--y[0:4]是一个有效的分割表达式。
由于slice分割操做不须要分配内存,也没有一般被保存在堆中的slice头部,这种表示方法使slice操做和在C中传递指针、长度对同样廉价。
其实slice在Go的运行时库中就是一个C语言动态数组的实现,要增长切片的容量必须建立一个新的、更大容量的切片,而后将原有切片的内容复制到新的切片。在对slice进行append等操做时,可能会形成slice的自动扩容。其扩容时的大小增加规则是:
下面的例子将切片 s
容量翻倍,先建立一个2倍 容量的新切片 t
,复制 s
的元素到 t
,而后将 t
赋值给 s
:
t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0 for i := range s { t[i] = s[i] } s = t
循环中复制的操做能够由 copy 内置函数替代,返回复制元素的数目。此外, copy
函数能够正确处理源和目的切片有重叠的状况。
一个常见的操做是将数据追加到切片的尾部。必要的话会增长切片的容量,最后返回更新的切片:
func AppendByte(slice []byte, data ...byte) []byte { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice }
Go提供了一个内置函数 append,也实现了这样的功能。
func append(s []T, x ...T) []T //append 函数将 x 追加到切片 s 的末尾,而且在必要的时候增长容量。 a := make([]int, 1) // a == []int{0} a = append(a, 1, 2, 3) // a == []int{0, 1, 2, 3}
若是是要将一个切片追加到另外一个切片尾部,须要使用 ...
语法将第2个参数展开为参数列表。
a := []string{"John", "Paul"} b := []string{"George", "Ringo", "Pete"} a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])" // a == []string{"John", "Paul", "George", "Ringo", "Pete"}
因为切片的零值 nil
用起来就像一个长度为零的切片,咱们能够声明一个切片变量而后在循环 中向它追加数据:
// Filter returns a new slice holding only // the elements of s that satisfy fn() func Filter(s []int, fn func(int) bool) []int { var p []int // == nil for _, v := range s { if fn(v) { p = append(p, v) } } return p }
切片操做并不会复制底层的数组。整个数组将被保存在内存中,直到它再也不被引用。 有时候可能会由于一个小的内存引用致使保存全部的数据。
以下, FindDigits
函数加载整个文件到内存,而后搜索第一个连续的数字,最后结果以切片方式返回。
var digitRegexp = regexp.MustCompile("[0-9]+") func FindDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) return digitRegexp.Find(b) }
这段代码的行为和描述相似,返回的 []byte
指向保存整个文件的数组。由于切片引用了原始的数组, 致使 GC 不能释放数组的空间;只用到少数几个字节却致使整个文件的内容都一直保存在内存里。要修复整个问题,能够将须要的数据复制到一个新的切片中:
func CopyDigits(filename string) []byte { b, _ := ioutil.ReadFile(filename) b = digitRegexp.Find(b) c := make([]byte, len(b)) copy(c, b) return c }
使用 append
实现一个更简洁的版本:
8 func CopyDigitRegexp(filename string) []byte { 7 b,_ := ioutil.ReadFile(filename) 6 b = digitRefexp.Find(b) 5 var c []intb 4 // for _,v := range b{ 3 c =append(c, b) 2 //} 1 return c 0 }
Go有两个数据结构建立函数:make和new,也是两种不一样的内存分配机制。
make和new的基本的区别是new(T)
返回一个*T
,返回的是一个指针,指向分配的内存地址,该指针能够被隐式地消除引用)。而make(T, args)
返回一个普通的T。一般状况下,T内部有一些隐式的指针。因此new返回一个指向已清零内存的指针,而make返回一个T类型的结构。更详细的区别在后面内存分配的学习里研究。