原文: The Go Handbook – Learn Golang for Beginners

Golang 是一门非常棒的简洁且快速的现代编程语言。

它是编译的、开源的、强类型的。

Golang——也被称为 Go——由谷歌工程师创建,主要目标如下:

  • 让他们的项目编译(和运行)更快
  • 要简单,这样人们可以在短时间内学会
  • 要足够底层,但也要避免过于关注底层
  • 可移植(经过编译的 Go 程序不需要其他文件的支持,就可以跨平台运行,因此它们很轻易分发)
  • 简洁而枯燥、稳定、可预测,从而减少犯错的机会
  • 易于利用多处理器系统

Go 是为了取代 C 和 C++ 代码库。它旨在通过垃圾回收机制使并发和内存管理等事情变得更简单。

此外,由于其与 C 语言的互操作特性,可以在 C 和 C++ 代码库中使用 Go 语言。

你可以用 Go 做很多不一样的任务,它既可以解决简单的问题也可以解决复杂的问题。

你可以使用 Go 来创建命令行工具和网络服务器,它广泛用于许多不同的场景。

举个例子,Docker 和 K8s 是用 Go 编写的。

我最喜欢的静态网站生成工具 Hugo 是用 Go 编写的。

Caddy,一个非常流行的 web 服务器是用 GO 编写的。

众多被广泛使用的工具都是基于这门语言创建的。

本手册将向你介绍 Go 编程语言,以便你开始使用 Go 编程。

你可以点击链接获得 GO 初学者手册的 pdf 版本和 ePub 版本

目录

  1. 如何开始使用 Go
  2. 如何安装 Go
  3. 如何设置编辑器
  4. 如何用 Go 编写 Hello, World!
  5. 如何编译和运行 Go 程序
  6. Go 的工作空间
  7. 深入 Go 语言
  8. Go 中的变量
  9. Go 的基础类型
  10. Go 中的字符串
  11. Go 中的数组
  12. Go 中的切片
  13. Go 中的 map
  14. Go 中的循环
  15. Go 中的条件语句
  16. Go 中的运算符
  17. Go 中的结构体
  18. Go 中的函数
  19. Go 中的指针
  20. Go 中的方法
  21. Go 中的接口
  22. 更多内容

如何开始使用 Go

在我们深入了解语言的细节之前,你应该了解以下几点。

首先, https://go.dev 是语言的官网。 在官网你可以获得以下资源:

......等等。

如何安装 Go

https://go.dev/doc/install 下载适用于你电脑操作系统的软件包。

运行并安装,在最后一个步骤你需要在命令行设置 go 命令:

欢迎进行 Go 的安装
欢迎进行 Go 的安装
安装成功
安装成功

打开命令行并运行 go version 你会看到以下内容:

展示你当前的 Go 版本
展示你当前的 Go 版本

注意: 在运行程序之前,你可能需要打开一个新的命令行,因为安装程序将 Go 二进制文件文件夹添加到了路径中。

Go 安装文件的确切位置取决于你的操作系统。

在 Mac 系统中,它在 /usr/local/go , 运行文件在 /usr/local/go/bin

在 Windows 系统中,它在 C:\Program Files\go

在 Windows 和 Mac 安装中 Go 执行文件路径都是自动设定的。

在 Mac 你可以在 Homebrew 中使用 brew install golang 命令安装。 这样方式更容易升级到最新版本。

在 Linux 上,你必须将 Go 二进制文件文件夹添加到你的环境变量中,然后才能在使用以下命令将 Linux 包解压缩到 /usr/local/go之后运行 go 命令:

echo 'export PATH=$PATH:/usr/local/go/bin' >> $HOME/.profile
source $HOME/.profile

如何设置编辑器

我推荐使用 Visual Studio Code(也叫 VS Code)作为你的编辑器。

请阅读在 Visual Studio Code 的 Go,了解快速安装并运行的方法。安装Go 的扩展

VS Code 中的 Go 扩展
VS Code 中的 Go 扩展

这个扩展将让你的生活更加轻松。因为它提供智能感知(语法高亮显示、自动补全、悬停信息、错误高亮显示…)和其他功能,如自动格式化、安装软件包的菜单选项、测试等。

如何用 Go 编写 Hello, World!

现在我们准备创建我们第一个 Go 程序!

程序员的传统是让第一个程序打印 “Hello, World!” 字符串到命令行。所以我们先做这个,然后解释我们是如何做到的。

或许你可以在你主目录下创建一个文件夹,保存你所有编写的项目和测试。

在这,创建一个新的文件夹,比如取名叫 hello

在这,创建一个叫 hello.go 的文件(你可以用任何想要用的名字)。

文件内容如下:

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}
Go
Go "Hello, World!" 代码

这是你编写的第一个 Go 程序!

让我们逐行分析一下。

package main

我们以包的形式组织 Go 程序。

每个.go 文件首先要声明它是哪个包的一部分。

一个包可以由多个文件组成,也可以仅由一个文件组成。

一个程序可以由多个包组成。

这个 main 是程序识别可执行程序的入口。

import "fmt"

我们使用 import 关键字导入包。

fmt 是Go提供的内置包,提供输入/输出的工具函数。

我们有一个大的标准库,可以随时使用,从网络连接到数学、加密、图像处理、文件系统访问等等。

你可以在官方文档阅读 fmt 包提供的所有功能。

func main() {
	
}

这里我们声明 main() 函数。

什么是函数?稍后我们将看到更多关于它们的信息,但同时让我们假设一个函数是一个分配了名称的代码块,包含一些指令。

main 函数是特殊的,因为这也是程序启动的地方。

在这个简单的例子中,我们只是有一个函数——程序从这个函数开始,然后结束。

fmt.Println("Hello, World!")

这是我们定义的函数的内容。

我们调用了我们之前导入的 fmt 包中 Println() 函数,将字符串作为参数传递。

根据文档根据格式指定器进行格式化并写入标准输出”。

看看这些文档,因为它们很棒。它们甚至有你可以运行的示例:

Go 基础的函数模板
Go 基础的函数模板

我们可以用 “dot” 语法 fmt.Println() 指定该函数由该包提供。

代码在执行 main 函数之后,它没有做其他的事就结束了执行。

如何编译和运行 Go 程序

现在在 hello 文件夹下打开命令行,用以下命令运行程序:

go run hello.go
Go 输出 Hello world
Go 输出 Hello world

我们的程序运行成功,它会在命令行输出 “Hello, World!” 。

go run 会先编译并运行程序。

你可以用 go build 创建一个可执行文件

go build hello.go

这里会创建一个名为 hello 的可执行文件,你可以执行:

执行 Go 的可执行文件
执行 Go 的可执行文件

在引言部分我提到过 Go 是可移植的。

现在你可以分发这个二进制文件,每个人都可以按原样运行程序,因为二进制文件已经打包好了,可以执行了。

该程序将在我们构建它的相同架构上运行。

我们可以使用 GOOSGOARCH 环境变量为不同的架构创建不同的二进制文件,如下所示:

GOOS=windows GOARCH=amd64 go build hello.go

这将会创建 hello.exe 文件,可以在 64 位的 Windows 机器上运行:

Hello.exe 执行
Hello.exe 执行

设置 64 位的 Mac 的环境变量为 GOOS=darwin GOARCH=amd64 ,Linux 的环境变量是GOOS=linux GOARCH=amd64

这是 Go 最好的特性之一。

Go 的工作空间

关于 Go 中的一个特殊的点,我们称为工作区

这个工作区是 Go 中的“家目录”。

Go 默认的路径在 $HOME/go 下,所以你可以在你的家目录中看到 go 文件夹。

它会在你安装包(待会儿我们看一下)进行创建。

例如我在 VS Code 中加载 hello.go 文件那一刻,它提示我安装[ gopls ](https://pkg.go.dev/golang.org/x/tools/gopls) 命令,开发调试工具(dlv), 和静态检查

它们在 $HOME/go 下自动安装:

`$HOME/go`
`$HOME/go`

当你使用 go install 安装库时,它们会保存在这里。

这就是我们所说的 GOPATH

你可以修改环境变量 GOPATH 以更改 Go 安装包的位置。

这在同时处理不同的项目并且希望隔离所使用的库时非常有用。

深入 Go 语言

现在我们已经理解了第一部分,我们运行了第一个 Hello, World! 程序, 我们可以深入 Go 语言了。

该语言没有语义上有意义的空格。它像 C、C++、Rust、Java、JavaScript,但是不像 Python,其中空格是有意义的,用于创建块,而不是花括号。

分号是可选的,就像在 JavaScript 中一样(不同于 C、C++、Rust 或 Java)。

Go 非常重视缩进和视觉顺序。

在我们安装Go程序的时候自带了 gofmt 命令,我们可以用它对 Go 程序进行格式化。VSCode 中在底层使用它对 Go 源码文件进行格式化。

这是非常有趣和创新的,因为格式和问题,如制表符与空格或“我应该把花括号放在循环定义的同一行还是下一行”,都是浪费时间。

语言创造者定义了规则,每个人都使用这些规则。

这对于拥有大型团队的项目非常有用。

我推荐你在 VS Code 中设置“保存时格式化” 和“粘贴时格式化”:

在 VS Code 中设置 Go 的粘贴时格式化和保存时格式化
在 VS Code 中设置 Go 的粘贴时格式化和保存时格式化

你可以使用常用的 C / C++ / JavaScript / Java 语法在 Go 中写注释:

// this is a line comment

/*
multi
line
comment
*/

Go 中的变量

在编程语言中,首先要做的事情之一是定义变量。

在 Go 中我们用 var 关键字声明变量:

var age = 20

你可以在包级别定义变量:

package main

import "fmt"

var age = 20

func main() {
	fmt.Println("Hello, World!")
}

或是在函数中:

package main

import "fmt"

func main() {
	var age = 20

	fmt.Println("Hello, World!")
}

在包级别定义,变量在组成包的所有文件中都可见。一个包可以由多个文件组成,你只需要创建另一个文件并在顶部使用相同的包名。

在函数级别定义,变量仅在该函数中可见。它在调用函数时初始化,在函数结束执行时销毁。

我们使用以下示例:

var age = 20

我们设置变量 age 的值为 20

这使得 Go 确定变量 age类型int

稍后我们将看到更多关于类型的信息,但你应该知道有很多不同的类型,从 intstringbool 开始。

我们可以不给变量设置值,但是必须设置它们的类型如下:

var age int
var name string
var done bool

当你知道值时,你可以直接用短的变量声明如 := 运算符:

age := 10
name := "Roger"

变量名你可以使用小写字母,数字和 _ 或者可以使用类似于 _ 的其他字符。

名字是区分大小写的。

如果名字太长,通常可使用驼峰命名法,例如我们想表现车的名字就用 carName

你可以使用赋值运算符 = 给一个变量赋予新的值。

var age int
age = 10
age = 11

如果你有一个变量在编程过程中永远都不会变,你可以使用 const 关键字来声明这个变量:

const age = 10

你可以一行代码中声明多个变量:

var age, name

将它们全部都初始化:

var age, name = 10, "Roger"

//or

age, name := 10, "Roger"

在程序中使用未声明的变量,程序会报错,而且无法通过编译。

在 VS Code 中你可以看到警告如下:

使用未声明变量的警告
使用未声明变量的警告

以下是编译的报错:

使用未声明变量的编译器报错
使用未声明变量的编译器报错

如果你声明了一个变量且没有给这个变量一个初始值,它会自动初始化一个对应类型的初始值,例如 integer 类型的值为 0 而字符串的值是一个空的字符串。

Go 的基础类型

Go 是一门强类型语言。

我们可以看到你怎么定义一个变量,指定它的类型:

var age int

或者你可以直接给变量赋予初始值,让 Go 来推断它的类型:

var age = 10

这些是 Go 中的基础类型:

  • 整型(intint8int16int32runeint64uintuintptruint8uint16uint64
  • 浮点型(float32float64),用于表示带小数点的数
  • 复数类型(complex64complex128),常用于科学计算中
  • 字符型(byte),表示一个 ASCII 字符
  • 字符串(string), 一个byte的集合
  • 布尔型(bool)表示 true 或 false

我们有很多不同类型的整数类型,在大多数情况下你只会用到 int,并且你可能会选择一个更专业的方法进行优化(而不是在学习时需要考虑的事情)。

在你使用 64 位系统的时候 int 类型默认为 64 位, 使用 32 位系统的时候 int 类型默认为32位,其他的与此类似。

uint 类型是无符号的 int 类型,如果你知道这个数字不是负数,你可以用这个类型存储比现在大两倍的数字。

所有的基础类型都是值类型, 这意味着当它们作为参数传递或从函数返回时,它们通过值传递给函数。

Go 中的字符串

Go 中的字符串是 byte 序列。

像我们所看到的一样,你可以定义字符串如下:

var name = "test"

其中很重要一点是不像其他语言,字符串定义只能使用双引号表示,而不是单引号。

获得字符串的长度,可以使用内置函数 len()

len(name) //4

你可以用方括号访问单独字符,使用索引来取得你想要的字符:

name[0] //"t" (indexes start at 0)
name[1] //"e"

你可以使用切片获取到字符串:

name[0:2] //"te"
name[:2]  //"te"
name[2:]  //"st"

你可以创建一个字符串的副本:

var newstring = name[:]

你可以将字符串赋值给一个新的变量,如下:

var first = "test"
var second = first

字符串是 不可变 的, 所以你无法修改字符串的值。

如果你给 first 赋予一个新值,second 的值依然是 "test"

var first = "test"
var second = first

first = "another test"

first  //"another test"
second //"test"

字符串是引用类型,意味着如果你将一个字符串传递给一个方法,字符串引用会被复制,而不是它的值,但是字符串是不可变的,所在在使用过程中它和 int 类型并没有很大的区别,例如。

你可以通过 + 运算符连接两个字符串:

var first = "first"
var second = "second"

var word = first + " " + second  //"first second"

Go提供了 strings 库来进行字符串的操作。

我们已经知道怎么在 “Hello, World!” 的案例中导入一个包。

这里你可以导入 strings

package main

import (
    "strings"
)

你可以使用它了。

在例子中我们使用 HasPrefix() 函数来判断一个字符串的开头是否是以另一个子串开头的:

package main

import (
    "strings"
)

func main() {
    strings.HasPrefix("test", "te") // true
}

你可以在这找到所有的函数列表:https://pkg.go.dev/strings

以下是你经常会用到的函数列表:

  • strings.ToUpper() 返回一个新的字符串, 大写
  • strings.ToLower() 返回一个新的字符串, 小写
  • strings.HasSuffix() 检查是否以某子串结尾
  • strings.HasPrefix() 检查是否以某子串开头
  • strings.Contains() 检查是否包含某字串
  • strings.Count() 计算一个某子串在当前字符串出现的次数
  • strings.Join() 创建一个新的字符串并连接多个字符串
  • strings.Split() 创建一个数组来保存通过特殊字符串对字符串进行分割的结果,例如通常使用空格
  • strings.ReplaceAll() 使用替换,可以使用一个新的字符串替换掉原字符中的字符串

Go 中的数组

数组是单个类型的序列。

我们可以这种方式定义数组:

var myArray [3]string //an array of 3 strings

或者你也可以给数组赋予初始值:

var myArray = [3]string{"First", "Second", "Third"}

在这个例子中,你可以让 Go 来帮你进行数组长度的推断:

var myArray = [...]string{"First", "Second", "Third"}

数组只能包含同一种类型的数据。

数组不能动态扩容——在 Go 中你必须声明数组的长度。这和类型一样是数组的一部分。当然,你不能使用一个没有声明长度的数组。

由于这个限制,数组在 Go 中很少使用,我们经常用到切片(稍后我们会讲到更多)。切片的底层是数组。所以我们需要知道它的工作原理。

你可以通过中括号获得数组中的每一个值正如我们之前获取字符串中单个字符一样:

myArray[0] //indexes start at 0
myArray[1]

你可以给数组中的成员设置新的值:

myArray[2] = "Another"

你可以通过 len() 函数来获取数组的长度:

len(myArray)

数组是 值类型。 这意味着复制一个数组:

anotherArray := myArray

传递一个数组给一个函数,或者一个函数返回数组,创建的都是原数组的副本。

这也是和其他编程语言的不同之处。

这里创建一个简单的示例,我们将一个新的值赋值给副本的一个元素,这个过程中不会修改原数组的元素:

var myArray = [3]string{"First", "Second", "Third"}
myArrayCopy := myArray
myArray[2] = "Another"

myArray[2]     //"Another"
myArrayCopy[2] //"Third"

记住你只能在数组中加入同一类型的数据,所以在例子中设置 myArray[2] = 2 会报错。

底层的元素存储在连续的内存当中。

Go 中的切片

切片是一种很像数组的数据结构,但它的大小是可以改变的。

底层,切片使用了数组且它是建立在数组之上的抽象概念使得本身变得更加灵活和方便使用(可以把数组想象为底层结构)。

你可以把切片作为高级语言中使用数组的一种使用方式。

你可以类似于数组定义切片,省略长度:

var mySlice []string //a slice of strings

你可以初始化切片的值:

var mySlice = []string{"First", "Second", "Third"}

//or

mySlice := []string{"First", "Second", "Third"}

你可以用 make() 函数创建一个有明确长度的空切片:

mySlice := make([]string, 3) //a slice of 3 empty strings

你可以用已经存在的切片创建一个新的切片,添加一个或多个元素进去:

mySlice := []string{"First", "Second", "Third"}

newSlice := append(mySlice, "Fourth", "Fifth")

注意我们需要给 append() 的结果赋给一个新的切片,否则我们将得到一个编译器错误。原来的切片没有改变,我们将得到一个全新的切片。

你也可以使用 copy() 函数来创建一个重复的切片,这样它就不会共享另一个切片的内存,而是独立的:

mySlice := []string{"First", "Second", "Third"}

newSlice := make([]string, 3)

copy(newSlice, mySlice)

如果你复制的切片没有足够的空间(比原来的短),则只复制第一个元素(只到有空间为止)。

你可以从数组中初始化一个切片:

myArray := [3]string{"First", "Second", "Third"}

mySlice = myArray[:]

多个切片可以使用与底层数组相同的数组:

myArray := [3]string{"First", "Second", "Third"}

mySlice := myArray[:]
mySlice2 := myArray[:]

mySlice[0] = "test"

fmt.Println(mySlice2[0]) //"test"

这两个切片现在共享同一块内存。修改一个切片底层数组也会跟着改变,并导致从该数组生成的另一个切片也会被修改。

与数组一样,每一个切片中的元素同样存储在内存的连续内存之中。

如果你知道你必须用到切片,你可以在初始化的时候设置所需的容量。这种方式,在你需要更多空间的时候,这些空间已经准备好了(替代了选择和移动切片到从老的内存空间到新的内存的方式,并减少了垃圾回收)。

我们可以通过 make() 函数的第三个参数来指定容量

newSlice := make([]string, 0, 10)
//an empty slice with capacity 10

例如字符串,你可以以下语法获得切片中的一部分:

mySlice := []string{"First", "Second", "Third"}

newSlice := mySlice[:2] //get the first 2 items
newSlice2 := mySlice[2:] //ignore the first 2 items
newSlice3 := mySlice[1:3] //new slice with items in position 1-2

Go 中的 map

map 是 Go 中一种常见的数据类型。

在其他语言被称为_字典_ 或 哈希表关联数组

下面是创建一个 map 的方式:

agesMap := make(map[string]int)

你不需要对 map 设置容纳多少项。

你可以通过这种方式添加新的元素到 map 中:

agesMap["flavio"] = 39

你可以用以下语法初始化 map 并赋值:

agesMap := map[string]int{"flavio": 39}

你可以通过键来获取对应的值:

age := agesMap["flavio"]

你可以通过 delete() 函数来删除 map 中的元素:

delete(agesMap, "flavio")

Go 中的循环

Go 中最好的特性是它只提供最少的选择。

我们只有一个循环关键字:for

你可以像这样使用它:

for i := 0; i < 10; i++ {
	fmt.Println(i)
}

我们首先初始化一个循环的变量, 我们设置一个条件用于检查我们的循环是否应该结束。最后我们设置 post 语句, 在每一次循环后执行, 这里例子中是增长 i

i++ 增长 i 变量.

< 运算符 用于比较 i10 的值,会返回 truefalse, 决定循环体是否执行。

我们不需要用圆括号来包围代码块,与 C 和 JavaScript 不太一样。

其他语言有各种不同的循环结构,但是 Go 中只有这一个,我们可以有像 while 一样的循环,如果你熟悉一门有它的语言,像这样:

i := 0

for i < 10 {
	fmt.Println(i)
  i++
}

我们完全可以忽略条件,在我们想要中止可以使用 break

i := 0

for {
	fmt.Println(i)

	if i < 10 {
		break
	}

  i++
}

我在循环体中使用了一个 if 声明,但是在我们没有看到条件语句!我们下一步看。

我现在想要介绍一种东西 range

我们可以使用以下语法通过 for 来迭代数组:

numbers := []int{1, 2, 3}

for i, num := range numbers {
	fmt.Printf("%d: %d\n", i, num)
}

//0: 1
//1: 2
//2: 3

注意:我使用 fmt.Printf() 它允许我们使用占位符 %d,意思是整数,而 \n 意思是加入一个换行符。

在你不需要索引时,一般可以使用以下语法:

for _, num := range numbers {
  //...
}

我们可以使用 _ 语法,它表示 “忽略 这个”,以避免Go编译器产生一个错误:“你没有使用 i 变量!”。

Go 中的条件语句

我们使用 if 声明一个条件从而执行不同的代码:

if age < 18 {
	//underage
}

else 部分是可选的:

if age < 18 {
	//underage
} else {
  //adult
}

或者可以使用多个 if

if age < 12 {
	//child
} else if age < 18  {
  //teen
} else {
	//adult
}

如果你在 if 中定义了任何的变量,那么只能在 if 中使用(else 中也一样且你需要在 {} 中写新的代码块)。

如果你有很多不同的 if 声明来检查同一个条件,使用 switch 是更好的选择:

switch age {
case 0: fmt.Println("Zero years old")
case 1: fmt.Println("One year old")
case 2: fmt.Println("Two years old")
case 3: fmt.Println("Three years old")
case 4: fmt.Println("Four years old")
default: fmt.Println(i + " years old")
}

与 C, JavaScript和其他语言相比,你不需要在每一个 case 中写 break

Go 中的运算符

到目前为止,我们已经在代码示例中使用了一些运算符, 如 =:=<

让我们了解更多。

我们有赋值运算符 =:= 我们用来定义和初始化变量:

var a = 1

b := 1

我们有比较运算符 ==!= 用来比较两个参数并会返回一个布尔值:

var num = 1
num == 1 //true
num != 1 //false

还有 <<=>>=

var num = 1
num > 1 //false
num >= 1 //true
num < 1 //false
num <= 1 //true

我们有双元(要求有两个参数)算术运算符, 像 +-*/%.

1 + 1 //2
1 - 1 //0
1 * 2 //2
2 / 2 //1
2 % 2 //0

+ 可以用来连接字符串:

"a" + "b" //"ab"

我们有单元运算符 ++-- 用来对一个数字进行自增或自减:

var num = 1
num++ // num == 2
num-- // num == 1

注意:不像 C 和 JavaScript 像这样 ++num 让一个数字预先操作。当然,运算符不会返回任何值。

我们有逻辑运算符来帮我们进行基本的 truefalse 的判断,使用:&&||!

true && true  //true
true && false //false
true || false //true
false || false //false
!true  //false
!false //true

这三个是最主要。

Go 中的结构体

结构体 是一种 类型,它包括一个或多个变量。它像是一个变量的集合。我们将它们称为字段。 它们可以有不同的类型。

这是一个结构体定义的示例:

type Person struct {
	Name string
	Age int
}

注意这里我们使用大写的名字作为字段名,不然它们在包内将会是私有的。且当你想让结构体作为参数传递给另一个库的函数时,像我们使用 JSON 及数据库时,这些字段都将无法访问。

一旦我们定义了一个结构体,我们就可以用这个类型初始化一个变量:

flavio := Person{"Flavio", 39}

且我们可以用以下语法修改字段的值:

flavio.Age //39
flavio.Name //"Flavio"

你也可以使用这种方式将一个结构体初始化赋给一个变量:

flavio := Person{Age: 39, Name: "Flavio"}

你也可以只初始化一个字段:

flavio := Person{Age: 39}

或者不初始化任何值:

flavio := Person{}

//or

var flavio Person

或者在之后设值:

flavio.Name = "Flavio"
flavio.Age = 39

结构体很常用,因为你可以对不相关的数据进行分组,并将其传递给函数或从函数传递给函数,保存在切片中,等等。

一旦定义,结构体就是像 intstring 这样的类型,这意味着你也可以在其他结构中使用它:

type FullName struct {
	FirstName string
	LastName string
}

type Person struct {
	Name FullName
	Age int
}

Go 中的函数

一个函数是一块代码,它被赋予了一个名字且包含了一些指令。

在 “Hello, World!” 示例中我们创建了一个 main 函数, 那是程序的入口。

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!")
}

这是一个特殊的函数。

通常我们使用自定义的名字来定义函数:

func doSomething() {

}

你可以像这样调用它:

doSomething()

一个函数可以设置入参,我们需要给参数设置类型,如下:

func doSomething(a int, b int) {

}

doSomething(1, 2)

ab 是函数内部参数的名字。

一个函数可以返回一个值,像这样:

func sumTwoNumbers(a int, b int) int {
	return a + b
}

result := sumTwoNumbers(1, 2)

注意这里我们指定的返回的值的类型。

一个函数可以返回多个或一个值:

func performOperations(a int, b int) (int, int) {
	return a + b, a - b
}

sum, diff := performOperations(1, 2)

有趣的是很多语言只能返回一个值。

函数内部定义的任何变量都是函数的本地变量。

一个函数也可以不限制参数的个数,且这样的函数我们称它为 可变函数

func sumNumbers(numbers ...int) int {
	sum := 0
	for _, number := range numbers {
		sum += number
	}
	return sum
}

total := sumNumbers(1, 2, 3, 4)

Go 中的指针

Go 支持使用指针。

假设你有一个变量:

age := 20

使用 &age 你获得这个变量的指针,它是内存地址。

当你拥有一个变量的指针时,你可以使用 * 运算符获取它的值:

age := 20
ageptr = &age
agevalue = *ageptr

通常当你想要传递一个参数给你调用的函数时。Go 默认会在函数内部复制这个变量的值,所以这不会改变 age 的值:

func increment(a int) {
	a = a + 1
}

func main() {
	age := 20
	increment(age)

	//age is still 20
}

你可以像这样使用指针:

func increment(a *int) {
	*a = *a + 1
}

func main() {
	age := 20
	increment(&age)

	//age is now 21
}

Go 中的方法

你可以给一个结构赋值一个函数,在这种情况下我们称它为“方法”。

示例:

type Person struct {
	Name string
	Age int
}

func (p Person) Speak() {
	fmt.Println("Hello from " + p.Name)
}

func main() {
	flavio := Person{Age: 39, Name: "Flavio"}
	flavio.Speak()
}

你可以定义一个方法为指针接收或值接收。

上述例子中我们展示的是值接收,这个接收器会复制结构体的结构。

这里将会有一个指针接收,接收结构实例的指针:

func (p *Person) Speak() {
	fmt.Println("Hello from " + p.Name)
}

Go 中的接口

接口是一个类型,可以定义一个或多个方法声明。

方法没有被实现,只有它们的签名:名字、参数类型和返回值类型。

像这样:

type Speaker interface {
	Speak()
}

现在你有一个函数,可以接纳任何类型来实现接口定义所有方法:

func SaySomething(s Speaker) {
	s.Speak()
}

我们可以让任何一个结构体来实现这些方法:

type Speaker interface {
	Speak()
}

type Person struct {
	Name string
	Age int
}

func (p Person) Speak() {
	fmt.Println("Hello from " + p.Name)
}

func SaySomething(s Speaker) {
	s.Speak()
}

func main() {
	flavio := Person{Age: 39, Name: "Flavio"}
	SaySomething(flavio)
}

更多内容

这份手册初步介绍了 Go 编程语言。

基于这里的基础,你现在可以学更多东西:垃圾回收、错误处理、并发和网络、文件系统接口,等等。

海阔凭鱼跃,天高任鸟飞。

我的建议是选择一个你想要创建的程序,然后开始学习你需要的东西。

它是有趣且值得做的。

注意:你可以点击链接获得 GO 初学者手册的 pdf 版本和 ePub 版本