控制结构
if-else
在if-else
结构中,if
可以包含一个初始化声明
if val := 10; val > max {
// do something
}
需要注意的是使用:=
声明变量的作用域只在if
结构之中,如果使用if-else
就在这当中。
如果在变量在if
之前已经存在,那么该变量原来的值会被隐藏的。
package main
import "fmt"
func main() {
var val int = 10 // 该变量没有被使用
if val := 20; val > 10 { // var val int = 20
fmt.Printf("yes\n") // 作用域在if-else结构当中
} else {
fmt.Println("no")
}
}
测试多返回值函数的错误
Go语言的函数经常用两个返回值来表示执行是否成功:返回某个值+true表示成功,返回零值(或nil)和false表示失败。也可以使用error类型的变量(var err error
)。
value, err := pack1.Function1(param1)
if err != nil {
fmt.Printf("An error occured in pack1.Function1 with parameter %v", param1)
return err
}
如果我们想要在错误发生的同时终止程序的运行,我们可以使用 os
包的 Exit
函数:
if err != nil {
fmt.Printf("Program stopping with error %v", err)
os.Exit(1)
}
switch
当成功匹配到某个分支,执行完相应的代码就会退出整个switch
,不需要break
,如果想要继续执行后续分支代码,可以使用fallthrough
switch i {
case 0: fallthrough
case 1:
f() // 当 i == 0 时函数也会被调用
}
season.go
func main() {
month := rand.Intn(13)
fmt.Println("月份是:", month)
switch month{
case 3, 4, 5:
fmt.Println("春天")
case 6, 7, 8:
fmt.Println("夏天")
case 9, 10, 11:
fmt.Println("秋天")
case 12, 1, 2:
fmt.Println("冬天")
default:
fmt.Println("月份错误")
}
}
for
比起python中的for,Go的for结构并不需要括号。例如for(i:=0; i < 5; i++){ // do}
这是无效代码。
计数器循环最佳实践:
for i := 0 ; i < 5 ; i++{
fmt.Printf("This is the %d iteration\n", i)
}
当然也可以使用多个计数器:
for i, j := 0, N; i < j; i, j = i+1, j-1 {
// do something
}
for_character.go
func main() {
// 2层for嵌套
//for i := 1; i < 26; i++{
// for j := 0; j < i; j++{
// fmt.Printf("G")
// }
// fmt.Printf("\n")
//}
// 1层for和字符串连接
var str string
for i := 1; i < 26; i++{
str += "G"
fmt.Printf("%s\n", str)
}
}
for
的第二种形式:for 条件语句{ // do}
类似while
i := 5
for i > 0{
i -= 1
fmt.Printf("%d", i)
}
第三种:无限循环
for true{
// do something
// 可以在循环体内部用 break 退出循环体
// 也可以在指定时刻使用 return 退出函数体
}
for-range
for ix, val := range coll { }
其中val一般是索引的值的拷贝,一般只有只读性质,如果val是指针,则会产生指针的拷贝,对val修改会修改原值。
for pos, char := range str{}
可以遍历rune字符串。
func main() {
str := "中文"
for i := 0 ; i < len(str); i++{
fmt.Printf("Character on position %d : %c\n",i, str[i])
}
// 使用 for-range 可以很方便地遍历
for pos, val := range str{
fmt.Printf("Charater on position %d : %c\n", pos, val)
}
}
break 与 continue
需要注意的是continue
只能使用与for
循环中
标签与goto
Go建议标签都使用大写字母
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
// break LABEL1 会直接退出整个循环体
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
标签和goto配合使用可以模拟循环。
特别注意 使用标签和 goto 语句是不被鼓励的:它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方案来实现相同的需求。
一个建议使用 goto 语句的示例会在第 15.1 章的 simple_tcp_server.go 中出现:示例中在发生读取错误时,使用 goto 来跳出无限读取循环并关闭相应的客户端链接。
如果您必须使用 goto,应当只使用正序的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败。
函数 function
- 普通函数
- 匿名函数/lambda函数
- 方法(Methods)
函数参数、返回值以及它们的类型被统称为函数签名。
Go语言是不允许重载的。
值传递 引用传递
默认值传递,也可以传递指针&arg
。一些数据类型如slice
、map
、interface
、channel
是默认引用传递的。
Go推荐使用明明返回值,例:
// 非命名返回(不推荐)
func getX2AndX3(input int) (int, int) {
return 2 * input, 3 * input
}
// 命名返回值
func getX2AndX3_2(input int) (x2 int, x3 int) {
x2 = 2 * input
x3 = 3 * input
// return x2, x3
return
}
_
空白符可以用来存放一些不需要的返回值。将返回值赋予_
则会被自动丢弃。
引用传递的一个例子
package main
import (
"fmt"
)
// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}
func main() {
n := 0
reply := &n
Multiply(10, 5, reply)
fmt.Println("Multiply:", *reply) // Multiply: 50
}
函数Multiply(a, b int, reply *int)
没有返回值,但是通过reply指针修改了外部的值。
变长参数
func myFunc(a, b, ...int)
变长参数可以作为对应类型的 slice 进行二次传递。
例子:一个接受变长参数的函数,每个元素换行打印。其中printS2
接受的是[]string
类型的参数。
func main() {
var s string = "你好啊"
s2 := "中国"
s3 := "哈喽"
printS(s, s2, s3)
}
func printS(s ...string) {
if len(s) == 0{
fmt.Println("string is empty")
}
fmt.Println("this is printS")
for _, val := range s{
fmt.Printf("%s\n", val)
}
printS2(s)
}
func printS2(s []string){
fmt.Println("this is printS2")
for _, val := range s{
fmt.Printf("%s\n", val)
}
}
defer与追踪
关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return
语句之后)一刻才执行某个语句或函数。
func main() {
function1()
}
func function1() {
fmt.Printf("In function1 at the top\n")
defer function2()
fmt.Printf("In function1 at the bottom!\n")
}
func function2() {
fmt.Printf("Function2: Deferred until the end of the calling function!")
}
/*
out put:
In function1 at the top
In function1 at the bottom!
Function2: Deferred until the end of the calling function!
*/
defer将function2()推迟到function1()执行完毕再执行。
看一些实际的例子更好理解:
1。 关闭文件流
defer file.Close()
2。释放锁
mu.Lock()
defer mu.Unlock()
3。打印最终报告
printHeader()
defer printFooter()
4。关闭数据库连接
printHeader()
defer printFooter()
合理使用defer
让代码更加易读和优雅。
使用defer实现代码追踪
func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
func a() {
trace("a")
defer untrace("a")
fmt.Println("in a")
}
func b() {
trace("b")
defer untrace("b")
fmt.Println("in b")
a()
}
func main() {
b()
}
/*
output
entering: b
in b
entering: a
in a
leaving: a
leaving: b
*/
内置函数
递归函数
经典如fib数列
func fibonacci(n int) (res int) {
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
return
}
递归实现的几个函数:
// 累加
func func1(i int) (sum int){
if i == 0 {
return sum
}
sum += i
i -= 1
return sum + func1(i)
}
// 递归打印
func func2(i int){
if i == 0{
return
}
fmt.Println(i)
i -= 1
func2(i)
}
// 阶乘(会益处,可以使用big包)
func func3(i int)(out int){
if i == 0 {
return 1
}
return i * func3(i-1)
}
将函数作为参数
函数可以作为其他函数的参数进行传递,然后在其他函数内调用,这种做法称为回调
闭包
匿名函数,例
func main(){
f()
}
func f(){
fv := func() { fmt.Println("hello world")}
fv()
}
匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁,详见第 6.9 节中的示例。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装,详见下一节中的示例。另一个不错的应用就是使用闭包来完成更加简洁的错误检查(详见第 16.10.2 节)
工厂函数
一个返回值为另一个函数的函数可以被称为工厂函数。理解例子:
func MakeAddSuffix(suffix string) func(string) string {
return func(name string) string {
if !strings.HasSuffix(name, suffix) {
return name + suffix
}
return name
}
}
func main(){
addJpeg := MakeAddSuffix(".jpeg")
addBmp := MakeAddSuffix(".bmp")
println(addJpeg("file"))
println(addBmp("file2"))
}
// output:
// file.jpeg
// file2.bmp
MakeAddSuffix
的签名是func(string) string
,他的返回值是一个匿名函数func(name string) string
,这个匿名函数的作用是检查字符串是否以suffix
结尾。
addJPG := MakeAddSuffix(".jpeg")
// 相当于
addJPG
func(name string) string{
if !strings.HasSuffix(name, ".jpeg"){
return name + suffix
}
return name
}
使用闭包调试
使用runtime
或者log
包来定位函数执行的位置。
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()
计算函数耗时
time
包中的Now()
和Sub()
通过内存缓存来提升性能
存在大量重复计算的时候可以用使用这种方法,用空间换取时间。
例:
// fib_memorization
const LIM = 41
var fibs [LIM]uint64
func main() {
var res uint64 = 0
for i := 0 ; i < LIM ; i++{
res = fibo(i)
fmt.Printf("fit %d result: %d\n", i, res)
}
}
func fibo(n int) (res uint64) {
if fibs[n] != 0{
res = fibs[n]
return
}
if n <= 1 {
res = 1
} else {
res = fibo(n-1) + fibo(n-2)
}
fibs[n] = res
return
}