函数与指针

上一篇我们学了 Go 的基础语法,所有代码都写在 main 函数里。但真实的程序不可能把所有逻辑塞在一个函数中,我们需要把代码拆分成多个函数,让代码更清晰。

这篇还会讲 Go 的指针。Go 的指针很简单——没有指针运算,只有取地址和解引用,够用就行。另外,Go 的函数还支持多返回值,非常实用。

函数

函数就是一段可以重复调用的代码块,给它一个名字,需要的时候调用就行。

定义与调用

一个函数由 func 关键字、函数名、参数列表、返回类型和函数体组成:

注意参数列表的写法:a, b int 表示 ab 都是 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 作为参数

前面说基本类型(intstring 等)是值传递,需要指针才能修改。但切片(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 和结构体。