第 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 编程技能。

参考文献

文章导航

独立页面

这是书籍中的独立页面。

书籍首页

评论区