这一章是理解Go语言精髓的关键,其设计哲学与PHP等传统面向对象语言有很大不同。
5.1 方法 (Methods) #
在Go中,你可以为任何你自定义的类型(包括结构体)附加函数,这种特殊的函数就叫做“方法”。这很像PHP类中的方法。
package main
import "fmt"
type User struct {
FirstName string
LastName string
}
// FullName 是一个“方法”,它属于 User 类型
// (u User) 被称为“接收者”(receiver)
// 这就像PHP中的 $this
func (u User) FullName() string {
return u.FirstName + " " + u.LastName
}
// SetFirstName 是一个使用指针接收者的方法
// 当你需要修改接收者自身时,必须使用指针
// 类似于PHP中方法内部的 $this->firstName = ...
func (u *User) SetFirstName(newName string) {
u.FirstName = newName
}
func main() {
user := User{"John", "Doe"}
fmt.Println(user.FullName()) // "John Doe"
// Go会自动处理指针,所以可以直接在值类型上调用指针接收者的方法
user.SetFirstName("Jane")
fmt.Println(user.FullName()) // "Jane Doe"
// 也可以显式使用指针
pUser := &User{"Alice", "Wonder"}
pUser.SetFirstName("Bob")
fmt.Println(pUser.FullName()) // "Bob Wonder"
}
5.2 接口 (Interfaces) - Go的组合哲学 #
接口是Go的灵魂。它与PHP的接口有很大不同。
- PHP接口:一个类必须显式地使用
implements
关键字声明它实现了某个接口。 - Go接口:是隐式实现的。如果一个类型定义了某个接口要求的所有方法,那么它就自动地、无需声明地实现了该接口。这被称为“非侵入式接口”或“鸭子类型”(Duck Typing)。
“If it walks like a duck and it quacks like a duck, then it must be a duck.”
package main
import "fmt"
// Shaper 是一个接口,它要求实现者必须有一个Area() float64方法
type Shaper interface {
Area() float64
}
// --- 定义两个结构体 ---
type Rectangle struct {
Width, Height float64
}
type Circle struct {
Radius float64
}
// --- 为它们实现Area()方法 ---
// Rectangle 实现了 Shaper 接口,因为它有 Area() float64 方法
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Circle 也实现了 Shaper 接口
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
// --- 定义一个使用接口的函数 ---
// 这个函数不关心传来的是矩形还是圆形,它只关心传来的东西有没有Area()方法
func PrintShapeArea(s Shaper) {
fmt.Printf("Area of the shape is: %f\n", s.Area())
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
circ := Circle{Radius: 3}
// 我们可以把具体的类型(Rectangle, Circle)赋值给接口变量(Shaper)
// 并且把它们传给同一个函数 PrintShapeArea
PrintShapeArea(rect)
PrintShapeArea(circ)
// 你甚至可以创建一个接口类型的切片
shapes := []Shaper{rect, circ}
for _, shape := range shapes {
PrintShapeArea(shape)
}
}
这种设计带来了极大的灵活性和解耦。你可以在不修改已有代码的情况下,通过实现接口来扩展功能。这正是Go推崇的“组合优于继承”的体现。
5.3 错误处理 #
Go没有try-catch
异常机制。取而代之的是,函数通常会返回一个error
类型的值作为其多个返回值之一。
error
是一个内置的接口类型。任何实现了Error() string
方法的类型都可以作为error
。
type error interface {
Error() string
}
这种模式强制开发者显式地检查和处理错误,而不是像PHP那样可能忘记catch
一个异常导致程序崩溃。
package main
import (
"fmt"
"strconv" // 用于字符串转换
)
func main() {
// strconv.Atoi (string to integer) 会返回一个int和一个error
s := "123"
i, err := strconv.Atoi(s)
if err != nil {
// 如果err不为nil,说明发生了错误
fmt.Println("An error occurred:", err)
return // 通常会在这里处理错误并返回
}
fmt.Printf("Successfully converted '%s' to %d\n", s, i)
s = "not a number"
i, err = strconv.Atoi(s)
if err != nil {
fmt.Println("An error occurred:", err)
// An error occurred: strconv.Atoi: parsing "not a number": invalid syntax
return
}
fmt.Printf("Successfully converted '%s' to %d\n", s, i)
}
PHP的try-catch
vs. Go的if err != nil
:
- 控制流:
try-catch
会打断正常的代码执行流,跳转到catch
块。Go的错误处理是正常的控制流的一部分,代码更线性、易于阅读。 - 显式性: Go强制你关注每一个可能出错的函数调用。虽然代码看起来会多一些
if err != nil
的重复块,但这让代码更健壮,错误路径被清晰地定义出来。 - 错误类型: 在Go中,错误就是普通的值,你可以像操作其他值一样操作它,比如传递、包装、检查类型等。