跳到主要内容

Go 里定义变量、零值思维与结构体

变量怎么定义

var name string
var age int = 18
var city = "Hangzhou"

count := 3
ok := true
  • var name string:声明后先拿零值
  • var age int = 18:显式类型 + 显式赋值
  • var city = "Hangzhou":交给编译器推断
  • count := 3:短变量声明,只能在函数内部用

平时基本就记两句:

  • 函数内部优先 :=
  • 包级变量只能用 var(不能 :=);先声明后赋值、想强调类型时也用 var

零值思维

Go 很适合先想:这个类型的零值,能不能直接拿来用?

  • int0
  • boolfalse
  • string""
  • pointer / slice / map / func / interface / channil
  • struct → 每个字段都是各自的零值
var total int
var found bool

上面已经够用了,不用再写 0false

结构体也一样:

type Config struct {
Port int
Debug bool
Name string
}

var cfg Config

这时 cfg 就已经是一个完整的零值结构体了。很多时候可以先声明,再按需补字段:

var user User
user.Name = "AsMuin"
user.Age = 18

一个很常见的区别:nil slicenil map

slice 可以懒一点:

var data []int
data = append(data, 1, 2, 3)

map 不行,写入前一般要先 make

var m map[string]int
// m["a"] = 1 // panic

m = make(map[string]int)
m["a"] = 1

一句话:slice 可以先 nilappendmap 写入前一般要先 make
补充:nil slice 不能按下标读写;nil map 返回零值不 panic,会 panic。

结构体怎么写更稳

初始化时优先具名字段:

type User struct {
Name string
Age int
}

u := User{Name: "AsMuin", Age: 18}

少用这种按顺序塞值的写法:

u := User{"AsMuin", 18}

原因无非三个:更清楚、改字段顺序不容易炸、字段多时更好读。

如果零值本来就合理,也没必要把所有字段写满:

type ServerConfig struct {
Host string
Port int
TLS bool
Timeout int
}

cfg := ServerConfig{
Host: "127.0.0.1",
Port: 8080,
}

前提只有一个:这些零值在你的业务语义里真的是合法默认值。

newmake

p := new(User) // *User
s := make([]int, 0, 8) // slice
m := make(map[string]int)
ch := make(chan int, 1)
  • new(T):给你一个 *T,里面装着零值 T
  • make(...):给 slice / map / chan 用,返回的是类型本身

所以:结构体指针常见 new(User)&User{},切片 / map / channel 用 make

所谓结构体字段"展开"

Go 里更准确的说法其实不是展开,而是:嵌入后字段被提升了

type Base struct {
ID int
}

type User struct {
Base
Name string
}

u := User{
Base: Base{ID: 1},
Name: "AsMuin",
}

fmt.Println(u.ID) // 1
fmt.Println(u.Base.ID) // 1

这里不是 JS 那种 ...spreadUser 里依然有个 Base 字段,只是 Base.ID 被提升了,所以可以直接写 u.ID

方法也会一起提升:

type Base struct{}

func (Base) Ping() {}

type Worker struct {
Base
}

var w Worker
w.Ping()

Go 没有对象 spread,但可以复制再改

old := User{Name: "AsMuin", Age: 18}
next := old
next.Name = "Jason"

结构体赋值是字段级拷贝。字段都是基本类型时,这就是一份独立副本。

若字段里有 slice / map / 指针,拷贝的是头部或地址,底层数据仍共享(浅拷贝):

type Bag struct {
Items []string
}

a := Bag{Items: []string{"x"}}
b := a
b.Items[0] = "y"
// a.Items[0] 也是 "y"

想基于旧值生成新值时,next := old 很顺手,但改引用类型字段前要清楚是否共享。

顺便一提,结构体指针访问字段时也不用手动解引用:

p := &User{Name: "AsMuin"}
p.Name = "Jason"

Go 会自动帮你处理 (*p).Name 这层语法。

总结

  • 函数内优先 :=,包级变量用 var
  • 先想零值能不能直接用
  • nil slice 适合 appendnil map 写入前先 make
  • 结构体初始化优先具名字段;赋值是浅拷贝
  • 所谓字段展开,本质是嵌入后的字段提升