上一篇我们学了 Go 的基础语法,所有代码都写在 main 函数里。但真实的程序不可能把所有逻辑塞在一个函数中,我们需要把代码拆分成多个函数,让代码更清晰。
这篇还会讲 Go 的指针。Go 的指针很简单——没有指针运算,只有取地址和解引用,够用就行。另外,Go 的函数还支持多返回值,非常实用。
函数
函数就是一段可以重复调用的代码块,给它一个名字,需要的时候调用就行。
定义与调用
一个函数由 func 关键字、函数名、参数列表、返回类型和函数体组成:
注意参数列表的写法:a, b int 表示 a 和 b 都是 int 类型。如果类型相同可以合并写,不同类型就分开:func foo(a int, b string)。
Go 不需要前向声明,函数写在 main 的前面后面都行,编译器会自动处理。
多返回值
Go 的函数可以返回多个值,这是 Go 的一大特色。最经典的用法是同时返回结果和错误信息,不过我们先看一个简单的例子:
_ 是 Go 的空白标识符,表示"我不需要这个值"。Go 编译器要求所有变量都必须被使用,用 _ 就能避免"声明了但没使用"的编译错误。
命名返回值
Go 还支持给返回值起名字,这样在函数体内可以直接给返回值赋值,最后用一个空的 return 返回:
命名返回值本质上就是语法糖,效果和直接 return a/b, a%b 一样。函数比较短的时候用不用都行,函数比较长的时候命名返回值能让代码更清晰。
指针
指针基础
指针就是一个存储内存地址的变量。Go 的指针很简单,只有两个操作符:
&—— 取地址:&a得到变量a的内存地址*—— 解引用:*p通过指针访问它指向的值
var p *int = &a 的意思是:声明一个指向 int 的指针 p,让它指向变量 a 的地址。之后通过 *p 就可以读取或修改 a 的值。
Go 的指针不能做算术运算(不能 p++ 之类的),这让指针更安全,不容易出错。
指针传参
Go 的函数参数默认是值传递,传进去的是值的副本。如果想在函数里修改外面的变量,就需要传指针:
注意 swap 函数中的 *a, *b = *b, *a,这是 Go 的多重赋值语法,一行就能完成交换,不需要临时变量。
切片和 map 作为参数
前面说基本类型(int、string 等)是值传递,需要指针才能修改。但切片(slice)和 map 就不一样了——它们本身就包含指向底层数据的指针,所以传递时不需要额外加 *,在函数内修改会直接影响外面:
简单记:基本类型传值(副本),切片和 map 天然是引用语义。这和基本类型不同,传递切片和 map 时不需要用指针,函数内部的修改会直接反映到外面。
错误处理(error)
Go 没有异常机制,函数遇到错误时通过多返回值把错误显式带回来,调用方必须检查。
返回值顺序有惯例:结果在前,error 在后。nil 代表没错,非 nil 代表有错。调用方用 if err != nil 判断是 Go 代码里最密集的模式。
造错误有两个常用工具:errors.New 造一个简单错误值,fmt.Errorf 支持格式化字符串。fmt.Errorf 里的 %w 动词专门用来包裹上层错误,保留原始错误链。
要判断错误是不是某个预定义类型,用 errors.Is(err, ErrXxx) 而不是 err == ErrXxx,因为中间可能被 %w 包过好几层,直接 == 比不中。
不要用 _ 偷偷忽略 error,除非你很清楚这个错误不可能出现。吞错是线上事故的常见来源,排查时根本找不到报错点。这里讲的是怎么用 error,自定义 error 类型要用到接口,那是后面的内容。
闭包(匿名函数)
Go 支持匿名函数,也就是没有名字的函数。匿名函数可以赋值给变量,也可以直接调用:
闭包最实用的场景之一是做计数器——每次调用都能记住上一次的状态:
makeCounter 返回的匿名函数"记住了"外部变量 count,每次调用都会修改并返回它。不同的闭包实例各自维护自己的 count,互不干扰。
闭包在 Go 中还有一个非常常见的用法——配合 sort.Slice 自定义排序:
sort.Slice 在刷算法题时用得非常多,它的第二个参数就是一个闭包,用来定义排序规则。闭包能直接访问外面的切片变量,写起来非常方便。
小结
这篇讲了三个核心内容:
函数让你把代码拆成可复用的小块。指针让你能在函数中修改外面的变量。闭包让函数可以"记住"外部变量。
关键点回顾:
- 多返回值是 Go 的特色,配合
_忽略不需要的返回值 &取地址,*解引用,Go 指针不支持算术运算- 基本类型传值需要指针才能修改,切片和 map 天然是引用语义
- 闭包在排序(
sort.Slice)中非常常用
下一篇我们来学 Go 语言中常用的数据结构,包括数组、切片、字符串处理、map 和结构体。