基本类型和它们的值的字面表示形式

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

基本内置类型

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

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

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

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

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

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

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

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

intuint类型的值的尺寸依赖于操作系统架构。 在64位的架构上,它们的值是64位的;在32位的架构上,它们的值是32位的。 编译器必须保证uintptr类型的值的尺寸能够存下任意一个内存地址。

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

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

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

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

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

// 一些类型别名声明
type boolean = bool // boolean和bool表示同一个类型
type Text = string  // Text和string表示同一个类型
type U8 = uint8     // U8、uint8和 byte表示同一个类型
type char = rune    // char、rune和int32表示同一个类型

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

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

零值

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

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

一个值的字面表示形式是指在代码中这个值的文字体现形式。 一个值可能会有很多种字面表示形式。

一个基本类型的值的字面表示形式也称为一个字面常量,或者叫一个无名常量。

布尔值的字面表示形式

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

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

整数类型值的字面表示形式

整数类型值有三种字面表示形式:十进制形式(decimal)、八进制形式(octal)和十六进制形式(hex)。 比如,下面的三个字面值均表示十进制的15:
0xF // 十六进制表示(必须使用0x或者0X开头)
017 // 八进制表示(必须使用0开头)
15  // 十进制表示(必须不能用0开头)

下面的程序打印出两个true
package main

func main() {
	println(15 == 017) // true
	println(15 == 0xF) // true
}

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

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

注意:下一个版本(Go 1.13)将开始支持整数的二进制形式(binary),一个二进制表示必须以0b或者0B开头。 八进制形式也得到了扩展,一个八进制表示将也可以以0o或者0O开头。 比如,下面为十进制数15的二进制和八进制表示:
0b1111
0B1111
0o17
0O17

浮点数类型值的字面表示形式

一个浮点数的完整字面表示形式包含一个整数部分、一个小数点、一个小数部分和一个指数部分。 常常地,某些部分可以根据情况省略掉。一些例子:
1.23
01.23 // == 1.23
.23
1.
// 一个e或者E随后的数值是指数值。指数值
// 必须为一个可以带符号的十进制整数字面值。
1.23e2  // == 123.0
123E2   // == 12300.0
123.E+2 // == 12300.0
1e-1    // == 0.1
.1e0    // == 0.1
0e+5    // == 0.0

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

注意:下一个版本(Go 1.13)将开始支持浮点数十六进制文字表示,用来表示在内存中能够精确表示的浮点数。 一些合法的浮点数的十六进制文字表示例子:
0x1p-2     // == 0.25
0x2.p10    // == 2048.0
0x1.Fp+0   // == 1.9375
0X.8p-0    // == 0.5
0X1FFFP-16 // == 0.1249847412109375

而下面这几个均是不合法的浮点数的十六进制文字表示。
0x.p1    // 浮点数的十六进制文字表示必须包含至少一个数字
1p-2     // p指数形式只能出现在浮点数的十六进制文字表示中
0x1.5e-2 // e和E不能出现在浮点数的十六进制文字表示中

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

虚部的字面表示形式

一个虚部值的字面表示形式由一个浮点数字面值或者一个整数字面值加一个小写的i组成。 一些例子:
1.23i
1.i
.23i
123i
0123i   // == 123i
1.23E2i // == 123i
1e-1i

虚部字面值用来表示复数的虚部。下面是一些复数值的字面表示形式:
1 + 2i       // == 1.0 + 2.0i
1. - .1i     // == 1.0 + -0.1i
1.23i - 7.89 // == -7.89 + 1.23i
1.23i        // == 0.0 + 1.23i

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

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

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

一些合法和不合法使用下划线的例子:
// 合法的使用下划线的例子
6_9          // == 69
0_33_77_22   // == 0337722
0x_Bad_Face  // == 0xBadFace
0X_1F_FFP-16 // == 0X1FFFP-16
0b1011_0111 + 0xA_B.Fp2i

// 非法的使用下划线的例子
_69        // 下划线不能出现在首尾
69_        // 下划线不能出现在首尾
6__9       // 下划线不能相连
0_xBadFace // x不是一个合法的八进制数字
1_.5       // .不是一个合法的十进制数字
1._5       // .不是一个合法的十进制数字

rune值的字面形式

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

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

一个rune字面形式由若干包在一对单引号中的字符组成。 包在单引号中的字符序列表示一个Unicode码点值。 rune字面形式有几个变种,其中最常用的一种变种是将一个rune值对应的Unicode字符直接包在一对单引号中。比如:
'a' // 一个英文字符
'π'
'众' // 一个中文字符
下面这些rune字面形式的变种和'a'是等价的 (字符a的Unicode值是97)。
'\141'   // 141是97的八进制表示
'\x61'   // 61是97的十六进制表示
'\u0061'
'\U00000061'

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

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

func main() {
	println('a' == 97)
	println('a' == '\141')
	println('a' == '\x61')
	println('a' == '\u0061')
	println('a' == '\U00000061')
	println(0x61 == '\x61')
	println('\u4f17' == '众')
}

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

如果一个rune字面形式中被单引号包起来的部分含有两个字符, 并且第一个字符是\,第二个字符不是xuU,那么这两个字符将被转义为一个特殊字符。 目前支持的转义组合为:
\a   (rune值:0x07) 铃声字符
\b   (rune值:0x08) 退格字符(backspace)
\f   (rune值:0x0C) 换页符(form feed)
\n   (rune值:0x0A) 换行符(line feed or newline)
\r   (rune值:0x0D) 回车符(carriage return)
\t   (rune值:0x09) 水平制表符(horizontal tab)
\v   (rune值:0x0b) 竖直制表符(vertical tab)
\\   (rune值:0x5c) 一个反斜杠(backslash)
\'   (rune值:0x27) 一个单引号(single quote)

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

一个例子:
	println('\n') // 10
	println('\r') // 13
	println('\'') // 39

	println('\n' == 10)     // true
	println('\n' == '\x0A') // true

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

字符串值的字面表示形式

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

Go字符串的字面表示形式有两种。 一种是解释型字面表示(interpreted string literal,双引号风格)。 另一种是直白字面表示(raw string literal,反引号风格)。 下面的两个字符串表示形式是等价的:
// 解释形式
"Hello\nworld!\n\"你好世界\""

// 直白形式
`Hello
world!
"你好世界"`

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

\\x\u\U开头的rune字面形式(不包括两个单引号)也可以出现在双引号风格的字符串字面形式中。比如:
// 这几个字符串字面形式是等价的。
"\141\142\143"
"\x61\x62\x63"
"abc"

// 这几个字符串字面形式是等价的。
"\u4f17\xe4\xba\xba"
      // “众”的Unicode值为4f17,它的UTF-8
      // 编码为三个字节:0xe4 0xbc 0x97。
"\xe4\xbc\x97\u4eba"
      // “人”的Unicode值为4eba,它的UTF-8
      // 编码为三个字节:0xe4 0xba 0xba。
"\xe4\xbc\x97\xe4\xba\xba"
"众人"

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

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

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

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

以后,我们常常把一个值的字面形称为一个字面值。

任何字符串的字面形式都可以表示任何字符串类型的值。

预声明的两个常量falsetrue都可以表示任何布尔类型的值。 (再次提醒,falsetrue不属于严格意义上的字面值。)

当一个数值型的字面值用来表示一个整数基本类型的值时,舍入是不允许的。 比如,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 无。
注意几个溢出的例子:

Go语言101项目目前同时托管在GithubGitlab上。 欢迎各位在这两个项目中通过提交bug和PR的方式来改进完善Go语言101中的各篇文章。

本书微信公众号名称为"Go 101"。每个工作日此公众号将尽量发表一篇和Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。

赞赏