第6章:基本类型和它们的字面量表示

基本类型和它们的字面量表示

类型(type)可以被看作是值(value)的模板,值可以被看作是类型的实例。 这篇文章将介绍内置(或称为预声明的)基本类型和它们字面量的表示形式。 本篇文章不介绍组合类型。

基本内置类型

Go支持如下内置基本类型:

  • 一种内置布尔类型:bool
  • 11种内置整数类型:int8uint8int16uint16int32uint32int64uint64intuintuintptr
  • 两种内置浮点数类型:float32float64
  • 两种内置复数类型:complex64complex128
  • 一种内置字符串类型:string

内置类型也称为预声明类型。

这17种内置基本类型(type)各自属于一种Go中的类型种类(kind)。 尽管所有的内置基本类型的名称都是非导出标识符(第5章), 我们可以不用引入任何代码包而直接使用这些类型。

除了boolstring类型,其它的15种内置基本类型都称为数值类型(整型、浮点数型和复数型)。

Go中有两种内置类型别名(type alias):

  • byteuint8的内置别名。 我们可以将byteuint8看作是同一个类型。
  • runeint32的内置别名。 我们可以将runeint32看作是同一个类型。

u开头的整数类型称为无符号整数类型。 无符号整数类型的值都是非负的。 一个数值类型名称中的数字表示每个这个类型的值将在内存中占有多少二进制位(以后简称位)。二进制位常称为比特(bit)。 比如,一个uint8的值将占有8位。 我们称uint8类型的值的尺寸是8位。 因此,最大的uint8值是255(28-1), 最大的int8值是127(27-1), 最小的int8值是-128(-27)。

任一个类型的所有值的尺寸都是相同的,所以一个值的尺寸也常称为它的类型的尺寸。

更多的时候,我们使用字节(byte)做为值尺寸的度量单位。 一个字节相当于8个比特。所以uint32类型的尺寸为4,即每个uint32值占用4个字节。

uintptrint以及uint类型的值的尺寸依赖于具体编译器实现。 通常地,在64位的架构上,intuint类型的值是64位的;在32位的架构上,它们是32位的。 编译器必须保证uintptr类型的值的尺寸能够存下任意一个内存地址。

一个complex64复数值的实部和虚部都是float32类型的值。 一个complex128复数值的实部和虚部都是float64类型的值。

在内存中,所有的浮点数都使用IEEE-754格式存储。

一个布尔值表示一个真假。在内存中,一个布尔值只有两种可能的状态。 这两种状态使用两个预声明(或称为内置)的常量(falsetrue)来表示。 关于常量声明,下一篇文章(第7章)将做详细解释。

从逻辑上说,一个字符串值表示一段文本。 在内存中,一个字符串存储为一个字节(byte)序列。 此字节序列体现了此字符串所表示的文本的UTF-8编码形式。 我们可以从Go中的字符串(第19章)一文中获取更多关于字符串的知识。

尽管布尔和字符串类型分类各自只有一种内置类型, 我们可以声明定义更多自定义布尔和字符串类型。 所以,Go代码中可以出现很多布尔和字符串类型(数值类型也同样)。 下面是一个类型声明的例子。 在这些例子中,type是一个关键字。

  1. // 一些类型定义声明
  2. type status bool // status和bool是两个不同的类型
  3. type MyString string // MyString和string是两个不同的类型
  4. type Id uint64 // Id和uint64是两个不同的类型
  5. type real float32 // real和float32是两个不同的类型
  6. // 一些类型别名声明
  7. type boolean = bool // boolean和bool表示同一个类型
  8. type Text = string // Text和string表示同一个类型
  9. type U8 = uint8 // U8、uint8和 byte表示同一个类型
  10. type char = rune // char、rune和int32表示同一个类型

我们将上面定义的real类型和内置类型float32都称为float32类型 (注意这里的第二个float32是一个泛指,而第一个高亮的float32是一个特指)。 同样地,MyStringstring都被称为字符串(string)类型,statusbool都被称为布尔(bool)类型。

我们将在Go类型系统概述(第14章)一文中学习到更多关于自定义类型的知识。

零值

每种类型都有一个零值。一个类型的零值可以看作是此类型的默认值。

  • 一个布尔类型的零值表示真假中的假。
  • 数值类型的零值都是零(但是不同类型的零在内存中占用的空间可能不同)。
  • 一个字符串类型的零值是一个空字符串。

基本类型的字面量表示形式

一个值的字面形式称为一个字面量,它表示此值在代码中文字体现形式(和内存中的表现形式相对应)。一个值可能会有很多种字面量形式。

布尔值的字面量形式

Go白皮书没有定义布尔类型值字面量形式。 我们可以将falsetrue这两个预声明的具名常量当作布尔类型的字面量形式。 但是,我们应该知道,从严格意义上说,它们不属于字面量。具名常量声明将在下一篇文章中介绍和详细解释。

布尔类型的零值可以使用预声明的false来表示。

整数类型值的字面量形式

整数类型值有四种字面量形式:十进制形式(decimal)、八进制形式(octal)、十六进制形式(hex)和二进制形式(binary)。 比如,下面的三个字面量均表示十进制的15:

  1. 0xF // 十六进制表示(必须使用0x或者0X开头)
  2. 0XF
  3. 017 // 八进制表示(必须使用0、0o或者0O开头)
  4. 0o17
  5. 0O17
  6. 0b1111 // 二进制表示(必须使用0b或者0B开头)
  7. 0B1111
  8. 15 // 十进制表示(必须不能用0开头)

(注意:二进制形式和以0o0O开头的八进制形式从Go 1.13开始才支持。)

下面的程序打印出两个true

  1. package main
  2. func main() {
  3. println(15 == 017) // true
  4. println(15 == 0xF) // true
  5. }

注意这里的==是一个等于比较操作符。 操作符将在后续的文章常用操作符(第8章)一文中详细解释。

整数类型的零值的字面量一般使用0表示。 当然,000x0等也是合法的整数类型零值的字面量形式。

浮点数类型值的字面量形式

一个浮点数的完整十进制字面量形式可能包含一个十进制整数部分、一个小数点、一个十进制小数部分和一个以10为底数的整数指数部分。 整数指数部分由字母e或者E带一个十进制的整数字面量组成(xEn表示x乘以10n的意思,而xE-n表示x除以10n的意思)。 常常地,某些部分可以根据情况省略掉。一些例子:

  1. 1.23
  2. 01.23 // == 1.23
  3. .23
  4. 1.
  5. // 一个e或者E随后的数值是指数值(底数为10)。
  6. // 指数值必须为一个可以带符号的十进制整数字面量。
  7. 1.23e2 // == 123.0
  8. 123E2 // == 12300.0
  9. 123.E+2 // == 12300.0
  10. 1e-1 // == 0.1
  11. .1e0 // == 0.1
  12. 0010e-2 // == 0.1
  13. 0e+5 // == 0.0

从Go 1.13开始,Go也支持另一种浮点数字面量形式:十六进制浮点数字面量。 在一个十六进制浮点数字面量中,

  • 一个十六进制浮点数字面量必须以一个以2为底数的整数指数部分。 这样的一个整数指数部分由字母p或者P带一个十进制的整数字面量组成(yPn表示y乘以2n的意思,而yP-n表示y除以2n的意思)。
  • 和整数的十六进制字面量一样,一个十六进制浮点数字面量也必须使用0x或者0X开头。 和整数的十六进制字面量不同的是,一个十六进制浮点数字面量可以包括一个小数点和一个十六进制小数部分。

一些合法的浮点数的十六进制字面量例子:

  1. 0x1p-2 // == 1.0/4 = 0.25
  2. 0x2.p10 // == 2.0 * 1024 == 2048.0
  3. 0x1.Fp+0 // == 1+15.0/16 == 1.9375
  4. 0X.8p1 // == 8.0/16 * 2 == 1.0
  5. 0X1FFFP-16 // == 0.1249847412109375

而下面这几个均是不合法的浮点数的十六进制字面量。

  1. 0x.p1 // 整数部分表示必须包含至少一个数字
  2. 1p-2 // p指数形式只能出现在浮点数的十六进制字面量中
  3. 0x1.5e-2 // e和E不能出现在十六进制浮点数字面量的指数部分中

注意:下面这个表示是合法的,但是它不是浮点数的十六进制字面量。事实上,它是一个减法算术表达式。其中的e为是十进制中的140x15e为一个整数十六进制字面量,-2并不是此整数十六进制字面量的一部分。 (算术运算将在后续的文章常用操作符(第8章)一文中详细介绍。)

  1. 0x15e-2 // == 0x15e - 2 (整数相减表达式)

浮点类型的零值的标准字面量形式为0.0。 当然其它很多形式也是合法的,比如0..00e00x0p0等。

虚部字面量形式

一个虚部值的字面量形式由一个浮点数字面量或者一个整数字面量和其后跟随的一个小写的字母i组成。 在Go 1.13之前,如果虚部中i前的部分为一个整数字面量,则其必须为并且总是被视为十进制形式。 一些例子:

  1. 1.23i
  2. 1.i
  3. .23i
  4. 123i
  5. 0123i // == 123i(兼容性使然。见下)
  6. 1.23E2i // == 123i
  7. 1e-1i
  8. 011i // == 11i(兼容性使然。见下)
  9. 00011i // == 11i(兼容性使然。见下)
  10. // 下面这几行从Go 1.13开始才能编译通过。
  11. 0o11i // == 9i
  12. 0x11i // == 17i
  13. 0b11i // == 3i
  14. 0X.8p-0i // == 0.5i

注意:在Go 1.13之前,虚部字面量中字母i前的部分只能为浮点数字面量。 为了兼容老的Go版本,从Go 1.13开始,一些虚部字面量中表现为(不以0o0O开头的)八进制形式的整数字面量仍被视为浮点数字面量。 比如上例中的011i0123i00011i

虚部字面量用来表示复数的虚部。下面是一些复数值的字面量形式:

  1. 1 + 2i // == 1.0 + 2.0i
  2. 1. - .1i // == 1.0 + -0.1i
  3. 1.23i - 7.89 // == -7.89 + 1.23i
  4. 1.23i // == 0.0 + 1.23i

复数零值的标准字面表示为0.0+0.0i。 当然0i.0i0+0i等表示也是合法的。

数值字面表示中使用下划线分段来增强可读性

从Go 1.13开始,下划线_可以出现在整数、浮点数和虚部数字面量中,以用做分段符以增强可读性。 但是要注意,在一个数值字面表示中,一个下划线_不能出现在此字面表示的首尾,并且其两侧的字符必须为(相应进制的)数字字符或者进制表示头。

一些合法和不合法使用下划线的例子:

  1. // 合法的使用下划线的例子
  2. 6_9 // == 69
  3. 0_33_77_22 // == 0337722
  4. 0x_Bad_Face // == 0xBadFace
  5. 0X_1F_FFP-16 // == 0X1FFFP-16
  6. 0b1011_0111 + 0xA_B.Fp2i
  7. // 非法的使用下划线的例子
  8. _69 // 下划线不能出现在首尾
  9. 69_ // 下划线不能出现在首尾
  10. 6__9 // 下划线不能相连
  11. 0_xBadFace // x不是一个合法的八进制数字
  12. 1_.5 // .不是一个合法的十进制数字
  13. 1._5 // .不是一个合法的十进制数字

rune值的字面量形式

上面已经提到,rune类型是int32类型的别名。 因此,rune类型(泛指)是特殊的整数类型。 一个rune值可以用上面已经介绍的整数类型的字面量形式表示。 另一方面,很多各种整数类型的值也可以用本小节介绍的rune字面量形式来表示。

在Go中,一个rune值表示一个Unicode码点。 一般说来,我们可以将一个Unicode码点看作是一个Unicode字符。 但是,我们也应该知道,有些Unicode字符由多个Unicode码点组成。 每个英文或中文Unicode字符值含有一个Unicode码点。

一个rune字面量由若干包在一对单引号中的字符组成。 包在单引号中的字符序列表示一个Unicode码点值。 rune字面量形式有几个变种,其中最常用的一种变种是将一个rune值对应的Unicode字符直接包在一对单引号中。比如:

  1. 'a' // 一个英文字符
  2. 'π'
  3. '众' // 一个中文字符

下面这些rune字面量形式的变种和'a'是等价的 (字符a的Unicode值是97)。

  1. '\141' // 141是97的八进制表示
  2. '\x61' // 61是97的十六进制表示
  3. '\u0061'
  4. '\U00000061'

注意:\之后必须跟随三个八进制数字字符(0-7)表示一个byte值, \x之后必须跟随两个十六进制数字字符(0-9,a-f和A-F)表示一个byte值, \u之后必须跟随四个十六进制数字字符表示一个rune值(此rune值的高四位都为0), \U之后必须跟随八个十六进制数字字符表示一个rune值。 这些八进制和十六进制的数字字符序列表示的整数必须是一个合法的Unicode码点值,否则编译将失败。

下面这些println函数调用都将打印出true

  1. package main
  2. func main() {
  3. println('a' == 97)
  4. println('a' == '\141')
  5. println('a' == '\x61')
  6. println('a' == '\u0061')
  7. println('a' == '\U00000061')
  8. println(0x61 == '\x61')
  9. println('\u4f17' == '众')
  10. }

事实上,在日常编程中,这四种rune字面量形式的变种很少用来表示rune值。 它们多用做字符串的双引号字面量形式中的转义字符(详见下一小节)。

如果一个rune字面量中被单引号包起来的部分含有两个字符, 并且第一个字符是\,第二个字符不是xuU,那么这两个字符将被转义为一个特殊字符。 目前支持的转义组合为:

  1. \a (rune值:0x07) 铃声字符
  2. \b (rune值:0x08) 退格字符(backspace
  3. \f (rune值:0x0C) 换页符(form feed
  4. \n (rune值:0x0A) 换行符(line feed or newline
  5. \r (rune值:0x0D) 回车符(carriage return
  6. \t (rune值:0x09) 水平制表符(horizontal tab
  7. \v (rune值:0x0b) 竖直制表符(vertical tab
  8. \\ (rune值:0x5c) 一个反斜杠(backslash
  9. \' (rune值:0x27) 一个单引号(single quote

其中,\n在日常编程中用得最多。

一个例子:

  1. println('\n') // 10
  2. println('\r') // 13
  3. println('\'') // 39
  4. println('\n' == 10) // true
  5. println('\n' == '\x0A') // true

rune类型的零值常用 '\000''\x00''\u0000'等来表示。

字符串值的字面量形式

在Go中,字符串值是UTF-8编码的, 甚至所有的Go源代码都必须是UTF-8编码的。

Go字符串的字面量形式有两种。 一种是解释型字面表示(interpreted string literal,双引号风格)。 另一种是直白字面表示(raw string literal,反引号风格)。 下面的两个字符串表示形式是等价的:

  1. // 解释形式
  2. "Hello\nworld!\n\"你好世界\""
  3. // 直白形式
  4. `Hello
  5. world!
  6. "你好世界"`

在上面的解释形式(双引号风格)的字符串字面量中,每个\n将被转义为一个换行符,每个\"将被转义为一个双引号字符。 双引号风格的字符串字面量中支持的转义字符和rune字面量基本一致,除了一个例外:双引号风格的字符串字面量中支持\"转义,但不支持\'转义;而rune字面量则刚好相反。

\\x\u\U开头的rune字面量(不包括两个单引号)也可以出现在双引号风格的字符串字面量中。比如:

  1. // 这几个字符串字面量是等价的。
  2. "\141\142\143"
  3. "\x61\x62\x63"
  4. "\x61b\x63"
  5. "abc"
  6. // 这几个字符串字面量是等价的。
  7. "\u4f17\xe4\xba\xba"
  8. // “众”的Unicode值为4f17,它的UTF-8
  9. // 编码为三个字节:0xe4 0xbc 0x97。
  10. "\xe4\xbc\x97\u4eba"
  11. // “人”的Unicode值为4eba,它的UTF-8
  12. // 编码为三个字节:0xe4 0xba 0xba。
  13. "\xe4\xbc\x97\xe4\xba\xba"
  14. "众人"

在UTF-8编码中,一个Unicode码点(rune)可能由1到4个字节组成。 每个英文字母的UTF-8编码只需要一个字节;每个中文字符的UTF-8编码需要三个字节。

直白反引号风格的字面表示中是不支持转义字符的。 除了首尾两个反引号,直白反引号风格的字面表示中不能包含反引号。 为了跨平台兼容性,直白反引号风格的字面表示中的回车符(Unicode码点为0x0D) 将被忽略掉。

字符串类型的零值在代码里用 ""`` 表示。

基本数值类型字面量的适用范围

一个数值型的字面量只有在不需要舍入时,才能用来表示一个整数基本类型的值。 比如,1.0可以表示任何基本整数类型的值,但1.01却不可以。 当一个数值型的字面量用来表示一个非整数基本类型的值时,舍入(或者精度丢失)是允许的。

每种数值类型有一个能够表示的数值范围。 如果一个字面量超出了一个类型能够表示的数值范围(溢出),则在编译时刻,此字面量不能用来表示此类型的值。

下表是一些例子:

字面表示此字面表示可以表示哪些类型的值(在编译时刻)
256除了int8和uint8类型外的所有的基本数值类型。
255除了int8类型外的所有的基本数值类型。
-123除了无符号整数类型外的所有的基本数值类型。
123所有的基本数值类型。
123.000
1.23e2
‘a’
1.0+0i
1.23所有浮点数和复数基本数值类型。
0x10000000000000000
(16 zeros)
3.5e38除了float32和complex64类型外的所有浮点数和复数基本数值类型。
1+2i所有复数基本数值类型。
2e+308无。

注意几个溢出的例子:

  • 字面量0x10000000000000000需要65个比特才能表示,所以在运行时刻,任何基本整数类型都不能精确表示此字面量。
  • 在IEEE-754标准中,最大的可以精确表示的float32类型数值为3.40282346638528859811704183484516925440e+38,所以3.5e38不能表示任何float32和complex64类型的值。
  • 在IEEE-754标准中,最大的可以精确表示的float64类型数值为1.797693134862315708145274237317043567981e+308,因此2e+308不能表示任何基本数值类型的值。
  • 尽管0x10000000000000000可以用来表示float32类型的值,但是它不能被任何float32类型的值所精确表示。上面已经提到了,当使用字面量来表示非整数基本数值类型的时候,精度丢失是允许的(但溢出是不允许的)。

本书由老貘历时三年写成。目前本书仍在不断改进和增容中。你的赞赏是本书和Go101.org网站不断增容和维护的动力。

(请搜索关注微信公众号“Go 101”或者访问github.com/golang101/golang101获取本书最新版)