Go学习笔记(一)
这周学了下Go,这里记录了一下学习的笔记,主要来自官方Go指南 《Go 语言之旅》
包
每个 Go 程序都是由包构成的。
程序从 main 包开始运行。
本程序通过导入路径 "fmt" 和 "math/rand" 来使用这两个包。
按照约定,包名与导入路径的最后一个元素一致。例如,"math/rand" 包中的源码均以 package rand 语句开始。
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
这是官方教程的第一段,可以发现 Go 和 C 语言不同,不是通过一个个文件的方式来管理代码,而是通过包
C 语言是把代码放在 .c 文件中,然后通过编写对应的 .h 文件,最后 #include 需要的 .h 文件
Go 则是把代码放在 .go 文件里,通过 package 指定一个包名,最后直接 import 这个包
由于 Go 的程序是从 main 这个包开始的,而一个程序只能有一个 main 函数,所以 main 函数就必须在 main 这个包里
导入和导出
导入很简单,可以向上面一样,将所有需要导入的包,写在一个 import 里,这是一种分组的形式
另一种就是和 C 语言一样,一个包一个 import
import "fmt"
import "math"
官方是推荐采用分组导入语句
在 Go 里面导出更加简单,不需要主动导出,如果一个名字以大写字母开头,那么它就是已导出的。
也就是包中首字母大写的函数、变量、常量之类的都可以在其他包中访问到,反之就无法访问
类型声明
Go 的类型声明和 C不同,是类型写在后面,变量名写在前面,类似于 python 、 typescript 的类型注解的方式,参考 这篇关于 Go 语法声明的文章可以了解这种类型声明形式出现的原因
简单来说就是 Go 的开发者觉得 C 在复杂的函数声明时,比如下面这种会难以分辨,不知道该把参数名往哪个位置加,也不知道 fp 是个什么东西
int (*(*fp)(int (*)(int, int), int))(int, int)
而 Go 采用的是一种从左往右的说明的方式
x: int
p: pointer to int
a: array[3] of int
x int
p *int
a [3]int
这样可以保证始终是最左边是参数名,而类型在参数名的右边
f func(func(int,int) int, int) func(int, int) int
函数
go 里面函数声明的方式就是通过 func 关键字,参数都是统一的左边变量名,右边类型,最后函数的返回值的类型同样是在最右边
func add(x int, y int) int {
return x + y
}
连续两个或多个相同的已命名形参可以省略前面几个的类型,只留下最后一个的类型
func add(x, y int) int {
return x + y
}
在 Go 中 函数可以返回任意数量的返回值,通过,
连接
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
对于已经命名的函数返回值,可以通过直接返回的方式
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
没有参数的 return
语句会返回已命名的返回值,这就是直接返回
变量
Go 中变量声明的方式类似 JavaScript,是通过 var 关键字
var c, python, java bool
同样的,我们也可在声明的同时初始化,不过每个变量必须都对应一个,也就是要么都初始化要么都不初始化
如果初始化的值存在,可以省略类型,变量会从初始值中推导出类型(类似于 C11 的 auto 关键字的作用)
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
还有一种偷懒的声明方式,叫短变量声明
在函数中,简洁赋值语句 :=
可在类型明确的地方代替 var
声明。
函数外的每个语句都必须以关键字开始(var
, func
等等),因此 :=
结构不能在函数外使用。
package main
import "fmt"
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 的基本类型有
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 表示一个 Unicode 码点
float32 float64
complex64 complex128
本例展示了几种类型的变量。 同导入语句一样,变量声明也可以“分组”成一个语法块。
int
, uint
和 uintptr
在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。 当你需要一个整数值时应使用 int
类型,除非你有特殊的理由使用固定大小或无符号的整数类型。
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v
", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v
", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v
", z, z)
}
Type: bool Value: false
Type: uint64 Value: 18446744073709551615
Type: complex128 Value: (2+3i)
零值
没有明确初始值的变量声明会被赋予它们的 零值。
零值是:
- 数值类型为
0
, - 布尔类型为
false
, - 字符串为
""
(空字符串)。
类型转换
表达式 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)
与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换,否则会报错
常量
常量的声明与变量类似,只不过是使用 const
关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 :=
语法声明。
const Pi = 3.14
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
package main
import "fmt"
const (
// 将 1 左移 100 位来创建一个非常大的数字
// 即这个数的二进制是 1 后面跟着 100 个 0
Big = 1 << 100
// 再往右移 99 位,即 Small = 1 << 1,或者说 Small = 2
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
这里如果 Big
是个变量,就会报错,提示超出 int 范围,因为 Go 的类型推导里,没有小数点的整数常量就会推导为 int 类型,但常量的类型推导,不是初始化时进行的,而是根据后面使用时的上下文推导
上面的代码运行不会报错,运行结果如下
21
0.2
1.2676506002282295e+29
流程控制
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)
}
初始化语句和后置语句是可选的。
package main
import "fmt"
func main() {
sum := 1
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
此时你可以去掉分号,因为 C 的 while
在 Go 中叫做 for
。
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
再省略循环条件,就是无限循环了
package main
func main() {
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))
}
if 的简短语句
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
该语句声明的变量作用域仅在 if 之内。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
if 和 else
在 if
的简短语句中声明的变量同样可以在任何对应的 else
块中使用。
(在 main
的 fmt.Println
调用开始前,两次对 pow
的调用均已执行并返回其各自的结果。)
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
", v, lim)
}
// 这里开始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
运行结果如下
27 >= 20
9 20
switch
switch
是编写一连串 if - else
语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break
语句。 除非以 fallthrough
语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.
", os)
}
}
没有条件的 switch
没有条件的 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() {
defer fmt.Println("world")
fmt.Println("hello")
}
运行结果如下,这个一般用在并发控制比较多
hello
world
defer 栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
运行结果如下
counting
done
9
8
7
6
5
4
3
2
1
0
未完待续...