女王控的博客

Go 入门学习

入门

Hello, World

我们以现已成为传统的 hello world 案例来开始吧

go 复制代码
package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

Go 是一门编译型语言,Go 语言的工具链将源代码及其依赖转换成计算机的机器指令(译注:静态编译)。Go 语言提供的工具都通过一个单独的命令 go 调用,go 命令有一系列子命令。最简单的一个子命令就是 run。这个命令编译一个或多个以 .go 结尾的源文件,链接库文件,并运行最终生成的可执行文件(本书使用 $ 表示命令行提示符。)

bash 复制代码
$ go run helloworld.go

毫无意外,这个命令会输出:

复制代码
Hello, 世界

Go 语言原生支持 Unicode,它可以处理全世界任何语言的文本。

如果不只是一次性实验,你肯定希望能够编译这个程序,保存编译结果以备将来之用,可以用 build 子命令:

bash 复制代码
$ go build helloworld.go

这个命令生成一个名为 helloworld 的可执行的二进制文件(译注:Windows 系统下生成的可执行文件是 helloworld.exe,增加了 .exe 后缀名),之后你可以随时运行它(译注:在 Windows 系统下在命令行直接输入 helloworld.exe 命令运行),不需任何处理(译注:因为静态编译,所以不用担心在系统库更新的时候冲突,幸福感满满)。

bash 复制代码
$ ./helloworld
Hello, 世界

执行 go get gopl.io/ch1/helloworld 命令,就会从网上获取代码,并放到对应目录中(需要先安装 Git 或 Hg 之类的版本管理工具,并将对应的命令添加到 PATH 环境变量中。

来讨论下程序本身。Go 语言的代码通过包(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个 .go 源代码文件组成,目录定义包的作用。每个源文件都以一条 package 声明语句开始,这个例子里就是 package main,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。

Go 的标准库提供了 100 多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如 fmt 包,就含有格式化输出、接收输入的函数。Println 是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。

main 包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在 main 里的 main 函数也很特殊,它是整个程序执行时的入口(译注:C 系语言差不多都这样)。main 函数所做的事情就是程序做的。当然了,main 函数一般调用其它包里的函数完成很多工作(如:fmt.Println)。

必须告诉编译器源文件需要哪些包,这就是跟随在 package 声明后面的 import 声明扮演的角色。hello world 例子只用到了一个包,大多数程序需要导入多个包。

必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。这项严格要求避免了程序开发过程中引入未使用的包(译注:Go 语言编译过程没有警告信息,争议特性之一)。

import 声明必须跟在文件的 package 声明之后。随后,则是组成程序的函数、变量、常量、类型的声明语句(分别由关键字 func、var、const、type 定义)。这些内容的声明顺序并不重要(译注:最好还是定一下规范)。这个例子的程序已经尽可能短了,只声明了一个函数,其中只调用了一个其他函数。为了节省篇幅,有些时候示例程序会省略 package 和 import 声明,但是,这些声明在源代码里有,并且必须得有才能编译。

一个函数的声明由 func 关键字、函数名、参数列表、返回值列表(这个例子里的 main 函数参数列表和返回值都是空的)以及包含在大括号里的函数体组成。第五章进一步考察函数。

Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析(译注:比如行末是标识符、整数、浮点数、虚数、字符或字符串文字、关键字 break、continue、fallthrough 或 return 中的一个、运算符和分隔符 ++--)]} 中的一个)。举个例子,函数的左括号 { 必须和 func 函数声明在同一行上,且位于末尾,不能独占一行,而在表达式 x + y 中,可在 + 后换行,不能在 + 前换行(译注:以 + 结尾的话不会被插入分号分隔符,但是以 x 结尾的话则会被分号分隔符,从而导致编译错误)。

Go 语言在代码格式上采取了很强硬的态度。gofmt 工具把代码格式化为标准格式(译注:这个格式化工具没有任何可以调整代码格式的参数,Go 语言就是这么任性),并且 go 工具中的 fmt 子命令会对指定包,否则默认为当前目录中所有 go 源文件应用 gofmt 命令。本书中的所有代码都被 gofmt 过。你也应该养成格式化自己的代码的习惯。以固定方式规定标准的代码格式可以避免无尽的无意义的琐碎争执(译注:也导致了 Go 语言的 TIOBE 排名较低,因为缺少撕逼的话题)。更重要的是,这样可以做多种自动源码转换,如果放任 Go 语言代码格式,这些转换就不大可能了。

很多文本编辑器都可以配置为保存文件时自动执行 gofmt,这样你的源代码总会被恰当地格式化。还有个相关的工具:goimports,可以根据代码需要,自动地添加或删除 import 声明。这个工具并没有包含在标准的分发包中,可以用下面的命令安装:

bash 复制代码
$ go get golang.org/x/tools/cmd/goimports

对于大多数用户来说,下载、编译包、运行测试用例、察看 Go 语言的文档等等常用功能都可以用 go 的工具完成。10.7 节详细介绍这些知识。

命令行参数

go 复制代码
// Echo1 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}
go 复制代码
// Echo2 prints its command-line arguments.
package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}
go 复制代码
s := "" // 一条短变量声明,最简洁,但只能用在函数内部,而不能用于包变量
var s string // 依赖于字符串的默认初始化零值机制,被初始化为 ""
var s = "" // 用得很少,除非同时声明多个变量
var s string = "" // 显式地标明变量的类型,当变量类型与初值类型相同时,类型冗余,但如果两者类型不同,变量类型就必须了

实践中一般使用前两种形式中的某个,初始值重要的话就显式地指定变量的类型,否则使用隐式初始化。

每次循环迭代字符串 s 的内容都会更新。+= 连接原字符串、空格和下个参数,产生新字符串,并把它赋值给 s。s 原来的内容已经不再使用,将在适当时机对它进行垃圾回收。

如果连接涉及的数据量很大,这种方式代价高昂。一种简单且高效的解决方案是使用 strings 包的 Join 函数:

go 复制代码
func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
}

最后,如果不关心输出格式,只想看看输出值,或许只是为了调试,可以用 Println 为我们格式化输出。

go 复制代码
fmt.Println(os.Args[1:])

这条语句的输出结果跟 strings.Join 得到的结果很像,只是被放到了一对方括号里。切片都会被打印成这种格式。

// TODO https://golang-china.github.io/gopl-zh/preface-zh.html

评论

阅读上一篇

博客 Gatsby 插件改造
2023-11-28 16:17:27

阅读下一篇

系统设计深入学习
2023-09-14 13:47:01
0%