Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64
四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64
四种无符号整数类型
这里还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int
和uint
;其中int是应用最广泛的数值类型。这两种类型都有同样的大小,32或64bit,但是我们不能对此做任何的假设;因为不同的编译器即使在相同的硬件平台上可能产生不同的大小
无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合
最后,还有一种无符号的整数类型uintptr
,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方
算术运算符+、-、*和/
可以适用于整数、浮点数和复数,但是取模运算符%
仅用于整数间的运算
对于每种类型T,如果转换允许的话,类型转换操作T(x)
将x转换为T类型:
func main() {
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"
}
任何大小的整数字面值都可以用以0
开始的八进制格式书写,例如0666
;或用以0x
或0X
开头的十六进制格式书写,例如0xdeadbeef
。十六进制数字可以用大写或小写字母。如今八进制数据通常用于POSIX操作系统上的文件访问权限标志,十六进制数字则更强调数字值的bit位模式
当使用fmt包打印一个数值时,我们可以用%d、%o或%x
参数控制输出的进制格式,就像下面的例子:
func main() {
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)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
}
请注意fmt的两个使用技巧:
1、通常Printf格式化字符串包含多个%
参数时将会包含对应相同数量的额外操作数,但是%
之后的[1]
副词告诉Printf函数再次使用第一个操作数
2、第二,%
后的#
副词告诉Printf在用%o、%x或%X
输出时生成0、0x或0X
前缀
Go语言提供了两种精度的浮点数,float32和float64
一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度;通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散,并且float32能精确表示的正整数并不是很大
小数点前面或后面的数字都可能被省略(例如.707或1.)。很小或很大的数最好用科学计数法书写,通过e或E来指定指数部分:
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗克常数
打印e的幂,打印精度是小数点后三个小数精度和8个字符宽度:
for x := 0; x < 8; x++ {
fmt.Printf("x = %d e^x = %8.3f\n", x, math.Exp(float64(x)))
}
Go语言提供了两种精度的复数类型:complex64
和complex128
,分别对应float32
和float64
两种浮点数精度。内置的complex
函数用于构建复数,内建的real
和imag
函数分别返回复数的实部和虚部:
func main() {
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"
}
上面x和y的声明语句还可以简化:
x := 1 + 2i
y := 3 + 4i
如果一个浮点数面值或一个十进制整数面值后面跟着一个i,例如3.141592i或2i,它将构成一个复数的虚部,复数的实部是0:
fmt.Println(1i * 1i) // "(-1+0i)"
另外我,复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的
一个布尔类型的值只有两种:true和false
布尔值可以和&&(AND)和||(OR)
操作符结合,并且有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值
在Go语言中,布尔值并不会隐式转换为数字值0或1,反之亦然
因为字符串是不可修改的,因此尝试修改字符串内部数据的操作也是被禁止的,例如:
s[0] = 'L'
不变性意味着如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存
在一个双引号包含的字符串面值中,可以用以反斜杠\
开头的转义序列插入任意的数据。下面的换行、回车和制表符等是常见的ASCII控制代码的转义方式:
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\' 单引号(只用在 '\'' 形式的rune符号面值中)
\" 双引号(只用在 "..." 形式的字符串面值中)
\\ 反斜杠
可以通过十六进制或八进制转义在字符串面值中包含任意的字节。一个十六进制的转义形式是\xhh
。一个八进制转义形式是\ooo
,包含三个八进制的o数字(0到7)
一个原生的字符串面值形式是这样的,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行
func main() {
const GoUsage = `Go is a tool for managing Go source code.
Usage:
go command [arguments]
...`
fmt.Println(GoUsage)
}
例如内置的len
函数可以返回一个字符串中的字节数目
func main() {
s := "hello, world"
fmt.Println(len(s)) // "12"
d := "你好,世界"
fmt.Println(len(d)) // "15"
}
第i个字节并不一定是字符串的第i个字符,因为对于非ASCII字符的UTF8编码会要两个或多个字节
UTF8是一个将Unicode码点编码为字节序列的变长编码。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理
变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多3个字节就能确定当前字符编码的开始字节的位置
如果我们真的关心每个Unicode字符,我们可以使用其它处理方式。考虑下面的这个例子中的字符串,它混合了中西两种字符,为了处理这些真实的字符,我们需要一个UTF8解码器。unicode/utf8包提供了该功能,我们可以这样使用:
func main() {
s := "Hello, 世界"
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}
}
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包
1、strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能
2、bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效
3、strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换
4、unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类
将一个整数转为字符串,一种方法是用fmt.Sprintf
返回一个格式化的字符串;另一个方法是用strconv.Itoa(“整数到ASCII”)
:
func main() {
x := 123
y := fmt.Sprintf("%d", x)
fmt.Println(y, strconv.Itoa(x)) // "123 123"
}
如果要将一个字符串解析为整数,可以使用strconv包的Atoi或ParseInt函数,还有用于解析无符号整数的ParseUint函数:
func main() {
x, _ := strconv.Atoi("123")
y, _ := strconv.ParseInt("123", 10, 64)
fmt.Println(x)
fmt.Println(y)
}
ParseInt函数的第三个参数是用于指定整型数的大小;例如16表示int16,0则表示int。在任何情况下,返回的结果y总是int64类型,你可以通过强制类型转换将它转为更小的整数类型
一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改
所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等
常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof
iota 常量生成器
常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一,例如:
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
周日将对应0,周一为1,如此等等
我们也可以在复杂的常量表达式中使用iota,下面是一个更复杂的例子,每个常量都是1024的幂:
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)