我正在参加「掘金·启航计划」
1. 包、变量和函数
- 导入多个包时,可以用两种方式:
import "fmt"
import "math"
或者
import (
"fmt"
"math"
)
- 在Go中,以大写字母开头的名字属于导出的名字,当导入一个包时,只能访问其中导出的名字,任何未导出的名字无法在包的外部访问。
- 类型名在变量名之后。
Rob Pike解释了这样设计的原因:以独立的语法为代价换取更清晰的表达。
C语言中使用的语法会导致复杂的表达式难以解析,而Go的语法类似于:
x: int
p: pointer to int
a: array[3] of int
只有指针是个例外,对比 slice:
var a []int,x = a[1]指针的类型语法和表达式语法相同:
var p *int,x = *p所以在类型转换时,slice可以使用
[]int("hi"),但是指针的类型必须加括号(*int)(nil)避免造成混淆。
- 当函数有多个连续的命名形参类型相同时,可以省略最后一个之前的类型声明,例如:
x int, y int和x, y int - 一个函数可以返回任意数量的结果
- Go函数的返回值可以是命名的,这些命名返回值被视作定义在函数顶部的变量。没有参数的
return语句将返回命名返回值,称为直接返回。为了可读性,直接返回应当只用在短函数中。
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
var可以声明一组变量,这些变量可以是包/函数级别的。- 变量声明可以包含初始值,如果存在初始值,变量的类型可以省略(使用初始值的类型)。
- 在函数中,变量的短赋值语句
:=可以代替var声明;但是在函数外,所有语句都以关键字开头,所以不能使用:=语句。
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}
- Go的基本类型如下。
int/uint/uintptr根据系统的位数可依是32-bit或者64-bit。
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
变量也可以成组声明:
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
- 没有显式初始化的变量会被赋予对应的零值。如
0/false/"" - 语句
T(v)会将变量v转换为类型T。Go必须进行显式的类型转换。
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
- 声明变量时如果省略了类型,那么变量的类型将根据右值进行推断。当右值声明了类型时,类型与右值一样;如果右值包含未声明类型的常量,新变量的类型将取决于常量的精度。
- 常量使用
const进行声明,方法与变量一样,但不可使用:=。 - 数值常量是高精度的值,未指明类型的数值常量将根据上下文决定其类型。如下所示,注意
int变量最多只能存储64-bit的整数。
const Big = 1 << 100
2. 流程控制语句
- Go只有一种循环结构,就是
for循环。 for循环结构包含三部分:初始化语句(第一次循环之前执行)、条件表达式(每次循环之前执行)和后置语句(每次循环之后执行),其中初始化语句声明的变量只在for作用域内可见。这三部分没有小括号包围,但是循环体的大括号{}是必需的。- 初始化语句和后置语句是可选的,如果两者不存在还可以进一步省略分号。因此在Go中,
for循环可以实现C语言中while循环的作用。
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
}
- 如果进一步去掉条件表达式,可以得到一个无限循环。
- Go的
if语句与for循环类似,条件表达式无需小括号,但是大括号是必需的,关键词必须和大括号在同一行。在条件表达式之前可以执行一个简单语句,其中声明的变量仅在if的作用域(包括其它else分支)内可见。
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else if v == lim {
fmt.Printf("%g == %g\n", v, lim)
} else {
fmt.Printf("%g > %g\n", v, lim)
}
// can't use v here, though
return lim
}
- Go中的
switch语句:只会执行选中的case,相当于每个case之后默认加了break;每个case不必是常量,也不必是整数; - 没有条件表达式的
switch语句相当于switch true,可以用于替代冗长的if-then-else语句。
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n", os)
}
}
defer语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
func main() {
fmt.Println("counting")
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
// 输出 done 2 1 0
3. 更多类型
- 指针持有值的内存地址,取地址/解引用与C语言类似。Go的指针没有算术运算。
var p *int
i := 42
p = &i
*p = 21
- 结构体是字段的集合,其中的字段通过点运算符访问。也可以使用结构体的指针访问字段,使用时可以省略解引用,用
p.X代替(*p).X。 - 结构体字面量通过直接列出字段的值来分配一个结构体,使用
Name:语法可以只列出部分字段。
type Vertex struct {
X int
Y int
}
var (
v1 = Vertex{1, 2} // has type Vertex
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
p = &Vertex{1, 2} // has type *Vertex
)
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(v)
}
- 类型
[n]T表示包含n个T类型值的数组(array) 。数组的大小属于数组类型的一部分,所以数组大小固定。 - 类型
[]T是包含T类型元素的切片(slice) 。切片可以看作数组的引用,为数组提供了一种动态的视图(view)。 - 切片不存储任何数据,它只描述一个潜在数组的片段,由两个索引指定的左闭右开区间组成
a[low:high]。 - 修改切片的元素会修改其对应的潜在数组的元素,而与其共享潜在数组的其它切片能看到这些修改。因此处理共享潜在数组的切片时要格外小心。
- 切片的字面量与数组字面量类似,但是没有长度。数组的字面量:
[3]bool{true, true, false},切片的字面量:[]bool{true, true, false}。后者会首先创建一个与前者一样的数组,然后生成引用该数组的切片。 - 使用切片时省略边界索引会采用默认值:下边界为0,上边界为切片的长度。
- 切片有长度和容量两个不同的属性。长度
len(s)代表切片中的元素个数;容量cap(s)表示从切片的第一个元素开始计算,其潜在数组中的元素个数。 - 切片的零值是
nil,长度和容量均为0,没有潜在的数组。
var s []int,s是nil
s := []int{},s不是nil
s := make([]int, 0),s不是nil
- 切片可以包含任何类型,包括其它切片。
make函数通过创建一个置零的数组并返回其引用来创建切片。可以通过参数指定长度和容量。append函数可以向切片中增加新的元素并将新的切片返回。如果切片原来的潜在数组大小不足,将会自动分配一个新的潜在数组并返回对应切片。注意原来共享潜在数组的切片此时不再共享。for循环的range形式可以遍历切片或映射。当遍历切片时,每次迭代会返回两个值,一个是索引,一个是索引对应元素的拷贝,可以使用_跳过索引或者元素值,需要时也可以只保留索引。
for i, _ := range pow / for _, value := range pow / for i := range pow
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
a := primes[1:4] // (1)
var b []int // (2) nil
c := []int{2, 3, 5} // (3)
c = c[:2]
c = c[1:]
d := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
e := make([]int, 5) // (4)
f := make([]int, 0, 5) // (5)
f = append(f, 2, 3, 4)
}
- 类型map是键-值对的集合。
- map的零值是
nil,没有键,也不能添加键。 make函数可以初始化一个可以使用的给定类型的map并返回。- map的字面量与结构体类似,但是需要键。可以省略字面量元素中的类型名。
- 插入或更新map的元素:
m[key]=elem - 检索map的元素:
elem = m[key] - 删除map的元素:
delete(m, key) - 检查键是否存在:
elem, ok = m[key]。如果key不存在,ok为false,elem为元素类型的零值;如果key存在,ok为true,elem为对应的元素。
type Vertex struct {
Lat, Long float64
}
var m1 map[string]Vertex // nil
var m2 = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
func main() {
m3 := make(map[string]int)
m3["Answer"] = 42
}
- 函数也是一种值,可以作为函数的参数和返回值。
- Go函数可以是闭包。闭包引用了函数体之外的变量,可以访问并赋值引用的变量。该函数与这些变量绑定在一起。
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
4. 空值
- 未显式初始化的变量会被赋予一个对应类型的空值。
- 不同类型对应的空值不同:
-
- 值类型:布尔类型
false,数值类型0,字符串"",数组和结构体会递归地初始化元素和字段。 - 应用类型:包括指针、函数、接口、信道、切片、map,均为
nil。
- 值类型:布尔类型