《Go程序设计语言》 学习记录与理解

《Go程序设计语言》 学习记录与理解

Chp2 程序结构

变量

通用声明:

1
2
3
var name type = expression

var str string = "HelloWorld"

短变量声明:

1
2
freq := rand.Float64() * 3.0
t := 0.0

:=代表声明,而=代表赋值

短变量声明最少声明一个新变量,否则代码将无法通过编译

指针

使用&指向另一个变量获取对象的指针,通过*访问指针的内容。

1
2
x := 1
p := &x

new

表达式new(T)创建一个未命名的T类型变量,初始化为T类型的零值,并返回其地址。

1
2
p := new(int)
*p = 2

类型声明

type声明定义一个新的命名类型,它和某个已有类型使用同样的底层类型。

1
type name underlying-type

例如

1
2
type Celsius float64
type Fahrenheit float64

虽然这两种类型的底层类型是相同的,但他们之间并不能相互赋值。同理,也不能比较,计算等操作。下面一段代码在go中无法通过编译

1
2
3
var AbsoluteZeroC Celsius = -273.15
var test Fahrenheit
test = AbsoluteZeroC

但我们可以通过强制类型转换使得赋值成立

1
2
3
var AbsoluteZeroC Celsius = -273.15
var test Fahrenheit
test = Fahrenheit(AbsoluteZeroC)

Chp3 基本数据

整数

有符号整数:int8, int16, int32, int64,分别代表8位,16位,32位,64位

无符号整数:uint8,uint16,uint32,uint64

int和uint大小为32位或64位,视运行平台而定

rune是int32的同义词,常常用于指明一个值是Unicode码点

byte是uint8的同义词,强调一个值是原始数据

浮点数

float32 有效数字约6位

float64 有效数字约15位

复数

complex64 由两个float32组成

complex128 由两个float64组成

根据给定的虚部和实部创建复数,使用real和imag函数提取实部和虚部

1
2
3
4
5
x := complex(1,2)
y := complex(3,4)
fmt.Println(x*y)
fmt.Println(real(x*y))
fmt.Println(imag(x*y))

UTF-8

Go中字符串使用UTF8标准以字节为单位对Unicode码点作变长编码。

对于"Hello, 世界"这个字符串而言,其含有13个字节,本质上是9个Unicode编码。对UTF8的处理需要用到对应的字符串处理函数。

1
2
3
4
5
6
s := "Hello, 世界"
for i := 0 ; i < len(s) ;{
r, size := utf8.DecodeRuneInString(s[i:])
fmt.Printf("%d\t%c\n", i, r)
i += size
}

当[]rune转换作用于UTF-8编码的字符串时,返回该字符串的Unicode码点序列

1
2
3
4
5
6
7
8
s := "你好,世界"
fmt.Printf("% x\n", s)
r := []rune(s)
fmt.Printf("%x \n", r)

输出
e4 bd a0 e5 a5 bd ef bc 8c e4 b8 96 e7 95 8c
[4f60 597d ff0c 4e16 754c]

字节slice

字符串可以和字节slice相互转换

1
2
3
s := "世界"
b := []byte(s)
s2 := string(b)

bytes为高效处理字节slice提供了Buffer类型。Buffer起初为空,大小随着各种类型数据的写入而增长。

1
2
3
4
5
6
7
8
9
10
11
12
func int2Str(values []int) string{
var buf bytes.Buffer
buf.WriteByte('[')
for i , v := range values{
if i > 0 {
buf.WriteString(", ")
}
fmt.Fprintf(&buf, "%d", v)
}
buf.WriteByte(']')
return buf.String()
}

字符串与其他类型的转换

要将其他类型转换为字符串,一种选择是使用fmt.Sprintf,另一种做法是用函数strconv包中的转换函数

常量

1
2
3
4
const (
e = 2.7182818
pi = 3.1415926
)

常量生成器itoa

作用类似于其他语言中的枚举,使用itoa省去繁琐的赋值

1
2
3
4
5
6
7
8
9
10
type weekday int
const (
sunday weekday = iota
monday
tuesday
wednesday
thursday
friday
saturday
)

Sunday的值为0,Monday的值为1,以此类推

1
2
3
4
5
6
7
8
9
10
11
const (
_ = 1 << (10 * iota)
KiB
Mib
GiB
TiB
PiB
EiB
ZiB
YiB
)

在这个例子中常量表示1024的幂

无类型常量

无类型常量的精度比基本类型更高,至少达到256位。在上例中的ZiB与YiB的值过大,用哪种整型都无法存储,但他们都是合法常量且可以用在下列的表达式中

1
fmt.Println(YiB/ZiB)

Chp4复合数据类型

数组

1
2
3
var balance [10] float32
var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
balance := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

如果数组长度不确定,可以在[]中填写…,编译器会根据元素个数自行推断数组的长度:

1
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}

可以显示地传递一个数组的指针给函数,这样在函数内部对数组的任何修改都会反应到原始数组上面。

1
2
3
func zero(ptr* [32]byte){
*ptr = [32]byte{}
}

由于数组的长度是固定的,在大部分情况下我们很少使用数组。

slice

slice初始化

1
test := []int{1,2,3,4}
1
2
3
4
5
var slice1 []type = make([]type, len)
// 也可以简写为
slice1 := make([]type, len)
// 也可以指定容量,capacity为可选参数
make([]T, length, capacity)
1
2
months := [...]string{1:"1", 2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",
8:"8", 9:"9",10:"10",11:"11",12:"12",}

slice操作符s[i:j]创建了一个新的slice,这个新的slice应用了序列s中从i到j-1索引位置的所有元素。

如果slice的应用超过了被应用对象的容量,即cap(s),就会引发错误。如果超出了被引用对象的长度,即len(s),那么slice会比原slice长

1
2
3
4
5
6
7
summer := months[6:9]

// endlessSumeer := summer[:20] 错误
endlessSumeer := summer[:5]
fmt.Println(endlessSumeer)

// 输出 [6 7 8 9 10]

和数组不同的是,数组无法做比较,因此不能用==来测试两个slice是否拥有相同的元素。标准库中提供了byte.Equal用来比较字节slice。若想比较其他类型的slice,我们就得自己写函数来比较。

slice允许和nil做比较,nil为零值。

但是若想检查一个slice是否为空,应使用len(s) == 0 而不是 s == nil,因为s != nil的情况下slice也有可能是空的。

map

内置函数make可以用来创建map

1
ages := make(map[string]int)

也可以直接赋值初始化

1
ages := map[string]int{}
1
2
3
4
ages := map[string]int{
"alice": 31,
"charlie": 34,
}

通过delete函数从字典中根据键移除元素

1
delete(ages, "alice")

无法使用指针获取map元素的地址

结构体

1
2
3
4
5
6
7
8
9
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}

结构体变量和结构体指针都通过.来访问结构体中的元素

没有任何成员变量的结构体称为空结构体,写作struct{}

结构体嵌套

go允许我们定义不带名称的结构体成员,只需要指定类型即可。这种结构体成员称作匿名成员。下列代码中的Circle就被嵌套进了Wheel中。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Point struct {
X, Y int
}

type Circle struct{
Point
Radius int
}

type Wheel struct{
Circle
Spokes int
}

通过结构体嵌套,我们就能够直接访问到需要的变量而不是需要指定一大串中间变量(如果Circle是成员变量,设其名称为c,则访问w的X属性的方法为w.c.center.X)

1
2
var w Wheel
w.X = 8

结构体字面量初始化不能使用

1
w := Wheel{8,8,5,20}

结构体字面量必须遵循形状类型的定义

1
w := Wheel{Circle{Point{8,8}, 5}, 5}

或使用

1
2
3
4
5
6
7
w := Wheel{
Circle: Circle{
Point: Point{X: 8, Y: 8},
Radius: 5,
},
Spokes: 20,
}

JSON

通过json.Marshal将Go的数据结构转换为JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Movie struct{
Title string
Year int `json:"released"`
Color bool `json:"color, omitempty"`
Actors []string
}

var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"first", "second"}},
}

func main(){
data, err := json.Marshal(movies)
// 也可以使用 data, err := json.MarshalIndent(movies, "", " ") 让缩进更好看
if err != nil{
log.Fatalf("JSON failed")
}
fmt.Printf("%s\n", data)
}

得到

1
2
3
4
5
6
7
8
9
10
[
{
"Title": "Casablanca",
"released": 1942,
"Actors": [
"first",
"second"
]
}
]

可以发现成员变量year对应的转换为了released,而Color不见了。这是通过成员标签定义实现的。

在定义结构体时在Color后加入了json:"color,omitempty",标签值的第一部分制定了Go结构体成员对应JSON中字段的名字。第二个字段omitempty表示如果这个成员的值是零值或者为空,则不输出这个成员到JSON中。

marshal的逆操作将JSON字符串解码为Go数据结构,通过json.Unmarshal实现。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const IssuesURL = "https://api.github.com/search/issues"

type IssuesSearchResult struct{
TotalCount int `json:"total_count"`
Items []*Issue
}

type Issue struct{
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string

}

type User struct {
Login string
HTMLURL string `json:"html_url"`
}

func SearchIssues(terms []string)(*IssuesSearchResult, error){
q := url.QueryEscape(strings.Join(terms, " "))
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil{
return nil, err
}


if resp.StatusCode != http.StatusOK{
resp.Body.Close()
return nil, fmt.Errorf("search query failed")
}

var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil{
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}

func main(){
result, err := SearchIssues([]string{"repo:golang/go is:open json decoder"})
if err != nil{
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items{
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}

}

Chp5 函数

1
2
3
func name(parameter-list) (result-list){
body
}

Chp6 方法

方法的声明和普通函数的声明类似,只是在函数名字前面多了一个参数。这个参数把这个方法绑定到这个参数呼对应的类型上。

1
2
3
func (p Point) Distance(q Point) float64{
return math.Hypot(q.X - p.X, q.Y - p.Y)
}

Chp7 接口

接口类型是对其他类型行为的概括与抽象。通过使用接口,我们可以写出更加灵活和通用的函数,这些函数不需要绑定在一个特定的实现类型上。Go接口的独特支出在于它是隐式实现的。不需要声明一个类型实现了哪些接口,只要提供接口所必须的方法即可。

Chp8

Chp9

Chp10

Chp11

Chp12