2.1 基本类型

向新手介绍Go语言时,解释一下Go中各种类型变量在内存中的布局通常有利于帮助他们加深理解。

先看一些基础的例子:

基本类型 - 图1

变量i属于类型int,在内存中用一个32位字长(word)表示。(32位内存布局方式)

变量j由于做了精确的转换,属于int32类型。尽管i和j有着相同的内存布局,但是它们属于不同的类型:赋值操作 i = j 是一种类型错误,必须写成更精确的转换方式:i = int(j)

变量f属于float类型,Go语言当前使用32位浮点型值表示(float32)。它与int32很像,但是内部实现不同。

接下来,变量bytes的类型是[5]byte,一个由5个字节组成的数组。它的内存表示就是连起来的5个字节,就像C的数组。类似地,变量primes是4个int的数组。

结构体和指针

与C相同而与Java不同的是,Go语言让程序员决定何时使用指针。举例来说,这种类型定义:

  1. type Point struct { X, Y int }

先来定义一个简单的struct类型,名为Point,表示内存中两个相邻的整数。

基本类型 - 图2

Point{10,20}表示一个已初始化的Point类型。对它进行取地址表示一个指向刚刚分配和初始化的Point类型的指针。前者在内存中是两个词,而后者是一个指向两个词的指针。

结构体的域在内存中是紧挨着排列的。

  1. type Rect1 struct { Min, Max Point }
  2. type Rect2 struct { Min, Max *Point }

基本类型 - 图3

Rect1是一个具有两个Point类型属性的结构体,由在一行的两个Point—四个int代表。Rect2是一个具有两个*Point类型属性的结构体,由两个*Point表示。

使用过C的程序员可能对Point*Point的不同毫不见怪,但用惯Java或Python的程序员们可能就不那么轻松了。Go语言给了程序员基本内存层面的控制,由此提供了诸多能力,如控制给定数据结构集合的总大小、内存分配的次数、内存访问模式以及建立优秀系统的所有要点。

字符串

有了前面的准备,我们就可以开始研究更有趣的数据类型了。

基本类型 - 图4

(灰色的箭头表示已经实现的但不能直接可见的指针)

字符串在Go语言内存模型中用一个2字长的数据结构表示。它包含一个指向字符串存储数据的指针和一个长度数据。因为string类型是不可变的,对于多字符串共享同一个存储数据是安全的。切分操作str[i:j]会得到一个新的2字长结构,一个可能不同的但仍指向同一个字节序列(即上文说的存储数据)的指针和长度数据。这意味着字符串切分可以在不涉及内存分配或复制操作。这使得字符串切分的效率等同于传递下标。

(说句题外话,在Java和其他语言里有一个有名的“疑难杂症”:在你分割字符串并保存时,对于源字符串的引用在内存中仍然保存着完整的原始字符串—即使只有一小部分仍被需要,Go也有这个“毛病”。另一方面,我们努力但又失败了的是,让字符串分割操作变得昂贵—包含一次分配和一次复制。在大多数程序中都避免了这么做。)