第一章:参数界的“薛定谔的猫”——当我们在谈论参数时到底在说什么?
还记得第一次点外卖的迷惑行为吗?看着菜单图片上淋着芝士的汉堡(形参),实际收到的是被压扁的三明治(实参)。Go语言函数的参数传递,每天都在上演这样的“卖家秀vs买家秀”戏码。
形式参数(Formal Parameter)就像你写在求职简历上的“精通Python+Go+分布式系统”——是函数定义时吹出去的牛逼。而实际参数(Actual Parameter)就像入职后第一天被要求同时修复三个线上bug时——你真实的技术水平。
举个栗子🌰:
// 简历上写的(形参)
func hireMe(skill string) {
fmt.Printf("我能搞定%s\n", skill)
}
// 实际工作时的(实参)
func main() {
realSkill := "同时写文档、改bug、应付产品经理"
hireMe(realSkill) // 传入真实的你
}
输出结果:
我能搞定同时写文档、改bug、应付产品经理
看到没?形参skill是函数hireMe对外宣称的能力,实参realSkill才是你实际要面对的工作内容。这就是为什么面试造火箭,入职拧螺丝——形参和实参从来都不是同一个世界的存在!
第二章:值传递——函数的“柏拉图式恋爱”
值传递就像网恋奔现:你把精心P过的照片(实参)发给对方,函数拿着你的照片(形参)疯狂心动,但永远触碰不到真实的你。
灵魂画手示意图:
你的美颜自拍 --复制一份--> 函数收到的电子照片
(实参在内存地址0x123) (形参在内存地址0x456)
代码还原奔现现场:
package main
import "fmt"
// 网恋对象以为的你(形参)
func blindDate(photo string) {
photo = "卸妆后的样子" // 试图修改照片
fmt.Printf("约会对象看到的是:%s\n", photo)
}
func main() {
myPhoto := "精修磨皮大眼照片" // 真实的你(实参)
blindDate(myPhoto)
fmt.Printf("但实际你的照片还是:%s\n", myPhoto) // 原图未受影响
}
运行结果:
约会对象看到的是:卸妆后的样子
但实际你的照片还是:精修磨皮大眼照片
为什么myPhoto没有被修改?
因为Go在值传递时,实参myPhoto把自己的副本(拷贝)传给了形参photo,两个变量住在不同的内存地址,形参再怎么折腾,也影响不到实参本尊。
更扎心的例子——修改数值:
func addSalary(money int) {
money += 10000 // 幻想老板给你加薪
fmt.Printf("梦中工资:%d\n", money)
}
func main() {
actualSalary := 5000 // 现实工资
addSalary(actualSalary)
fmt.Printf("实际到账:%d\n", actualSalary) // 残酷的现实
}
输出:
梦中工资:15000
实际到账:5000
看吧,在值传递的世界里,所有的改变都只是你的一厢情愿!
第三章:引用传递——和函数“穿同一条裤子”
如果说值传递是网恋,那引用传递就是同居——你们共用同一个空间,你乱扔的袜子(修改数据)对方立马就能看到。
引用传递的经典CP:切片(slice)和映射(map)
package main
import "fmt"
// 和你共用同一个衣柜(形参接收引用)
func messUpCloset(closet []string) {
closet[0] = "皱巴巴的衬衫" // 直接修改共享空间里的衣服
fmt.Printf("同居对象把衣服弄成了:%s\n", closet[0])
}
func main() {
myCloset := []string{"崭新的白衬衫", "笔挺的西裤"} // 实参切片
fmt.Printf("出门前衣柜里的:%s\n", myCloset[0])
messUpCloset(myCloset) // 传入的是引用,不是副本!
fmt.Printf("回家后衣柜里的:%s\n", myCloset[0]) // 你的衣服真的被弄皱了
}
运行结果:
出门前衣柜里的:崭新的白衬衫
同居对象把衣服弄成了:皱巴巴的衬衫
回家后衣柜里的:皱巴巴的衬衫
为什么切片被修改了?
因为切片在Go中是基于数组的引用类型,传递切片时传递的是底层数组的引用(指针),形参和实参指向同一个内存地址。
指针传递——明明白白搞关系
如果你想要基本类型(如int、string)也能被函数修改,那就得明确地使用指针:
// 明确表示要共享银行账户(指针形参)
func sharedAccount(balance *int) {
*balance -= 1000 // 直接动你账户里的钱
fmt.Printf对象取走了1000,余额:%d\n", *balance)
}
func main() {
myBalance := 5000 // 实参
sharedAccount(&myBalance) // 把账户访问权交给对方
fmt.Printf("查账发现余额真的变了:%d\n", myBalance)
}
输出:
对象取走了1000,余额:4000
查账发现余额真的变了:4000
重点总结传递方式:
|
传递类型 |
类似关系 |
是否影响实参 |
适用场景 |
|
值传递 |
网恋 |
❌ |
不需要修改原数据 |
|
引用传递 |
同居 |
✅ |
切片、map等引用类型 |
|
指针传递 |
联名账户 |
✅ |
需要修改基本类型 |
第四章:变长参数——函数的“自助餐模式”
有时候你也不知道要传多少个参数,就像吃自助餐——拿盘子的时侯豪情万丈,实际能吃下的就那么几口。
变长参数语法:...类型
package main
import "fmt"
// 自助餐取餐区(变长形参)
func buffet(foods ...string) {
fmt.Printf("你拿了%d种食物:", len(foods))
for _, food := range foods {
fmt.Printf("%s ", food)
}
fmt.Println()
}
func main() {
// 场景1:正常点餐
buffet("寿司", "牛排", "蛋糕")
// 场景2:胃口不好只拿一样
buffet("沙拉")
// 场景3:大胃王模式
manyFoods := []string{"龙虾", "鹅肝", "烤肉", "冰淇淋", "水果"}
buffet(manyFoods...) // 切片展开语法
}
输出:
你拿了3种食物:寿司 牛排 蛋糕
你拿了1种食物:沙拉
你拿了5种食物:龙虾 鹅肝 烤肉 冰淇淋 水果
变长参数的底层原理:
Go编译器会把变长参数转换为切片,所以foods ...string实际上就是foods []string。
实用技巧——混合固定参数和变长参数:
// 固定套餐 + 自助加料
func comboMeal(mainCourse string, extras ...string) {
fmt.Printf("主菜:%s,加料:", mainCourse)
for _, extra := range extras {
fmt.Printf("%s ", extra)
}
fmt.Println()
}
func main() {
comboMeal("牛肉面", "鸡蛋", "豆干", "青菜")
comboMeal("炒饭") // 可以不加料
}
第五章:实战演练——从外卖系统看参数传递
让我们用一个完整的外卖系统例子,把今天学的所有知识点串起来:
package main
import "fmt"
// 值传递:用户评价(不影响原评分)
func addReview(score int, comment string) {
score += 1 // 平台默认加1分福利
fmt.Printf("显示评分:%d分,评价:%s\n", score, comment)
}
// 引用传递:修改订单状态(需要影响原订单)
func updateOrderStatus(order map[string]string) {
order["status"] = "已完成"
fmt.Printf("订单状态更新为:%s\n", order["status"])
}
// 指针传递:更新用户余额(需要修改原值)
func deductBalance(balance *float64, amount float64) {
if *balance >= amount {
*balance -= amount
fmt.Printf("扣款成功,余额:%.2f\n", *balance)
} else {
fmt.Printf("余额不足,当前余额:%.2f\n", *balance)
}
}
// 变长参数:计算总价
func calculateTotal(prices ...float64) {
total := 0.0
for _, price := range prices {
total += price
}
fmt.Printf("共计%d件商品,总价:%.2f元\n", len(prices), total)
}
func main() {
fmt.Println("=== 外卖系统启动 ===")
// 场景1:用户评价(值传递)
originalScore := 4
addReview(originalScore, "味道不错,配送很快")
fmt.Printf("实际评分还是:%d分\n", originalScore) // 未改变
// 场景2:更新订单(引用传递)
order := map[string]string{
"id": "ORDER123",
"status": "配送中",
"rider": "张三",
}
fmt.Printf("原订单状态:%s\n", order["status"])
updateOrderStatus(order)
fmt.Printf("现订单状态:%s\n", order["status"]) // 已改变
// 场景3:支付扣款(指针传递)
userBalance := 100.0
deductBalance(&userBalance, 68.5)
fmt.Printf("确认余额:%.2f元\n", userBalance) // 已改变
// 场景4:计算总价(变长参数)
calculateTotal(25.5, 18.0, 12.8, 8.0)
// 场景5:从切片计算总价
itemPrices := []float64{32.0, 15.5, 9.9}
calculateTotal(itemPrices...)
}
运行这个完整示例,你会看到:
=== 外卖系统启动 ===
显示评分:5分,评价:味道不错,配送很快
实际评分还是:4分
原订单状态:配送中
订单状态更新为:已完成
现订单状态:已完成
扣款成功,余额:31.50元
确认余额:31.50元
共计4件商品,总价:64.30元
共计3件商品,总价:57.40元
第六章:避坑指南——参数传递的那些“坑”
- 切片的部分引用陷阱
func modifySlice(s []int) {
s[0] = 999 // 这能修改原切片
s = append(s, 666) // 但这可能创建新切片!
}
- map的安全并发访问
// 错误做法:多个goroutine同时修改map
// 正确做法:用sync.Mutex或使用并发安全map
- 接口值的传递机制
var writer io.Writer = &Buffer{}
// 接口值实际上是一个(类型, 值)对
// 传递接口值时传递的是这个对的副本
总结:参数传递的本质
记住这个万能公式:
- 值传递 = 给对方你的身份证复印件
- 引用传递 = 给对方你家钥匙
- 指针传递 = 明确告诉对方你的银行密码
在Go的世界里,理解参数传递就是理解数据的所有权和生命周期。形参是函数承诺要处理的数据格式,实参是真实世界扔给函数的数据本身。
下次写函数时,不妨问问自己:这个参数应该是网恋对象(值传递)、同居伴侣(引用传递)还是联名账户(指针传递)?想清楚这个,你的代码bug率至少降低50%!

被折叠的 条评论
为什么被折叠?



