文章目录
1. 前言
(1) Go的现状
Go 语言是由 Google 在2009年出品的一门通用型计算机编程语言。作为在近年来快速崛起的编程语言,深受大家的喜爱,许多大厂都已经拥抱 Go 语言,包括以 Java 打天下的阿里巴巴,更别提深爱着 Go 语言的滴滴、今日头条、小米、奇虎 360、京东等明星公司。同时,创业公司也很喜欢 Go 语言,主要因为其入门快、程序库多、运行迅速,很适合快速构建互联网软件产品,比如轻松筹、快手、知乎、探探、美图、猎豹移动等等。
(2) Go的优点
开发人员在为项目选择语言时,不得不在快速开发和性能之间做出选择。C和C++这类语言提供了很快的执行速度,而 Ruby 和 Python 这类语言则擅长快速开发。Go语言在这两者间架起了桥梁,不仅提供了高性能的语言,同时也让开发更快速。
可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。
语言层面支持并发,这个就是Go最大的特色,天生的支持并发。Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。
跨平台编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。。
(3) 与Go的不解之缘
我是在2016年才开始接触这门语言的,当时学习搜索引擎,于是用Golang写了个简单的搜索引擎,准备近期梳理好代码后,放到GitHub上,也可以供大家学习参考,作为一个入门还是很合适的。
之前也通过《Go 并发编程实战》的作者郝林老师18年生病,社区发起了捐款,虽然最后10个小时筹集的10万元他都退了回去,能够支持的就是在极客时间上参与了他的《Go语言核心36讲》学习,感谢他在Go语言上的布道。
(4) 知识体系结构
整个知识体系结构如下,我会通过学习与实践,带领大家进行系统的了解。
基础概念:讲述 Go 语言基础中的基础,包括一些基本概念和运作机制。
数据类型和语句:Go 语言中的数据类型大都是很有特色的,只有理解了它们才能真正玩转 Go 语言。
程序的测试:单元测试甚至接口测试其实都应该是程序员去做的,且应该受到重视。
Go 语言开源项目学习:这部分将会讲述如何使用 Go 来实现一个简单的搜索引擎。
2. 工作区和GOPATH
(1) 环境安装
安装包下载:https://golang.google.cn/
完成安装后配置以下3个环境变量:
- GOROOT:Go 语言安装根目录的路径,也就是 GO 语言的安装路径。
- GOPATH:若干工作区目录的路径。是我们自己定义的工作空间。
- GOBIN:GO 程序生成的可执行文件(executable file)的路径。
(2) GOPATH 的意义
可以把 GOPATH 简单理解成 Go 语言的工作目录,它的值是一个目录的路径,也可以是多个目录路径,每个目录都代表 Go 语言的一个工作区(workspace)。
我们需要利于这些工作区,去放置 Go 语言的源码文件(source file),以及安装(install)后的归档文件(archive file,也就是以“.a”为扩展名的文件)和可执行文件(executable file)。
Go 语言项目在其生命周期内的所有操作(编码、依赖管理、构建、测试、安装等)基本上都是围绕着 GOPATH 和工作区进行的。
Go 语言源码的组织方式
Go 语言的源码也是以代码包为基本组织单位的。在文件系统中,这些代码包其实是与目录一一对应的。由于目录可以有子目录,所以代码包也可以有子包。
代码包的名称一般会与源码文件所在的目录同名。如果不同名,那么在构建、安装的过程中会以代码包名称为准。
每个代码包都会有导入路径。代码包的导入路径是其他代码在使用该包中的程序实体时,需要引入的路径。在实际使用程序实体之前,我们必须先导入其所在的代码包。具体的方式就是import该代码包的导入路径。就像这样:
import "github.com/labstack/echo"
Go 语言源码的组织方式就是以环境变量 GOPATH、工作区、src 目录和代码包为主线的。一般情况下,Go 语言的源码文件都需要被存放在环境变量 GOPATH 包含的某个工作区(目录)中的 src 目录下的某个代码包(目录)中。
源码安装后的结果
源码文件会以代码包的形式组织起来,一个代码包其实就对应一个目录。安装某个代码包而产生的归档文件是与这个代码包同名的。放置它的相对目录就是该代码包的导入路径的直接父级。比如,一个已存在的代码包的导入路径是
github.com/labstack/echo
如何安装:
go install github.com/labstack/echo
构建和安装 Go 程序的过程
构建使用命令go build,安装使用命令go install。
构建和安装代码包的时候都会执行编译、打包等操作,并且,这些操作生成的任何文件都会先被保存到某个临时的目录中。
如果安装的是库源码文件,那么结果文件会被搬运到它所在工作区的 pkg 目录下的某个子目录中。
如果安装的是命令源码文件,那么结果文件会被搬运到它所在工作区的 bin 目录中,或者环境变量GOBIN指向的目录中。
构建和安装的不同之处,以及执行相应命令后得到的结果文件都会出现在哪里。
go get
与 Go 源码的安装联系紧密的命令。
go get golang.org/x/sync/semaphore
命令go get会自动从一些主流公用代码仓库(比如 GitHub)下载目标代码包,并把它们安装到环境变量GOPATH包含的第 1 工作区的相应目录中。如果存在环境变量GOBIN,那么仅包含命令源码文件的代码包会被安装到GOBIN指向的那个目录。
3. 命令源码文件
如果一个源码文件声明属于main包,并且包含一个无参数声明且无结果声明的main函数,那么它就是命令源码文件。 就像下面这段代码:
package main
import "fmt"
func main() {
fmt.Println("Hello, world!")
}
例如代码文件名为hello.go,运行改代码:
go run hello.go
(1) 命令源码文件怎样接收参数
基本代码
package main
import (
// 需在此处添加代码。[1]
"flag"
"fmt"
)
var name string
func init() {
// 需在此处添加代码。[2]
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
// 需在此处添加代码。[3]
flag.Parse()
fmt.Printf("Hello, %s!\n", name)
}
执行命令后,对应的输出
go run .\demo.go
Hello, everyone!
(2) 传入参数,查看参数说明
# 传参
go run .\demo.go -name="world"
Hello, world!
# 查看参数说明
go run .\demo.go --help
Usage of C:\Users\JasonLi\AppData\Local\Temp\go-build409250058\b001\exe\demo.exe:
-name string
The greeting object. (default "everyone")
exit status 2
(3) 自定义命令源码文件的参数使用说明
对变量flag.Usage重新赋值,在main函数体的开始处加入如下代码
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
重新执行命令
go run .\demo.go --help
Usage of question:
-name string
The greeting object. (default "everyone")
exit status 2
下面咱们再深入一层,我们在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
// flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
flag.CommandLine = flag.NewFlagSet("", flag.PanicOnError)
flag.CommandLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
执行–help后如下:
go run .\demo.go --help
Usage of question:
-name string
The greeting object. (default "everyone")
panic: flag: help requested
goroutine 1 [running]:
flag.(*FlagSet).Parse(0xc0000a2060, 0xc000066450, 0x1, 0x1, 0xc000097f50, 0x4058c6)
C:/Go/src/flag/flag.go:981 +0xff
flag.Parse(...)
C:/Go/src/flag/flag.go:996
main.main()
D:/code/go/demo/demo.go:35 +0x7f
exit status 2
flag.PanicOnError
和flag.ExitOnError
都是预定义在flag包中的常量。
flag.ExitOnError
的含义是,告诉命令参数容器,当命令后跟--help
或者参数设置的不正确的时候,在打印命令参数使用说明后以状态码2结束当前程序。
状态码2代表用户错误地使用了命令,flag.PanicOnError
与之的区别是在最后抛出 panic
。
上述两种情况都会在我们调用flag.Parse
函数时被触发。
调整以上代码,灵活定制命令参数容器,更不会影响到全局变量flag.CommandLine
package main
import (
"flag"
"fmt"
"os"
)
var name string
var cmdLine = flag.NewFlagSet("", flag.PanicOnError)
//var cmdLine = flag.NewFlagSet("question", flag.ExitOnError)
func init() {
cmdLine.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", "question")
flag.PrintDefaults()
}
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
// flag.Parse()
cmdLine.Parse(os.Args[1:])
fmt.Printf("Hello, %s!\n", name)
}
4. 库源码文件
库源码文件是不能被直接运行的源码文件,它仅用于存放程序实体,这些程序实体可以被其他代码使用
创建一个文件 demo1.go 代码如下
package main
import (
"flag"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
flag.Parse()
hello(name)
}
将 hello()
函数写到 demo2.go 中
package main
import "fmt"
func hello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
运行代码
go run .\demo1.go .\demo2.go
Hello, everyone!
go run .\demo1.go .\demo2.go -name="world"
Hello, world!
也可先编译,再运行
go build ./
.\learning36-3.exe
Hello, everyone!
.\learning36-3.exe -name="world"
Hello, world!
代码包声明的基本规则:
-
第一条规则,同目录下的源码文件的代码包声明语句要一致。要同属于一个代码包,对所有源码文件都适用。如果目录中有命令源码文件,那么其他种类的源码文件也应该声明属于main包。这也是我们能够成功构建和运行它们的前提。
-
第二条规则,源码文件声明的代码包的名称可以与其所在的目录的名称不同。在针对代码包进行构建时,生成的结果文件的主名称与其父目录的名称一致。对于命令源码文件而言,构建生成的可执行文件的主名称会与其父目录的名称相同。
命令源码文件中的代码拆分到其他代码包
将demo2.go移动到,mylib/demo2.go中,如何直接编译demo1.go,将mylib/demo2.go当成外部包
package mylib
import "fmt"
func Hello(name string) {
fmt.Printf("Hello, %s!\n", name)
}
上面代码做了2个改动:
-
package与父目录保持一致:为了不让该代码包的使用者产生困惑,我们总是应该让声明的包名与其父目录的名称一致。
-
函数定义为首字母大写:因为名称的首字母为大写的程序实体才可以被当前包外的代码引用,否则它就只能被当前包内的其他代码引用。
go install learning36-3/mylib/
# 能看到,pkg\windows_amd64\learning36-3
调整demo1.go代码
package main
import (
"flag"
"learning36-3/mylib"
)
var name string
func init() {
flag.StringVar(&name, "name", "everyone", "The greeting object.")
}
func main() {
flag.Parse()
mylib.Hello(name)
}
运行代码
go run .\demo1.go
Hello, everyone!
Reference:
https://golang.google.cn/ref/spec
极客时间 - Go语言核心36讲