#《Go 语音圣经(中文版)》

#基础数据类型

#复数

Go语言提供了两种精度的复数类型:complex64complex128,分别对应float32float64两种浮点数精度。

内置的complex函数用于构建复数,内建的realimag函数分别返回复数的实部和虚部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"math/cmplx"
)

func main() {
var x complex128 = complex(1, 2) // 等价于: x := 1 + 2i
var y complex128 = complex(3, 4) // 等价于: y := 3 + 4i
fmt.Println(x * y)
fmt.Println(real(x * y))
fmt.Println(imag(x * y))
fmt.Println(1i * 1i)
fmt.Println(cmplx.Sqrt(-1))
}
1
2
3
4
5
6
[12:26:47] onns@Onns ~/Documents/code/go/go-bible/ch3 $ go run 3.7.go
(-5+10i)
-5
10
(-1+0i)
(0+1i)

复数也可以用==!=进行相等比较,只有两个复数的实部和虚部都相等的时候它们才是相等的。[1]

math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数。

#布尔型

布尔值并不会隐式转换为数字值01,必须使用一个显式的if语句辅助转换。

#字符串

一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte0,但是通常是用来包含人类可读的文本。

文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列。

内置的len函数可以返回一个字符串中的字节数目,索引操作s[i]返回第i个字节的字节值。

字符串可以用==<进行比较,比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺序。

不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。

一个原生的字符串面值形式是`…`,使用反引号代替双引号。

Unicode码点对应Go语言中的rune整数类型(runeint32等价类型)。

UTF8编码使用14个字节来表示每个Unicode码点:

1
2
3
4
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
"unicode/utf8"
)

func main() {
s := "Hello, 世界"
fmt.Println(len(s))
fmt.Println(utf8.RuneCountInString(s))
for i := 0; i < len(s); {
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}
for i, r := range "Hello, 世界" {
fmt.Printf("%d\t%q\t%d\n", i, r, r)
}
n := 0
for _, _ = range s {
n++
}
fmt.Println(n)
n = 0
for range s {
n++
}
fmt.Println(n)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ go run 3.8.go
13
9
0 H
1 e
2 l
3 l
4 o
5 ,
6
7 世
10 界
0 'H' 72
1 'e' 101
2 'l' 108
3 'l' 108
4 'o' 111
5 ',' 44
6 ' ' 32
7 '世' 19990
10 '界' 30028
9
9

\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字。

Go语言的range循环在处理字符串的时候,会自动隐式解码UTF8字符串。

UTF8字符解码如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符\uFFFD

UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// -------------------------------------------
// Created By : Onns onns@onns.xyz
// File Name : 3.9.go
// Purpose :
// Creation Date : 2021-04-14 15:24:14
// Last Modified : 2021-04-14 15:24:18
// -------------------------------------------

package main

import (
"fmt"
)

func main() {
s := "プログラム"
fmt.Printf("% x\n", s)
r := []rune(s)
fmt.Printf("%x\n", r)
fmt.Println(string(r))

s = "program プログラム"
fmt.Printf("% x\n", s)
r = []rune(s)
fmt.Printf("%x\n", r)
fmt.Println(string(r))

fmt.Println(string(65))
fmt.Println(string(0x4eac))
fmt.Println(string(1234567))
}
1
2
3
4
5
6
7
8
9
10
$ go run 3.9.go
e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0
[30d7 30ed 30b0 30e9 30e0]
プログラム
70 72 6f 67 72 61 6d 20 e3 83 97 e3 83 ad e3 82 b0 e3 83 a9 e3 83 a0
[70 72 6f 67 72 61 6d 20 30d7 30ed 30b0 30e9 30e0]
program プログラム
A


% x参数用于在每个十六进制数字前插入一个空格。

将一个整数转型为字符串意思是生成以只包含对应Unicode码点字符的UTF8字符串,如果对应码点的字符是无效的,则用\uFFFD无效字符作为替换。

#字符串和数字的转换

  1. fmt.Sprintf返回一个格式化的字符串。
  2. strconv.Itoa()将数字转为字符串。

fmt.Printf函数的%b%d%o%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含附加额外信息的时候。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// -------------------------------------------
// Created By : Onns onns@onns.xyz
// File Name : 3.10.go
// Purpose :
// Creation Date : 2021-04-14 15:40:40
// Last Modified : 2021-04-14 15:48:43
// -------------------------------------------

package main

import (
"fmt"
"strconv"
)

func main() {
x := 123
y := fmt.Sprintf("%d", x)
z := strconv.Itoa(x)
fmt.Println(y, z)

fmt.Println(strconv.FormatInt(int64(x), 2))

a, _ := strconv.Atoi("123")
fmt.Printf("a type: %T\n", a)
b, _ := strconv.ParseInt("123", 10, 64)
fmt.Printf("b type: %T\n", b)
}
1
2
3
4
5
$ go run 3.10.go
123 123
1111011
a type: int
b type: int64

ParseInt函数的第三个参数是用于指定整型数的大小,例如16表示int160则表示int。在任何情况下,返回的结果总是int64类型,可以通过强制类型转换将它转为更小的整数类型。

#常量

1
2
3
4
5
6
const pi = 3.14159
// 可以批量声明多个常量
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)

所有常量的运算都可以在编译期完成。

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// -------------------------------------------
// Created By : Onns onns@onns.xyz
// File Name : 3.11.go
// Purpose :
// Creation Date : 2021-04-14 15:56:50
// Last Modified : 2021-04-14 15:57:37
// -------------------------------------------

package main

import (
"fmt"
)

func main() {
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d)
}
1
2
$ go run 3.11.go
1 1 2 2

常量声明可以使用iota常量生成器初始化,在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// -------------------------------------------
// Created By : Onns onns@onns.xyz
// File Name : 3.12.go
// Purpose :
// Creation Date : 2021-04-14 16:00:53
// Last Modified : 2021-04-14 16:01:55
// -------------------------------------------

package main

import (
"fmt"
)

func main() {

type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
fmt.Println(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)

}
1
2
$ go run 3.12.go
0 1 2 3 4 5 6

有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。

编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有256bit的运算精度。

通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。[2]

只有常量可以是无类型的。


  1. 浮点数的相等比较是危险的,需要特别小心处理精度问题。 ↩︎

  2. 这里我是懵逼的,有机会再看看。 ↩︎