7. 面向对象编程
面向对象编程(Object-Oriented Programming,简称OOP)是一种编程范式,通过将数据和行为封装在对象中,以提高代码的可重用性、可维护性和扩展性。虽然Go语言不像传统的OOP语言(如Java、C++)那样提供类和继承的概念,但它通过结构体、方法、接口以及组合等特性,充分支持面向对象的编程风格。本章将详细介绍Go语言中的面向对象编程特性,包括方法、接口、组合与继承等内容。通过丰富的示例和深入的解释,帮助你全面理解和应用这些特性。
7.1 Go 的面向对象特性
Go语言虽然没有类(Class)和继承(Inheritance)的概念,但它通过以下几个关键特性实现了面向对象编程的核心理念:
- 结构体(Structs):用于定义具有多个字段的复合数据类型,类似于其他语言中的类。
- 方法(Methods):可以为结构体类型定义方法,赋予结构体行为。
- 接口(Interfaces):定义了一组方法签名,任何实现了这些方法的类型都满足该接口,实现了多态性。
- 组合(Composition):通过结构体嵌套实现代码复用和类型扩展,代替传统的继承。
- 多态(Polymorphism):通过接口实现不同类型的统一操作。
封装(Encapsulation)
封装是OOP的核心概念之一,通过封装,可以隐藏对象的内部实现细节,仅暴露必要的接口。Go通过导出(首字母大写)和未导出(首字母小写)的标识符实现封装。
示例:
package main
import "fmt"
// 定义结构体
type Person struct {
Name string // 导出字段
age int // 未导出字段
}
// 定义方法访问未导出字段
func (p *Person) GetAge() int {
return p.age
}
func (p *Person) SetAge(a int) {
if a >= 0 {
p.age = a
}
}
func main() {
p := Person{Name: "Alice"}
// p.age = 30 // 编译错误: age 是未导出的字段
p.SetAge(30)
fmt.Printf("Name: %s, Age: %d\n", p.Name, p.GetAge()) // 输出: Name: Alice, Age: 30
}
输出:
Name: Alice, Age: 30
解释:
Person
结构体中,Name
字段是导出的,可以在包外访问,而age
字段是未导出的,只能在包内访问。- 通过
GetAge
和SetAge
方法,控制对age
字段的访问和修改,实现了封装。
方法(Methods)
方法是与特定类型关联的函数,赋予类型特定的行为。在Go中,方法的定义与函数类似,只是在函数名前添加接收者(Receiver)部分。
基本语法:
func (receiver Type) MethodName(parameters) returnTypes {
// 方法体
}
示例:
package main
import "fmt"
// 定义结构体
type Rectangle struct {
Width, Height float64
}
// 定义方法计算面积
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// 定义方法计算周长
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("面积: %.2f\n", rect.Area()) // 输出: 面积: 50.00
fmt.Printf("周长: %.2f\n", rect.Perimeter()) // 输出: 周长: 30.00
}
输出:
面积: 50.00
周长: 30.00
解释:
Rectangle
结构体定义了矩形的宽度和高度。Area
和Perimeter
方法分别计算矩形的面积和周长。
指针接收者与值接收者:
方法的接收者可以是值类型或指针类型。选择哪种接收者取决于方法是否需要修改接收者的字段,以及是否希望避免大结构体的复制。
示例:
package main
import "fmt"
// 定义结构体
type Counter struct {
count int
}
// 值接收者方法
func (c Counter) IncrementValue() {
c.count++
fmt.Println("Inside IncrementValue:", c.count)
}
// 指针接收者方法
func (c *Counter) IncrementPointer() {
c.count++
fmt.Println("Inside IncrementPointer:", c.count)
}
func main() {
c := Counter{count: 10}
c.IncrementValue() // 修改的是副本
fmt.Println("After IncrementValue:", c.count) // 输出: 10
c.IncrementPointer() // 修改的是原始值
fmt.Println("After IncrementPointer:", c.count) // 输出: 11
// 使用指针变量调用方法
cp := &c
cp.IncrementPointer()
fmt.Println("After cp.IncrementPointer:", c.count) // 输出: 12
}
输出:
Inside IncrementValue: 11
After IncrementValue: 10
Inside IncrementPointer: 11
After IncrementPointer: 11
Inside IncrementPointer: 12
After cp.IncrementPointer: 12
解释:
IncrementValue
方法接收者为值类型,只修改方法内部的副本,不影响原始结构体。IncrementPointer
方法接收者为指针类型,修改的是原始结构体的字段。
注意事项:
- 一致性:建议为同一类型的方法统一使用值接收者或指针接收者,避免混淆。
- 性能:对于大型结构体,使用指针接收者可以避免复制,提高性能。
- 可修改性:使用指针接收者可以在方法中修改接收者的字段。
7.2 方法
方法是与特定类型关联的函数,赋予类型特定的行为。Go语言中的方法可以定义在结构体类型上,使得结构体不仅具有数据,还具有行为。
方法的定义与调用
定义方法:
方法的定义与函数类似,不同之处在于方法有一个接收者(Receiver),用于指定该方法属于哪个类型。
基本语法:
func (receiver Type) MethodName(parameters) returnTypes {
// 方法体
}
receiver
:接收者,通常是结构体类型的实例,可以是值类型或指针类型。Type
:接收者的类型。MethodName
:方法名称。parameters
:方法的参数列表。returnTypes
:方法的返回值类型。
示例:
package main
import "fmt"
// 定义结构体
type Circle struct {
Radius float64
}
// 定义方法计算面积
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
// 定义方法计算周长
func (c Circle) Circumference() float64 {
return 2 * 3.14159 * c.Radius
}
func main() {
circle := Circle{Radius: 5}
fmt.Printf("面积: %.2f\n", circle.Area()) // 输出: 面积: 78.54
fmt.Printf("周长: %.2f\n", circle.Circumference()) // 输出: 周长: 31.42
}
输出:
面积: 78.54
周长: 31.42
解释:
Circle
结构体定义了圆的半径。Area
和Circumference
方法分别计算圆的面积和周长。
方法的接收者
方法的接收者可以是值类型或指针类型,不同类型的接收者具有不同的行为和性能影响。
值接收者(Value Receiver):
- 接收者为值类型时,方法接收的是类型的副本。
- 在方法中对接收者的修改不会影响原始变量。
- 适用于不需要修改接收者数据的方法。
指针接收者(Pointer Receiver):
- 接收者为指针类型时,方法接收的是类型的地址。
- 可以在方法中修改接收者的字段,影响原始变量。
- 避免大结构体的复制,提高性能。
- 适用于需要修改接收者数据的方法。
示例:
package main
import "fmt"
// 定义结构体
type BankAccount struct {
Owner string
Balance float64
}
// 值接收者方法
func (ba BankAccount) DepositValue(amount float64) {
ba.Balance += amount
fmt.Printf("[DepositValue] 新余额: %.2f\n", ba.Balance)
}
// 指针接收者方法
func (ba *BankAccount) DepositPointer(amount float64) {
ba.Balance += amount
fmt.Printf("[DepositPointer] 新余额: %.2f\n", ba.Balance)
}
func main() {
account := BankAccount{Owner: "John Doe", Balance: 1000}
account.DepositValue(500) // 修改的是副本
fmt.Printf("余额 after DepositValue: %.2f\n", account.Balance) // 输出: 1000.00
account.DepositPointer(500) // 修改的是原始变量
fmt.Printf("余额 after DepositPointer: %.2f\n", account.Balance) // 输出: 1500.00
}
输出:
[DepositValue] 新余额: 1500.00
余额 after DepositValue: 1000.00
[DepositPointer] 新余额: 1500.00
余额 after DepositPointer: 1500.00
解释:
DepositValue
方法接收者为值类型,仅修改方法内部的副本,原始余额不变。DepositPointer
方法接收者为指针类型,修改的是原始余额。
选择接收者类型的建议:
- 如果方法需要修改接收者的字段,使用指针接收者。
- 对于大型结构体,使用指针接收者以避免复制,提高性能。
- 如果方法不需要修改接收者,并且结构体较小,使用值接收者。
- 为保持一致性,建议为同一类型的方法统一使用指针接收者或值接收者。
方法的嵌套与组合
虽然Go语言不支持类的继承,但可以通过结构体嵌套和组合实现类似的功能,赋予结构体更丰富的行为。
示例:
package main
import "fmt"
// 定义基础结构体
type Animal struct {
Name string
}
// 定义方法
func (a Animal) Speak() {
fmt.Printf("%s makes a sound.\n", a.Name)
}
// 定义子结构体,通过嵌套结构体实现组合
type Dog struct {
Animal
Breed string
}
// 重写Speak方法
func (d Dog) Speak() {
fmt.Printf("%s barks.\n", d.Name)
}
func main() {
a := Animal{Name: "Generic Animal"}
a.Speak() // 输出: Generic Animal makes a sound.
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
d.Speak() // 输出: Buddy barks.
}
输出:
Generic Animal makes a sound.
Buddy barks.
解释:
Dog
结构体通过嵌套Animal
结构体,实现了Animal
的字段和方法。Dog
结构体重写了Animal
的Speak
方法,实现了多态性。
注意事项:
- Go语言不支持方法的覆盖(Override)和继承(Inheritance),但可以通过组合和接口实现类似的功能。
- 通过结构体嵌套,可以实现代码复用和类型扩展,增强结构体的功能。
7.3 接口
接口(Interface)是Go语言中实现多态性的核心机制。接口定义了一组方法的签名,任何实现了这些方法的类型都满足该接口。通过接口,可以编写更加灵活和可扩展的代码。
定义接口
接口使用type
关键字和interface
关键字定义,包含了一组方法签名。
基本语法:
type InterfaceName interface {
Method1(parameters) returnTypes
Method2(parameters) returnTypes
// ...
}
示例:
package main
import "fmt"
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
解释:
Shape
接口定义了两个方法:Area
和Perimeter
,均返回float64
类型的值。
实现接口
在Go语言中,不需要显式声明一个类型实现了某个接口,只需定义了接口中的所有方法即可。这样的隐式实现机制提高了代码的灵活性和简洁性。
示例:
package main
import "fmt"
// 定义接口
type Shape interface {
Area() float64
Perimeter() float64
}
// 定义结构体
type Rectangle struct {
Width, Height float64
}
// 实现接口方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// 定义另一个结构体
type Circle struct {
Radius float64
}
// 实现接口方法
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func main() {
var s Shape
s = Rectangle{Width: 10, Height: 5}
fmt.Printf("Rectangle Area: %.2f\n", s.Area()) // 输出: Rectangle Area: 50.00
fmt.Printf("Rectangle Perimeter: %.2f\n", s.Perimeter()) // 输出: Rectangle Perimeter: 30.00
s = Circle{Radius: 7}
fmt.Printf("Circle Area: %.2f\n", s.Area()) // 输出: Circle Area: 153.94
fmt.Printf("Circle Perimeter: %.2f\n", s.Perimeter()) // 输出: Circle Perimeter: 43.98
}
输出:
Rectangle Area: 50.00
Rectangle Perimeter: 30.00
Circle Area: 153.94
Circle Perimeter: 43.98
解释:
Rectangle
和Circle
结构体分别实现了Shape
接口的所有方法。- 通过接口变量
s
,可以统一调用不同类型的Shape
的Area
和Perimeter
方法,实现多态性。
注意事项:
- 一个类型实现了接口中的所有方法,就自动满足该接口,无需显式声明。
- 接口可以嵌套其他接口,增强接口的功能。
空接口与类型断言
空接口(Empty Interface):
空接口interface{}
不包含任何方法签名,所有类型都满足空接口。它通常用于需要处理任意类型的数据。
示例:
package main
import "fmt"
func printAnything(a interface{}) {
fmt.Println(a)
}
func main() {
printAnything(100)
printAnything("Hello, World!")
printAnything(true)
printAnything(3.14)
}
输出:
100
Hello, World!
true
3.14
解释:
printAnything
函数接受一个空接口类型的参数,可以接收任何类型的值。
类型断言(Type Assertion):
类型断言用于将接口类型转换为具体类型。它可以检查接口值是否持有特定的类型,并安全地转换。
基本语法:
value, ok := interfaceValue.(ConcreteType)
value
:转换后的具体类型值。ok
:布尔值,表示转换是否成功。
示例:
package main
import "fmt"
func main() {
var i interface{} = "Go Language"
s, ok := i.(string)
if ok {
fmt.Println("字符串长度:", len(s)) // 输出: 字符串长度: 12
} else {
fmt.Println("不是字符串类型")
}
n, ok := i.(int)
if ok {
fmt.Println("整数:", n)
} else {
fmt.Println("不是整数类型") // 输出: 不是整数类型
}
}
输出:
字符串长度: 12
不是整数类型
类型切换(Type Switch):
类型切换用于根据接口值的实际类型执行不同的代码块。它是一种更简洁和安全的方式来处理多种类型。
基本语法:
switch v := interfaceValue.(type) {
case Type1:
// 处理Type1
case Type2:
// 处理Type2
default:
// 处理其他类型
}
示例:
package main
import "fmt"
func main() {
var i interface{} = 3.14
switch v := i.(type) {
case int:
fmt.Println("整数:", v)
case float64:
fmt.Println("浮点数:", v)
case string:
fmt.Println("字符串:", v)
default:
fmt.Println("未知类型")
}
}
输出:
浮点数: 3.14
解释:
type
关键字用于检测接口值的实际类型,并在不同的case
中执行相应的代码块。
注意事项:
- 安全性:类型断言和类型切换可以防止运行时错误,确保类型转换的安全性。
- 性能:频繁的类型断言可能会影响性能,应在必要时使用。
7.4 组合与继承
Go语言不支持传统的类继承,但通过结构体嵌套和组合,可以实现代码复用和类型扩展的功能。此外,通过接口,可以实现多态性,使得不同类型能够以统一的方式被处理。
结构体嵌套
结构体嵌套(Struct Embedding)是Go语言中实现组合的方式之一。通过将一个结构体嵌入到另一个结构体中,可以复用嵌入结构体的字段和方法。
示例:
package main
import "fmt"
// 定义基础结构体
type Address struct {
City string
ZipCode string
}
// 定义Person结构体,嵌套Address
type Person struct {
Name string
Age int
Address // 嵌入结构体,实现组合
}
// 定义方法
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s. I am %d years old.\n", p.Name, p.Age)
}
func main() {
p := Person{
Name: "Eve",
Age: 28,
Address: Address{
City: "New York",
ZipCode: "10001",
},
}
p.Greet() // 输出: Hello, my name is Eve. I am 28 years old.
fmt.Println("City:", p.City) // 直接访问嵌套结构体的字段,输出: City: New York
}
输出:
Hello, my name is Eve. I am 28 years old.
City: New York
解释:
Person
结构体嵌套了Address
结构体,Person
可以直接访问Address
的字段和方法。- 通过嵌套结构体,实现了字段和方法的复用,类似于继承。
注意事项:
-
命名冲突:如果嵌套结构体中有与外层结构体同名的字段或方法,会导致命名冲突。Go语言会优先选择外层结构体的字段或方法。
示例:
package main import "fmt" type Animal struct { Name string } type Dog struct { Animal Name string // 与嵌套的Animal结构体中的Name字段冲突 } func main() { d := Dog{ Animal: Animal{Name: "Generic Animal"}, Name: "Buddy", } fmt.Println("Dog's Name:", d.Name) // 输出: Buddy fmt.Println("Animal's Name:", d.Animal.Name) // 输出: Generic Animal }
输出:
Dog's Name: Buddy Animal's Name: Generic Animal
-
方法继承:嵌套结构体的方法也会被外层结构体继承,可以直接调用。
示例:
package main import "fmt" type Animal struct{} func (a Animal) Speak() { fmt.Println("Animal speaks") } type Dog struct { Animal } func main() { d := Dog{} d.Speak() // 调用嵌套结构体的方法,输出: Animal speaks }
多态(Polymorphism)
多态性允许不同类型的对象以统一的接口进行交互。在Go语言中,通过接口实现多态性,使得不同类型的对象能够实现相同的方法集合,从而可以被同一个接口变量引用和调用。
示例:
package main
import "fmt"
// 定义接口
type Speaker interface {
Speak()
}
// 定义结构体1
type Human struct {
Name string
}
// 实现接口方法
func (h Human) Speak() {
fmt.Printf("Hi, I am %s.\n", h.Name)
}
// 定义结构体2
type Dog struct {
Breed string
}
// 实现接口方法
func (d Dog) Speak() {
fmt.Printf("Woof! I am a %s.\n", d.Breed)
}
func main() {
var s Speaker
s = Human{Name: "Alice"}
s.Speak() // 输出: Hi, I am Alice.
s = Dog{Breed: "Golden Retriever"}
s.Speak() // 输出: Woof! I am a Golden Retriever.
}
输出:
Hi, I am Alice.
Woof! I am a Golden Retriever.
解释:
Speaker
接口定义了一个Speak
方法。Human
和Dog
结构体分别实现了Speak
方法。- 通过接口变量
s
,可以引用不同类型的对象,实现多态性。
注意事项:
- 接口的实现是隐式的:只要类型实现了接口中的所有方法,就自动满足该接口,无需显式声明。
- 接口变量的动态类型:接口变量在运行时可以持有不同类型的值,实现灵活的多态性。
7.5 指针与结构体
指针是存储变量内存地址的变量。在Go语言中,指针与结构体结合使用,可以提高程序的性能,避免大量数据的复制,同时实现对结构体的修改和共享。通过指针,可以有效地管理内存和数据,特别是在处理大型结构体或需要频繁修改数据时尤为重要。
指针基础
1. 声明指针
使用*
符号声明指针类型。
var p *int
解释:
p
是一个指向int
类型的指针,初始值为nil
。
2. 获取变量的地址
使用&
符号获取变量的内存地址。
a := 10
p := &a
fmt.Println("a的地址:", p) // 输出: a的地址: 0xc0000140b0
3. 解引用指针
使用*
符号访问指针指向的值。
fmt.Println("p指向的值:", *p) // 输出: p指向的值: 10
4. 修改指针指向的值
通过指针修改变量的值。
*p = 20
fmt.Println("修改后的a:", a) // 输出: 修改后的a: 20
完整示例:
package main
import "fmt"
func main() {
var a int = 10
var p *int = &a
fmt.Println("变量a的值:", a) // 输出: 10
fmt.Println("指针p的地址:", p) // 输出: a的地址
fmt.Println("指针p指向的值:", *p) // 输出: 10
// 修改指针指向的值
*p = 30
fmt.Println("修改后的a:", a) // 输出: 30
}
输出:
变量a的值: 10
指针p的地址: 0xc0000140b0
指针p指向的值: 10
修改后的a: 30
指针与结构体
将指针与结构体结合使用,可以避免复制整个结构体,尤其是当结构体较大时,提高程序的性能。此外,通过指针,可以在函数中修改结构体的字段。
1. 定义结构体并使用指针
package main
import "fmt"
// 定义结构体
type Person struct {
Name string
Age int
}
func main() {
p := Person{Name: "Alice", Age: 25}
fmt.Println("原始结构体:", p) // 输出: {Alice 25}
// 获取结构体的指针
ptr := &p
// 修改指针指向的结构体字段
ptr.Age = 26
fmt.Println("修改后的结构体:", p) // 输出: {Alice 26}
}
输出:
原始结构体: {Alice 25}
修改后的结构体: {Alice 26}
解释:
ptr
是指向Person
结构体p
的指针。- 通过指针
ptr
修改Age
字段,直接影响原始结构体p
。
2. 结构体指针作为函数参数
通过将结构体指针作为函数参数,可以在函数中修改结构体的字段,而无需返回修改后的结构体。
package main
import "fmt"
// 定义结构体
type Rectangle struct {
Width, Height float64
}
// 定义函数,接受结构体指针并修改字段
func Resize(r *Rectangle, width, height float64) {
r.Width = width
r.Height = height
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Println("原始矩形:", rect) // 输出: {10 5}
Resize(&rect, 20, 10)
fmt.Println("修改后的矩形:", rect) // 输出: {20 10}
}
输出:
原始矩形: {10 5}
修改后的矩形: {20 10}
解释:
Resize
函数接受Rectangle
结构体的指针,通过指针修改结构体的Width
和Height
字段。
3. 指针接收者方法
前面章节中提到方法接收者可以是指针类型,这样可以在方法中修改结构体的字段。
package main
import "fmt"
// 定义结构体
type Counter struct {
count int
}
// 定义指针接收者方法
func (c *Counter) Increment() {
c.count++
}
func main() {
c := Counter{count: 0}
fmt.Println("初始计数:", c.count) // 输出: 0
c.Increment()
fmt.Println("计数 after Increment:", c.count) // 输出: 1
// 使用指针变量
cp := &c
cp.Increment()
fmt.Println("计数 after cp.Increment:", c.count) // 输出: 2
}
输出:
初始计数: 0
计数 after Increment: 1
计数 after cp.Increment: 2
解释:
Increment
方法使用指针接收者,可以直接修改Counter
结构体的count
字段。
指针的高级用法
1. 指针与切片
切片本身是引用类型,包含指向底层数组的指针。通过指针,可以修改切片的元素,或在函数中传递切片指针以实现更灵活的操作。
示例:
package main
import "fmt"
func modifySlice(s *[]int) {
(*s)[0] = 100
*s = append(*s, 200)
}
func main() {
s := []int{1, 2, 3}
fmt.Println("原始切片:", s) // 输出: [1 2 3]
modifySlice(&s)
fmt.Println("修改后的切片:", s) // 输出: [100 2 3 200]
}
输出:
原始切片: [1 2 3]
修改后的切片: [100 2 3 200]
解释:
modifySlice
函数接受切片的指针,通过指针修改切片的第一个元素,并添加新的元素。
2. 指针与Map
Map是引用类型,通常不需要使用指针传递Map,因为Map本身就是引用的。但在某些情况下,可以使用指针传递Map,以便在函数中重新分配或替换整个Map。
示例:
package main
import "fmt"
func modifyMap(m *map[string]string) {
(*m)["Germany"] = "Berlin"
}
func main() {
capitals := map[string]string{
"China": "Beijing",
"USA": "Washington",
"Japan": "Tokyo",
}
modifyMap(&capitals)
fmt.Println("修改后的Map:", capitals) // 输出: map[China:Beijing Germany:Berlin Japan:Tokyo USA:Washington]
}
输出:
修改后的Map: map[China:Beijing Germany:Berlin Japan:Tokyo USA:Washington]
解释:
modifyMap
函数接受Map的指针,通过指针添加新的键值对到Map中。
3. 指针数组
数组中可以存储指针类型的元素,适用于需要引用和共享数据的场景。
示例:
package main
import "fmt"
func main() {
a, b, c := 1, 2, 3
ptrArr := []*int{&a, &b, &c}
for i, ptr := range ptrArr {
fmt.Printf("ptrArr[%d] 指向的值: %d\n", i, *ptr)
}
// 修改通过指针数组修改原始变量
*ptrArr[0] = 10
fmt.Println("修改后的a:", a) // 输出: 10
}
输出:
ptrArr[0] 指向的值: 1
ptrArr[1] 指向的值: 2
ptrArr[2] 指向的值: 3
修改后的a: 10
解释:
ptrArr
是一个存储指向int
类型的指针数组。- 通过指针数组,可以修改原始变量
a
的值。
注意事项
-
指针的零值:未初始化的指针为
nil
。在使用指针前,确保其已被正确初始化,避免运行时错误。示例:
var p *int // fmt.Println(*p) // 运行时错误: invalid memory address or nil pointer dereference
-
避免悬挂指针:确保指针指向的变量在指针使用期间保持有效,避免指针指向已经释放或超出作用域的变量。
示例:
func getPointer() *int { x := 10 return &x } func main() { p := getPointer() fmt.Println(*p) // 不安全:x 已经超出作用域,可能导致未定义行为 }
解决方案: 使用堆分配(通过
new
函数或返回结构体实例)确保变量在函数外仍然有效。 -
使用指针优化性能:对于大型结构体,使用指针传递可以避免复制整个结构体,提高性能。
示例:
type LargeStruct struct { Data [1000]int } func process(ls LargeStruct) { // 复制整个结构体 // ... } func processPointer(ls *LargeStruct) { // 传递指针 // ... }
-
nil指针检查:在使用指针前,最好检查指针是否为
nil
,以避免运行时错误。if p != nil { fmt.Println(*p) } else { fmt.Println("指针为nil") }