整型
Go语言同时提供了有符号和无符号类型的整数运算:
1. int8、 int16、 int32和int64四种有符号整数类型,分别对应8、 16、 32、 64bit大小的有符号整数
2. uint8、 uint16、 uint32和uint64四种无符号整数类型
3. int和uint,这两种是对应特定CPU平台机器字大小的有符号和无符号整数;
其中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位。数的值域为-2^{n-1} 到 2^{n-1}-1; 无符号整数的所有bit位都用于表示非负数,值域是0到2^{n}-1。例如: int8类型整数的值域是从-128到127(-2^7, 2^7 - 1)而uint8类型整数的值域是从0到255(0, 2^8 - 1)。
在Go语言中, %取模运算符的符号和被取模数的符号总是一致的, 因此 -5%3 和 -5%-3 结果都是-2。 除法运算符 / 的行为则依赖于操作数是否为全为整数, 比如 5.0/4.0 的结果是1.25, 但是5/4的结果是1, 因为整数除法会向着0方向截断余数。
位运算
& 位运算 AND 都为1才是1
| 位运算 OR 只要有一个是1 那么就是1
^ 位运算 XOR 有一个为1则就是1;两个1 或者两个0则全是 0
&^ 位清空 (AND NOT) a &^ b = (a^b) & b
<< 左移 可以看做是 乘以2的几次方
>> 右移 可以看做是 除以2的几次方
func main() {
var x uint8 = 1 << 1 | 1 << 5 //00100010
var y uint8 = 1 << 1 | 1 << 2 //00000110
fmt.Printf("%08b \n", x)
fmt.Printf("%08b \n", y)
fmt.Printf("%08b \n", x&y) //00000010
fmt.Printf("%08b \n", x|y) //00100110
fmt.Printf("%08b \n", x^y) //00100100
fmt.Printf("%08b \n", x&^y) //00100000
}
在 x<<n 和 x>>n 移位运算中, 决定了移位操作bit数部分必须是无符号数; 算术上, 一个 x<<n 左移运算等价于乘以2^n, 一个 x>>n 右移运算等价于除以2^n。
func main() {
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
}
请注意fmt的两个使用技巧:
1. %之后的 [1] 副词告诉Printf函数再次使用第一个操作数
2. %后的 # 副词告诉Printf在用%o、%x或%X输出时生成0、 0x或0X前缀
浮点数
Go语言提供了两种精度的浮点数, float32和float64。一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度; 通常应该优先使用float64类型,因为float32类型的累计计算误差很容易扩散。
很小或很大的数最好用科学计数法书写, 通过e或E来指定指数部分:
const Avogadro = 6.02214129e23 // 阿伏伽德罗常数
const Planck = 6.62606957e-34 // 普朗克常数
func main() {
fmt.Printf("%f\n", Avogadro)
fmt.Printf("%f\n", Planck)
}
正无穷大和负无穷大, 分别用于表示太大溢出的数字和除零的结果:
func main() {
var z float64
fmt.Println(1/z) //+Inf
fmt.Println(-1/z) //-Inf
}
复数
Go语言提供了两种精度的复数类型: complex64和complex128, 分别对应float32和float64两种浮点数精度。
布尔型
一个布尔类型的值只有两种: true和false。
布尔值可以和&&( AND) 和||( OR)操作符结合,并且有短路行为:如果运算符左边值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。
因为 && 的优先级比 || 高( 助记: && 对应逻辑乘法, || 对应逻辑加法,乘法比加法优先级要高),下面形式的布尔表达式是不需要加小括弧:
if 'a' <= c && c <= 'z' ||
'A' <= c && c <= 'Z' ||
'0' <= c && c <= '9' {
// ...ASCII letter or digit...
}
字符串
一个字符串是一个不可改变的字节序列。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列内置。
len函数可以返回一个字符串中的字节数目,索引操作s[i]返回第i个字节的字节值。子字符串操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节( 并不包含j本身) 生成一个新字符串。 生成的新字符串将包含j-i个字节。
Unicode、UTF-8
它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应GO语言中的rune(int32等价)整数类型。
UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码使用1到4个字节来表示每个Unicode码点。 ASCII部分字符只使用1个字节, 常用字符部分使用2或3个字节表示。
每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符。如果第一个字节的高端bit为0, 则表示对应7bit的ASCII字符;如果第一个字节的高端bit是110, 则说明需要2个字节;后续的每个高端bit都以10开头。
0xxxxxxx runes 0-127 (ASCII)
110xxxxx 10xxxxxx 128-2047
1110xxxx 10xxxxxx 10xxxxxx 2048-65535
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff
GO语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符,有两种形式:
1. \uhhhh对应16bit的码点值
2. \Uhhhhhhhh对应32bit的码点值
其中h是一个十六进制的某个数字,一般很少需要使用32bit码点值。下面的字符串面值表示相同值:
"世界"
"\xe4\xb8\x96\xe7\x95\x8c"
"\u4e16\u754c"
"\U00004e16\U0000754c"
处理中文+英文的字符串
func main() {
s := "hello, 世界"
fmt.Println(len(s)) //13
fmt.Println(utf8.RuneCountInString(s)) //9
}
字符串包含13个字节, 以UTF8形式编码, 但是只对应9个Unicode字符。为了处理这些真实的字符, 我们需要一个UTF8解码器:
func main() {
s := "hello, 世界"
for i :=0; i< len(s);{
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%c \t", r)
i += size
}
}
每一次调用DecodeRuneInString函数都返回一个r和size, r对应字符本身, siez是r采用UTF8编码后的编码字节长度。
下面是对上面代码的优化, Go语言的range循环在处理字符串的时候, 会自动隐式解码UTF8字符串:
func main() {
s := "hello, 世界"
for _, r := range s{
fmt.Printf("%c \t", r)
}
}
rune类型
UTF8字符串作为交换格式是非常方便的, 但是在程序内部采用rune序列可能更方便,因为rune大小一致, 支持数组索引和方便切割。
将[]rune类型转换应用到UTF8编码的字符串, 将返回字符串编码的Unicode码点序列。
func main() {
s := "你好, 世界"
r := []rune(s)
fmt.Printf("%x \n", r) //[4f60 597d 2c 20 4e16 754c]
}
如果是将一个[]rune类型的Unicode字符slice或数组转为string, 则对它们进行UTF8编码:
fmt.Println(string(r))
将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串:
fmt.Println(string(65)) // "A", not "65"
fmt.Println(string(0x4eac)) // "京"
常量
常量表达式的值在编译期计算, 而不是在运行期。
批量声明多个常量:
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
一个常量的声明也可以包含一个类型和一个值, 但是如果没有显式指明类型, 那么将从右边的表达式推断类型。
const noDelay time.Duration = 0
如果是批量声明的常量, 除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式, 对应的常量类型也一样的。
const (
a = 1
b
c = 2
d
)
iota 常量生成器
它用于生成一组以相似规则初始化的常量, 但是不用每行都写一遍初始化表达式。 在一个const声明语句中, 在第一个声明的常量所在的行,
iota将会被置为0, 然后在每一个有常量声明的行加一。
下面是来自time包的例子:
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Println(Sunday, Monday, Tuesday, Saturday)
}
它首先定义了一个Weekday命名类型, 然后为一周的每天定义了一个常量, 从周日0开始。这种类型一般被称为枚举类型。
下面是一个更复杂的例子, 每个常量都是1024的幂:
const (
_ = 1 << (10 * iota)
KiB
MiB
GiB
TiB
)
func main() {
fmt.Println(KiB, MiB, GiB, TiB)
}