03基础数据类型

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          

 

原文地址:https://www.cnblogs.com/gqtcgq/p/7806357.html