原始数据类型
整数
有符号整数int8、int16、int32、int64,无符号整数uint8、uint16、uint32、uint64,还有平台依赖的 int 和 uint (可能是32位,也可能是64位),使用平台依赖的整数的理由是效率最高(因为等于机器字长)。
rune 表示 Unicode码点(code point),其实就是 int32. 而 byte 就是 uint8.
还有大小不明确的无符号整数 uintptr,用来底层编程和C程序库交互,差不多就是 void *
golang的位运算和C略有不同,golang没有取反运算符~,而是用异或运算符^作为单元运算符表示取反(可以理解为 ^0b10100101 = 0b11111111^0b10100101),另外,golang多一个位清空运算符(AND NOT)&^,^和&^的差别是,对于a^b和a&^b,前者当b中的位为1时,将a中对应位取反,后者将a中对应位取0,对于b中位为0,两者相同(即不变)
var a, b uint8
a, b = 0b10101101, 0b00001010
fmt.Printf("%08b\n", a^b)
fmt.Printf("%08b\n", a&^b)
fmt.Printf("%08b\n", ^a)
fmt.Printf("%08b\n", ^b)
输出
10100111
10100101
01010010
11110101
无符号整数通常只会用于特定领域或场合(如位运算、加密解密、压缩等),对于字符串长度、数组长度等,通常都是用有符号整数,因为对于无符号整数 i,执行 i-- 操作是不可能让 i >= 0 为false的,这在循环中会带来严重错误
浮点
float32、float64,float32的有效数字大约6位(例如 float32(1 << 24) == float32(1 << 24 +1)),所以绝大多数情况应该用float64 (有效数字15位)
复数
和C语言不同,golang内在支持复数:complex64 和 complex128,分别由 float32 和 float64 构成。构造一个复数需要用内置函数 complex(x, y),内置函数 real(z)和imag(z)则提取复数z的实部和虚部,对于字面复数值,也可以直接自然写 1+2i 、3-4i 这样的值。math/cmplx包提供了各种复数运算。
布尔型
布尔值为 true 或 false,golang并不能自动把true和false隐式转换成1和0,也不能显式强制转换,确实需要时,只能编写函数 (以下代码,注释掉方式的编译通不过)
package main
func main() {
x := true
k := 1
//n := int(x)
n := btoi(x)
//y := bool(k)
y := itob(k)
println(n)
println(y)
}
func btoi(b bool) int {
if b {
return 1
}
return 0
}
func itob(i int) bool { return i != 0 }
字符串
golang字符串和C风格SZ字符串不同,golang字符串就是不可变的字节序列,它是可以包含0值字节的(更像包含长度信息的Pascal字符串)!
通常,golang文本字符串被解读成按UTF-8编码的Unicode码点(一个码点对应一个文字符号)序列,内置 len(s) 函数返回的是字节数而不是文字符号数目,下标访问操作 s[i] 则取得第 i 个字符(对于非ASCII字符,第i个字符并非第i个字节)
golang字符串是支持切片操作的,s[i:j] 表示 [i, j) 下标范围字符构成的子串,起始位置或终止位置不指定时,默认对应0 和 len(s),例如 s[:5]、s[7:]、s[:]。支持切片可以节省内存。
golang字符串用 + 实现串接操作,用 == 、< 等实现比较操作(按字节比较,大小按字典序),所以,golang需要类似strlen()的len(),却不需要strcat()、strcmp()那样的函数,用C++理解就是内在已经帮你重载好了运算符 + == < 之类。
C风格SZ字符串仅仅是字符数组,里面的字符是可以修改的(mutable),而golang字符串是无法改变的(immutable)。
s := "left foot"
t := s
s += ", right foot"
上面的操作中,并非字符串 left foot 被修改了,而是字符串left foot 和 , right foot 通过 + 操作串接产生一个新的字符串,并将新字符串赋值给s,当然,新的字符串和原来的字符串是可以部分共用底层内存的(t和s复用前面这些字节的内存)。golang字符串的特性使得字符串复制和切片的开销非常小。
golang源码总是utf-8编码,golang字符串也按UTF-8解读,我们可以直接将Unicode码点写入字符串字面量,如 "Hello, 世界",还可以用转义符 \ 将转义序列写入字符串字面量,\n 、\t、 \"和\\都和一般理解相同,\xhh 和 \ooo 形式可以插入数值指定的Unicode码点。如果希望非转义的字符串字面量,可以用反引号。使用反引号还可以将字符串写成多行(唯一的处理是回车被删除,换行则保留,从而所有平台都一致)。反引号字符串最常见的场合是正则表达式和多行文本(提示信息、HTML模板、JSON等)
golang中,一个Unicode码点,就是一个int32值,称之为 rune,即 rune 为 int32 别名。
UTF-8实现了对Unicode字符的变长编码:
0xxxxxxx 兼容 ASCII,1比特开始的必然不是 ASCII 字符,用 10xxxxxx 字节表示一种后续字节,而 110xxxxx 、1110xxxx、11110xxx 字节表示起始字节,分别对应后面还有1个字节(共2个字节)、后面还有2个字节(共3个字节)、后面还有3个字节(共4个字节)。这些字节区分用了前缀编码方式。
各种复杂的字符串(字符):
"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"
'世' '\u4e16' '\U00004e16'
利用golang字符串切片操作、字符串比较运算符==和UTF-8特性,判断一个字符串是否是另一个的子串非常方便:
package main
func main() {
s1 := "abcdefgh"
s2 := "cde"
println(Contains(s1, s2)) // true
}
func HasPrefix(s, prefix string) bool {
return len(s) >= len(prefix) && s[:len(prefix)] == prefix
}
func Contains(s, substr string) bool {
for i := 0; i < len(s); i++ {
if HasPrefix(s[i:], substr) {
return true
}
}
return false
}
码点和字符例子
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Hello, 世界"
fmt.Println(len(s)) // 13 个字节
fmt.Println(utf8.RuneCountInString(s)) // 9 个字符(码点)
println()
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\t%q\t%x\n", i, r, r, r)
i += size
}
println()
for i, r := range "Hello, 世界" {
fmt.Printf("%d\t%c\t%q\t%x\n", i, r, r, r)
}
}
上面程序后面两段功能一样,码点是可以被遍历的,所以输出为:
13
9
0 H 'H' 48
1 e 'e' 65
2 l 'l' 6c
3 l 'l' 6c
4 o 'o' 6f
5 , ',' 2c
6 ' ' 20
7 世 '世' 4e16
10 界 '界' 754c
0 H 'H' 48
1 e 'e' 65
2 l 'l' 6c
3 l 'l' 6c
4 o 'o' 6f
5 , ',' 2c
6 ' ' 20
7 世 '世' 4e16
10 界 '界' 754c
注意:'世'的码点为4e16,即 0100 1110 0001 0110,用UTF-8存放时用了3个字节,从而起始半字节为 1110,即“模板”为1110 xxxx 10xx xxxx 10xx xxxx,将对应比特填入,即得 1110 0100 1011 1000 1001 0110
字符串和字节slice可以显式转换,转换时会产生副本
s := "abc"
b := []byte(s)
s2 := string(b)
字符串和数字相互转换可以直接使用strconv包
常量
常量声明是直观的。C语言中的枚举类型可以用 iota 常量生成器实现:iota从0开始
type Weekday int
const (
Sunday Weekday = iota // 0
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday // 6
)
type Flags uint
const (
FlagUp Flags = 1 << iota // 1 << 0
FlagBroadcast // 1 << 1
FlagLoopback
FlagPointToPoint
FlagMulticast // 16
)
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1024*1024
GiB
TiB
PiB
EiB
ZiB
YiB
)
golang 常量可以是无类型的或者叫字面的(例如上面最后一个,没有一个整型能放下后面那种大数,但 YiB/ZiB 这样的操作的确可以进行)