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 ", o) // "438 666 0666" x := int64(0xdeadbeef) fmt.Printf("%d %[1]x %#[1]x %#[1]X ", x)
请注意fmt的两个使用技巧:通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的 [1] 副词告诉Printf函数再次使用第一个操作数;第二,%后的 # 副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。
字符使用 %c 参数打印,或者是用 %q 参数打印带单引号的字符:
ascii := 'a' unicode := '国' newline := ' ' fmt.Printf("%d %[1]c %[1]q ", ascii) // "97 a 'a'" fmt.Printf("%d %[1]c %[1]q ", unicode) // "22269 国 '国'" fmt.Printf("%d %[1]q ", newline) // "10 ' '"
二:浮点数
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 ", 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编码。例如:下面的字母串面值都表示相同的值:
"世界"
"xe4xb8x96xe7x95x8c"
"u4e16u754c"
"U00004e16U0000754c"
上面三个转义序列都为第一个字符串提供替代写法,但是它们的值都是相同的。
Unicode转义也可以使用在rune字符中。下面三个字符是等价的:
'世' 'u4e16' 'U00004e16'
对于小于256字符值可以写在一个十六进制转义字节中,例如'x41'对应字符'A',但是对于更大的字符则必须使用u或U转义形式。因此,'xe4xb8x96'并不是一个合法的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 %c ", i, r) i += size }
每一次调用DecodeRuneInString函数都返回一个r和长度,r对应字符本身,长度对应r采用UTF8编码后的编码字节数目。
但是这种编码方式是笨拙的,我们需要更简洁的语法。幸运的是,Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。需要注意的是对于非ASCII,索引更新的步长将超过1个字节。
for i, r := range "Hello, 世界" { fmt.Printf("%d %q %d ", 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 ", s) // "e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0" r := []rune(s) fmt.Printf("%x ", 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, " type is ", reflect.TypeOf(a)) fmt.Println("b is ", b, " type is ", reflect.TypeOf(b)) fmt.Println("c is ", c, " type is ", reflect.TypeOf(c)) fmt.Println("d is ", d, " type is ", reflect.TypeOf(d)) fmt.Println("e is ", e, " type is ", reflect.TypeOf(e)) fmt.Println("f is ", f, " type 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, " type is ", reflect.TypeOf(a)) fmt.Println("b is ", b, " type is ", reflect.TypeOf(b)) fmt.Println("c is ", c, " type is ", reflect.TypeOf(c)) fmt.Println("d is ", d, " type is ", reflect.TypeOf(d)) fmt.Println("e is ", e, " type is ", reflect.TypeOf(e)) fmt.Println("f is ", f, " type 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, " type is ", reflect.TypeOf(a)) fmt.Println("b is ", b, " type is ", reflect.TypeOf(b)) fmt.Println("c is ", c, " type is ", reflect.TypeOf(c)) fmt.Println("d is ", d, " type is ", reflect.TypeOf(d)) fmt.Println("e is ", e, " type is ", reflect.TypeOf(e)) fmt.Println("f is ", f, " type is ", reflect.TypeOf(f)) fmt.Println("g is ", g, " type 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, " mask0 is ", mask0) fmt.Println("bit1 is ", bit1, " mask1 is ", mask1) fmt.Println("bit3 is ", bit3, " mask3 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, " type is ", reflect.TypeOf(a)) fmt.Println("b is ", b, " type 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