Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。基础类型包括:数字、字符串和布尔型。复合数据类型包括:数组和结构体。引用类型包括指针、切片、字典、函数、通道,它们都是对程序中一个变量或状态的间接引用,这意味着对任一引用类型数据的修改都会影响所有该引用的拷贝。
一:整型和运算符
1:Go语言同时提供了有符号和无符号类型的整数。有符号整型数类型有int8、int16、int32和int64四种,无符号整形数类型是uint8、uint16、uint32和uint64四种。
还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型。int和uint有同样的大小:32或64bit,但是我们不能对此做任何的假设,因为不同的编译器即使在相同的硬件平台上可能产生不同的大小。
rune类型是和int32等价的类型,用于表示一个Unicode字符。这两个名称可以互换使用。同样的,byte是uint8类型的等价类型。
最后,还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
注意,不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是不同的类型,即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式的类型转换操作,反之亦然。
2:下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照优先级递减的顺序的排列(同一行表示相同的优先级):
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序。
算术运算符+、-、 * 和 / 可以适用与于整数、浮点数和复数;取模运算符%仅用于整数间的运算,在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此 -5%3 和 -5%-3 结果都是-2;除法运算符 / 的行为则依赖于操作数是否为全为整数,比如 5.0/4.0 的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型:==、!= 、< 、<= 、> 、>= 。
这里是一元的加法和减法运算符:
+ 一元加法 (无效果)
- 负数
对于整数,+x是0+x的简写,-x则是0-x的简写;对于浮点数和复数,+x就是x,-x则是x 的负数。
Go还提供了以下的bit位操作运算符,前面4个操作运算符并不区分是有符号还是无符号数:
& 位运算 AND
| 位运算 OR
^ 位运算 XOR
&^ 位清空 (AND NOT)
<< 左移
>> 右移
位操作运算符 ^ 作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反;位操作运算符 &^ 用于按位置零(AND NOT):表达式 z = x &^ y ,如果y中对应bit位为1的话,z中的bit位为0,否则对应的bit位等于x相应的bit位的值。
在 x<<n 和 x>>n 移位运算中,决定了移位操作bit数部分必须是无符号数;被操作的x数可以是有符号或无符号数。算术上,一个 x<<n 左移运算等价于乘以2 ,一个 x>>n 右移运算等价于除以2 。
左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。
尽管Go语言提供了无符号数和运算,但是无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。
一般来说,需要一个显式的转换将一个值从一种类型转化位另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解:
var apples int32 = 1 var oranges int16 = 2 var compote int = apples + oranges // compile error
这种类型不匹配的问题可以有几种不同的方法修复,最常见方法是将它们都显式转型为一个常见类型:
var compote = int(apples) + int(oranges)
浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值类型转换,因为截断的行为可能依赖于具体的实现:
f := 1e100 // a float64 i := int(f) // 结果依赖于具体实现
任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。
当使用fmt包打印一个数值时,我们可以用%d、%o或%x参数控制输出的进制格式,就像下面的例子:
o := 0666 fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666" x := int64(0xdeadbeef) fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
请注意fmt的两个使用技巧:通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的 [1] 副词告诉Printf函数再次使用第一个操作数;第二,%后的 # 副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。
字符使用 %c 参数打印,或者是用 %q 参数打印带单引号的字符:
ascii := ‘a‘ unicode := ‘国‘ newline := ‘\n‘ fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a ‘a‘" fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 ‘国‘" fmt.Printf("%d %[1]q\n", newline) // "10 ‘\n‘"
二:浮点数
1:Go语言提供了两种精度的浮点数,float32和float64。这些浮点数类型的取值范围可以从很微小到很巨大。浮点数的范围极限值可以在math包找到。常量math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应的math.MaxFloat64常量大约是1.8e308。它们分别能表示的最小值近似为1.4e-45和4.9e-324。
2:一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度。通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大(译注:因为float32的有效bit位只有23个,其它的bit位用于指数和符号;当整数大于23bit能表达的范围时,float32的表示将出现误差):
var f float32 = 16777216 // 1 << 24 fmt.Println(f == f+1) // "true"!
3:浮点数的字面值可以直接写小数部分,像这样:const e = 2.71828 // (approximately)
小数点前面或后面的数字都可能被省略(例如.707或1.)。很小或很大的数最好用科学计数法书写,通过e或E来指定指数部分:
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数 const Planck = 6.62606957e-34 // 普朗克常数
4:用Printf函数的%g参数打印浮点数,将采用更紧凑的表示形式打印,并提供足够的精度,但是对应表格的数据,使用%e(带指数)或%f的形式打印可能更合适。所有的这三个打印形式都可以指定打印的宽度和控制打印精度。
for x := 0; x < 8; x++ { fmt.Printf("x = %d e^x = %8.3f\n", x, math.Exp(float64(x))) }
5:math包中还提供了IEEE754浮点数标准中定义的特殊值的创建和测试:正无穷大和负无穷大,分别用于表示太大溢出的数字和除零的结果;还有NaN非数,一般用于表示无效的除法操作结果0/0或Sqrt(-1).
函数math.IsNaN用于测试一个数是否是非数NaN,math.NaN则返回非数对应的值。虽然可以用math.NaN来表示一个非法的结果,但是测试一个结果是否是非数NaN则是充满风险的,因为NaN和任何数都是不相等的(译注:在浮点数中,NaN、正无穷大和负无穷大都不是唯一的,每个都有非常多种的bit模式表示):
nan := math.NaN() fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
三:复数
1:Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部:
var x complex128 = complex(1, 2) // 1+2i var y complex128 = complex(3, 4) // 3+4i fmt.Println(x*y) // "(-5+10i)" fmt.Println(real(x*y)) // "-5" fmt.Println(imag(x*y)) // "10"
2:如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0。
3:在常量算术规则下,一个复数常量可以加到另一个普通数值常量(整数或浮点数、实部或虚部),我们可以用自然的方式书写复数,就像1+2i或与之等价的写法2i+1。上面x和y的声明语句还可以简化:
x := 1 + 2i y := 3 + 4i
4:复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的(译注:浮点数的相等比较是危险的,需要特别小心处理精度问题)。
math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数:
fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)"
四:布尔型
1:一个布尔类型的值只有两种:true和false。if和for语句的条件部分都是布尔类型的值;并且==和<等比较操作也会产生布尔型的值;一元操作符 ! 对应逻辑非操作,因此 !true 的值为 false;
2:布尔值可以和 && 和 || 操作符结合,并且可能会有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不在被求值;
3:在Go中,布尔值并不会隐式转换为数字值0或1,反之亦然:
a := true b := a + 1 c := int(a) + 1
上面的语句中,不管是隐式转换,还是显示转换,都是不合法的,会报编译错误:
cannot convert 1 to type bool invalid operation: a + 1 (mismatched types bool and int) cannot convert a (type bool) to type int
整数c也不能隐式转换为布尔类型:
c := 1 if c { … }
上面的语句会报编译错误:
non-bool c (type int) used as if condition
如果需要bool和数值类型之间的相互转换,可以自行包装一个函数。
五:字符串
1:一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode字符(rune)序列
2:内置的len函数可以返回一个字符串中的字节数目(不是字符数目),s[i]返回第i个字节的字节值,如果试图访问超出字符串索引范围的字节将会导致panic异常。
3:子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(并不包含j本身)生成一个新字符串。如果索引超出字符串范围将导致panic异常;如果j小于i的话,则会报编译错误。
不管i还是j都可能被忽略,当它们被忽略时将采用0作为开始位置,采用len(s)作为结束的位置。
4:+操作符将两个字符串链接构造一个新字符串;
字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序。
5:字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串:
s := "left foot" t := s s += ", right foot"
这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包含原先的字符串值。
因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的:
s[0] = ‘L‘ // compile error: cannot assign to s[0]
不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存。
6:字符串值也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号即可:
"Hello, 世界"
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但是不能超过 \377 (译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。
7:一个原生的字符串面值形式是`...` ,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,因此一个程序中的原生字符串面值可能跨越多行。原生字符串面值用于编写正则表达式会很方便,同时被广泛应用于HTML模板、JSON面值、命令行提示信息以及那些需要扩展到多行的场景。
const GoUsage = `Go is a tool for managing Go source code. Usage: go command [arguments] ...`
8:UTF8是一个将Unicode字符编码为字节序列的变长编码。UTF8编码使用1到4个字节来表示每个Unicode字符,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。
每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode字符也是采用类似的策略处理。
0xxxxxxx runes 0-127 (ASCII)
110xxxxx 10xxxxxx 128-2047 (values <128 unused)
1110xxxx 10xxxxxx 10xxxxxx 2048-65535 (values <2048 unused)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
9:Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数组,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。
10:Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode字符输入特殊的字符。有两种形式:\uhhhh对应16bit的字符值,\Uhhhhhhhh对应32bit的字符值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应字符的UTF8编码。例如:下面的字母串面值都表示相同的值:
"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"
上面三个转义序列都为第一个字符串提供替代写法,但是它们的值都是相同的。
Unicode转义也可以使用在rune字符中。下面三个字符是等价的:
‘世‘ ‘\u4e16‘ ‘\U00004e16‘
对于小于256字符值可以写在一个十六进制转义字节中,例如‘\x41‘对应字符‘A‘,但是对于更大的字符则必须使用\u或\U转义形式。因此,‘\xe4\xb8\x96‘并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的字符。
11:得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀:
func HasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix }
另一方面,如果我们真的关心每个Unicode字符,我们可以使用其它处理方式。考虑前面的第一个例子中的字符串,字符串包含13个字节,以UTF8形式编码,但是只对应9个Unicode字符:
import "unicode/utf8" s := "Hello, 世界" fmt.Println(len(s)) // "13" fmt.Println(utf8.RuneCountInString(s)) // "9"
为了处理这些真实的字符,我们需要一个UTF8解码器。unicode/utf8包提供了该功能,我们可以这样使用:
for i := 0; i < len(s); { r, size := utf8.DecodeRuneInString(s[i:]) fmt.Printf("%d\t%c\n", i, r) i += size }
每一次调用DecodeRuneInString函数都返回一个r和长度,r对应字符本身,长度对应r采用UTF8编码后的编码字节数目。
但是这种编码方式是笨拙的,我们需要更简洁的语法。幸运的是,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。需要注意的是对于非ASCII,索引更新的步长将超过1个字节。
for i, r := range "Hello, 世界" { fmt.Printf("%d\t%q\t%d\n", i, r, r) }
可以使用一个简单的循环来统计字符串中字符的数目,像这样:
n := 0 for _, _ = range s { n++ }
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符‘\uFFFD‘,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号。
12:UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。string接受到[]rune的类型转换,可以将一个UTF8编码的字符串解码为Unicode字符序列:
// "program" in Japanese katakana s := "プログラム" fmt.Printf("% x\n", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0" r := []rune(s) fmt.Printf("%x\n", r) // "[30d7 30ed 30b0 30e9 30e0]"
注意,在第一个Printf中的 % x 参数用于在每个十六进制数字前插入一个空格。
如果是将一个[]rune类型的Unicode字符slice或数组转为string,则对它们进行UTF8编码:
fmt.Println(string(r)) // "プログラム"
将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:
fmt.Println(string(65)) // "A", not "65" fmt.Println(string(0x4eac)) // "京"
如果对应码点的字符是无效的,则用‘\uFFFD‘无效字符作为替换:
fmt.Println(string(1234567)) // ""
13:标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
bytes包也提供了很多类似功能的函数,但是针对的是和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效。
strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
14:将一个整数转为字符串,一种方法是用fmt.Sprintf返回一个格式化的字符串;另一个方法是用strconv.Itoa():
x := 123 y := fmt.Sprintf("%d", x) fmt.Println(y, strconv.Itoa(x)) // "123 123"
FormatInt和FormatUint函数可以用不同的进制来格式化数字:
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含附加额外信息的时候:
s := fmt.Sprintf("x=%b", x) // "x=1111011"
如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint函数:
x, err := strconv.Atoi("123") // x is an int y, err := strconv.ParseInt("123", 10, 64) // base 10, up to 64 bits
ParseInt函数的第三个参数是用于指定(参数整)型数的大小;例如16表示int16,0则表示int。在任何情况下,strconv.ParseInt返回的结果总是int64类型,你可以通过强制类型转换将它转为更小的整数类型。
有时候也会使用fmt.Scanf来解析输入的字符串和数字,特别是当字符串和数字混合在一行的时候,它可以灵活处理不完整或不规则的输入。
六:常量
常量有布尔常量、字符常量、整型常量、浮点型常量、复数常量以及字符串常量。其中,字符常量、整型常量、浮点型常量和复数常量统称为数字常量。
1无类型常量
常量分为有类型(typed)的和无类型的(untyped)。字面常量(比如2, ‘a’, “abc”, 1.3等),true,false,iota,以及那些只包含无类型常量操作数的常量表达式,都是无类型的。
编译器为无类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。
无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。例如,math.Pi是无类型的浮点数常量,可以直接用于任意需要浮点数或复数的地方:
var x float32 = math.Pi var y float64 = math.Pi var z complex128 = math.Pi
如果math.Pi被确定为特定类型,比如float64,那么结果精度可能会不一样,同时对于需要float32或complex128类型值的地方则会需要一个强制类型转换:
const Pi64 float64 = math.Pi var x float32 = float32(Pi64) var y float64 = Pi64 var z complex128 = complex128(Pi64)
无类型常量都有一个默认类型,该类型就是当该常量用于需要一个有类型值的上下文时,会被隐式转换的类型。比如一个没有显式类型的变量声明(像i := 0)的语句。无类型常量的默认类型,根据常量值的类型,有:bool, rune, int, float64, complex128, string。比如:
a := 1234567 b := 1.23454 c := ‘a‘ d := 1 + 2i e := "abcdefg" f := true fmt.Println("a is ", a, "\ttype is ", reflect.TypeOf(a)) fmt.Println("b is ", b, "\ttype is ", reflect.TypeOf(b)) fmt.Println("c is ", c, "\ttype is ", reflect.TypeOf(c)) fmt.Println("d is ", d, "\ttype is ", reflect.TypeOf(d)) fmt.Println("e is ", e, "\ttype is ", reflect.TypeOf(e)) fmt.Println("f is ", f, "\ttype is ", reflect.TypeOf(f))
结果是:
a is 1234567 type is int b is 1.23454 type is float64 c is 97 type is int32 d is (1+2i) type is complex128 e is abcdefg type is string f is true type is bool
其中,字符’a’的类型为int32,这是因为:”rune is an alias for int32 and is equivalent to int32 in all ways”。
如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样:
var i = int8(0) var i int8 = 0
注意:只有常量可以是无类型的。无类型常量这个概念,使得在Go中使用常量更加自由。因为Go中不允许不同类型的操作数混合使用,比如不能将float64和int相加,甚至不能将int32和int相加。但是使用无类型常量却是可以的。
2:声明常量
常量使用关键字const声明,如果表达式的值是无类型常量,则声明的常量也是无类型的:
const limit = 512 //无类型常量 const top uint16 = 1421 //常量,类型:uintl6
可以使用const关键字一次将多个常量声明组合在一起,这些常量的类型不必相同:
const a, b, c = 3, 4.0, "foo" // a = 3, b = 4.0, c = "foo" const u, v float32 = 0, 3 // u = 0.0, v = 3.0 const ( size int64 = 1024 eof = -1 // untyped integer constant )
带有”( … )”的多个常量声明中,除了第一个声明之外,其余的常量声明中,常量值都可以省略。那些省略的常量值的声明,其值和类型都与它前面没有省略值的那个常量相同。如:
const ( a = 1 b c = "foo" d = 3.4 e f ) fmt.Println("a is ", a, "\ttype is ", reflect.TypeOf(a)) fmt.Println("b is ", b, "\ttype is ", reflect.TypeOf(b)) fmt.Println("c is ", c, "\ttype is ", reflect.TypeOf(c)) fmt.Println("d is ", d, "\ttype is ", reflect.TypeOf(d)) fmt.Println("e is ", e, "\ttype is ", reflect.TypeOf(e)) fmt.Println("f is ", f, "\ttype is ", reflect.TypeOf(f))
结果是:
a is 1 type is int b is 1 type is int c is foo type is string d is 3.4 type is float64 e is 3.4 type is float64 f is 3.4 type is float64
在常量声明中,预定义的标识符iota表示的是连续的无类型整型常量(successive untyped integer constants)。当出现”const”关键字时,iota的值就被重置为0,每一个常量声明,都会使iota加1。比如:
const ( a = 3 // iota is reset to 0 b // iota is not used but still incremented c = iota // iota is 2 now d = "foo" e = iota // iota is 4 now f // f的值与e相同,也是iota,而此时iota值为5 g // g的值与e相同,也是iota,而此时iota值为6 ) fmt.Println("a is ", a, "\ttype is ", reflect.TypeOf(a)) fmt.Println("b is ", b, "\ttype is ", reflect.TypeOf(b)) fmt.Println("c is ", c, "\ttype is ", reflect.TypeOf(c)) fmt.Println("d is ", d, "\ttype is ", reflect.TypeOf(d)) fmt.Println("e is ", e, "\ttype is ", reflect.TypeOf(e)) fmt.Println("f is ", f, "\ttype is ", reflect.TypeOf(f)) fmt.Println("g is ", g, "\ttype is ", reflect.TypeOf(g))
结果是:
a is 3 type is int b is 3 type is int c is 2 type is int d is foo type is string e is 4 type is int f is 5 type is int g is 6 type is int
需要注意的是,如果在同一行常量声明中,声明了多个常量,则iota的值不会发生变化,因为同一行内的多个常量,属于一个常量声明:
const ( bit0, mask0 = 1 << iota, (1<<iota) - 1 // bit0 == 1, mask0 == 0 bit1, mask1 // bit1 == 2, mask1 == 1 _, _ // skips iota == 2 bit3, mask3 // bit3 == 8, mask3 == 7 ) fmt.Println("bit0 is ", bit0, "\tmask0 is ", mask0) fmt.Println("bit1 is ", bit1, "\tmask1 is ", mask1) fmt.Println("bit3 is ", bit3, "\tmask3 is ", mask3)
结果是:
bit0 is 1 mask0 is 0 bit1 is 2 mask1 is 1 bit3 is 8 mask3 is 7
3:常量表达式
常量表达式仅包含常量操作数,并且表达式的值在编译时就会确定。
除了移位操作、布尔操作外,如果二元操作符的两个操作数是不同类型的无类型常量,则表达式结果的类型,取决于“整型、字符、浮点、复数”中靠后的类型。比如,一个无类型整数除以一个无类型浮点数,结果是一个浮点数;
常量比较表达式的结果,总是无类型布尔常量;
移位操作符的右操作数必须是无符号整数类型,或者是一个能表示uint的无类型常量(比如7.0也可以)。如果移位操作符的左操作数是一个无类型常量,则表达式的结果是一个整数,其他情况,左操作数必须是一个整数类型,并且结果类型与左操作数相同;
const a = 2 + 3.0 // a == 5.0 (untyped floating-point constant) const b = 15 / 4 // b == 3 (untyped integer constant) const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant) const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division) const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division) const d = 1 << 3.0 // d == 8 (untyped integer constant) const e = 1.0 << 3 // e == 8 (untyped integer constant) const f = int32(1) << 33 // illegal (constant 8589934592 overflows int32) const g = float64(2) >> 1 // illegal (float64(2) is a typed floating-point constant) const h = "foo" > "bar" // h == true (untyped boolean constant) const j = true // j == true (untyped boolean constant) const k = ‘w‘ + 1 // k == ‘x‘ (untyped rune constant) const l = "hi" // l == "hi" (untyped string constant) const m = string(k) // m == "x" (type string) const Σ = 1 - 0.707i // (untyped complex constant) const Δ = Σ + 2.0e-4 // (untyped complex constant) const Φ = iota*1i - 1/1i // (untyped complex constant)
除了移位操作符,如果一个操作数是无类型常量,而另一个操作数却不是,则该无类型常量会隐式转换成另一个操作数的类型。
const a int32 = 2 << 3.0 const b = 2.0 + a fmt.Println("a is ", a, "\ttype is ", reflect.TypeOf(a)) fmt.Println("b is ", b, "\ttype is ", reflect.TypeOf(b))
a是一个int32类型的常量,而2.0是一个无类型常量,因此b的类型为int32:
a is 16 type is int32 b is 18 type is int32
常量表达式的中间数或结果值的精确度,可以远大于任何预定义类型,比如下面的语句是合法的:
const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant) const Four int8 = Huge >> 98 // Four == 4