这是我参与「第五届青训营 」伴学笔记创作活动的第3天
GO语言圣经笔记
1. 入门
1.1基本结构
Go语言是一门静态编译型语言,其提供的工具都通过一个单独的命令 go 调用,go 命令有一系列子命令。最简单的一个子命令就是 run。
package main
import "fmt"
func main() {
fmt.Println("Hello, World! ^_^ ")
}
在VS code中新建01_helloworld.go文件,输入上述代码,通过command + J调出终端,输入以下代码运行go程序。
go run 01_helloworld.go
在MAC端下,输入go build 01_helloworld.go,用来编译go程序,编译后的go程序可以直接运行。
包(package)
每个go程序都是由包构成,每个源文件都以一条 package 声明语句开始,表示该文件属于哪个包。
程序从main包开始运行,在package声明后面导入(import)一系列需要用到的包。
按照约定,包名与导入路径的最后一个元素一致。
package main
按照惯例,我们在每个包的包声明前添加注释;对于 main package,注释包含一句或几句话,从整体角度对程序做个描述。
main 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 main 里的 main 函数是整个程序执行时的入口。
import "fmt"
Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如 fmt 包,就含有格式化输出、接收输入的函数。
package main
import (
"fmt"
"math/rand"
)
func main() {
rand.Seed(86) //初始化种子
fmt.Println("My favorite number is", rand.Intn(100))
fmt.Println("My favorite number is", rand.Intn(100))
fmt.Println("My favorite number is", rand.Intn(100))
}
"math/rand" 包中的源码均以 package rand 语句开始,在rand包官方文档里可以查到rand包中的各个函数的用法,本例用rand.Intn(n)输出伪随机数。通常使用非固定种子time.Now().UnixNano()。
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println("My favorite number is", rand.Intn(100))
fmt.Println("My favorite number is", rand.Intn(100))
fmt.Println("My favorite number is", rand.Intn(100))
}
导出名
在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
package main
import (
"fmt"
"math"
)
func main() {
// fmt.Println(math.pi) 报错
fmt.Println(math.Pi) //输出:3.141592653589793
}
函数
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func name(parameter-list) (result-list) {
body
}
形式参数列表描述了函数的参数名以及参数类型。
返回值列表描述了函数返回值的变量名以及类型。如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。如果一个函数声明不包括返回值列表,那么函数体执行完毕后,不会返回任何值。
下面,是4种方法声明拥有2个int型参数和1个int型返回值的函数
func add(x int, y int) int {return x + y}
func sub(x, y int) (z int) { z = x - y; return}
func first(x int, _ int) int { return x }
func zero(int, int) int { return 0 }
fmt.Printf("%T\n", add) // "func(int, int) int"
fmt.Printf("%T\n", sub) // "func(int, int) int"
fmt.Printf("%T\n", first) // "func(int, int) int"
fmt.Printf("%T\n", zero) // "func(int, int) int"
每一次函数调用都必须按照声明顺序为所有参数提供实参(参数值)。在函数调用时,Go语言没有默认参数值,也没有任何方法可以通过参数名指定形参,因此形参和返回值的变量名对于函数调用者而言没有意义。
注意类型在变量名之后。当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
(参考 这篇关于 Go 语法声明的文章了解这种类型声明形式出现的原因。)
多值返回
函数可以返回任意数量的返回值。
swap 函数返回了两个字符串。
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func swap(x, y int) (int, int) {
return y, x
}
func main() {
a, b := 42, 13
fmt.Println(add(42, 13)) //输出:55
fmt.Println(swap(a, b)) //输出:13 42
fmt.Printf("%T\n", add) //输出:func(int, int) int
}
变量及初始化
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
//可替换为i, j, c, python, java := 1, 2, true, false, "yes!"
fmt.Println(i, j, c, python, java) //输出:1 2 true false no!
}
短变量声明
在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
零值
没有明确初始值的变量声明会被赋予它们的零值。
零值是:
- 数值类型为
0, - 布尔类型为
false, - 字符串为
""(空字符串)
常量
常量的声明与变量类似,只不过是使用 const 关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 := 语法声明。
类型转换
与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。
表达式 T(v) 将值 v 转换为类型 T。
一些关于数值的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者,更加简单的形式:
i := 42
f := float64(i)
u := uint(f)
类型推导
在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。
当右值声明了类型时,新变量的类型与其相同:
var i int
j := i // j 也是一个 int
不过当右边包含未指明类型的数值常量时,新变量的类型就可能是 int, float64 或 complex128 了,这取决于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
尝试修改示例代码中 v 的初始值,并观察它是如何影响类型的。
package main
import "fmt"
const Pi = 3.14
func main() {
v := 42// 修改这里!
fmt.Printf("v is of type %T\n", v)
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
}
结尾分号
Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析。
例:x+y,在+后换行可以,在x后换行则会编译错误
格式化
gofmt 文件名可以格式化代码
输入/输出
print:输出操作数的默认格式,空格隔开。
println:输出操作数的默认格式,空格隔开,换行。
printf:根据格式说明符打印。
1.2流程控制语句
for
Go 只有一种循环结构:for 循环。
基本的 for 循环由三部分组成,它们用分号隔开:
- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。
一旦条件表达式的布尔值为 false,循环迭代就会终止。
注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum) //输出:45
}
与C语言相同,初始化语句和后置语句是可选的。
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum) //输出:1024
}
此时去掉分号相当于while
for sum < 1000 {
sum += sum
}
无限循环
如果省略循环条件,该循环就不会结束。即for { }
if
Go的if语句与for循环类似,表达式外无需小括号( ),而大括号{ }则是必须的。
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4)) //输出:1.4142135623730951 2i
}
同for一样,if语句可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在if之内。
在 if 的简短语句中声明的变量同样可以在任何对应的 else 块中使用。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
switch
switch是编写一连串if - else语句的简便方法,类似于C。
区别:
- 在于GO提供了每个
case的break语句,除非以fallthrough语句结束,否则分支会自动终止。 - case 无需为常量,且取值不必为整数。
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
fmt.Println(today) //today输出的星期string
fmt.Println(today + 3) //若执行加法则输出对应天数后的星期string
fmt.Println(time.Saturday) //输出.后的星期string
today += 3
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}
没有条件的 switch
同switch true一样,这种形式能将一长串if-then-else写得更加清晰。
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
defer
defer语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}