go基本语法

basic go grammar

based on a tour of go

包 变量 函数

  • 相同package的不同文件共享变量、函数等

  • import时可以分组导入写在括号内,也可以拆开写

    1
    2
    3
    4
    5
    6
    7
    import (
    "fmt"
    "math
    )

    import "fmt"
    import "math"
  • 只有第一个字母大写的变量函数等是已导出的(exported)。导入其他包时只能使用已导出的类型。

  • 函数可以接收多个参数,如果参数相同可以只写最后一个,可以有多个返回值,类型在变量之后。具体关于声明位置的思考

    1
    2
    3
    func add(x, y int, z float64) int {
    return x + y + int(z)//z:抹去小数点后
    }
  • 基于多返回值,swap可以一行完成

    1
    a,b = b,a
  • Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。返回值的名称应当具有一定的意义,它可以作为文档使用。没有参数的 return 语句返回已命名的返回值。也就是 直接 返回。直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。

    1
    2
    3
    4
    5
    func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
    }
  • var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。就像在这个例子中看到的一样,var 语句可以出现在包或函数级别。

    1
    2
    3
    4
    5
    var c, python, java bool
    func main() {
    var i int
    fmt.Println(i, c, python, java)
    }
  • 变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。

    1
    2
    var i, j int = 1, 2 //这里没有int也会定义成int
    var c, python, java = true, false, "no!"
  • 基本类型
    | 类型 | size(byte) | 默认初始值(表达) |
    | —————————————————— | ————— | ————————— |
    | bool | 1 | false |
    | string | - | “” |
    | int int8 int16 int32 int64 | - | 0 |
    | uint uin8 uint16 uint32 uint64 uintptr | - | 0 |
    | byte // uint8 别名 | 1 | 0 |
    | rune // int32别名 表示一个unicode 码点 | 4 | 0 |
    | float32 float64 | - | 0.0 |
    | complex64 complex128 | - | 0+0i |

  • 类型转换必须显式进行

  • 没有类似char类型的变量,s[i]取出来的都是byte类型,s[a:b]倒是一个string,要打印单个字符还是要再转换回string:string(c) 或者格式化输出%q

  • 未指明的类型(a := ...; var = ...)会尝试进行推导

  • 常量用const限定,可以是字符、字符串、布尔值或数值,不能用:=声明

  • 一个未指定类型的常量由上下文来决定其类型。

    1
    2
    3
    4
    5
    6
    7
    const (
    // 将 1 左移 100 位来创建一个非常大的数字
    // 即这个数的二进制是 1 后面跟着 100 个 0
    Big = 1 << 100
    // 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2
    Small = Big >> 99
    )//注意这个big定义是可以的,不会爆,但是如果想用int方式用就会在那里爆

格式化输出

fmt中几种输出

  • Print: 输出到控制台(不接受任何格式化,它等价于对每一个操作数都应用 %v)

  • Println: 输出到控制台并换行

  • Printf : 只可以打印出格式化的字符串。只可以直接输出字符串类型的变量(不可以输出整形变量和整形 等)
  • Sprintf:格式化并返回一个字符串而不带任何输出。
  • Fprintf:来格式化并输出到 io.Writers 而不是 os.Stdout。
1
2
3
4
5
6
fmt.Print(str)
fmt.Println(tmp)
fmt.Printf("%d",a)
s := fmt.Sprintf("a %s", "string")
fmt.Printf(s)
fmt.Fprintf(os.Stderr, “an %s\n”, “error”)
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
普通占位符
占位符 说明 举例 输出
%v 相应值的默认格式。 Printf("%v", people) {zhangsan},
%+v 打印结构体时,会添加字段名 Printf("%+v", people) {Name:zhangsan}
%#v 相应值的Go语法表示 Printf("#v", people) main.Human{Name:"zhangsan"}
%T 相应值的类型的Go语法表示 Printf("%T", people) main.Human
%% 字面上的百分号,并非值的占位符 Printf("%%") %
布尔占位符
占位符 说明 举例 输出
%t true 或 false。 Printf("%t", true) true
整数占位符
占位符 说明 举例 输出
%b 二进制表示 Printf("%b", 5) 101
%c 相应Unicode码点所表示的字符 Printf("%c", 0x4E2D) 中
%d 十进制表示 Printf("%d", 0x12) 18
%o 八进制表示 Printf("%d", 10) 12
%q 单引号围绕的字符字面值,由Go语法安全地转义 Printf("%q", 0x4E2D) '中'
%x 十六进制表示,字母形式为小写 a-f Printf("%x", 13) d
%X 十六进制表示,字母形式为大写 A-F Printf("%x", 13) D
%U Unicode格式:U+1234,等同于 "U+%04X" Printf("%U", 0x4E2D) U+4E2D
浮点数占位符
%b 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat中的 'b' 转换格式一致。例如 -123456p-78
%e 科学计数法,例如 -1234.456e+78
%E 科学计数法,例如 -1234.456E+78
%f 有小数点而无指数,例如 123.456
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出
%f: default width, default precision
%9f width 9, default precision
%.2f default width, precision 2
%9.2f width 9, precision 2
%9.f width 9, precision 0
字符串与字节切片
占位符 说明 举例 输出
%s 输出字符串表示(string类型或[]byte) Printf("%s", []byte("Go语言")) Go语言
%q 双引号围绕的字符串,由Go语法安全地转义 Printf("%q", "Go语言") "Go语言"
%x 十六进制,小写字母,每字节两个字符 Printf("%x", "golang") 676f6c616e67
%X 十六进制,大写字母,每字节两个字符 Printf("%X", "golang") 676F6C616E67
其它标记
占位符 说明 举例 输出
%p 指针,十六进制表示,前缀 0x
+ 总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。
Printf("%+q", "中文") "\u4e2d\u6587"
- 在右侧而非左侧填充空格(左对齐该区域)
# 备用格式:为八进制添加前导 0(%#o) Printf("%#U", '中') U+4E2D
为十六进制添加前导 0x(%#x)或 0X(%#X),为 %p(%#p)去掉前导 0x;
如果可能的话,%q(%#q)会打印原始 (即反引号围绕的)字符串;
如果是可打印字符,%U(%#U)会写出该字符的
Unicode 编码形式(如字符 x 会被打印成 U+0078 'x')。
' ' (空格)为数值中省略的正负号留出空白(% d);
以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开
0 填充前导的0而非空格;对于数字,这会将填充移到正负号之后

控制流

for

Go只有for没有while。基本的 for 循环由三部分组成,它们用分号隔开:

  • 初始化语句:在第一次迭代前执行,可以空
  • 条件表达式:在每次迭代前求值
  • 后置语句:在每次迭代的结尾执行,可以空

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。当只有条件判断时,分号可以省,这时候它长得就像while。

1
2
3
4
5
6
7
8
for i := 0; i < 10; i++ {
sum += i
}
for sum < 1000 {
sum += sum
}
for {
} // 无限循环

if

无需小括号,大括号必须。

for 一样, if 语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内。

1
2
3
4
5
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}

switch

相比于C的
switch,Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。

1
2
3
4
5
6
7
8
9
10
11
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
case f() //也ok
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}

switch 的 case 语句从上到下顺次执行,直到匹配成功时停止,后续不会被执行,即使是个函数。

没有条件的 switch 同 switch true 一样。这种形式能将一长串 if-then-else 写得更加清晰。

1
2
3
4
5
6
7
8
9
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}

defer

defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。如果return被拆解成rval=...ret,defer语句可以理解成插在中间。

它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源、打印结束语,以及错误处理(try-catch-finally)。

1
2
3
4
5
6
7
8
9
10
11
12
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
}

推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。

运行推迟的函数可能对返回值有影响!!

1
2
3
4
func c() (i int) {
defer func() { i++ }()
return 1
} // return 2

详细解读可见此文

指针和复杂结构

pointer

类型 *T 是指向 T 类型值的指针。其零值为 nil: var p *int

& 操作符会生成一个指向其操作数的指针。p = &i

* 操作符表示指针指向的底层值。*p = 21

Go 没有指针运算。(如p++)

struct

一个结构体(struct)就是一组字段(field),使用点号来访问成员。如果p是一个指针,可以用p.X访问成员(类似c中p->x)

初始化时,使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Vertex struct {
X, Y int
} //print出来就是{1,2}

var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)

func main() {
v := Vertex{1, 2}
v.X = 1
}

数组

类型 [n]T 表示拥有 nT 类型的值的数组。数组的长度是其类型的一部分,因此数组不能改变大小。

1
2
primes := [6]int{2, 3, 5, 7, 11, 13}
var a [2]string

切片 slice

类型 []T 表示一个元素类型为 T 的切片。切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔,它会选择一个半开区间,包括第一个元素,但排除最后一个元素。默认上下界是0和长度,因此可以省略。

1
2
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]

切片是数组的引用,切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。

切片文法类似于没有长度的数组文法。下面这样会创建一个数组,然后构建一个引用了它的切片:

1
2
3
4
5
6
7
8
9
10
11
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}

切片拥有 长度容量。切片的长度就是它所包含的元素个数。切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。切片 s 的长度和容量可通过表达式 len(s)cap(s) 来获取。可以理解为切片有一个左指针和右指针,左指针只能往右走,即左边的元素丢了就是丢了,又指针还可以扩展回去。

切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。

切片可以用内建函数 make 来创建,make 函数会分配一个元素为零值的数组并返回一个引用了它的切片,要指定它的容量,需向 make 传入第三个参数:

1
2
a := make([]int, 5) //len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5) //len=0 cap=5 []

切片可包含任何类型,甚至包括其它的切片。

1
2
3
4
5
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。func append(s []T, vs ...T) []T

1
2
var s []int
s = append(s, 2, 3, 4)

要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。

1
2
3
4
5
6
> t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
> for i := range s {
> t[i] = s[i]
> } //or use copy(t,s)
> s = t
>

初始化二维切片,大致有两种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func main() {
// 方法0
row, column := 3, 4
var answer [][]int
for i := 0; i < row; i++ {
inline := make([]int, column)
answer = append(answer, inline)
}
fmt.Println(answer)

// 方法1
answer1 := make([][]int, row)
for i := range answer1 {
answer1[i] = make([]int, column)
}
fmt.Println(answer1)
}

对于slice,更多分析见此文

range

for 循环的 range 形式可遍历切片或映射。当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。可以将下标或值赋予 _ 来忽略它。若你只需要索引,忽略第二个变量即可。

1
2
3
4
5
6
7
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i) // == 2**i
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}

映射 map

映射将键映射到值。映射的零值为 nilnil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var m map[string]int
m = make(map[string]int)
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
} //若顶级类型只是一个类型名,你可以在文法的元素中省略它。

插入、修改、获取、删除、检测类似python字典

1
2
3
4
5
m[key] = elem // insert or modify
elem = m[key] // get
delete(m, key) // delete
elem, ok = m[key] // exist->elem, true; not exist->nil, false
elem, ok := m[key] // if ok not declared

函数闭包

类似c,函数可以作为一种值,可以传递。

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。

1
2
3
4
5
6
7
8
9
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
pos := adder()
pos(2)