Go
引言
非常入门的一本书?准确来说是一篇GO的入门文章,用于学学英语和简单了解GO还不错,并且词汇和语法都比较简洁易懂。
话不多说,下面就来看看这篇文章的翻译。
原文
https://www.freecodecamp.org/news/go-beginners-handbook/
1.介绍GO
它是由谷歌工程师创建的,设计这门语言的主要目标是:
- 提高项目的编译和运行速度
- 入门门槛足够低,但是也要避免过于简单和低级。
- 具备可移植(编译后的 Go 程序是二进制文件,不需要其他文件就能运行,而且是跨平台的,因此可以很容易地分发)
- 单调、稳定、可预测,犯错的机会少 。
- 能充分发挥多线程优势
2.如何开始?
在深入了解该语言的具体内容之前,您应该了解以下几件事。
首先,https://go.dev 是该语言的主页。
下面是首选的入门资源:
- 从 https://go.dev/doc/install 下载 Go 二进制文件(go 命令和其他相关工具)。
- 参考 Go 官方文档: https://go.dev/doc/
- 查看所有 Go 软件包: https://pkg.go.dev/
- 访问 Go Playground: https://go.dev/play/
- ...
3.安装 GO
安装GO的教程网上一抓一大把,个人使用的是Window环境,Windows 环境下只需要安装exe应用程序即可,简单无脑就不演示了。
4.开发 IDE 安装和配置
NOTE: you might have to open a new terminal before you can run the program, as the installer added the Go binaries folder to the path.
注意:由于安装程序在路径中添加了 Go 二进制文件夹,运行程序前可能需要打开一个新的终端。
在使用IDE之前,请先确保自己的环境变量可以正常使用GO。
本书使用的是 VS Code,具体可以阅读此网站了解:Go with Visual Studio Code。
[[【Go】Go in Visual Studio Code]]
VS Code安装之后,需要安装GO相关的扩展:marketplace.visualstudio.com/items?itemName=golang.go
5.你好,世界
- 创建一个新的文件夹,比如下面的 hello。
- 创建一个
hello.go
的文件,并且在文件内写入下面的内容:
package main
import "fmt"
func main() {
fmt.Println(" Hello, World!")
}
- 在文件所在路径,运行
go run hello.go
,如果结果如下说明运行成功:
xander@LAPTOP-47J243NL MINGW64 /e/adongstack/go/hello
$ go run hello.go
Hello, World!
下面来解释下上面的入门程序:
- 每个 .go 文件首先声明它是哪个软件包的一部分
- 一个软件包可以由多个文件组成,也可以只由一个文件组成。一个程序可以包含多个软件包。
main
函数是程序的入口点,也是可执行程序的标识。- 我们使用 import 关键字来导入程序包。
fmt is a built-in package provided by Go that provides input/ output utility functions.
fmt 是 Go 提供的一个内置包,提供输入/输出实用功能。
GO官方拥有庞大的标准库,从网络连接到数学、密码、图像处理、文件系统访问等,我们都可以使用它。
Standard library - Go Packages
比如,有关fmt
这个包的文档介绍可以查看这个地址了解:fmt package - fmt - Go Packages,根据文档,该函数功能是 "根据格式说明符进行格式化,并写入标准输出"。
我们使用 "点 "语法 fmt.Println()
来指定该函数由该程序包提供。
当代码执行完主函数后,就没有其他事情可做了,执行结束。
在main函数中,我们定义了fmt.Println(" Hello, World!")
这样一串代码。
6.编译并运行 Go 程序
本部分接着上一章节的入门程序介绍,解释如何编译并且运行go程序
go run hello.go
go run 工具首先编译,然后运行指定的程序。
我们可以使用 go build
创建二进制文件:
go build hello.go
个人电脑上执行的结果如下:
PS E:\adongstack\go\hello> go build .\hello.go
PS E:\adongstack\go\hello> dir
目录: E:\adongstack\go\hello
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2023/9/19 13:33 1897472 hello.exe
-a---- 2023/9/19 13:32 84 hello.go
Go 是可移植的,因为本质编译之后本质就是二进制文件,这样每个人都可以按原样运行程序,并且二进制文件已经打包供执行。
程序将在我们构建它的相同架构上运行。
我们可以使用 GOOS
和 GOARCH
环境变量为不同的架构创建不同的二进制文件:
GOOS = windows GOARCH = amd64 go build hello.go
这将为 64 位 Windows 机器创建一个 hello.exe 可执行文件:
64 位 macOS(英特尔或苹果)的设置为
GOOS = darwin GOARCH = amd64
Linux 为 :
GOOS = linux GOARCH = amd64
这是 Go 的最佳功能之一。
7.工作空间
Go有一个特别之处就是我们所说的工作空间。
工作区是 Go 的 "大本营"。
默认情况下,Go 会选择 $ HOME/ go
路径,因此你会在主目录中看到一个 go 文件夹。
例如,当我在 VS Code 中加载 hello.go 文件时,它提示我安装 gopls 命令、Delve 调试器 (dlv) 和 staticcheck linter。
它们已自动安装在 $ HOME/ go
下:
当你使用 go install 安装软件包时,它们将被存储在这里。
这就是我们所说的 GOPATH。
你可以更改 GOPATH 环境变量来改变 Go 安装包的位置。
当你同时在不同的项目中工作,并希望隔离你使用的库时,这一点非常有用。
8.深入语言
既然我们已经有了初步的概念,并运行了第一个 Hello, World! 程序,那么我们就可以深入研究这门语言了。
这种语言没有语义上重要的空白。
就像 C、C + +、Rust、Java
和 JavaScript。与 Python
不同的是,Python
的空白是有意义的,它被用来创建块而不是大括号。
Go 非常重视缩进和视觉顺序。安装 Go 时,我们还可以使用 gofmt 命令行工具来格式化 Go 程序。
VS Code 在引擎盖下使用该工具来格式化 Go 源文件。
这是一个非常有意思的特性,语言创建者定义了规则,每个人都会使用这些规则。
这非常适合大型团队的项目。
本书建议在VS Code 当中设置 “Format on Save” 以及 “Format on Paste”:
Go 中的注释使用常见的 C / C + + / JavaScript / Java 语法:
9.变量
在 GO 里面可以是用 var
关键字定义变量:
var age = 20
var
关键字可以定义包级别变量:
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!")
}
在软件包级别定义的变量在组成软件包的所有文件中都是可见的。
一个软件包可以由多个文件组成,只需创建另一个文件,并在顶部使用相同的软件包名称即可。
在函数内部定义的变量,一个变量只能在函数中可见。
这使得 Go 判断变量 age 的类型是 int。
我们稍后会看到更多关于类型的内容,但你应该知道有许多不同的类型,首先是 int、string 和 bool。
我们也可以声明一个没有现存值的变量,但在这种情况下,我们必须像这样设置类型:
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 = 10, "Roger"
// or
age, name := 10, "Roger"
如果定义了变量但是没有使用,在VS Code 中会有相关提示。
这些错误信息实际是编译
如果您声明一个变量,但没有将其初始化为一个值,那么它会自动分配一个值,这个值取决于变量的类型,例如整数为 0,字符串为空字符串。
10.基本类型
Go 的基本类型有
- 整数(int、int8、int16、int32、rune、int64、uint、uintptr、uint8、uint16、uint64)
- 浮点数(float32、float64),用于表示小数
- 复数类型(complex64、complex128),用于数学运算
- 字节(byte),表示单个 ASCII 字符
- 字符串(string),一组字节
- 布尔类型(bool),表示真或假
我们有很多不同的类型来表示interger,大部分时间你都会使用int,你可能会选择一个更专业的类型来进行优化(刚开始学习时不需要考虑这些)。
int
类型针对 32位机器和64位的机器做了类型适配,
uint
是一个无符号的 int
,如果知道数值不会是负数,就可以用它来加倍存储数值。
11.字符串
Strings 在 Go 语言中是一串字节数组。
我们可以使用下面的语法定义一串字符串:
var name = "test"
值得注意的是,与其他语言不同,字符串的定义只能使用双引号,而不能使用单引号(比如 JS)。
可以使用len
函数获取字符串长度
len( name) // 4
您可以使用方括号访问单个字符,同时传递您要获取的字符的索引:
name[ 0] //" t" (indexes start at 0)
name[ 1] //" e"
可以使用下面的语法像Python语言一样对于字符串进行“切片”:
name[ 0: 2] //" te"
name[: 2] //" te"
name[ 2:] //" st"
使用下面的语法可以拷贝一个字符串:
var newstring = name[:]
您可以将一个字符串赋值给一个新变量:
var first = "test"
var second = first
Strings 是不可变变量,意味着你不能更新一个Strings,即使使用赋值运算符为第一项赋值,第二项的值仍然是 "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 在字符串包中提供了多个字符串实用程序。
我们已经在 "Hello, World!"示例中看到了如何导入包,下面介绍如何导入字符串:
以下是导入字符串的方法:
package main
import ( "strings" )
12.数组
数组是由单一类型的项目组成的序列。
我们这样定义一个数组:
var myArray [3] string // 包含 3 个字符串的数组
你可以使用以下值初始化数组:
var myArray = [3] string{" First", "Second", "Third"}
在这种情况下,你也可以让 Go 来帮你计数:
var myArray = [...] string{" First", "Second", "Third"}
注意,数组只能包含相同类型的值。
数组不能调整大小,必须在 Go 中明确定义数组的长度。
数组不能调整大小,必须在 Go 中明确定义数组的长度。
这是数组类型的一部分。此外,也不能使用变量来设置数组的长度。
由于这种限制,在 Go 中很少直接使用数组,而是使用 Slice
(稍后会详细介绍)。
注意 Slice 底层也是数组实现的,所以了解数组是基础。
数组可以通过方括号加下标值获取数组特定位置,然后针对特定位置设置新值。
myArray[ 0] // indexes start at 0
myArray[ 1]
数组也可以使用len()
函数获取数组长度:
数组是值类型。这意味着可以复制数组:
anotherArray := myArray
将数组传递给函数,或从函数中返回数组,都会创建原始数组的副本。
这与其他编程语言不同。
让我们举一个简单的例子,在复制一个数组项后,给它赋一个新值。
请看,拷贝并没有改变:
var myArray = [3] string{" First", "Second", "Third"}
myArrayCopy := myArray
myArray[ 2] = "Another"
myArray[ 2] //" Another"
myArrayCopy[ 2] //" Third"
请记住,你只能在数组中添加单一类型的项,因此设置 myArray[ 2] = 2
会引发错误。
底层元素在内存中连续存储。
低级元素持续存储在内存中。
13.分片
分片是一种类似于数组的数据结构,但它的大小可以改变。
切片使用数组,是建立在数组之上的一种抽象结构,它使切片更灵活、更有用(将数组视为低级结构)。
如果你知道需要对切片执行操作,你可以要求它拥有比最初需要的更大容量,这样当你需要更多空间时,空间就会随时可用(而不是找到切片并将其移动到一个有更多空间的新内存位置,然后通过垃圾回收处理旧位置)。
我们可以为 make() 添加第三个参数来指定容量:
newSlice := make([] string, 0, 10) // an empty slice with capacity 10
多个切片可以使用同一个数组作为底层数组:
myArray := [3] string{" First", "Second", "Third"} mySlice = myArray[:]
与字符串一样,使用该语法可以获取片段的一部分:
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
14.Maps
map
是go语言的常用数据类型。
agesMap := make( map[ string] int)
您无需设置地图可容纳的物品数量。您可以通过这种方式向map添加新内容:
agesMap[" flavio"] = 39
您也可以使用以下语法直接用值初始化映射:
agesMap := map[ string] int{" flavio": 39}
通过下面的语法可以获取map的key的值:
age := agesMap[" flavio"]
使用delete()
函数可以删除Map中对应的Key。
delete( agesMap, "flavio")
15.循环
Go 的循环语句关键字也是for
:
for i := 0; i < 10; i + + {
fmt.Println( i)
}
我们首先初始化一个循环变量,然后设置每次迭代时要检查的条件,以决定循环是否应该结束,最后在每次迭代结束时执行 post 语句,在本例中,该语句会递增 i。
i++ 使 i 变量递增。
< 操作符用于将 i 与数字 10 进行比较,并返回 true 或 false,以决定是否执行循环体。
与 C 或 JavaScript 等其他语言不同,我们不需要在该代码块周围使用括号。
此外还需要注意,Go 没有 while
这样的循环语法,如果想要实现类似的功能,可以使用for
进行模拟。
i := 0 for i < 10 {
fmt.Println( i) i + +
}
此外可以通过break
语句跳出循环。
for {
fmt.Println( i)
if i < 10 {
break
}
i ++
}
我在循环体中使用了 if
语句,但我们还没有看到条件!我们下一步再做。
现在我要介绍的是范围。
我们可以使用 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 来向终端打印任何值。
当不需要使用index
时,使用这种语法很常见:
for _, num := range numbers {
//...
}
使用表示 "忽略这个 "的特殊 _
字符,以避免 Go 编译器出现 "你没有使用 i 变量!"的错误。
16.条件式
条件语句的语法和其他语言类似:
if age < 12 { // child
} else if age < 18 {
// teen
} else {
// adult
}
和其他语言一样,在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 和其他语言相比,您不需要在每种情况后都有一个分隔符。
17.操作符
到目前为止,我们在代码示例中使用了一些运算符,如 =、:= 和 <。
我们使用赋值运算符 = 和 := 来声明和初始化变量:
var a = 1
b := 1
我们有比较运算符 == 和 != ,它们接受 2 个参数并返回布尔值。
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
那样将它们前置到数字上。
此外,该操作不会返回任何值。
我们有布尔运算符帮助我们根据真假值做出决定:&&
、||
和 !
:
true && true // true
true && false // false
true || false // true
false || false // false
!true // false
!false // true
18.结构
结构体是一种包含一个或多个变量的类型。它就像是变量的集合。我们称之为字段。它们可以有不同的类型。
下面是定义结构体的代码:
type Person struct {
Name string
Age int
}
请注意,我使用了大写字母作为字段名称,否则这些字段将成为软件包的私有字段,当您将结构体传递给另一个软件包提供的函数(如我们用于处理 JSON 或数据库的函数)时,就无法访问这些字段。
定义结构体后,我们就可以用该类型初始化变量:
flavio := Person{" Flavio", 39}
可以使用下面的方式获取结构体的字段数据:
flavio.Age // 39
flavio.Name //" Flavio"
结构体非常有用,因为它可以将不相关的数据分组,并将其传递给函数或从函数中传递出来,还可以存储在片段中,等等。
一旦定义,结构体就是一种类似 int 或字符串的类型,这意味着你也可以在其他结构体内部使用它:
type FullName struct {
FirstName string
LastName string
}
type Person struct {
Name FullName
Age int
}
19.Functions
函数是一个代码块,它被赋予一个名称,并包含一些指令。
在 "你好,世界!"示例中,我们创建了一个 main 函数,它是程序的入口。
package main import "fmt" func main() { fmt.Println(" Hello, World!") }
通常,我们用自定义名称定义函数:
func doSomething() {
然后通过下面的方式调用函数:
doSomething()
函数可以接受参数,我们必须这样设置参数的类型:
func doSomething( a int, b int) {
}
doSomething( 1, 2)
a 和 b 是函数内部参数的名称。
函数可以返回一个值,就像下面这样:
func sumTwoNumbers( a int, b int) int {
return a + b
}
result := sumTwoNumbers( 1, 2)
请注意,这里指定了返回值类型
GO 语言的return 可以返回超过一个值:
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)
20.指针
GO语言支持使用指针,假设使用下面的变量定义:
age := 20
有了变量指针后,就可以使用 * 运算符获取其指向的值:
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
}
21.函数
一个函数可以分配给一个结构体,在这种情况下,我们称之为方法。
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) }
22.接口
接口是一种定义了一个或多个方法签名的类型。方法没有实现,只有签名:名称、参数类型和返回值类型。类似于这样:
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)
}
23. 下一步行动
本手册介绍 Go 编程语言。除了这些基础知识,现在还有很多东西需要学习。
垃圾回收、错误处理、并发和网络、文件系统 API 等等。学习无止境。
我的建议是,选择一个你想构建的程序,然后开始学习你需要的东西。这会很有趣,也很有收获。
相关
【Linux】《The Command Line Handbook》 读书笔记(上半部分)
【Linux】《The Command Line Handbook》 读书笔记(下半部分)