零基础学习Go
基础语法
变量定义
1 | var a int // 变量名在前 类型在后 |
变量类型
变量类型
变量类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 代表一个Unicode码
float32 float64
complex64 complex128原生支持复数 i
GO强制类型转换
1
2
3
4
5
6func triangle() {
var a, b int = 3, 4
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
fmt.Println(c)
}
常量
- const数值可作为各种类型使用 (没有规定类型)
- 编译器可推算变量类型
- iota 自增值
1 | const ( |
条件语句
if语句
- if 条件没有括号
- if 条件里可以赋值
- if 条件可以多条语句用’;’分隔开
- if 条件里赋值的变量作用域就在这个if语句块里
1
2
3
4
5
6const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n",contents)
}
switch 语句
- switch 会自动break, 除非使用fallthrough
- switch 后面没有表达式
1
2
3
4
5
6
7
8
9
10
11func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprint(
"wrong score: %d", score)) // 打印报错信息
case score < 60:
g = "F"
}
return g
}
循环
- for
- for 的条件里不需要括号
- for 的条件里可以省略初始条件、结束条件、递增表达式
1
2
3
4
5
6
7
8
9
10func converToBin(n int) string {
result := ""
for ; n > 0; n/=2 {
lsb := n%2
result = strconv.Itoa(lsb) + result
}
return result
}
函数
- func
- 返回值类型写在最后面
- 可返回多个值
- 函数可作为参数
- 没有默认参数,可选参数
- 可变参数
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80package main
import (
"fmt"
"math"
"reflect"
"runtime"
)
func eval(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil
case "-":
return a - b, nil
case "*":
return a * b, nil
case "/":
q, _ := div(a, b)
return q, nil
default:
return 0, fmt.Errorf(
"unsupported operation: %s", op)
}
}
func div(a, b int) (q, r int) {
return a / b, a % b
}
// 函数作为参数
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args "+
"(%d, %d)\n", opName, a, b)
return op(a, b)
}
// 可变参数列表
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
// 交换两个传入的变量
// 返回值并重新赋值(推荐)
func swap(a, b int) (int, int) {
return b, a
}
// 利用指针传值
func swapT(a,b *int) {
*b, *a = *a, *b
}
func main() {
fmt.Println("Error handling")
if result, err := eval(3, 4, "x"); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println(result)
}
q, r := div(13, 3)
fmt.Printf("13 div 3 is %d mod %d\n", q, r)
fmt.Println("pow(3, 4) is:", apply(
func(a int, b int) int {
return int(math.Pow(
float64(a), float64(b)))
}, 3, 4))
fmt.Println("1+2+...+5 =", sum(1, 2, 3, 4, 5))
a, b := 3, 4
a, b = swap(a, b)
fmt.Println("a, b after swap is:", a, b)
}
指针
指针
- 不能运算
- 参数传递
- 值传递(只是拷贝值不改变原始变量)
- 引用传递
&a是指向a的地址1
2
3
4
5
6
7
8
9
10
11
12
13
14void pass_by_val(int a) {
a++
}
void pass_by_ref(int& a) {
a++
}
int main () {
int a = 3
pass_by_val(a) // 3 值传递
pass_by_ref(a) // 4 引用传递
}
& 和 * 的区别
- & 是取地址符号 , 即取得某个变量的地址 , 如 ; &a
- 是指针运算符 , 可以表示一个变量是*指针类型** , 也可以表示一个指针变量所指向的存储单元 , 也就是这个地址所存储的值 .
内建容器
数组、切片、容器
数组
- 数量写在类型前
- 数组是值类型
- [10]int 和 [20]int 是不同的类型
- go 语言中不直接使用数组而是使用切片
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
53package main
import "fmt"
func printArry(arr [5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
// 指针传递
func printArrayPoint(arr *[5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int
arr2 := [3]int{1, 2, 4}
arr3 := [...]int{2, 4, 5, 6, 7 } // 使用... 让编辑器计算个数
var grid [4][5]int // 四行五列
fmt.Println(arr1, arr2, arr3, grid)
for i := 0; i < len(arr2); i ++ {
fmt.Println(arr2[i])
}
// 使用range 关键字
for i := range arr2 {
fmt.Println(arr2[i])
}
for i, v := range arr2 {
fmt.Println(i, v)
}
// 使用_占位符 省略变量
for _, v := range arr2 {
fmt.Println(v)
}
fmt.Println("printArry: arr3")
printArry(arr3)
fmt.Println(arr3) // arr3[0] 没有改变 [2 4 5 6 7]
fmt.Println("printArry: arr3")
printArrayPoint(&arr3)
fmt.Println(arr3) // arr3[0] 改变 [100 4 5 6 7]
}
切片
slice(切片)
- slice 本身没有数据,是对底层array的一个view
- 可以进行Reslice
- slice扩展
可以向前扩展,不可以向后扩展 - slice实现
- ptr: slice取值的开端(ptr(s))
- len: slice长度(len(s))
- cap: 数组(cap(s))
- 向slice添加元素
- 添加元素如果超过cap, 系统会重新分配更大的底层数组(go语言采用垃圾回收机制,原来的数组如果不再被使用则会被垃圾回收)
- 由于值传递的关系,必须接收append的返回值(s = append(s, val))
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
40package main
import "fmt"
func updateSlice(s []int) {
s[0] = 100
}
func main() {
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
s := arr[2:6]
fmt.Println("arr[2:6]=", s) // [3 4 5 6]
fmt.Println("arr[:6]=", arr[:6]) // [1 2 3 4 5 6]
s1 := arr[2:]
fmt.Println("arr[2:]=", arr[2:]) // [3 4 5 6 7 8 9]
s2 := arr[:]
fmt.Println("arr[:]=", arr[:]) // [1 2 3 4 5 6 7 8 9]
updateSlice(s1)
fmt.Println(s1) // [100 4 5 6 7 8 9]
fmt.Println(arr)// 改变了arr [1 2 100 4 5 6 7 8 9]
updateSlice(s2)
fmt.Println(s2)//[100 2 100 4 5 6 7 8 9]
fmt.Println("Reslice")
s2 = s2[2:] // [100 4 5 6 7 8 9]
fmt.Println(s2)
s2 = s2[:5] // [100 4 5 6 7]
fmt.Println(s2)
s3 := append(s2, 10)
s4 := append(s3, 11)
s5 := append(s4, 12)
fmt.Println("s3, s4, s5 =", s3, s4, s5) // s3, s4, s5 = [100 4 5 6 7 10] [100 4 5 6 7 10 11] [100 4 5 6 7 10 11 12]
// s5 no longer view arr
fmt.Println("arr=", arr) // arr= [100 2 100 4 5 6 7 10 11]
}
切片操作
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
49package main
import "fmt"
func printSlice(s []int) {
fmt.Printf("%v, len=%d, cap=%d\n",
s, len(s), cap(s))
}
func sliceOps() {
fmt.Println("Creating slice")
var s []int // Zero value for slice is nil
for i := 0; i < 100; i++ {
printSlice(s)
s = append(s, 2*i+1)
}
fmt.Println(s)
s1 := []int{2, 4, 6, 8}
printSlice(s1)
s2 := make([]int, 16)
s3 := make([]int, 10, 32)
printSlice(s2)
printSlice(s3)
fmt.Println("Copying slice")
copy(s2, s1)
printSlice(s2)
fmt.Println("Deleting elements from slice")
s2 = append(s2[:3], s2[4:]...)
printSlice(s2)
fmt.Println("Popping from front")
front := s2[0]
s2 = s2[1:]
fmt.Println(front)
printSlice(s2)
fmt.Println("Popping from back")
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]
fmt.Println(tail)
printSlice(s2)
}
Map
Map的基础
Map的定义
- map[k]v, map[k1]map[k2]v
- make(map(k)v)
- 获取元素m[k]
- 当key 不存在时,获取Value类型的初始值
- 用value, ok := m[key]来判断是否存在key
1
2
3
4
5
6
7
8
9
10
11
12
13m := map[string]string{
"name": "ccmouse",
"course": "golang",
"site": "imooc",
"quality": "notbad",
}
m2 := make(map[string]int) // m2 == empty map
var m3 map[string]int // m3 == nil
fmt.Println("m, m2, m3:")
fmt.Println(m, m2, m3) // map[course:golang name:ccmouse quality:notbad site:imooc] map[] map[]
Map的遍历
- 使用range 遍历
- 不保证遍历顺序,如需顺序,需手动对key 排序
- 用len获取元素个数
1
2
3
4fmt.Println("Traversing map m")
for k, v := range m {
fmt.Println(k, v)
}
Map 操作
- map使用hash表,必须可以比较相等
- 除了slice、map、function的内建类型都可以作为key
- Struct类型不包含上述字段,也可以作为key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18fmt.Println("Getting values")
courseName := m["course"]
fmt.Println(`m["course"] =`, courseName)
if causeName, ok := m["cause"]; ok {
fmt.Println(causeName)
} else {
fmt.Println("key 'cause' does not exist")
}
fmt.Println("Deleting values")
name, ok := m["name"]
fmt.Printf("m[%q] before delete: %q, %v\n",
"name", name, ok)
delete(m, "name")
name, ok = m["name"]
fmt.Printf("m[%q] after delete: %q, %v\n",
"name", name, ok)
Map的例题
- 寻找最长不含有重复字符的子串
1 | // 不能处理中文 只能处理ASCII码 |
字符和字符串
- 使用range 遍历 pos, rune(位置,值)对
- 使用 utf8.RuneCountInString()获得字符数量
- 使用len 获取字节长度
- 使用[]byte获得字节
- 字符串其他操作(strings包)
- Fields、Split、Join
Fields 以连续的空白字符为分隔符,将 s 切分成多个子串,结果中不包含空白字符本身 - Contains、Index
- ToLower、ToUpper
- Trim、TrimRight、TrimLeft
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
38package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Yes我爱慕课网!" // UTF-8
fmt.Println(s)
for _, b := range []byte(s) {
fmt.Printf("%X ", b)
}
fmt.Println()
for i, ch := range s { // ch is a rune
fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()
fmt.Println("Rune count:",
utf8.RuneCountInString(s))
bytes := []byte(s)
fmt.Println(bytes)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("%c ", ch)
}
fmt.Println()
for i, ch := range []rune(s) {
fmt.Printf("(%d %c) ", i, ch)
}
fmt.Println()
}
- Fields、Split、Join
面向对象
面向对象
- go 仅支持封装,不支持继承和多态
- go 语言没有class, 只有struct
结构
结构的创建
- 使用自定义工厂函数
- 注意返回了局部变量的地址
1 | type treeNode struct { |
使用指针作为方法接收者
只有使用指针才能改变结构内容
1
2
3func (node *treeNode) setValue(value int) {
node.value = value
}nil指针也可以调用此方法
值接收者VS指针接收者
- 要改变内容必须使用指针接收者
- 结构过大也考虑使用指针接收者
- 建议一致性:如果有指针接收者,最好都是指针接收者
- 值接收者是Go 语言特有
- 值/指针接收者均可接收值/指针
封装
- 名字一般使用CamelCase(单词首字母大写)
- 首字母大写: public (针对包而言)
- 首字母小写: private (针对包而言)
包
- 每个目录只有一个包
- main包 包含可执行入口(main 函数)
- 为结构定义的方法必须放在同一个包内,但是可以是不同的文件
如何扩展系统类型或者别人的类型
- 定义别名
例子文件夹queue - 使用组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 使用组合方式
type myTreeNode struct {
node *tree.TreeNode // 不一定是指针也可以是具体的类型
}
func (myNode *myTreeNode) postOrder() {
if myNode == nil || myNode.node == nil {
return
}
left := myTreeNode{myNode.node.Left}
left.postOrder()
right := myTreeNode{myNode.node.Right}
right.postOrder()
myNode.node.Print()
}
// 具体的调用方式
myRoot := myTreeNode{&root}
myRoot .postOrder()
GOPATH 环境变量
GOPATH
- 默认在~/go(unix,Linx), %USERPROFILE%\go(windows)
- 建议就算使用默认GOPATH也进行相对应的设置
- 官方推荐:所有项目和第三方库都放在同一个GOPATH下
- 也可以将每个项目放置在不同的GOPATH下,那么需要给GOPATH 环境变量设置多个值,Go语言编译的时候会找到不同GOPATH中自己所要依赖的包。
- pwd:查看当前所在目录
- echo $GOPATH:查看当前项目的GOPATH
- cat ~/.bash_profile: 查看环境变量
go get 获取第三方库
- go get 安装gopm
- 使用gopm 获取无法下载的包
接口
duck typing
duck typing基本概念
- “像鸭子走路,像鸭子叫,长得像鸭子,那么就是鸭子。”
- 描述事物的外部行为而非内部结构
- 严格说go属于结构化类型系统,类似duck typing(看法不同)
其他语言中的duck typing
接口的定义
在上面链接的例子里:
使用者: download (使用者规定接口必须有什么方法)
实现者:fetcher (实现者不需要具体实现某个接口,只需要实现接口里面的方法即可)
接口由使用者定义
- 接口的实现是隐式的
- 接口只需要实现里面的方法
查看接口变量
- interface{} : 表示任何类型
- Type Assertion
- Type Switch
接口组合
常用系统接口
- Stringer
相当于toString() - Reader
- Writer
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144// main文件
package main
import (
"fmt"
"lichuanyang65/learngo/retriever/mock"
real2 "lichuanyang65/learngo/retriever/real"
"time"
)
type Retriever interface {
Get(url string) string
}
type Poster interface {
Post(url string, form map[string]string) string
}
// 接口的组合
type RetrieverPoster interface {
Retriever
Poster
// 还可以定义其他的方法
}
const url = "http://www.baidu.com"
func download (r Retriever) string {
return r.Get(url)
}
func post (poster Poster) string {
return poster.Post(url, map[string]string{
"keyWord": "test",
})
}
// 使用者组合,实现者不需要研究接口如何定义
// 对使用者session 来说实现者RetrieverPoster里面的两个接口如何实现不重要,只需要实现Post 和Get 函数即可
// 而实现者RetrieverPoster 并不需要声明它实现了哪些接口,只要它有相关接口的所定义的方法
func session (s RetrieverPoster) string {
s.Post(url, map[string]string{
"keyWord": "test",
"contents": "another fake test",
})
return s.Get(url)
}
func main() {
var r Retriever
fmt.Printf("%T %v\n", r)
r = mock.Retriever{"this is a fake test"} // 真实的值 值拷贝
// 如何区分不能类型的r
inspect(r)
r = &real2.Retriever{
"Mozill/5.0",
time.Minute,
} // 指针*real.Retriever &{Mozill/5.0 1m0s}
// 因此接口不会再次使用指针,接口内部可能存在已经使用指针的情况
inspect(r)
// Type assertion
// 使用.(具体的类型) 可以取得interface里面的这个类型
if mockRetriever, ok := r.(mock.Retriever); ok {
fmt.Println(mockRetriever.Contents)
} else {
fmt.Println("not a mock rettriever")
}
realRetriever := r.(*real2.Retriever)
fmt.Println(realRetriever.Timeout)
// 接口组合
// 同时实现了Get Post
retrieve := mock.Retriever{url}
s := RetrieverPoster(&retrieve)
fmt.Println(session(s))
//fmt.Println(download(r))
}
// 区分r内部的类型
func inspect(r Retriever) {
switch v := r.(type) {
case mock.Retriever:
fmt.Println("Contents:", v.Contents)
fmt.Printf("%T %v\n", r, r)
case *real2.Retriever:
fmt.Println("UserAgent", v.UserAgent)
fmt.Printf("%T %v\n", r, r)
}
}
// mock文件
// 这里有些不规范,在一个包里如果使用指针则应该都使用指针
package mock
// 这是一个假的 retriever 类似duck typing
type Retriever struct {
Contents string
}
// 模拟实现main 中的retriever 接口的mGet 方法
func (r Retriever) Get(url string) string {
return r.Contents
}
// 值接收者不会修改值
func (r *Retriever) Post(url string, form map[string]string) string {
r.Contents = form["contents"]
return r.Contents
}
// real
package real
import (
"net/http"
"net/http/httputil"
"time"
)
type Retriever struct {
UserAgent string
Timeout time.Duration
}
func (r Retriever) Get(url string) string {
resp, err := http.Get(url)
if err != nil {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if err != nil {
panic(err)
}
return string(result)
}
- Stringer
函数
闭包
!(闭包)[闭包. png]