第 3 章:Go 入门
Go 语言以简洁、实用和高效著称,是构建云原生应用和微服务的理想选择。本章将带你系统了解 Go 的核心语法与实践方法,为后续深入学习打下坚实基础。
Go 入门概述
本章将介绍 Go 语言的基础知识,涵盖如下内容:
- 一个“hello world”示例
- 函数的基本用法
- 结构体的操作与数据存储
- 结构体方法的定义与使用
- 包的使用与自定义包的创建
如果希望进一步深入学习 Go 语言,推荐阅读 Mark Summerfield 的 Programming in Go: Creating Applications for the 21st Century 以及 Alan A. A. Donovan 与 Brian W. Kernighan 合著的 The Go Programming Language。
Go 并不是传统意义上的面向对象语言,其设计强调敏捷性和简单性,适合解决实际问题。虽然很多人认为 Go 只适合构建命令行工具,但本书将展示其在云原生应用和微服务领域的强大能力。
Hello Cloud 示例
“hello world”是编程入门的经典示例。下面我们将通过 Go 打印“hello, cloud”,并展示 Unicode 支持。
代码示例:
package main
import "fmt"
func main() {
fmt.Println("Hello, cloud!")
fmt.Println("你好,云!")
}
上述代码包含包声明、导入 fmt 包、定义 main 函数,并通过 Println 打印字符串。Go 字符串支持 Unicode,适合多语言开发。
运行方法:
go run hello-cloud.go
建议在 Go 工作区目录下运行示例,有助于后续包管理和依赖处理。也可在 Go Playground ( http://play.golang.org ) 在线运行。
基本函数用法
Go 的函数可以有多个参数和返回值,语法类似 C 语言。以下是一个模拟投掷骰子的函数:
func dieRoll(size int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(size) + 1
}
此函数根据当前时间初始化随机数种子,返回 1 到 size 之间的随机数。需要注意,Go Playground 使用固定时间种子,结果可能一致。
调用示例:
dieRoll(20)
Go 支持多返回值函数:
func rollTwo(size1, size2 int) (int, int) {
return dieRoll(size1), dieRoll(size2)
}
还可以声明具名返回值:
func returnsNamed(input1 string, input2 int) (theResult string, err error) {
theResult = "modified " + input1 + ", " + strconv.Itoa(input2)
return theResult, err
}
完整代码示例:
package main
import (
"fmt"
"math/rand"
"strconv"
"time"
)
func dieRoll(size int) int {
rand.Seed(time.Now().UnixNano())
return rand.Intn(size) + 1
}
func rollTwo(size1, size2 int) (int, int) {
return dieRoll(size1), dieRoll(size2)
}
func returnsNamed(input1 string, input2 int) (theResult string, err error) {
theResult = "modified " + input1 + ", " + strconv.Itoa(input2)
return theResult, err
}
func main() {
fmt.Printf("Rolled a die of size %d, result: %d\n", 6, dieRoll(6))
res1, res2 := rollTwo(6, 10)
fmt.Printf("Rolled a pair of dice (%d,%d), results: %d, %d\n", 6, 10, res1, res2)
named, err := returnsNamed("globule", 42)
fmt.Printf("Named params returned: '%s', %v\n", named, err)
}
输出示例:
Rolled a die of size 6, result: 5
Rolled a pair of dice (6,10), results: 1, 6
Named params returned: 'modified globule, 42', <nil>
Go 的函数类型可以作为参数或存储在结构体、数组中,灵活实现多样功能。例如:
type dieRollFunc func(int) int
func fakeDieRoll(size int) int {
return 42
}
func getDieRolls() []dieRollFunc {
return []dieRollFunc{
dieRoll,
fakeDieRoll,
}
}
var rolls = getDieRolls()
for index, rollFunc := range rolls {
fmt.Printf("DieRoll Attempt #%d, result: %d\n", index, rollFunc(10))
}
结构体的定义与使用
Go 的结构体是字段的类型化集合,支持嵌套和指针。示例:
type person struct {
name string
age int
}
结构体创建方式:
var p = person{}
var p2 = person{"bob", 21}
var p3 = person{name: "bob", age: 21}
var p4 = &person{}
var p5 = &person{"bob", 21}
var p6 = &person{name: "bob", age: 21}
结构体字段通过点语法访问。复杂结构体示例:
type power struct {
attack int
defense int
}
type location struct {
x float32
y float32
z float32
}
type nonPlayerCharacter struct {
name string
speed int
hp int
power power
loc location
}
func main() {
fmt.Println("Structs...")
demon := nonPlayerCharacter{
name: "Alfred",
speed: 21,
hp: 1000,
power: power{attack: 75, defense: 50},
loc: location{x: 1075.123, y: 521.123, z: 211.231},
}
fmt.Println(demon)
anotherDemon := nonPlayerCharacter{
name: "Beelzebub",
speed: 30,
hp: 5000,
power: power{attack: 10, defense: 10},
loc: location{x: 32.03, y: 72.45, z: 65.231},
}
fmt.Println(anotherDemon)
}
输出示例:
Structs...
Alfred (1075.123047,521.122986,211.231003)
Beelzebub (32.029999,72.449997,65.231003)
Go 接口与方法
Go 支持为结构体添加方法,实现类似面向对象的功能,但并非传统 OOP 继承。示例:
type attacker struct {
attackpower int
dmgbonus int
}
type sword struct {
attacker
twohanded bool
}
type gun struct {
attacker
bulletsremaining int
}
func (s sword) Wield() bool {
fmt.Println("You've wielded a sword!")
return true
}
func (g gun) Wield() bool {
fmt.Println("You've wielded a gun!")
return true
}
接口定义与动态类型检查:
type weapon interface {
Wield() bool
}
func wielder(w weapon) bool {
fmt.Println("Wielding...")
return w.Wield()
}
只要类型实现了接口方法即可被识别为该接口,无需显式声明。示例:
sword1 := sword{attacker: attacker{attackpower: 1, dmgbonus: 5}, twohanded: true}
gun1 := gun{attacker: attacker{attackpower: 10, dmgbonus: 20}, bulletsremaining: 11}
wielder(sword1)
wielder(gun1)
输出:
Weapons: sword: {{1 5} true}, gun: {{10 20} 11}
Wielding...
You've wielded a sword!
Wielding...
You've wielded a gun!
如果类型未实现接口方法,则编译报错。只需补充方法即可通过类型检查。
Go 的接口机制支持隐式实现,极大提升扩展性。例如,fmt 包的 Stringer 接口:
func (loc location) String() string {
return fmt.Sprintf("(%f,%f,%f)", loc.x, loc.y, loc.z)
}
还可实现距离计算方法:
func (loc location) euclideanDistance(target location) float64 {
return math.Sqrt(
(loc.x-target.x)*(loc.x-target.x) +
(loc.y-target.y)*(loc.y-target.y) +
(loc.z-target.z)*(loc.z-target.z))
}
func (npc nonPlayerCharacter) distanceTo(target nonPlayerCharacter) float64 {
return npc.loc.euclideanDistance(target.loc)
}
使用第三方包
Go 鼓励通过包实现可复用组件。以 tablewriter 包为例,先通过命令获取:
go get github.com/olekukonko/tablewriter
使用示例:
package main
import (
"os"
"github.com/olekukonko/tablewriter"
)
func main() {
data := [][]string{
{"Alfred", "15", "10/20", "(10.32, 56.21, 30.25)"},
{"Beelzebub", "30", "30/50", "(1,1,1)"},
{"Hortense", "21", "80/80", "(1,1,1)"},
{"Pokey", "8", "30/40", "(1,1,1)"},
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"NPC", "Speed", "Power", "Location"})
table.AppendBulk(data)
table.Render()
}
运行后可在控制台输出格式化表格。遇到导入失败时,请确认包已正确下载并在 Go 工作区中构建。
创建自定义包
Go 的包机制支持代码复用和开源分享。创建包时,需注意导出类型和方法需以大写字母开头。示例:
npcs/types.go
package npcs
// Power describes the attack and defense power of an NPC
type Power struct {
Attack int
Defense int
}
// Location describes where in the virtual world an NPC exists
type Location struct {
X float64
Y float64
Z float64
}
// NonPlayerCharacter represents metadata for an in-game creature
type NonPlayerCharacter struct {
Name string
Speed int
HP int
Power Power
Loc Location
}
npcs/npcs.go
package npcs
import (
"fmt"
"math"
)
func (loc Location) String() string {
return fmt.Sprintf("(%f,%f,%f)", loc.X, loc.Y, loc.Z)
}
// EuclideanDistance returns the distance between two in-game locations
func (loc Location) EuclideanDistance(target Location) float64 {
return math.Sqrt(
(loc.X-target.X)*(loc.X-target.X) +
(loc.Y-target.Y)*(loc.Y-target.Y) +
(loc.Z-target.Z)*(loc.Z-target.Z))
}
// DistanceTo returns the distance between two in-game characters
func (npc NonPlayerCharacter) DistanceTo(target NonPlayerCharacter) float64 {
return npc.Loc.EuclideanDistance(target.Loc)
}
func (npc NonPlayerCharacter) String() string {
return fmt.Sprintf("%s %s", npc.Name, npc.Loc)
}
客户端应用示例:
package main
import (
"fmt"
"github.com/cloudnativego/go-primer/npcs"
)
func main() {
mob := npcs.NonPlayerCharacter{Name: "Alfred"}
fmt.Println(mob)
hortense := npcs.NonPlayerCharacter{Name: "Hortense",
Loc: npcs.Location{X: 10.0, Y: 10.0, Z: 10.0}}
fmt.Println(hortense)
fmt.Printf("Alfred is %f units from Hortense.\n",
mob.DistanceTo(hortense))
}
输出:
Alfred (0.000000,0.000000,0.000000)
Hortense (10.000000,10.000000,10.000000)
Alfred is 17.320508 units from Hortense.
总结
本章系统介绍了 Go 语言的基础语法、结构体、接口、包机制及第三方库的使用方法。通过实践示例,帮助读者掌握 Go 的核心能力,为后续云原生开发和微服务架构打下坚实基础。建议结合官方文档和推荐书籍进一步学习,夯实 Go 编程技能。
参考文献
- Programming in Go: Creating Applications for the 21st Century - addison-wesley.com
- The Go Programming Language - gopl.io
- Go 官方文档 - golang.org
- Go Playground - play.golang.org
- Go 字符串与 rune 类型 - blog.golang.org
- tablewriter 包 - github.com