02-Go语言基础

Introduction

Golang 只有25个关键词:

1
2
3
4
5
break    default      func    interface    select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

Hello, Go

1
2
3
4
5
6
7
packgae main

import "fmt"

func main() {
fmt.Println("Hello, world or 你好,世界")
}
  • Go 是通过 package 俩组织的,每个 Go 文件以package <pkgName开头,除了main包外,其余所有包都会被编译成*.a文件($GOPATH/pkg/$GOOS_$GOARCH),main包会被编译成可执行文件,并且其中的main函数被入口函数。
  • Go是天生支持UTF-8的

Go基础

变量

  • var
  • :=
  • _
  • 已声明,但是未使用的变量会在编译时报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基本格式
var variableName type
# 定义多个变量
var i1, i2, i3 int
# 初始化
var i1 int = 1
var i1, i2, i3 int = 1, 2, 3
# 忽略类型
var i1, i2, i3 = 1, 2, 3
# 使用`:=`
# `:=` 被称为简短声明,其只能用于函数内部,全局变量需要`var`关键字来声明
# `:=` 是声明变量并初始化,不能用于已经存在的变量中
i1, i2, i3 := 1, 2, 3
# `_` 是特殊的变量,用于丢弃不需要的值
_, a := 1, 2
#

常量

常量是在编译阶段就确定的值,且无法再运行时改变

1
2
3
const constantName type
const Pi float32 = 3.14
const Pi = 3.14
  • Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位), 若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit

内置基础类型

  • bool,默认值为false
  • 数值类型
    • uint, int:长度相同,具体长度由不同编译器实现决定
    • rune,int8,int16,int32,int64
    • byte,uint8,uint16,uint32,uint64
    • runeint32的别称,byteuint8的别称
    • 不同类型直接不能相互赋值
    • float32float64,没有float类型,默认为float64
    • complex128complex64复数(默认为complex128),如var c =1+2i
  • 字符串
    • string
    • 使用一对双引号或者反引号括起来定义,反引号可以用来声明多行字符串,即 Raw 字符串
    • Golang 字符串是不可变的,可以先将其转换成[]byte来修改
    • +运算符可以拼接字符串
  • 错误类型
    • error
    • Go 中 errors 包可以用来处理错误信息

Go 数据底层存储

Go Data

Some tricks

分组声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
"fmt"
"os"
)

const (
i = 10
s = "hello"
)

var (
a int64
b float64
)

iota

实现枚举,默认开始值为0,const 中每增加一行则加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const (
i1 = iota // 0
i2 = iota // 1
i3 // 常量声明省略值时,默认与上一个相同,所以`i3 = iota`,因此为 3
)

const (
newConstI = iota // 新的const声明,从0开始
a, b, c, d = iota // 同一行,均为1
)

const (
i = iota // 0
j = "hello"
k = iota // 2
)

一些约定

  • 大写字母的变量和函数时可导出的,在其他包中可读取的,小写变量则是指在包中可访问

array, slice, map

array

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# array 声明格式
var arr [n]type
var intArr [5]int
fmt.Printf("The second element is %d\n", intArr[2])

# 初始化
a := [3]int{1, 2, 3}
b := [5]int{1, 2, 3} // 实际内容为 {1, 2, 3, 0, 0}
c := [...]int{1, 2, 3}

# 嵌套数组
aa := [2][3]int{ int[3]{1, 2, 3}, int[3]{4, 5, 6} }
bb := [2][3]int{{1, 2, 3}, {1, 2, 3}}

slice

slice 通常可以当成动态数组使用,但其实是一个引用类型,数据本质上还是存储在,slice 总是指向一个底层的 array。

由于 slice 是引用类型,所以对 slice 中的元素进行修改的话,底层的 array 中是能感知到的。

slice 可以理解成一个结构体,其包含了三个元素:

  • pointer:指向slice开始位置的指针
  • len:slice 的长度
  • cap:slice 开始位置到数组最后位置的长度

slice and array

1
2
3
4
5
6
7
8
9
10
11
12
# slice
var islice []int
jslice := []int {1, 2, 3}

var arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var a, b []int

a = arr[2:5]
b = arr[3:5]
c := arr[5:] // arr[5:len(arr)]
d := arr[:8] // arr[0:8]
e := arr[2:4:7] // 指定容量,真实 cap = 7 - (4 - 2) = 5

slice 由几个常用的内置函数:

  • len:获取 slice 长度
  • cap:slice 的最大容量
  • append:想 slice 追加一个或者多个元素,返回一个与 slice 相同类型的 slice
    • append 会影响到 slice 所引用的数组内容(即没扩容时,其他 slice 会看到变化的内容)
    • 当底层数组无法容纳更多元素时,会发生扩容,新创建一个 array 保存元素,即
  • copy:拷贝 slice 到另一个 slice,并且返回复制的元素的个数
1
2
3
4
5
6
7
a = arr[2:5]
aLen := len(a) // 5-2=3
aCap := cap(a) // 10-2=8
a = append(a, 1)
a = append(a, 1, 2, 3)
var b []int
copy(a, b)

map

  • map 是无序的
  • map 长度不固定,也是一个引用类型
  • len 函数返回 map 的Key的个数
  • map 不是 thread-safe
  • 使用delte删除 map 的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var numbers string[int] // 这是只是声明了一个引用类型,并没有指向任何空间,即为 nil
numbers = make(map[string]int) // 这里才让 numbers 指向一个空间

numbers["one"] = 1
fmt.Println(numbers["one"])

// 声明并初始化
rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5}
// 第二个返回值来判断是否存在该 key
javaRating, ok := rating["Java"]

// newRating 和 rating 指向同一个 map
var newRating map[string]float32
newRating = rating

make & new

  • make 用于内建类型的内存分配(slice, map, channel),返回一个有初始值的 T 类型
  • new用于各种类型的内存分配,其分配一个零值填充的 T 类型的内存空间,并返回其指针/地址,即一个*T

流程和函数

流程控制

  • if
1
2
3
4
5
6
7
8
9
10
11
12
if i == 3 {
// some code
} else if i > 3 {
// some code
} else {
// some code
}

// 可以在 if 中声明变量
if i := func(); i > 10 {
fmt.Println(i)
}
  • goto
1
2
3
4
5
// 标签名大小写敏感
Here:
println(i)
i++
goto Here
  • for
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
sum := 0
for i:=0;i < 10;i++ {
if i % 2 {
continue
}
sum += i
}

i := 0
for i < 10 {
i++
}

i = 0
for {
if i > 10 {
break
}
}

// 配合 range 遍历 map 和 slice
for index, v := range []int{1, 2, 3} {
//
}
for k, v := range [string]int{"hello":1, "world":2} {
//
}

// 丢弃不需要的变量
for _, v := range [string]int{"hello":1, "world":2} {
//
}
  • switch
1
2
3
4
5
6
7
8
9
10
11
12
13
i := 10
switch i {
case 1:
// 不需要 break,默认会 break
fmt.Println("i is 1")
case 2, 3, 4:
fmt.Println("i is 2, 3, 4")
fallthrough // 通过 fallthrough 可以强制指向后面的代码,而不退出 switch
case 5:
fmt.Println("i is 5")
default:
fmt.Println("not match")
}

函数

1
2
3
func funcName(input1 type2, input2 type2) (output1 type1, output2 type2) {
return v1, v2
}
  • 使用func声明一个函数
  • 函数可以有多个参数,可以返回多个值
  • 返回值可以只写类型名,不谢返回值名称
  • 如果有返回值名称,可以不显式的写 return 要返回的值,
  • 如果只有一个返回值且不写返回值名称时,可以忽略返回值的括号
  • 如果有返回值,必须 return
  • 支持变长参数(使用 slice 实现)
  • 参数是传值的,所以需要相当于传入的参数是原本值的一个copy,如果需要修改的话,则需要传入一个指针
  • Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。
  • defer 可以让函数在执行到最后时,执行defer函数(按照逆序执行)
  • 函数可以作为值、类型:type typeName func(input1 inputType1, input2 inputType2) (result resultType1)
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
func Max(a, b int) int {
if a > b {
return a
}
return b
}

// 不显式的写返回值
func Sum(a, b int) (s int) {
s = a + b
return
}

// 返回多个值
func SumAndProduct(a, b, int) (int, int) {
return a + b, a * b
}

// 变长参数
func SumAll(numbers ...int) (s int) {
for _, num := range numbers {
s += num
}
return
}

func add1(pi *int) {
*pi = *pi + 1
}

func deferFuncExample() {
// 输出为
// b
// a
defer fmt.Println("a")
defer fmt.Println("b")
return
}

type opFunc func(a, b int) int

func opAll(op opFunc, numbers ...int) (res int) {
if len(numbers) == 0 {
return 0
}

res = numbers[0]

for i:=1;i < len(numbers); i++ {
res = op(res, numbers[i])
}
}

func opAllExample() {
fmt.Printf("Sum is %d\n", opAll(Sum, 1, 2, 3, 4))
}

Go 没有异常机制,但可以使用Panic and Recover

  • Panic:中断原有的控制流程,并进入 panic 状态,函数 F 调用 panic,F 的 defer 函数会执行,并返回到 F 的调用位置,此时调用位置也像是掉用了 panic 一样,继续中断,一直返回向上,直到 panic 的 goroutine 所有调用函数返回
  • Recover:让进入 panic 状态的 goroutine 恢复过来,recover函数只在 defer 中有效,如果在正常执行流程中调用 recover,则会无事发生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func f() {
panic("test panic")
}

func recoverF() {
defer func() {
if x := recover(); x != nil {
fmt.Println("Panic and Recover")
}
}()
f()
}

func callRecoverF() {
// 这里不会感知到 panic 的发生
reciverF()
}

两个特殊的函数:

  • main函数:
    • 只在package main
    • 没有返回值和参数
  • init函数
    • 导包时被执行
    • 可以定义多个 init 函数,但是建议只写一个

main and init

使用import关键字来导包:

  • 相对路径:import "../model"
  • 绝对路径:import "hello/world"
  • 使用.将包中所有变量函数导入:import . "fmt"
  • 别名:import f "fmt"
  • _忽略包不导入,但执行init函数:import _ "fmt"

Struct 类型