9.1go结构体
Go不是完全面向对象的,没有类的概念,所以结构体应该承担了更多的责任。
结构体定义
使用 type 和 struct 关键字定义:
type Person struct {
Name string
Age int
}
字段可以是任意类型,包括其他结构体或指针。
字段名以大写开头表示可导出(公开),小写表示私有(仅在包内可见)。
实例化
方式一:声明变量后赋值
var p1 Person
p1.Name = "Alice"
p1.Age = 30
方式二:结构体字面量
p2 := Person{Name: "Bob", Age: 25} // 指定字段名
p3 := Person{"Charlie", 28} // 按字段顺序初始化(需注意顺序)
方式三:使用 new 函数(返回指针)
p4 := new(Person)
p4.Name = "Dave" // Go自动解引用,等价于 (*p4).Name
结构体的零值
当声明一个结构体变量而不初始化时,其字段会被赋予对应类型的零值。
var u Person
fmt.Println(u.Name) // 输出: ""
fmt.Println(u.Age) // 输出: 0
结构体的构造函数
虽然 Go 不支持传统的构造函数,但可以通过定义函数来创建并初始化结构体实例,通常命名为 New【StructName】
。
func NewPerson(name string, age int) Person {
return Person{
Name: name,
Age: age,
}
}
p := NewPerson("Laura", 32)
fmt.Println(p.Name)
嵌套与匿名字段
结构体可以嵌套其他结构体,实现组合(类似继承):
type Employee struct {
Person // 匿名字段(嵌入Person结构体)
Position string
}
这样的话,Employee就嵌套了Person,访问Employee的Name可以直接用e.Name,而不需要e.Person.Name,这就是结构体嵌套匿名字段的作用。
// 访问嵌套字段
e := Employee{Person{"Eve", 40}, "Engineer"}
fmt.Println(e.Name) // 直接访问Person的字段(字段提升)
结构体方法
结构体可以定义方法,接收者为值或指针:
Go中的方法是在函数前面加上一个接收者。比如,给Person结构体定义一个方法:
这里要注意,如果使用值接收者,修改不会影响原结构体,而指针接收者会改变原结构体的值。
// 值接收者(操作副本)
// 指针接收者(操作原对象)
func main() {
p := Person{
Age: 29,
Name: "Diana",
}
p.Birthday()
fmt.Println(p.Age) // 输出: 29 (未改变)
p.BirthdayPointer()
fmt.Println(p.Age) // 输出: 30 (已改变)
}
type Person struct {
Name string
Age int
}
// 值接收者
func (p Person) Birthday() {
p.Age += 1
}
// 指针接收者
func (p *Person) BirthdayPointer() {
p.Age += 1
}
继承
Employee 结构体嵌入了 Person结构体,因此 Employee 可以直接访问 Person的字段和方法
e := Employee{Person{"Eve", 40}, "Engineer"}
fmt.Println(e.Name, e.Age)
e.Birthday()
fmt.Println(e.Name, e.Age)
e.BirthdayPointer()
fmt.Println(e.Name, e.Age)
结构体作为参数
结构体作为参数传递给函数,如果是值传递,函数内部对结构体的修改不会影响原变量;如果是传递指针,则会修改原变量。
-
值传递(拷贝副本)
直接将结构体作为值传递给函数,函数内部操作的是原结构体的副本,不会影响原对象。
示例:
package main
import "fmt"
type Point struct {
X int
Y int
}
// 值传递:修改副本,不影响原对象
func modifyValue(p Point) {
p.X = 100
fmt.Println("Inside modifyValue:", p) // 输出 {100 2}
}
func main() {
p := Point{X: 1, Y: 2}
modifyValue(p)
fmt.Println("After modifyValue:", p) // 输出 {1 2}(原对象未改变)
}
适用场景:
结构体较小,拷贝成本低。
不需要修改原结构体的逻辑。
-
指针传递(操作原对象)
传递结构体的指针(内存地址),函数内部操作的是原对象。
示例:
package main
import "fmt"
type Point struct {
X int
Y int
}
// 指针传递:修改原对象
func modifyPointer(p *Point) {
p.X = 100
fmt.Println("Inside modifyPointer:", *p) // 输出 {100 2}
}
func main() {
p := &Point{X: 1, Y: 2}
modifyPointer(p)
fmt.Println("After modifyPointer:", p) // 输出 &{100 2}(原对象已改变)
}
适用场景:
结构体较大,避免拷贝开销。
需要修改原结构体的字段。
结构体字段覆盖
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee struct {
Person // 嵌入Person结构体
Name string // 与Person中的Name字段同名
Salary float64
}
func main() {
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Name: "Eve",
Salary: 70000,
}
fmt.Println(e.Name) // 输出: Eve (外层Employee的Name字段)
fmt.Println(e.Person.Name) // 输出: Alice (内层Person的Name字段)
fmt.Println(e.Age) // 输出: 30
fmt.Println(e.Salary) // 输出: 70000
}
Employee
结构体嵌入了Person
结构体,并且两者都有一个名为Name
的字段。- 当访问
e.Name
时,默认访问的是Employee
结构体的Name
字段("Eve"),这遮蔽了内层Person
的Name
字段。 - 要访问内层
Person
的Name
字段,需要使用e.Person.Name
。
结构体方法覆盖
package main
import "fmt"
type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("Hello, I'm %s from Person.
", p.Name)
}
type Employee struct {
Person
Name string
}
func (e Employee) Greet() {
fmt.Printf("Hello, I'm %s from Employee.
", e.Name)
}
func main() {
p := Person{Name: "Alice"}
e := Employee{
Person: Person{Name: "Bob"},
Name: "Eve",
}
p.Greet() // 输出: Hello, I'm Alice from Person.
e.Greet() // 输出: Hello, I'm Eve from Employee.
// 调用嵌入结构体的方法
e.Person.Greet() // 输出: Hello, I'm Bob from Person.
}
-
Employee
结构体定义了自己的Greet
方法,覆盖了嵌入的Person
的Greet
方法。 - 调用
e.Greet()
会执行Employee
的Greet
方法。 - 如果需要调用被遮蔽的
Person
的Greet
方法,可以通过e.Person.Greet()
显式调用。
结构体可见性
如果结构体的名称或字段的名称以大写字母开头,那么其他包可以访问;否则,只能在当前包内访问。比如:
type person struct { // 小写,只能在包内使用
name string
age int
}
type Student struct { // 大写,公开,包外可见
Name string
Age int
}
结构体其他特性
比较
若所有字段可比较(非切片、map等),结构体可直接用 == 或 != 比较。
包含不可比较字段的结构体无法直接比较。
深浅拷贝
默认赋值是值拷贝(深拷贝)。
若字段含引用类型(如切片、指针),拷贝后共享底层数据。
作为Map的键需所有字段可比较:
type Point struct { X, Y int }
points := make(map[Point]string)
points[Point{1,2}] = "start"
匿名字段冲突
若多个匿名字段有同名字段,需显式指定:
type A struct { Name string }
type B struct { Name string }
type C struct { A; B }
c := C{}
c.A.Name = "Alice" // 必须明确指定