Go中必须用T而非T仅当需修改原值或T过大(如字段超4个、含切片/map/大数组);基础类型、切片、map、channel无需指针;&仅作用于变量,在声明时为类型修饰符、使用时为解引用;循环中取地址需防复用;函数收*T须首行判nil;指针本质是语义变更而非单纯性能优化。

Go 里的指针不是“C 那种指针”,它只是个存地址的变量,用对了省事,用错了 panic 一下就暴露问题。
什么时候必须用 *T 而不是 T?
只有两个硬性理由:要改原值,或者 T 太大(比如结构体字段超过 4 个、含切片/map/大数组)。
- 改原值:比如
func (u *User) SetName(name string)—— 不用指针,SetName改的只是副本,外面完全看不到 - 避免拷贝:一个含 10KB buffer 的结构体传值,每次调用都复制一次;传
*T就只传 8 字节地址 - 基础类型(
int、string、bool)几乎从不需要指针——传值快、安全、不用判nil - 切片
[]int、mapmap[string]int、channel 本身已是引用语义,再套一层*[]int属于典型画蛇添足
& 和 * 到底在哪儿用?别混淆上下文
& 只能作用于变量,* 在声明和使用时含义完全不同。
-
&x合法,&42或&(a + b)直接编译报错:字面量和表达式不可取地址 -
var p *int中的*是类型修饰符,表示“指向 int 的指针” -
*p = 10中的*是解引用操作,此时若p == nil,运行时 panic -
p.Name看似没写*,但其实是语法糖,等价于(*p).Name,一样会因p == nil崩溃
循环里存 *T 最容易掉进同一个坑
写 for _, name := range names { u := User{Name: name}; m[name] = &u },最后所有 map key 都指向最后一次迭代的 u 地址。
立即学习“go语言免费学习笔记(深入)”;
- 根本原因是:循环变量
u在栈上复用,每次迭代都覆盖同一内存位置 - 正确做法是每次构造新对象:
m[name] = &User{Name: name}—— Go 逃逸分析会自动把它分配到堆上 - 同理,
[]*User切片里如果多个元素指向同一个User{}实例,改一个就全变,这不是 bug,是预期行为,得看业务要不要共享
函数参数收 *T,第一行就得检查 nil
只要签名是 func doSomething(u *User),调用方就可能传 nil,不检查直接用 u.Name 必 panic。
- 检查必须放在函数入口:
if u == nil { return errors.New("user is required") } - 不要推迟到深层逻辑里才判,否则 panic 位置难定位,堆栈也看不出是哪层漏了
- 如果
nil是合法的(比如可选配置),参数名要明确,比如opts *Options,并在文档里写清 “nil means defaults” - 返回局部变量的指针?别干——Go 编译器通常能逃逸分析,但语义上不该依赖它;该返回结构体就返回值,该新建就
new(T)或&T{}
最常被忽略的点其实是:指针带来的不是性能提升,而是语义变更。你加一个 *,不只是少拷贝,还意味着“这个值可能被别人改”,并发下尤其危险。值类型才是 Go 的默认安全感来源。









