让我们首先来看看一些Go语言的代码。以下代码会从预定义的消息列表中随机选择一条,并打印到控制台:
package main
// 导入额外的功能包
import (
"errors"
"fmt"
"log"
"math/rand"
"strconv"
"time"
)
// 从https://en.wiktionary.org/wiki/Hello_World#Translations中摘取
var helloList = []string{
"Hello, world",
"Καλημέρα κόσμε",
"こんにちは世界",
"سلام دنیا",
"Привет, мир",
}
main() 函数的定义如下:
func main() {
// 使用当前时间为随机数生成器设置种子
rand.Seed(time.Now().UnixNano())
// 在列表范围内生成一个随机数
index := rand.Intn(len(helloList))
// 调用函数并接收多个返回值
msg, err := hello(index)
// 处理错误
if err != nil {
log.Fatal(err)
}
// 打印消息到控制台
fmt.Println(msg)
}
接下来,我们来看看 hello() 函数的定义:
func hello(index int) (string, error) {
if index < 0 || index >= len(helloList) {
// 创建一个错误,将整数类型转换为字符串
return "", errors.New("out of range: " + strconv.Itoa(index))
}
return helloList[index], nil
}
现在,我们逐步解析这段代码。
首先是脚本的开头部分:
package main
这是我们的包声明。所有Go语言文件必须以这种声明开始。如果你希望直接运行代码,需要将包命名为 main。如果不命名为 main,则可以将其用作库,并导入到其他Go代码中。创建一个可导入的包时,可以为它指定任意名称。所有位于同一目录下的Go文件都被认为是同一个包的一部分,这意味着所有文件必须具有相同的包名称。
接下来的代码部分是导入其他包的功能:
// 导入额外的功能包
import (
"errors"
"fmt"
"log"
"math/rand"
"strconv"
"time"
)
在这个例子中,所有导入的包都来自Go的标准库。Go的标准库质量很高且非常全面,强烈建议你充分利用它。如果一个包不是来自标准库,它会看起来像一个URL——例如,github.com/fatih/color。
Go有一个模块系统,使得使用外部包变得简单。要使用新的模块,只需将其添加到导入路径中。下一次构建代码时,Go会自动下载这个模块。
导入仅适用于声明它们的文件,这意味着你必须在同一个包和项目中反复声明相同的导入。但是不用担心——许多工具和Go编辑器会自动为你添加和删除导入:
// 从https://en.wiktionary.org/wiki/Hello_World#Translations中摘取
var helloList = []string{
"Hello, world",
"Καλημέρα κόσμε",
"こんにちは世界",
"سلام دنیا",
"Привет, мир",
}
在这里,我们声明了一个全局变量,它是一个字符串列表,并用数据进行初始化。Go中的字符串支持多字节的UTF-8编码,使其适用于任何语言。我们使用的列表类型叫做切片(slice)。Go中有三种列表类型:切片(slice)、数组(array)和映射(map)。这三种都是键值对集合,你可以使用键从集合中获取值。切片和数组使用数字作为键,第一个键总是0,并且这些数字是连续的,即没有中断。而在映射类型(map)中,你可以选择键的类型,用来根据其他数据查找映射中的值。
这里我们声明了一个函数。函数是一段在被调用时运行的代码。你可以将数据以一个或多个变量的形式传递给函数,并可选择性地从函数中接收一个或多个变量。Go中的 main() 函数是特殊的。main() 函数是Go代码的入口点。main 包中只能有一个 main() 函数。当你的代码运行时,Go会自动调用 main 来启动程序:
// 使用当前时间为随机数生成器设置种子
rand.Seed(time.Now().UnixNano())
// 在列表范围内生成一个随机数
index := rand.Intn(len(helloList))
在这段代码中,我们生成了一个随机数。首先,我们需要确保它是一个良好的随机数;为此,我们必须 设置种子(seed)。我们使用当前时间的Unix时间戳(以纳秒为单位)来设置种子。为了获取时间,我们调用了 time 包中的 Now 函数。Now 函数返回一个结构体类型的变量。结构体是属性和函数的集合,有点类似于其他语言中的对象。在这里,我们直接调用该结构体上的 UnixNano 函数。UnixNano 函数返回一个 int64 类型的变量,即64位整数,简单来说就是一个数字。这个数字被传递到 rand.Seed 函数中。rand.Seed 函数接受一个 int64 类型的变量作为输入。至此,我们已经成功地设置了随机数生成器的种子。
我们需要一个可以用来获取随机消息的数字。为此,我们使用 rand.Intn 函数。这个函数会返回一个介于 0 和你传入的数字之间的随机整数。虽然听起来有些奇怪,但这个方法非常适合我们的需求。这是因为我们的列表是一个切片(slice),其键从0开始,每个值递增1。这意味着最后一个索引比切片的长度少1。
为了说明这一点,以下是一些简单的代码:
package main
import (
"fmt"
)
func main() {
helloList := []string{
"Hello, world",
"Καλημέρα κόσμε",
"こんにちは世界",
"سلام دنیا",
"Привет, мир",
}
fmt.Println(len(helloList))
fmt.Println(helloList[len(helloList)-1])
fmt.Println(helloList[len(helloList)])
}
这段代码会打印列表的长度,并使用该长度打印最后一个元素。为了做到这一点,我们必须减去1;否则,我们会得到一个错误,这也是最后一行代码造成的。
解释一下,len(helloList) 给出了切片的长度,索引从0开始,所以有效的索引范围是0到 len(helloList) - 1。访问超出此范围的索引会导致运行时错误。
一旦我们生成了随机数,就将其分配给一个变量。我们使用 := 这种简短的变量声明方式,这在Go语言中非常流行。这种语法告诉编译器将该值赋给变量,并隐式选择合适的类型。这个简写方式是Go语言让人感觉像动态类型语言的众多特性之一:
// 调用函数并接收多个返回值
msg, err := hello(index)
然后,我们使用这个变量来调用名为 hello 的函数。稍后我们会详细了解 hello 函数。需要注意的是,我们从函数中接收了两个值,并使用 := 语法将它们赋给两个新的变量,msg 和 err,其中 err 是第二个值:
func hello(index int) (string, error) {
…
}
这段代码是 hello 函数的定义;我们暂时不展示函数的具体实现。函数是一个逻辑单元,在需要时被调用。调用函数时,调用它的代码会暂停运行,直到函数执行完成。函数是组织和理解代码的一个好工具。在 hello 函数的签名中,我们定义了它接受一个 int 类型的参数,并返回一个 string 类型的值和一个 error 类型的值。在Go中,error 作为返回值的最后一项是很常见的。函数体在 {} 之间定义,以下代码是在函数被调用时执行的:
if index < 0 || index >= len(helloList) {
// 创建一个错误,将整数类型转换为字符串
return "", errors.New("out of range: " + strconv.Itoa(index))
}
return helloList[index], nil
在这个函数体内,我们首先检查传入的索引是否在有效范围内。如果索引不在有效范围内,我们创建一个错误,并将整数转换为字符串,然后返回这个错误。否则,我们返回列表中对应索引的值以及 nil(表示没有错误)。
在这里,我们位于函数内部,函数体的第一行是一个 if 语句。if 语句在其 {} 之间的代码块会在布尔表达式为真时执行。布尔表达式是 if 和 { 之间的逻辑。在本例中,我们检查传入的 index 变量是否小于0或大于切片的最大可能索引值。
如果布尔表达式为真,我们的代码将返回一个空的 string 和一个 error 值。此时,函数的执行将停止,调用函数的代码将继续执行。如果布尔表达式不为真,if 语句中的代码将被跳过,函数将从 helloList 返回一个值,并返回 nil。在Go语言中,nil 表示没有值和没有类型:
// 处理错误
if err != nil {
log.Fatal(err)
}
在运行完 hello 函数后,首先需要检查它是否成功运行。我们可以通过检查存储在 err 中的 error 值来完成。如果 err 不等于 nil,则表示发生了错误。你会看到对 err 是否不等于 nil 的检查,而不是是否等于 nil 的检查,这样可以简化代码的逻辑。在发生错误的情况下,我们调用 log.Fatal,它会输出日志消息并终止应用程序。一旦应用程序被终止,就不会再执行任何代码:
// 将消息打印到控制台
fmt.Println(msg)
如果没有错误,则表示 hello 函数成功运行,msg 的值可以信赖并且有效。最后一步是通过终端将消息打印到屏幕上。
在这个简单的Go程序中,我们涵盖了许多关键概念,这些概念将在接下来的章节中详细探讨。
在这个练习中,我们将利用之前示例中学到的一些知识,打印一个随机数量(1到5之间)的星号(*****)到控制台。这个练习将帮助你感受使用Go语言的工作方式,并练习一些我们在后续学习中会用到的Go语言特性。开始吧:
package main
import (
"fmt"
"math/rand"
"strings"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
r := rand.Intn(5) + 1
stars := strings.Repeat("*", r)
fmt.Println(stars)
}
go run .
在这个练习中,我们通过定义 main 包和其中的 main() 函数,创建了一个可运行的Go程序。我们通过导入标准库中的包来生成随机数、重复字符串并输出到控制台。