- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
本文面向《Build Web Application with Golang》土耳其语版第 2.3 章《Kontrol ifadeleri ve fonksiyonlar》(控制语句与函数),围绕 Go 语言的控制流与函数体系展开系统讲解。读者将掌握
if/goto/for/switch的完整语法与使用陷阱,理解函数定义、多返回值、变参函数、值传递与指针传递、defer延迟执行、函数类型与高阶函数、panic/recover错误处理以及main/init与import的包机制,并能在后续章节的 Web 开发实战中直接运用这些基础能力。
引言:为什么控制语句与函数是 Web 开发的地基
任何一门编程语言,控制语句(分支、循环、跳转)与函数都是组织逻辑的最小单元。在 Go 中,它们的语法经过刻意精简——没有括号、没有while、没有try-catch、没有类继承,但保留了goto、defer、panic/recover等独特设计。本章(tr/02.3.md)将这些基础能力逐一拆解,为后续讲解 Web 请求处理、路由分发、数据库访问等章节打下语法基础。
仓库中与本章对应的完整可运行示例位于 de/code/src/apps/ch.2.3,包含main.go(控制语句汇总)、basic_functions、variadic_functions、pass_by_value_and_pointer、type_function、panic_and_recover、import_packages等子目录,本文会结合这些源码逐一印证。
一、控制语句:分支、跳转与循环
1.1if分支:无需括号,支持前置语句
if是最常用的关键字。Go 与 C/Java 最大的不同在于条件表达式不需要括号,但代码块必须使用大括号:
if x > 10 { fmt.Println("x 10'dan büyüktür") // x 大于 10 } else { fmt.Println("x 10'dan küçük veya eşittir") // x 小于或等于 10 }Go 还有一个非常实用的特性:在条件表达式之前声明变量,该变量的作用域仅限于if/else块内,块外无法访问(否则编译失败):
// 先定义 x,再检查是否大于 10 if x := computedValue(); x > 10 { fmt.Println("x 10'dan büyüktür") } else { fmt.Println("x 10'dan küçüktür") } // 这一行无法通过编译,因为 x 的作用域已结束 // fmt.Println(x)仓库示例 de/code/src/apps/ch.2.3/main.go 中的show_if_var()函数注释也明确说明:x只在该if/else块内可访问。多条件分支使用if-else if-else链:
if integer == 3 { fmt.Println("Sayı 3'e eşittir") // 等于 3 } else if integer < 3 { fmt.Println("Sayı 3'ten küçüktür") // 小于 3 } else { fmt.Println("Sayı 3'ten büyüktür") // 大于 3 }1.2goto跳转:谨慎使用
Go 保留goto关键字,可将程序控制流跳转到**同一函数体内预先定义的标签(label)**处。标签以冒号结尾,名称区分大小写:
func myFunc() { i := 0 Here: // 标签末尾加冒号 fmt.Println(i) i++ if i < 10 { goto Here // 跳转到 "Here" 标签 } }仓库示例 de/code/src/apps/ch.2.3/main.go 用goto加条件实现了循环效果。需要特别提醒:goto容易破坏代码可读性,实际开发中优先使用for循环。
1.3for循环:Go 唯一的循环语句
for是 Go 中最强大的控制逻辑,既能做经典的计数循环,也能当while使用。完整语法:
for ifade1; ifade2; ifade3 { // ... }ifade1:进入循环前执行一次(变量初始化);ifade2:循环条件(控制表达式);ifade3:每次循环结束时执行(迭代步进)。
示例:求 0~9 的和:
package main import "fmt" func main() { sum := 0 for index := 0; index < 10; index++ { sum += index } fmt.Println("sayıların toplamı= ", sum) } // 输出:sayıların toplamı= 45注意:Go 没有,逗号运算符,多变量更新应使用并行赋值i, j = i + 1, j - 1。
省略初始化与步进:如果不需要初始化或步进,可以省略:
sum := 1 for ; sum < 1000; { sum += sum }连分号一起省略:此时for就完全等价于while:
sum := 1 for sum < 1000 { sum += sum }break与continue:break跳出循环,continue跳过当前迭代进入下一次:
for index := 10; index > 0; index-- { if index == 5 { break // 或 continue } fmt.Println(index) } // 若为 break:输出 10、9、8、7、6 // 若为 continue:输出 10、9、8、7、6、4、3、2、1多层嵌套循环中,break还可以配合标签跳出指定外层循环。
range遍历:for配合range可以遍历slice、map等集合。遍历map时返回键和值:
for k, v := range m { fmt.Println("map'in k anahtarı:", k) // map 的键 fmt.Println("map'in k anahtarındaki değeri:", v) // 键对应的值 }由于 Go 支持多返回值且未使用的变量会导致编译错误,不需要的返回值用_忽略:
for _, v := range m { fmt.Println("map'in değeri:", v) }仓库示例 de/code/src/apps/ch.2.3/main.go 的show_loop_through_map()完整演示了 map 的range遍历。
1.4switch分支:自动 break,可多值匹配
当if-else链过长时,代码难以阅读和维护,此时switch是更好的选择:
switch anaIfade { case ifade1: // 匹配时执行的代码 case ifade2: // 匹配时执行的代码 case ifade3: // 匹配时执行的代码 default: // 都不匹配时执行的代码 }switch后的表达式与各case表达式的类型必须一致。switch非常灵活:条件不必是常量,会从上到下依次匹配直到条件满足;如果switch后不带表达式,则等价于switch true。
i := 10 switch i { case 1: fmt.Println("i 1'e eşittir") // i 等于 1 case 2, 3, 4: fmt.Println("i 2, 3 veya 4'e eşittir") // i 等于 2、3 或 4 case 10: fmt.Println("i 10'a eşittir") // i 等于 10 default: fmt.Println("Tek bildiğim i nin bir sayi olduğu") // 只知道 i 是数字 }关键特性:单个case可以匹配多个值(如case 2, 3, 4);case末尾无需写break,匹配执行后自动跳出。若希望匹配后继续执行下一个case,使用fallthrough:
sayı := 6 switch sayı { case 4: fmt.Println("sayı <= 4") fallthrough case 5: fmt.Println("sayı <= 5") fallthrough case 6: fmt.Println("sayı <= 6") fallthrough case 7: fmt.Println("sayı <= 7") fallthrough case 8: fmt.Println("sayı <= 8") fallthrough default: fmt.Println("default durumu") }程序输出:
sayı <= 6 sayı <= 7 sayı <= 8 default durumu可见fallthrough会无条件进入下一个case(不判断其条件),直至default。仓库示例 de/code/src/apps/ch.2.3/main.go 完整复现了上述switch与fallthrough的行为。
二、函数:定义、调用与多返回值
2.1 函数定义与基本语法
使用func关键字定义函数:
func fonksiyonAdı(parametre1 tür1, parametre2 tür2) (çıkış1 tür1, çıkış2 tür2) { // 函数体 // 可以有多处 return return dönüş1, dönüş2 }语法要点:
func关键字后跟函数名;- 函数可以有零个或多个参数,参数类型写在参数名之后,多个参数用
,分隔; - 函数可以返回多个值;
- 返回值的名字(如
çıkış1、çıkış2)可省略,只写类型; - 若只返回一个值,返回值不能加括号;
- 只要函数有返回值,函数体内就必须使用
return语句。
经典示例——计算两个数的最大值(de/code/src/apps/ch.2.3/basic_functions/main.go):
package 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(a, b int)的写法:当多个连续参数类型相同时,只需在最后一个参数后写一次类型,等价于a int, b int。max只返回一个值,因此无需命名返回值。
2.2 多返回值:Go 优于 C 的特性
Go 原生支持函数返回多个值。示例:同时返回两数的和与积(de/code/src/apps/ch.2.3/variadic_functions/main.go 目录中同名示例):
package main import "fmt" // 返回 A+B 与 A*B 的结果 func SumAndProduct(A, B int) (int, int) { return A + B, 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,编译器会自动返回这些命名变量:
func SumAndProduct(A, B int) (toplanan int, carpılan int) { toplanan = A + B carpılan = A * B return }最佳实践提醒:如果函数要在其他包中使用(函数名必须以大写字母开头),建议在return中明确写出完整的返回表达式,这样代码更易读。
2.3 变参函数(Variadic Functions)
Go 支持接收不定数量参数的函数,称为变参函数:
func myfunc(arg ...int) {}arg ...int告诉 Go 该函数接收可变数量的int参数,在函数体内arg是一个int类型的slice,可用range遍历:
for _, n := range arg { fmt.Printf("Sayı: %d\n", n) }2.4 值传递与指针:理解参数的两种传递方式
调用函数传参时,默认是值传递——函数拿到的是变量的副本,对副本的修改不会影响原变量。看这个例子:
package main import "fmt" // 给 a 加 1 的简单函数 func addOne(a int) int { a = a + 1 // 修改的是副本 return a // 返回新值 } func main() { x := 3 fmt.Println("x = ", x) // 输出 "x = 3" x1 := addOne(x) fmt.Println("x+1 = ", x1) // 输出 "x+1 = 4" fmt.Println("x = ", x) // 输出 "x = 3",x 未被改变 }想要修改原变量,必须传递变量的内存地址(指针)。参数类型改为*int,调用时用&x取地址:
package main import "fmt" func addOne(a *int) int { *a = *a + 1 // 通过指针修改原值 return *a } func main() { x := 3 fmt.Println("x = ", x) // 输出 "x = 3" x1 := addOne(&x) // 传入 x 的地址 fmt.Println("x+1 = ", x1) // 输出 "x+1 = 4" fmt.Println("x = ", x) // 输出 "x = 4",x 已被修改 }仓库示例 de/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go 用add_by_value与add_by_reference两个函数对照演示了这两种方式。
为什么使用指针?
- 允许多个函数共同操作同一个变量;
- 传递地址(8 字节)开销小,复制整个值(尤其是大结构体)在时间和空间上都不经济;
string、slice、map是引用类型,传参时默认按引用传递。注意:若想修改slice的长度,必须显式使用指针。
2.5defer:延迟执行,资源管理的利器
Go 提供了设计精良的defer(延迟)机制。一个函数内可以有多个defer语句,程序退出该函数时按后进先出(LIFO)顺序逆序执行。
以文件操作为例,不使用defer时,每个错误分支都要手动关闭文件,代码重复且易遗漏:
func ReadWrite() bool { file.Open("dosya") // 代码 if errorX { file.Close() return false } if errorY { file.Close() return false } file.Close() return true }使用defer后,关闭动作只写一次,且在函数返回前必然执行:
func ReadWrite() bool { file.Open("dosya") defer file.Close() if errorX { return false } if errorY { return false } return true }多个defer逆序执行,下面的代码会输出4 3 2 1 0:
for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) }仓库示例 de/code/src/apps/ch.2.3/main.go 的show_defer()也演示了多个defer的逆序输出效果。
2.6 函数作为值与类型:高阶函数
在 Go 中,函数也是一种值,可以用type定义函数类型。签名相同的函数被视为同一类型:
type türAdı func(parametre1 tür1, parametre2 tür2 [, ...]) (çıkış1 tür1 [, ...])这带来的最大好处是:函数可以作为参数传递。经典示例——用函数过滤切片(de/code/src/apps/ch.2.3/type_function/main.go):
package main import "fmt" type testInt func(int) bool // 定义函数类型变量 func isOdd(sayı int) bool { if sayı%2 == 0 { return false } return true } func isEven(sayı int) bool { if sayı%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("Slice'daki tek sayılar: ", odd) // 奇数 even := filter(slice, isEven) fmt.Println("Slice'daki çift sayılar: ", even) // 偶数 }testInt是一个函数类型变量,filter的参数和返回值与之匹配,从而实现把行为(判定奇偶)注入函数的高阶编程。这在配合接口使用时尤为有用,能让程序具备更强的灵活性与可扩展性。
2.7panic与recover:Go 的错误处理机制
Go 没有像 Java 那样的try-catch结构,而是用panic和recover处理异常情况。虽然有效,但panic不应过度使用。
panic:用于中断程序的正常执行流程并进入恐慌状态。当函数F调用panic时:F停止执行 → 执行F中已延迟的defer函数 →F返回到恐慌发生的位置。整个调用链上所有函数都会像F一样回退,直到当前goroutine的最顶层,程序才会终止。panic可以由程序显式调用触发,也可能由数组越界等运行时错误引发。
recover:用于把goroutine从恐慌状态中恢复。在defer函数内调用recover很有用(恐慌时普通函数已停止执行)。调用时,它会捕获传入panic的值;若没有发生恐慌,则返回nil。
panic的典型用法——环境变量缺失时直接终止(见 de/code/src/apps/ch.2.3/panic_and_recover/main.go):
var user = os.Getenv("USER") func init() { if user == "" { panic("$USER için bir değer bulunamadı") // 未找到 $USER 的值 } }panic的恢复示例——用defer+recover捕获恐慌并返回标志:
func panicYapar(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() // 如果 f() 引发 panic,会被 recover 捕获 return }三、程序启动与包机制:main、init与import
3.1main与init的执行顺序
init函数可以在所有包中使用,main函数只能在main包中使用;- 两者都没有参数、不返回任何值;
- 一个包内可以写多个
init,但强烈建议只写一个; - 每个包的
init是可选的,但main包必须且只能有一个main函数。
程序从main包开始执行。若main导入了其他包,编译时会一并纳入;即使一个包被多次导入,也只会被编译一次。初始化顺序如下:
- 包导入完成后,程序先初始化被导入包的变量与常量,再执行其
init; - 所有被导入包初始化完毕后,程序再初始化
main包的变量与常量,执行main包的init; - 最后执行
main函数。
下图(tr/images/2.3.init.png)展示了 Go 程序中包初始化的完整流程:
(图 2.6:Go 程序中包的初始化流程)
仓库示例 de/code/src/apps/ch.2.3/type_function/main.go 中定义了一个init(),运行程序时会先打印#init() was called.再进入main,直观印证了这一执行顺序。
3.2import:导入包的三种方式
Go 程序中的import非常常见:
import( "fmt" )导入后即可调用包内函数:
fmt.Println("hello world")fmt位于 Go 标准库,在$GOROOT/pkg目录下。Go 支持两种导入第三方包的路径写法:
- 相对路径:
import "./model" // 加载同目录下的包,不推荐这种方式 - 绝对路径:
import "shorturl/model" // 加载 "$GOPATH/pkg/shorturl/model" 下的包
仓库示例 de/code/src/apps/ch.2.3/import_packages/main.go 也明确指出:Go 不推荐相对路径导入,应使用$GOPATH/src为基准的完整路径(如apps/ch.2.3/import_packages/only_call_init)。
导入时还有三种特殊运算符,常令初学者困惑:
1. 点运算符(.)——导入后无需写包名即可调用其函数:
import( . "fmt" ) // 之后可直接写 Printf("Hello world"),而不必写 fmt.Printf("Hello world")2. 别名(f)——为包起别名:
import( f "fmt" ) // 之后写 f.Printf("Hello world") 代替 fmt.Printf("Hello world")3. 空标识符(_)——只导入包的init副作用,不直接使用其函数:
import ( "database/sql" _ "github.com/ziutek/mymysql/godrv" // 仅执行该包的 init() )该运算符常用于数据库驱动注册等场景:包通过init完成自注册,之后程序通过标准接口调用,无需直接引用该包。仓库示例 de/code/src/apps/ch.2.3/import_packages/main.go 中_ "apps/ch.2.3/import_packages/only_call_init"只触发only_call_init包的init()(见 de/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go),运行时仅打印only_call_init.init() was called.。
四、本章小结与实战建议
本章(tr/02.3.md)覆盖了 Go 语言编程的两大基石:
- 控制流:
if(无括号 + 前置语句)、goto(谨慎使用)、for(可当while用、range遍历集合、break/continue控制跳转)、switch(自动 break、多值 case、fallthrough穿透); - 函数体系:多返回值、命名返回值、变参函数、值传递与指针传递、
defer延迟清理、函数类型与高阶函数、panic/recover错误处理、main/init执行顺序与import的三种导入方式。
这些能力在后续 Web 开发实战中无处不在:路由分发依赖switch与函数类型,HTTP 处理器大量使用多返回值与defer关闭资源,数据库驱动的_导入、panic/recover守护程序稳定性等。建议读者在掌握本文示例后,动手运行仓库中的 de/code/src/apps/ch.2.3 系列示例,观察if前置变量的作用域、defer的逆序输出、fallthrough的穿透行为与init的执行时机,从而将这些语法真正内化为编写健壮 Web 应用的基本功。
继续阅读下一章 struct(结构体),即可学习用本章的函数与下一章的类型系统组合出更复杂的数据模型;如需回顾基础语法,可回到上一章 Go 基础或查看全书导读。
- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
相关推荐
Go 控制语句与函数实战指南:从《Build Web Application with Golang》第二章掌握 Go 核心语法
Go 控制语句与函数实战指南:从《Build Web Application with Golang》第二章掌握 Go 核心语法 本篇技术指南基于开源项目 bu
文档教程Go 控制语句与函数完全指南:以 build-web-application-with-golang 第 2.3 章为例掌握流程控制与函数机制
Go 控制语句与函数完全指南:以 build web application with golang 第 2.3 章为例掌握流程控制与函数机制 本文是开源 Go
文档教程Build Web Application with Golang 第二章精读:Go 的控制语句、函数与 panic/recover 机制
Build Web Application with Golang 第二章精读:Go 的控制语句、函数与 panic/recover 机制 本篇技术指南围绕开源
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考