ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

build-web-application-with-golang 第 2.3 章精讲:Go 控制语句与函数体系实战

build-web-application-with-golang 第 2.3 章精讲:Go 控制语句与函数体系实战 文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载Go 语言的流程控制与函数设计是 Web 后端开发的根基也是本开源电子书《Build Web Application with Golang》第 2.3 节的核心内容。本篇以 pt-br/02.3.md 为骨架结合仓库 pt-br/code/src/apps/ch.2.3 下的可运行示例源码系统讲解 Go 的if/goto/for/switch四大控制语句、函数定义与多返回值、变参、值传递与指针、defer、函数类型、panic/recover、init/main以及import的完整知识并给出可复制、可运行、可验证的实战代码。读完本节你将能独立写出控制流清晰、函数组织合理的 Go 程序并理解 Web 服务中常见的资源释放、错误恢复与包初始化机制。一、Go 流程控制从 if 到 goto流程控制是编程史上最重要的发明之一它让程序员用简单的控制语句表达复杂的逻辑。Go 的流程控制分为三类条件控制、循环控制和无条件跳转。本节代码示例集中在仓库 ch.2.3/main.go。1. if无需括号、支持初始化语句if可能是你在程序中用得最多的关键字满足条件就执行一件事否则执行另一件事。与 C 系语言不同Go 的if不需要括号if x 10 { fmt.Println(x is greater than 10) } else { fmt.Println(x is less than or equal to 10) }Go 的if最有用的特性是条件之前可以带一条初始化语句且这条语句中定义的变量作用域仅限if/else块内部块外无法访问// 初始化 x然后检查 x 是否大于 10 if x : computedValue(); x 10 { fmt.Println(x is greater than 10) } else { fmt.Println(x is less than 10) } // 以下代码无法编译因为 x 只在 if/else 块内可见 // fmt.Println(x)多条件判断使用if-else if-else链if integer 3 { fmt.Println(The integer is equal to 3) } else if integer 3 { fmt.Println(The integer is less than 3) } else { fmt.Println(The integer is greater than 3) }仓库 ch.2.3/main.go 中的show_if()与show_if_var()分别验证了普通条件判断与带初始化语句的if写法其中被注释掉的fmt.Println(x)正是对初始化变量仅限块内可见这一规则的编译期验证。2. goto谨慎使用的无条件跳转Go 保留了goto关键字它把控制流重新定向到同一函数内先前定义的 label。label 以冒号结尾且名称区分大小写func myFunc() { i : 0 Here: // label 以 : 结尾 fmt.Println(i) i goto Here // 跳转到 label Here }注意goto容易造成代码逻辑混乱应谨慎使用。仓库中的show_goto()加了if i 10的退出条件避免写成死循环可以作为安全使用goto的参考写法。二、for 循环Go 中唯一、也是最强大的循环Go 没有while和do-while循环统一由for承担。标准三表达式形式如下for expression1; expression2; expression3 { //... }其中expression1和expression3是变量定义或赋值expression2是条件判断。expression1在循环开始前执行一次expression3在每次迭代结束后执行。经典求和示例package main import fmt func main(){ sum : 0; for index:0; index 10 ; index { sum index } fmt.Println(sum is equal to , sum) } // 输出 sum is equal to 45需要多个赋值时Go 没有逗号运算符应使用并行赋值如i, j i 1, j - 1。for非常灵活可以省略不需要的表达式。省略expression1和expression3后sum : 1 for ; sum 1000; { sum sum }再省略分号就变成了与while完全等价的写法sum : 1 for sum 1000 { sum sum }循环中两个关键操作是break和continuebreak跳出整个循环continue跳过本次迭代进入下一轮在嵌套循环中break需要配合 label 使用for index : 10; index0; index-- { if index 5{ break // 或 continue } fmt.Println(index) } // break 输出 10、9、8、7、6 // continue 输出 10、9、8、7、6、4、3、2、1for与range配合可以遍历slice或mapfor k,v:range map { fmt.Println(maps key:,k) fmt.Println(maps val:,v) }Go 支持多返回值且对未使用的变量会报编译错误因此常用_丢弃不需要的返回值for _, v : range map{ fmt.Println(maps val:, v) }仓库 ch.2.3/main.go 的show_for_loop()与show_loop_through_map()完整演示了上述四种循环形态其中for sum 1000的注释也印证了编译器可自动移除多余分号这一行为。三、switch替代冗长 if-else 的多分支选择当if-else链越来越长、可读性和可维护性下降时应改用switch。基本结构switch sExpr { case expr1: some instructions case expr2: some other instructions case expr3: some other instructions default: other code }sExpr、expr1、expr2、expr3的类型必须一致。switch非常灵活条件不必是常量且按从上到下的顺序匹配第一个成立的分支如果switch后面没有表达式则等价于匹配true可用于布尔条件判断。看下面的经典例子i : 10 switch i { case 1: fmt.Println(i is equal to 1) case 2, 3, 4: fmt.Println(i is equal to 2, 3 or 4) case 10: fmt.Println(i is equal to 10) default: fmt.Println(All I know is that i is an integer) }注意两个与 C 系语言的关键差异一个case可以匹配多个值如case 2, 3, 4:匹配成功后不需要break会自动跳出switch块。如果希望继续匹配后续分支必须显式使用fallthrough。fallthrough示例integer : 6 switch integer { case 4: fmt.Println(integer 4) fallthrough case 5: fmt.Println(integer 5) fallthrough case 6: fmt.Println(integer 6) fallthrough case 7: fmt.Println(integer 7) fallthrough case 8: fmt.Println(integer 8) fallthrough default: fmt.Println(default case) }该程序输出integer 6 integer 7 integer 8 default case即从命中的case 6开始fallthrough让后续分支无条件依次执行直到default。四、函数基础定义、参数与多返回值使用func关键字定义函数完整语法如下func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { // 函数体 // 返回多个值 return value1, value2 }从中可以归纳出 Go 函数的关键规则用func定义函数funcName函数有零个、一个或多个参数参数类型写在参数名之后多个参数用逗号分隔函数可以返回多个值返回值可以命名如output1、output2也可以省略名字只写类型只有一个返回值且省略名字时返回值不需要括号函数没有返回值时可以完全省略返回值声明函数有返回值时函数体中必须使用return语句。看一个求最大值的实际例子见仓库 basic_functions/main.gopackage main import fmt // 返回 a 和 b 中的较大值 func max(a, b int) int { if a b { return a } return b } func main() { x : 3 y : 4 z : 5 max_xy : max(x, y) // 调用 max(x, y) max_xz : max(x, z) // 调用 max(x, z) fmt.Printf(max(%d, %d) %d\n, x, y, max_xy) fmt.Printf(max(%d, %d) %d\n, x, z, max_xz) fmt.Printf(max(%d, %d) %d\n, y, z, max(y,z)) // 直接内联调用 }max的两个参数都是int类型因此可以省略第一个参数的类型写成a, b int这条省略规则同样适用于更多参数。max只有一个返回值直接写返回类型即可这是它的简写形式。1. 多返回值Go 优于 C 的特性Go 原生支持多返回值见仓库 variadic_functions/main.go 中实现的SumAndProductpackage main import fmt // 返回 A B 与 A * B 的结果 func SumAndProduct(A, B int) (int, int) { return AB, A*B } func main() { x : 3 y : 4 xPLUSy, xTIMESy : SumAndProduct(x, y) fmt.Printf(%d %d %d\n, x, y, xPLUSy) fmt.Printf(%d * %d %d\n, x, y, xTIMESy) }上面的例子返回两个未命名的值也可以给返回值命名。命名返回值在函数内会被自动初始化只需写return即可返回若函数将被包外使用函数名以大写字母开头建议写出完整的return语句代码可读性更好func SumAndProduct(A, B int) (add int, Multiplied int) { add AB Multiplied A*B return }2. 可变参数Go 支持可变参数即函数可以接收不定数量的参数func myfunc(arg ...int) {}arg ...int表示这是一个可变参数函数参数类型为int。在函数体内arg会变成一个int类型的slice可以用range遍历for _, n : range arg { fmt.Printf(And the number is: %d\n, n) }五、值传递与指针理解 Go 参数传递的本质调用函数时传入的是原始变量的副本函数内的修改不会影响原变量。看这个证明示例仓库 pass_by_value_and_pointer/main.gopackage main import fmt // 给变量 a 加 1 的简单函数 func add1(a int) int { a a1 // 修改 a 的值 return a // 返回 a 的新值 } func main() { x : 3 fmt.Println(x , x) // 输出 x 3 x1 : add1(x) // 调用 add1(x) fmt.Println(x1 , x1) // 输出 x1 4 fmt.Println(x , x) // 输出 x 3 }虽然向add1传入了x但x的原始值并未改变原因很简单传给函数的是x的副本。如果想修改原始x就需要用到指针。变量存储在内存中拥有内存地址要修改变量的值就要修改该地址上存储的内容。因此add1需要知道x的内存地址即传入x并把参数类型改为指针类型*int。注意这里传的是指针的副本而不是值的副本package main import fmt // 给变量 a 加 1 的简单函数 func add1(a *int) int { *a *a1 // 修改 a 指向的值 return *a // 返回新值 } func main() { x : 3 fmt.Println(x , x) // 输出 x 3 x1 : add1(x) // 传入 x 的内存地址 fmt.Println(x1 , x1) // 输出 x1 4 fmt.Println(x , x) // 输出 x 4 }现在函数内部可以修改x的值了。为什么使用指针主要优势有三点允许更多函数共同操作同一个变量传递内存地址约 8 字节开销很低复制大变量在时间和空间上都不高效string、slice、map是引用类型默认以指针方式传入函数注意如果需要修改slice的长度仍需显式传递指针。六、defer延迟执行与资源释放defer是 Go 中设计精巧的关键字专门用于在函数返回前执行清理逻辑。一个函数中可以有多条defer语句它们按声明顺序的逆序执行。最典型的场景是文件资源释放——传统写法需要在每个错误分支重复file.Close()func ReadWrite() bool { file.Open(file) // 做一些任务 if failureX { file.Close() return false } if failureY { file.Close() return false } file.Close() return true }上面这段代码反复重复关闭逻辑。defer完美解决这个问题既让代码更整洁也更易读func ReadWrite() bool { file.Open(file) defer file.Close() if failureX { return false } if failureY { return false } return true }多条defer逆序执行的验证下面的代码会输出4 3 2 1 0。for i : 0; i 5; i { defer fmt.Printf(%d , i) }仓库 ch.2.3/main.go 的show_defer()用defer fmt.Println((last defer))与循环内的defer组合直观展示了 LIFO 的执行顺序。这也是编写 Web 服务时关闭数据库连接、响应体、锁等资源的标准模式。七、函数作为类型与值高阶函数的基础在 Go 中函数也是变量可以用type定义函数类型。签名相同的函数可以视为同一类型type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])这一特性最大的价值是允许把函数作为值传递。看仓库 type_function/main.go 的完整示例package main import fmt type testInt func(int) bool // 将函数定义为变量类型 func isOdd(integer int) bool { if integer%2 0 { return false } return true } func isEven(integer int) bool { if integer%2 0 { return true } return false } // 把函数 f 作为参数传给另一个函数 func filter(slice []int, f testInt) []int { var result []int for _, value : range slice { if f(value) { result append(result, value) } } return result } func main(){ slice : []int {1, 2, 3, 4, 5, 7} fmt.Println(slice , slice) odd : filter(slice, isOdd) // 把函数当作值使用 fmt.Println(Odd elements of slice are: , odd) even : filter(slice, isEven) fmt.Println(Even elements of slice are: , even) }这在配合接口使用本书第 2.6 节会深入时尤其有用testInt是一个函数类型变量filter的参数与返回值与testInt一致因此我们可以在保持代码灵活性的同时实现复杂逻辑——这正是高阶函数与依赖注入在 Go 中的实现基础。八、panic 与 recoverGo 的错误处理机制Go 没有 Java 式的try-catch结构而是用panic和recover处理错误。不过panic虽然强大不应频繁使用。panic是内建函数用于中断程序正常流程、进入恐慌状态。当函数F调用panic时F停止执行但其defer函数仍会执行随后F回到引发恐慌的调用点程序会一直向上回溯直到所有函数都以 panic 返回到 goroutine 的最顶层程序才会终止。panic可以由程序显式执行panic语句触发某些错误如访问数组的非法下标也会自动触发。recover是内建函数用于让 goroutine 从恐慌状态中恢复。把recover放在defer函数中才有意义因为程序处于恐慌状态时普通函数不会再执行。程序处于恐慌状态时recover会取回panic传入的值否则返回nil。触发panic的示例仓库 panic_and_recover/main.govar user os.Getenv(USER) func init() { if user { panic(no value for $USER) } }检查并恢复panic的示例func throwsPanic(f func()) (b bool) { defer func() { if x : recover(); x ! nil { b true } }() f() // 如果 f 引发恐慌这里会被恢复 return }仓库中的check_user()在$USER为空时主动panic(no value for $USER)而throwsPanic通过deferrecover捕获恐慌并返回true最终main打印didPanic true——这是一个完整可运行的制造恐慌 → 恢复恐慌闭环也是 Web 服务中防止单个请求崩溃拖垮整个进程的兜底模式。九、main 与 init程序初始化的先后顺序Go 有两类保留函数init和main。init可以出现在所有包中main只能出现在main包中且两者都不能带参数和返回值。虽然一个包可以写多个init函数但官方建议每个包只写一个。Go 程序会自动调用init()与main()无需手工触发。对每个包而言init是可选的但main包必须有且仅有一个main函数。程序的初始化与执行顺序如下程序从main包开始初始化如果main包导入了其他包这些包会在编译期被导入重复导入的包只编译一次导入包后依次初始化这些包内的常量与变量然后执行其中的init函数所有其他包初始化完毕后初始化main包内的常量与变量再执行main包的init函数最后执行main函数。上面的流程如下图所示。仓库 type_function/main.go 中定义了func init() { fmt.Println(\n#init() was called.) }运行该程序时你会看到#init() was called.在main输出之前打印——这是对初始化顺序最直接的运行期验证。十、import包导入的三种形式与三个特殊运算符Go 程序几乎离不开import标准写法如下import( fmt )之后即可使用该包的函数fmt.Println(hello world)fmt是 Go 标准库包位于$GOROOT/pkg。Go 支持两种路径方式导入第三方包相对路径import ./model加载同一目录下的包——不推荐这种写法绝对路径import shorturl/model加载$GOPATH/pkg/shorturl/model路径下的包。导入包时还有三个容易让初学者混淆的特殊运算符综合演示见仓库 import_packages/main.go1. 点运算符导入时省略包名调用包内函数时无需前缀import( . fmt )于是fmt.Printf(Hello world)可以写成Printf(Hello world)。2. 别名运算符为导入的包起别名调用时使用别名import( f fmt )于是fmt.Printf(Hello world)写成f.Printf(Hello world)。3. 下划线运算符_表示只导入该包并执行它的init函数不直接使用包内函数import ( database/sql _ github.com/ziutek/mymysql/godrv )该模式常用于数据库驱动的注册场景驱动包通过init完成自身注册业务代码只引用database/sql的标准接口。仓库 only_call_init/only_call_init.go 定义了一个只含init打印语句的包import_packages/main.go 用_ apps/ch.2.3/import_packages/only_call_init导入它——运行后only_call_init.init() was called.会在程序启动时被打印验证了_导入只触发init的行为同时该文件还用f fmt、. math和自定义包mymath见 pt-br/code/src/mymath/sqrt.go分别演示了别名、点导入与绝对路径导入。延伸阅读本节的代码样例全部位于 pt-br/code/src/apps/ch.2.3可对照原文逐一运行验证上一节Go 语言基础下一节struct 结构体完整目录见 pt-br/SUMMARY.md。赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐Build Web Application with Golang 第二章精读Go 的控制语句、函数与 panic/recover 机制Build Web Application with Golang 第二章精读Go 的控制语句、函数与 panic/recover 机制 本篇技术指南围绕开源文档教程Go 控制语句与函数实战指南从《Build Web Application with Golang》第二章掌握 Go 核心语法Go 控制语句与函数实战指南从《Build Web Application with Golang》第二章掌握 Go 核心语法 本篇技术指南基于开源项目 bu文档教程Build Web Application with Golang 第 2.3 节Go 语言控制结构与函数完全指南Build Web Application with Golang 第 2.3 节Go 语言控制结构与函数完全指南 本篇指南源自开源电子书《Build Web文档教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表