go语法基础
GOLANG sucks
程序入口
go项目的启动入口严格为main包下的main方法
导出名
理解为java的public和private权限修饰符
在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。例如,Pizza
就是个已导出名,Pi
也同样,它导出自 math
包。
pizza
和 pi
并未以大写字母开头,所以它们是未导出的。
在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
如hello.go的SayHello函数,当它被命名为sayHello时无法被包外调用
函数
传参格式为值在前,类型在后; 连续相同类型可以省略类型声明
func test(x int, y int) (int, int){} 等价于 func test(x, y int) (int, int){}
可以定义返回值,同样当连续相同类型可以省略类型声明
func test(x int, y int) (m int, n int){} 等价于 func test(x int, y int) (m, n int){}
当定义返回值时,没有参数的
return
语句将返回已命名的返回值。也就是直接
返回。func swap(x, y int) (m, n int) {
m = y
n = x
return // return后没有参数,即返回m和n
}
基本类型
Go 的基本类型有
1 | bool |
本例展示了几种类型的变量。 同导入语句一样,变量声明也可以“分组”成一个语法块。
int
, uint
和 uintptr
在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 当你需要一个整数值时应使用 int
类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
零值
没有明确初始值的变量声明会被赋予它们的 零值。
零值是:
- 数值类型为
0
, - 布尔类型为
false
, - 字符串为
""
(空字符串)。
变量声明
var
语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
var
语句可以出现在包或函数级别。
1 | package main |
声明的时候可以包含初始值
1 | package main |
短变量声明
在函数中,简洁赋值语句 :=
可在类型明确的地方代替 var
声明。
函数外的每个语句都必须以关键字开始(var
, func
等等),因此 :=
结构不能在函数外使用。
在声明一个变量而不指定其类型时(即使用不带类型的 :=
语法或 var =
表达式语法),变量的类型由右值推导得出。
1 | package main |
类型转换
表达式 T(v)
将值 v
转换为类型 T
。
一些关于数值的转换:
1 | var i int = 42 |
或者,更加简单的形式:
1 | i := 42 |
与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。
循环语句
go的循环只有for语句
注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号
{ }
则是必须的。
1 | for i := 0; i < 10; i++ { |
选择语句
if
Go 的
if
语句与for
循环类似,表达式外无需小括号( )
,而大括号{ }
则是必须的。if
语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在if
之内。1
2
3if i := 0; i < 10086{
//do something
}
switch
一样没有小括号,一样可以先执行一个简单语句
switch
是编写一连串 if - else
语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
- Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的
break
语句。 除非以fallthrough
语句结束,否则分支会自动终止。 - switch 的 case 无需为常量,且取值不必为整数。
- switch 的 case 语句从上到下顺次执行,直到匹配成功时停止
1 | func main() { |
没有条件的 switch 同 switch true
一样。
这种形式能将一长串 if-then-else 写得更加清晰。
1 | func main() { |
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
1 | func main() { |
指针
Go 拥有指针。指针保存了值的内存地址。
类型 *T
是指向 T
类型值的指针。其零值为 nil
。
1 | var p *int |
&
操作符会生成一个指向其操作数的指针。
1 | i := 42 |
*
操作符表示指针指向的底层值。
1 | fmt.Println(*p) // 通过指针 p 读取 i |
这也就是通常所说的“间接引用”或“重定向”。
与 C 不同,Go 没有指针运算。
结构体
1 | //定义 |
结构体字段可以通过结构体指针来访问。
如果我们有一个指向结构体的指针
p
,那么可以通过(*p).X
来访问其字段X
。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写p.X
就可以。
数组
1 | //定义 |
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T
表示一个元素类型为 T
的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
1 | a[low : high] //左闭右开 |
切片下界的默认值为 0
,上界则是该切片的长度。
对于数组
1 var a [10]int来说,以下切片是等价的:
1
2
3
4 a[0:10]
a[:10]
a[0:]
a[:]
切片并不存储任何数据,它只是描述了底层数组中的一段。
- 更改切片的元素会修改其底层数组中对应的元素。
- 与它共享底层数组的切片都会观测到这些修改。
切片拥有 长度 和 容量。
- 切片的长度就是它所包含的元素个数。
- 切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
- 切片
s
的长度和容量可通过表达式len(s)
和cap(s)
来获取。 - 可以通过重新切片来扩展一个切片(重新对切片赋值),给它提供足够的容量。
切片的零值是 nil
。nil 切片的长度和容量为 0 且没有底层数组。
向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append
函数。内建函数的文档对此函数有详细的介绍。
1 | func append(s []T, vs ...T) []T |
append
的第一个参数 s
是一个元素类型为 T
的切片,其余类型为 T
的值将会追加到该切片的末尾。
append
的结果是一个包含原切片所有元素加上新添加元素的切片。
当 s
的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
1 | var s []int //len=0 cap=0 [] |
数组vs切片
1 | [3]bool{true, true, false} |
动态数组(用 make 创建切片)
切片可以用内建函数 make
来创建,这也是你创建动态数组的方式。
make
函数会分配一个元素为零值的数组并返回一个引用了它的切片:
1 | a := make([]int, 5) // len(a)=5 |
要指定它的容量,需向 make
传入第三个参数:
1 | b := make([]int, 0, 5) // len(b)=0, cap(b)=5 |
映射
1 | var m map[string]Vertex |
映射的零值为 nil
。nil
映射既没有键,也不能添加键。
make
函数会返回给定类型的映射,并将其初始化。
1 | type Vertex struct { |
删除元素:
1 | delete(m, key) |
通过双赋值检测某个键是否存在:
1 | elem, ok = m[key] |
若 key
在 m
中,ok
为 true
;否则,ok
为 false
。
若 key
不在映射中,那么 elem
是该映射元素类型的零值。
同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。
range
for
循环的 range
形式可遍历切片或映射。
当使用 for
循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
1 | var pow = [8]int{1, 2, 4, 8, 16, 32, 64, 128} |
闭包
函数也是值。它们可以像其它值一样传递。
函数值可以用作函数的参数或返回值。
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
1 | func adder() func(int) int { |
方法
方法就是一类带特殊的 接收者 参数的函数。
方法接收者在它自己的参数列表内,位于 func
关键字和方法名之间。
1 | type Vertex struct { |
方法的一个非常有用的特性是能够将它们链接在一起,同时仍保持代码的清洁。
1 | p := &Person{} |
Tips:
- 接收者的类型定义和方法声明必须在同一包内
- 不能为内建类型(如int)声明方法。
接口
由一组方法签名定义的集合。
通过实现一个接口的所有方法来实现该接口。既然无需专门显式声明,也就没有“implements”关键字。
- 隐式接口从接口的实现中解耦了定义,这样接口的实现可以出现在任何包中,无需提前准备。
- 因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
接口也是值。它们可以像其它值一样传递。
- 接口值可以用作函数的参数或返回值。
即便接口内的具体值为 nil,方法仍然会被 nil 接收者调用。
1 | // I接口定义 |
空接口
指定了零个方法的接口值
空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)
空接口被用来处理未知类型的值。
1 | var i interface{} |
类型断言
提供了访问接口值底层具体值的方式
1 | t := i.(T) |
该语句断言接口值 i
保存了具体类型 T
,并将其底层类型为 T
的值赋予变量 t
。
若 i
并未保存 T
类型的值,该语句就会触发一个恐慌。
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
1 | t, ok := i.(T) |
若 i
保存了一个 T
,那么 t
将会是其底层值,而 ok
为 true
。
否则,ok
将为 false
而 t
将为 T
类型的零值,程序并不会产生恐慌。
1 | func main() { |
类型选择
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
1 | switch v := i.(type) { |
类型选择中的声明与类型断言 i.(T)
的语法相同,只是具体类型 T
被替换成了关键字 type
。
此选择语句判断接口值 i
保存的值类型是 T
还是 S
。在 T
或 S
的情况下,变量 v
会分别按 T
或 S
类型保存 i
拥有的值。在默认(即没有匹配)的情况下,变量 v
与 i
的接口类型和值相同。