包、变量与函数
程序从 main 包开始运行。可以通过导入路径 "math/rand" 来使用这个包,包名与导入路径的最后一个元素一致。例如,"math/rand" 包中的源码均以 package rand 语句开始。
1package main
2
3import (
4 "fmt"
5 "math/rand"
6)
7
8func main() {
9 fmt.Println("我最喜欢的数字是 ", rand.Intn(10))
10}
在 Go 中,只有首字母大写的标识符才会被导出,也就是说小写的标识符在其他包中是不可见的。可以自定义package常量,不过没有必要:
多此一举
1package main
2
3import (
4 "fmt"
5 "math"
6)
7
8func main() {
9 const pi = math.Pi
10 fmt.Println(pi)
11}
输出: 3.141592653589793
萌新的疑问
可以 import "math/rand" ,那么可不可以 import "math/rand" 呢?答案是不可以的,因为 math/rand 是标准库中真实存在的独立子包,而 Pi 是 math 包的导出常量,不属于包。以下是常用的子包:
math/rand:随机数相关math/big:大数运算math/cmplx:复数运算
函数可以返回任意数量的返回值。swap 函数返回了两个字符串。
1package main
2
3import "fmt"
4
5func swap(x, y string) (string, string) {
6 return y, x
7}
8
9func main() {
10 a, b := swap("hello", "world")
11 fmt.Println(a, b)
12}
输出: world hello
Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。没有参数的 return 语句会直接返回已命名的返回值,也就是「裸」返回值。裸返回语句在长的函数中它们会影响代码的可读性。
1package main
2
3import "fmt"
4
5func split(sum int) (x, y int) {
6 x = sum * 4 / 9
7 y = sum - x
8 return
9}
10
11func main() {
12 fmt.Println(split(17))
13}
输出:7 10
var 语句用于声明一系列变量,可以出现在包或函数的层级。
1package main
2
3import "fmt"
4
5var c, python, java bool
6
7func main() {
8 var i int
9 fmt.Println(i, c, python, java)
10}
输出:0 false false false
如果声明变量时提供了初始值,则类型可以省略;变量会从初始值中推断出类型。在这里通过fmt.Printf的%T打印变量的类型:
1package main
2
3import "fmt"
4
5var i, j = 1, 2
6
7func main() {
8 var c, python, java = true, false, "no!"
9 fmt.Println(i, j, c, python, java)
10 fmt.Printf("%T, %T, %T, %T, %T", i, j, c, python, java)
11}
输出:
1 2 true false no!
int, int, bool, bool, string
短变量声明操作符:=是用来创建具有适当名称和初始值的变量。使用这个操作符的主要目的是在函数中声明和初始化 局部变量 ,并缩小变量的范围。
1package main
2
3import "fmt"
4
5func main() {
6 var i, j int = 1, 2
7 k := 3
8 c, python, java := true, false, "no!"
9
10 fmt.Println(i, j, k, c, python, java)
11 fmt.Printf("%T, %T, %T, %T, %T, %T", i, j, k, c, python, java)
12}
输出:
1 2 3 true false no!
int, int, int, bool, bool, string
Go 的基本类型有:
-
bool -
string -
intint8int16int32int64 -
uintuint8uint16uint32uint64uintptr -
byte
// uint8 的别名 -
rune
// int32 的别名,表示一个 Unicode 码位 -
float32float64 -
complex64complex128
// 复数的64位浮点数表示
1package main
2
3import (
4 "fmt"
5 "math/cmplx"
6)
7
8var (
9 ToBe bool = false
10 MaxInt uint64 = 1<<64 - 1
11 z complex128 = cmplx.Sqrt(-5 + 12i)
12)
13
14func main() {
15 fmt.Printf("类型:%T 值:%v\n", ToBe, ToBe)
16 fmt.Printf("类型:%T 值:%v\n", MaxInt, MaxInt)
17 fmt.Printf("类型:%T 值:%v\n", z, z)
18}
输出:
类型:bool 值:false
类型:uint64 值:18446744073709551615
类型:complex128 值:(2+3i)
rune 类型是 Go 语言的一种特殊数字类型。在 builtin/builtin.go 文件中,它的定义:type rune = int32 ;官方对它的解释是:rune 是类型 int32 的别名,在所有方面都等价于它,用来区分字符值跟整数值。使用单引号定义,返回采用 UTF-8 编码的 Unicode 码点。Go 语言通过 rune 处理中文,支持国际化多语言。
UTF-8 编码规则:
- ASCII 字符(0-127):1 字节
- 带变音符号的拉丁/希腊/西里尔字母:2 字节
- 常用汉字(CJK 统一表意文字):3 字节
- 生僻字/历史文字/表情符号:4 字节
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 s := "Go语言编程"
9
10 fmt.Println(len("Go语言编程")) // 输出:14
11 fmt.Println(len([]rune("Go语言编程"))) // 输出:6
12 fmt.Println(s[0:8]) // 输出:Go语言
13 fmt.Println(s[0:7]) // 输出:Go语��
14 fmt.Println(string([]rune(s)[:4])) // 输出:Go语言
15
16 // byte
17 fmt.Println([]byte(s)) // 输出:[71 111 232 175 173 232 168 128 231 188 150 231 168 139]
18 // rune
19 fmt.Println([]rune(s)) // 输出:[71 111 35821 35328 32534 31243]
20}
字符串由字符组成,字符的底层由字节组成,而一个字符串在底层的表示是一个字节序列。在 Go 语言中,字符可以被分成两种类型处理:对占 1 个字节的英文类字符,可以使用 byte (或者 unit8 );对占 1 ~ 4 个字节的其他字符,可以使用 rune (或者 int32 ),如中文、特殊符号等。
Go 语言把字符分 byte 和 rune 两种类型处理。byte 是类型 unit8 的别名,用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节。rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值。
字符串在底层的表示是由单个字节组成的一个不可修改的字节序列,字节使用 UTF-8[1] 编码标识 Unicode[2] 文本。Unicode 文本意味着 .go 文件内可以包含世界上的任意语言或字符,该文件在任意系统上打开都不会乱码。UTF-8 是 Unicode 的一种实现方式,是一种针对 Unicode 可变长度的字符编码,它定义了字符串具体以何种方式存储在内存中。UFT-8 使用 1 ~ 4 为每个字符编码。
Go 语言把字符分 byte 和 rune 两种类型处理。byte 是类型 unit8 的别名,用于存放占 1 字节的 ASCII 字符,如英文字符,返回的是字符原始字节。rune 是类型 int32 的别名,用于存放多字节字符,如占 3 字节的中文字符,返回的是字符 Unicode 码点值。如下图所示:
常量的声明与变量类似,只不过使用 const 关键字。常量可以是字符、字符串、布尔值或数值,但不能用 := 语法声明。
1package main
2
3import "fmt"
4
5const Pi = 3.14
6
7func main() {
8 const world = "世界"
9 fmt.Println("Hello", world)
10 fmt.Println("Happy", Pi, "Day")
11
12 const Truth = true
13 fmt.Println("Go rules?", Truth)
14
15 fmt.Printf("%T, %T, %T", world, Truth, Pi)
16}
输出:
Hello 世界
Happy 3.14 Day
Go rules? true
string, bool, float64
数值常量是高精度的值。int 类型可以存储最大 64 位的整数,根据平台不同有时会更小。
1package main
2
3import "fmt"
4
5const Pi = 3.14
6
7func main() {
8 const world = "世界"
9 fmt.Println("Hello", world)
10 fmt.Println("Happy", Pi, "Day")
11
12 const Truth = true
13 fmt.Println("Go rules?", Truth)
14
15 fmt.Printf("%T, %T, %T", world, Truth, Pi)
16}
输出:
21
0.2
1.2676506002282295e+29
在Go中,单引号括起来的是rune类型,也就是int32的别名,用来表示Unicode码点。而双引号括起来的才是字符串,比如"d"就是string类型。python转过来的真的有好多细节不适应 : (
1package main
2
3import "fmt"
4
5func main() {
6 v := 'd'
7 fmt.Printf("v is of type %T\n", v)
8 // 输出: v is of type int32
9}
流程控制语句:for、if、else、switch 和 defer
Go 只有一种循环结构:for 循环。基本的 for 循环由三部分组成,它们用分号隔开:
- 初始化语句:在第一次迭代前执行
- 条件表达式:在每次迭代前求值
- 后置语句:在每次迭代的结尾执行
初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。一旦条件表达式求值为 false,循环迭代就会终止。
1package main
2
3import "fmt"
4
5func main() {
6 sum := 0
7 for i := 0; i < 10; i++ {
8 sum += i
9 }
10 fmt.Println(sum)
11}
输出:45 (1+2+3+4+5+6+7+8+9)
初始化语句和后置语句是可选的
1package main
2
3import "fmt"
4
5func main() {
6 sum := 1
7 for ; sum < 1000; {
8 sum += sum
9 fmt.Println(sum)
10 }
11 fmt.Println(sum)
12}
输出结果
2
4
8
16
32
64
128
256
512
1024
1024
怎么写 Go 中的「while」语句呢?其实就是 for 循环去掉了分号:
1package main
2
3import "fmt"
4
5func main() {
6 sum := 1
7 for sum < 1000 {
8 sum += sum
9 fmt.Println(sum)
10 }
11 fmt.Println(sum)
12}
输出结果
2
4
8
16
32
64
128
256
512
1024
1024
if 语句与 for 循环类似:
1package main
2
3import (
4 "fmt"
5 "math"
6)
7
8func sqrt(x float64) string {
9 if x < 0 {
10 return sqrt(-x) + "i"
11 }
12 return fmt.Sprint(math.Sqrt(x))
13}
14
15func main() {
16 fmt.Println(sqrt(2), sqrt(-4))
17}
输出:1.4142135623730951 2i
和 for 一样,if 语句可以在条件表达式前执行一个简短语句。该语句声明的变量作用域仅在 if 之内。(在pow最后的 return 语句处使用 v ,会报错 undefined: v )
1package main
2
3import (
4 "fmt"
5 "math"
6)
7
8func pow(x, n, lim float64) float64 {
9 if v := math.Pow(x, n); v < lim {
10 return v
11 }
12 return lim
13}
14
15func main() {
16 fmt.Println(
17 pow(3, 2, 10),
18 pow(3, 3, 20),
19 )
20}
输出:9 20
在 if 的简短语句中声明的变量同样可以在对应的任何 else 块中使用。(在 main 的 fmt.Println 调用开始前,两次对 pow 的调用均已执行并返回其各自的结果。)
1package main
2
3import (
4 "fmt"
5 "math"
6)
7
8func pow(x, n, lim float64) float64 {
9 if v := math.Pow(x, n); v < lim {
10 return v
11 } else {
12 fmt.Printf("%g >= %g\n", v, lim)
13 }
14 // can't use v here, though
15 return lim
16}
17
18func main() {
19 fmt.Println(
20 pow(3, 2, 10),
21 pow(3, 3, 20),
22 )
23}
输出:
27 >= 20
9 20
switch 语句是编写一连串 if - else 语句的简便方法。它运行第一个 case 值 值等于条件表达式的子句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只会运行选定的 case,而非之后所有的 case。 在效果上,Go 的做法相当于这些语言中为每个 case 后面自动添加了所需的 break 语句。在 Go 中,除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不限于整数。
1package main
2
3import (
4 "fmt"
5 "runtime"
6)
7
8func main() {
9 fmt.Print("Go 运行的系统环境:")
10 switch os := runtime.GOOS; os {
11 case "darwin":
12 fmt.Println("macOS.")
13 case "linux":
14 fmt.Println("Linux.")
15 default:
16 // freebsd, openbsd,
17 // plan9, windows...
18 fmt.Printf("%s.\n", os)
19 }
20}
输出:Go 运行的系统环境:Linux.
switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。如:
1switch i {
2 case 0:
3 case f():
4}
注意: 在 i==0 时,f 不会被调用。
1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 fmt.Println("周六是哪天?")
10 today := time.Now().Weekday()
11 switch time.Saturday {
12 case today + 0:
13 fmt.Println("今天。")
14 case today + 1:
15 fmt.Println("明天。")
16 case today + 2:
17 fmt.Println("后天。")
18 default:
19 fmt.Println("很多天后。")
20 }
21}
输出:
周六是哪天?
很多天后。
无条件的 switch 同 switch true 一样。这种形式能将一长串 if-then-else 写得更加清晰。
1package main
2
3import (
4 "fmt"
5 "time"
6)
7
8func main() {
9 t := time.Now()
10 switch {
11 case t.Hour() < 12:
12 fmt.Println("早上好!")
13 case t.Hour() < 17:
14 fmt.Println("下午好!")
15 default:
16 fmt.Println("晚上好!")
17 }
18}
输出:晚上好!
defer 语句会将函数推迟到外层函数返回之后执行。推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
1package main
2
3import "fmt"
4
5func main() {
6 defer fmt.Println("world")
7
8 fmt.Println("hello")
9}
输出:
hello
world
推迟调用的函数调用会被压入一个栈中。 当外层函数返回时,被推迟的调用会按照后进先出的顺序调用。
1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("counting")
7
8 for i := 0; i < 10; i++ {
9 defer fmt.Println(i)
10 }
11
12 fmt.Println("done")
13}
输出结果
counting
done
9
8
7
6
5
4
3
2
1
0
小结
作为一个 python 爱好者,转到 go 后在许多小角落都会有些迷茫,比如大小写敏感、单双引号不通用等等。但是还是记录一下自己的学习进程,敦促自己尽快打好 go 的基础,之后打算找几个项目练练手。