go数据类型

基本类型

先看几个简单的例子:

变量 i 的类型是 int,在内存中使用一个有符号32位单元表示(所有的图片显示了32位的内存布局;在当前的实现中,只有指针类型在64位机器上有扩展——int 仍然是32位——尽管一个实现可以选择使用64位。)

变量 j 的类型是 int32,因为有明确的类型转换。即使 i 和 j 有着相同的类型结构,他们的类型是不同的;赋值 i = j 会产生类型错误,因此必须写成显式的类型转换:i = int(j)。

变量 f 的类型是 float,在当前的实现中代表这一个32位的浮点值。它和 int32 有着相同的内存封装,但内部布局不同。

结构和指针

现在开始加快速度了。变量 bytes 的类型是 [5]byte,一个5个字节的数组。它的内存表示仅仅是这 5 个字节,一个接这一个,就像 C 的数组。 类似的,primes 是一个 4 个 int 组成的数组。

Go,像C,不像 Java,给予程序员对于使用指针的控制。例如,这个类型定义:

type Point struct { X, Y int }

定义了一个简单的结构类型 Point,在内存中表示两个相邻的整数。

复合文本语法 Point {10, 20} 产生了初始化的 Point。取一个复合文本的地址产生了一个新分配的且已经初始化的 Point。前者在内存中占用 2 个单元,后者在内存中是指向两个单元的指针。

结构体中的字段在内存中连续排列。

type Rect1 struct { Min, Max Point }
type Rect2 struct { Min, Max *Point }

Rect1,一个有着两个 Point 字段的结构,在一行中被两个 Point 表示——4 个 int。Rect2,一个有着两个 *Point 字段的结构,被两个 *Point 所表示。

使用 C 的程序员可能不会惊讶于 Point 字段 和 *Point 字段的区别,但只使用 Java 或者 Python (或其它)的程序员可能会惊讶于需要做出决定(是否使用指针)。通过给予程序员对于基本内存布局的控制,Go 提供了控制给定的数据结构集合的总大小,分配空间的数量,以及访问内存的方式的能力,这些对于构建性能良好的系统是很重要的。

字符串

有了前面说的内容,我们可以继续讨论更有趣的数据类型。

灰色的箭头代表着实现中生成的指针,而在程序中并不是直接可见的。)

string 在内存中是用一个 2 单元的结构来表示,包括指向字符串数据的指针和长度。因为字符串是不可变的,让多个字符串共享相同的存储是安全的,因此,切片字符串可能会产生具有不同指针和长度的新的两单元的结构,但它们仍引用了相同的内存序列。这意味着切片不用分配内存或是复制就可以完成,使得字符串切片和进行显式的索引一样有效率。

(顺带说一下,在 Java 和 其它语言中有一个著名的争论。当你将字符串切片保存成一个小的片段时,对于原始(字符串)的引用使整个原始字符串保存在内存中,即使只有很小的一部分是需要的。Go 也有这个争论。我们尝试并且拒绝了使字符串切片开销大的方案(分配空间和复制),大多数的程序会避免它。

切片

切片是对数组中一块区域的引用。它是一个 3 个单元的结构,包括一个指向首元素的指针,切片的长度,和容量。长度是像 x[i] 这样的索引操作的上边界,而容量是像 x[i : j] 这样的切片操作的上边界。

就像切片字符串,切片数组并没有造成复制:它只是创建了一个新的结构保存着不同指针,长度和容量。例如,复合文本 []int{2, 3, 5, 7, 11} 创建了一个新的包含 5 个值的数组,然后设置切片 x 的字段来描述数组。切片表达式 x[1:3]没有分配更多的数据:它只是写入新的切片结构的字段来引用相同的后台存储。例如,长度为 2 时,y[0] 和 y[1] 是仅有的有效索引。但容量是 4,y[0:4]是有效的切片表达式。(查阅 Effective Go 获得更多关于长度和容量,以及切片如何使用的内容)

因为切片是多单元的结构,不是指针,切片操作并不需要分配内存,即使是切片头,通常会被保存在栈上。这样的表示使得切片与在 C 中显式传递指针和长度对一样轻便。Go 原来将切片表示成一个指向上面所展示的数据结构的指针,但是这样做以为着每个切片操作分配一个新的内存对象。即使是快速的分配器,也为垃圾收集器(GC)产生了很多不必要的工作。并且我们发现,正如上面字符串的例子,程序员会避免切片操作而更倾向于传递明确的索引。移除间接取值和分配使得切片足够轻便,避免了在大多数情况下传递明确的索引。

New 和 Make

Go 有两个数据结构创建函数:new 和 make。其中的区别在早期通常令人迷惑,但是很快就变得自然。基本的区别是,new(T) 返回 *T,一个 Go 程序可以隐含的解引用(图中的黑色指针)。而 make(T, args) 返回原始的 T,不是指针。通常 T 的内部有一些隐含的指针(图中的灰色指针)。New 返回了一个指向已被清零的内存的指针,而 make 返回一个复杂的结构。

这是区分两个函数的一种方法,但更重要的是与C/C++的传统区分开:如果定义 make(*T) 返回一个指向新分配的 T 的指针,现在的 new(Point) 可以写成 make(*Point)。我们曾经尝试这样做过,但无疑它和人们期望的分配函数很不一样。

posted @ 2013-04-16 16:48  zhepama  阅读(664)  评论(0编辑  收藏  举报