the-way-to-go day2

本文探讨了Go语言的if-else结构、变量作用域、多返回值函数、错误处理、switch语句、for循环的使用技巧、闭包、工厂函数及defer特性。通过实例解析了控制结构在实际编程中的应用和最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

控制结构

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。一些数据类型如slicemapinterfacechannel是默认引用传递的。

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
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值