基本类型和它们的值的字面表示形式
类型(type)可以被看作是值(value)的模板,值可以被看作是类型的实例。这篇文章将介绍内置(或称为预声明的)基本类型和它们的值的字面表示形式。本篇文章不介绍组合类型。
基本内置类型
Go支持如下内置基本类型:
- 一种内置布尔类型:
bool
。 - 11种内置整数类型:
int8
、uint8
、int16
、uint16
、int32
、uint32
、int64
、uint64
、int
、uint
和uintptr
。 - 两种内置浮点数类型:
float32
和float64
。 - 两种内置复数类型:
complex64
和complex128
。 - 一种内置字符串类型:
string
。 内置类型也称为预声明类型。
这17种内置基本类型(type)各自属于一种Go中的类型种类(kind)。尽管所有的内置基本类型的名称都是非导出标识符,我们可以不用引入任何代码包而直接使用这些类型。
除了bool
和string
类型,其它的15种内置基本类型都称为数值类型(整型、浮点数型和复数型)。
Go中有两种内置类型别名(type alias):
byte
是uint8
的内置别名。我们可以将byte
和uint8
看作是同一个类型。rune
是int32
的内置别名。我们可以将rune
和int32
看作是同一个类型。 以u
开头的整数类型称为无符号整数类型。无符号整数类型的值都是非负的。一个数值类型名称中的数字表示每个这个类型的值将在内存中占有多少二进制位(以后简称位)。二进制位常称为比特(bit)。比如,一个uint8
的值将占有8位。我们称uint8
类型的值的尺寸是8位。因此,最大的uint8
值是255
(28-1),最大的int8
值是127
(27-1),最小的int8
值是-128
(-27)。
任一个类型的所有值的尺寸都是相同的,所以一个值的尺寸也常称为它的类型的尺寸。
更多的时候,我们使用字节(byte)做为值尺寸的度量单位。一个字节相当于8个比特。所以uint32
类型的尺寸为4,即每个uint32
值占用4个字节。
uintptr
、int
以及uint
类型的值的尺寸依赖于具体编译器实现。通常地,在64位的架构上,int
和uint
类型的值是64位的;在32位的架构上,它们是32位的。编译器必须保证uintptr
类型的值的尺寸能够存下任意一个内存地址。
一个complex64
复数值的实部和虚部都是float32
类型的值。一个complex128
复数值的实部和虚部都是float64
类型的值。
在内存中,所有的浮点数都使用IEEE-754格式存储。
一个布尔值表示一个真假。在内存中,一个布尔值只有两种可能的状态。这两种状态使用两个预声明(或称为内置)的常量(false
和true
)来表示。关于常量声明,下一篇文章将做详细解释。
从逻辑上说,一个字符串值表示一段文本。在内存中,一个字符串存储为一个字节(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是一个特指)。同样地,MyString
和string
都被称为字符串(string)类型,status
和bool
都被称为布尔(bool)类型。
我们将在Go类型系统概述一文中学习到更多关于自定义类型的知识。
零值
每种类型都有一个零值。一个类型的零值可以看作是此类型的默认值。
基本类型的值的字面表示形式
一个值的字面表示形式是指在代码中这个值的文字体现形式。一个值可能会有很多种字面表示形式。
一个基本类型的值的字面表示形式也称为一个字面常量,或者叫一个无名常量。
布尔值的字面表示形式
Go白皮书没有定义布尔类型值字面表示形式。我们可以将false
和true
这两个预声明的有名常量当作布尔类型值字面表示形式。但是,我们应该知道,从严格意义上说,它们不属于字面表示形式。有名常量声明将在下一篇文章中介绍和详细解释。
布尔类型的零值可以使用预声明的false
来表示。
整数类型值的字面表示形式
整数类型值有四种字面表示形式:十进制形式(decimal)、八进制形式(octal)、十六进制形式(hex)和二进制形式(binary)。比如,下面的三个字面值均表示十进制的15:
0xF // 十六进制表示(必须使用0x或者0X开头)
0XF
017 // 八进制表示(必须使用0、0o或者0O开头)
0o17
0O17
0b1111 // 二进制表示(必须使用0b或者0B开头)
0B
15 // 十进制表示(必须不能用0开头)
(注意:二进制形式和以0o
或0O
开头的八进制形式从Go 1.13开始才支持。)
下面的程序打印出两个true
。
package main
func main() {
println(15 == 017) // true
println(15 == 0xF) // true
}
注意这里的==
是一个等于比较操作符。操作符将在后续的文章常用操作符一文中详细解释。
整数类型的零值的字面形式一般使用0
表示。当然,00
和0x0
等也是合法的整数类型零值的字面表示形式。
浮点数类型值的字面表示形式
一个浮点数的完整字面表示形式包含一个十进制整数部分、一个小数点、一个十进制小数部分和一个整数指数部分。常常地,某些部分可以根据情况省略掉。一些例子(xEn
表示x
乘以10n
的意思,而xE-n
表示x
除以10n
的意思):
1.23
01.23 // == 1.23
.23
1.
// 一个e或者E随后的数值是指数值(底数为10)。
// 指数值必须为一个可以带符号的十进制整数字面值。
1.23e2 // == 123.0
123E2 // == 12300.0
123.E+2 // == 12300.0
1e-1 // == 0.1
.1e0 // == 0.1
0010e-2 // == 0.1
0e+5 // == 0.0
从Go 1.13开始,Go也支持另一种浮点数字面表示形式(权称为十六进制浮点数文字表示)。在一个十六进制浮点数文字表示中,
- 和整数的十六进制文字表示一样,浮点数的十六进制文字表示也必须使用
0x
或者0X
开头。 - 和整数的十六进制文字表示不同的是,字母
p
或者P
可以出现在浮点数的十六进制文字表示中,其后跟随着一个幂指数(底数为2)。 - 另外要注意,
e
和E
不能出现在浮点数的十六进制文字表示中。一些合法的浮点数的十六进制文字表示例子(yPn
表示y
乘以2n
的意思,而yP-n
表示y
除以2n
的意思):
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
为是十进制中的14
,0x15e
为一个整数十六进制文字表示,-2
并不是此整数十六进制文字表示的一部分。(算术运算将在后续的文章常用操作符一文中详细介绍。)
0x15e-2 // == 0x15e - 2 (整数相减表达式)
浮点类型的零值的标准字面表示形式为0.0
。当然其它很多形式也是合法的,比如0.
、.0
、0e0
和0x0p0
等。
虚部的字面表示形式
一个虚部值的字面表示形式由一个浮点数字面值或者一个整数字面值和其后跟随的一个小写的i
组成。一些例子:
1.23i
1.i
.23i
123i
0123i // == 123i
1.23E2i // == 123i
1e-1i
(注意,在Go 1.13之前,如果虚部中i
前的部分为一个整数字面值,则其必须为十进制形式。)
虚部字面值用来表示复数的虚部。下面是一些复数值的字面表示形式:
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
、.0i
、0+0i
等表示也是合法的。
数值字面表示中使用下划线分段来增强可读性
从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字面形式中被单引号包起来的部分含有两个字符,并且第一个字符是\
,第二个字符不是x
、u
和U
,那么这两个字符将被转义为一个特殊字符。目前支持的转义组合为:
\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
)将被忽略掉。
字符串类型的零值在代码里用 ""
或``
表示。
基本类型字面值的适用范围
以后,我们常常把一个值的字面形称为一个字面值。
任何字符串的字面形式都可以表示任何字符串类型的值。
预声明的两个常量false
和true
都可以表示任何布尔类型的值。(再次提醒,false
和true
不属于严格意义上的字面值。)
当一个数值型的字面值用来表示一个整数基本类型的值时,舍入是不允许的。比如,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类型的值所精确表示。上面已经提到了,当使用字面值来表示非整数基本数值类型的时候,精度丢失是允许的(但溢出是不允许的)。
Go语言101项目目前同时托管在Github和Gitlab上。欢迎各位在这两个项目中通过提交bug和PR的方式来改进完善Go语言101中的各篇文章。
本书微信公众号名称为"Go 101"。每个工作日此公众号将尽量发表一篇和Go语言相关的原创短文。各位如果感兴趣,可以搜索关注一下。