一、结构体(Struct)
结构体是一种聚合数据类型,可以将零个或多个任意类型的变量组合在一起,形成一个新类型。每个变量称为结构体的字段。
1. 结构体声明
使用 type 和 struct 关键字定义结构体:
type Person struct {
Name string
Age int
Email string
}
type Person struct:定义了一个名为Person的结构体类型。- 大括号内是字段列表,每个字段有名称和类型。
2. 结构体初始化
有多种方式创建结构体变量:
方式1:声明并初始化所有字段(按顺序)
var p1 Person = Person{"张三", 30, "zhangsan@example.com"}
缺点:必须按字段顺序提供所有值,且不能省略。
方式2:使用字段名(推荐)
p2 := Person{
Name: "李四",
Age: 25,
Email: "lisi@example.com",
}
字段顺序可以任意,未指定的字段会被赋予零值。
方式3:先声明后赋值
var p3 Person
p3.Name = "王五"
p3.Age = 28
p3.Email = "wangwu@example.com"
方式4:使用 new 关键字(返回指针)
p4 := new(Person) // 等价于 &Person{}
p4.Name = "赵六"
p4.Age = 22
方式5:取地址符初始化
p5 := &Person{
Name: "孙七",
Age: 35,
}
3. 访问和修改字段
使用点号 . 访问或修改字段:
fmt.Println(p2.Name) // 输出:李四
p2.Age = 26
4. 结构体嵌套(组合)
Go 没有继承,但可以通过嵌套结构体实现“组合”的效果。
type Address struct {
City string
Street string
}
type Employee struct {
Name string
Age int
Address // 匿名字段(只有类型,没有字段名)
}
当使用匿名字段时,我们可以直接访问内嵌结构体的字段,就像它们是外层结构体的字段一样:
e := Employee{
Name: "小张",
Age: 28,
Address: Address{
City: "北京",
Street: "长安街",
},
}
fmt.Println(e.City) // 直接访问,输出:北京
fmt.Println(e.Street) // 直接访问,输出:长安街
如果内嵌结构体和外层有同名字段,则需要通过内嵌类型名来访问(外层字段会“遮蔽”内层字段)。
5. 结构体方法
Go 可以为任何类型(包括结构体)定义方法,方法就是带接收者的函数。
// 值接收者
func (p Person) SayHello() {
fmt.Printf("你好,我是 %s,今年 %d 岁。\n", p.Name, p.Age)
}
// 指针接收者(可以修改结构体内容)
func (p *Person) SetAge(age int) {
p.Age = age
}
调用方法:
p := Person{Name: "小明", Age: 18}
p.SayHello() // 输出:你好,我是小明,今年 18 岁。
p.SetAge(20)
fmt.Println(p.Age) // 20
- 值接收者:方法内对接收者的修改不影响原变量。
- 指针接收者:可以修改原变量,且避免复制大结构体时的开销。
6. 结构体标签(Tag)
结构体标签是附加在字段上的元信息,常用于序列化(如 JSON)、验证等场景。标签是字符串,通过反引号包裹。
type User struct {
ID int `json:"id"` // 转 JSON 时字段名为 id
Name string `json:"name"`
Email string `json:"email,omitempty"` // 为空时忽略
}
使用 encoding/json 包进行序列化:
u := User{ID: 1, Name: "张三"}
data, _ := json.Marshal(u)
fmt.Println(string(data)) // {"id":1,"name":"张三"}
标签通过 reflect 包读取,框架或工具可以利用它们实现各种功能。
二、接口(Interface)
接口定义了一组方法签名,它只描述“能做什么”,而不关心具体的类型。在 Go 中,接口是隐式实现的:如果一个类型实现了接口中定义的所有方法,那么它就自动实现了该接口,无需显式声明。
1. 接口的定义
使用 type 和 interface 关键字定义接口:
type Speaker interface {
Speak() string
}
接口 Speaker 要求任何实现它的类型必须有一个 Speak() string 方法。
2. 接口的实现
假设我们有两个结构体:Person 和 Dog,它们都实现了 Speak 方法:
type Person struct {
Name string
}
func (p Person) Speak() string {
return "你好,我是 " + p.Name
}
type Dog struct {
Breed string
}
func (d Dog) Speak() string {
return "汪汪!我是 " + d.Breed
}
现在,Person 和 Dog 都隐式地实现了 Speaker 接口。
3. 使用接口实现多态
我们可以声明一个接口类型的变量,它可以保存任何实现了该接口的值:
func main() {
var s Speaker
s = Person{Name: "小明"}
fmt.Println(s.Speak()) // 输出:你好,我是 小明
s = Dog{Breed: "金毛"}
fmt.Println(s.Speak()) // 输出:汪汪!我是 金毛
}
同一个接口变量,根据其底层具体类型的不同,表现出不同的行为,这就是多态。
接口还可以作为函数参数,接受任何实现了该接口的类型:
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
MakeSound(Person{Name: "小红"})
MakeSound(Dog{Breed: "柯基"})
}
4. 空接口 interface{} 和 any
空接口 interface{} 没有定义任何方法,因此所有类型都实现了空接口。它可以用来表示任意类型的值。从 Go 1.18 开始,any 是 interface{} 的别名,含义相同。
var i interface{}
i = 42
i = "hello"
i = Person{Name: "测试"}
// 使用 any
var a any = 3.14
空接口经常用于需要处理未知类型的情况,但使用时需要配合类型断言来恢复具体类型。
5. 类型断言
类型断言用于提取接口值中的具体类型。语法:value, ok := 接口变量.(具体类型)。ok 表示断言是否成功,避免 panic。
var s Speaker = Person{Name: "小明"}
// 类型断言
p, ok := s.(Person)
if ok {
fmt.Println("s 是 Person 类型,姓名:", p.Name)
} else {
fmt.Println("s 不是 Person 类型")
}
// 直接断言,不检查会 panic
// d := s.(Dog) // panic
也可以使用 type switch 进行多种类型判断:
switch v := s.(type) {
case Person:
fmt.Println("Person:", v.Name)
case Dog:
fmt.Println("Dog:", v.Breed)
default:
fmt.Println("未知类型")
}
6. 接口组合
接口可以通过嵌套其他接口来创建新接口,类似于结构体嵌套。例如:
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合接口
type ReadWriter interface {
Reader
Writer
}
任何类型只要同时实现了 Reader 和 Writer 接口的方法,就自动实现了 ReadWriter。
7. 常用接口示例
Go 标准库中有许多有用的接口,例如 fmt.Stringer:
type Stringer interface {
String() string
}
任何实现了 String() string 方法的类型,在使用 fmt.Print 等函数时会自动调用该方法获取字符串表示。
func (p Person) String() string {
return fmt.Sprintf("Person(Name=%s, Age=%d)", p.Name, p.Age)
}
func main() {
p := Person{Name: "小明", Age: 20}
fmt.Println(p) // 输出:Person(Name=小明, Age=20)
}
三、总结
- 结构体:用于定义复杂数据类型,支持嵌套和方法,是数据组织的基石。
- 接口:定义行为规范,支持隐式实现,是实现多态和解耦的关键工具。
- 结构体和接口结合使用,可以构建出灵活、可扩展的程序。
接下来可以尝试自己编写一个小程序,例如:
- 定义一个
Shape接口,包含Area() float64方法 - 实现
Circle、Rectangle结构体,并计算面积 - 使用接口变量存储不同形状,并输出面积
如果还有疑问,欢迎随时提问!你已经掌握了 Go 语言的核心内容,继续加油!