Go中结构体必须用type显式定义,不可内联;字段首字母决定导出性;方法只能绑定命名类型;字段标签须用反引号且格式严格;嵌入可提升字段和方法但不自动实现接口。

结构体定义必须用 type 声明,不能直接写在变量声明里
Go 中结构体是用户自定义类型,必须通过 type 关键字显式定义。常见错误是试图像 Python 或 JS 那样“内联”写结构体,比如 var u {Name string} —— 这会报错:expected type, found '{'。
正确做法是先定义类型,再声明变量:
type User struct {
Name string
Age int
Email string
}
func main() {
u := User{Name: "Alice", Age: 30}
fmt.Println(u.Name) // Alice
}
- 字段首字母大写(如
Name)表示导出(public),小写(如email)为未导出(private) - 结构体字面量初始化时,若按字段顺序赋值,可省略字段名;但推荐显式写字段名,避免后续加字段后出错
- 嵌套结构体支持匿名字段(即“提升字段”),但需谨慎:多个同类型匿名字段会导致冲突
方法必须绑定到命名类型,不能绑定到结构体字面量或内置类型别名
给结构体加方法时,接收者类型必须是已命名的类型(如 User),不能是 struct{...} 字面量,也不能是未命名的别名(如 type MyInt int 后对 int 写方法)。
以下写法非法:
立即学习“go语言免费学习笔记(深入)”;
// ❌ 错误:接收者是匿名结构体
func (u struct{ Name string }) GetName() string { return u.Name }
// ❌ 错误:对底层类型 int 定义方法(MyInt 是 int 别名,但方法必须绑定 MyInt)
func (i int) Double() int { return i * 2 }
正确方式:
type User struct {
Name string
}
func (u User) GetName() string { // 值接收者
return u.Name
}
func (u *User) SetName(n string) { // 指针接收者(可修改原值)
u.Name = n
}
- 值接收者拷贝整个结构体,适合小结构体;指针接收者避免拷贝,且能修改原值
- 同一类型上混用值/指针接收者不推荐——会导致方法集不一致(例如接口实现可能失效)
- 如果结构体含
sync.Mutex等不可拷贝字段,必须用指针接收者,否则编译报错:cannot copy sync.Mutex
字段标签(struct tag)不是注释,必须是反引号包裹的字符串,且格式严格
结构体字段后的 `json:"name"` 这类内容叫字段标签(tag),由反射读取,常用于序列化。它不是注释,语法有硬性要求:
- 必须用反引号
`包裹,不能用双引号或单引号 -
键值对之间用空格分隔,如
`json:"name" validate:"required"` - 键名不能含空格、冒号或引号;值必须是双引号字符串(内部可转义)
-
标准库只识别
json、xml、sql等少数 tag,自定义 tag 需手动解析(用reflect.StructTag.Get)
典型用法:
type Product struct {
ID int `json:"id"`
Title string `json:"title,omitempty"` // 空值不序列化
Price float64 `json:"price,string"` // 输出为字符串格式
Hidden bool `json:"-"` // 完全忽略
}注意:omitempty 对数值类型(int, float64)只判断是否为零值,对指针/切片/映射则判断是否为 nil。
嵌入结构体与接口实现的关系容易被忽略
Go 支持通过匿名字段“嵌入”结构体,从而获得字段和方法的提升(promotion)。但提升仅限于**导出字段和方法**,且不会自动实现接口。
例如:
type Logger struct{}
func (Logger) Log(s string) { fmt.Println(s) }
type App struct {
Logger // 匿名嵌入
Name string
}
func main() {
a := App{}
a.Log("hello") // ✅ 可直接调用
// 但 App 类型本身并未实现某个 interface{Log(string)},
// 除非显式为 App 实现该接口,或确保嵌入类型满足接口定义且接收者一致
}
- 嵌入类型的方法接收者是其自身类型(如
Logger),调用时会自动传入嵌入字段的值/地址 - 若嵌入的是指针类型(如
*Logger),提升的方法接收者也必须匹配(即*Logger的方法才能被提升) - 两个嵌入类型若有同名方法,会编译报错:
ambiguous selector,必须显式限定:a.Logger.Log()
真正容易卡住的地方在于:嵌入让调用变方便,但接口实现仍需类型自身满足方法集 —— 这点不靠嵌入自动完成。










