go语言学习(二)--第一个GO程序

[TOC]

1.第一个Go程序

​ 几乎所有的程序开始都是以Hello World小例子作为开始。我们也不面熟(或者说尊重传统),下面我们从一个简单Go语言版本的Hello World来初窥Go这门新语言的模样。

​ hello.go

1
2
3
4
5
6
7
package main
import "fmt" //导入我们需要使用的包 ,我们需要使用里面的Println函数
func main() {
fmt.Println("Hello, world. 你好, 世界!")
}

代码解读:

​ 每个Go源码文件的开头都是以一个package开头,表示该Go代码所属的包。包是Go中最基本的分发单位,也会工程管理中依赖关系的体现。要生成可执行文件,必须建立一个名字为main的包,并且在该包中包含一个叫main()的函数(该函数为Go可执行程序的起点,也只能含有一个)。

​ main()函数定义为不能带参数、返回值。命令行的参数保存在os包中的os.Args中,命令行的参数解析可以通过flag包解析,在后面章节中我们回看到如何使用flag包。

​ 在包的声明后会含有一系列的import语句,用于导入该程序所依赖的包。(有一点需要注意:⚠️ 在源码文件中不能包含没有使用的包,否则会编译错误)

​ 所有的Go函数都是以func关键字开头一个常规的函数包涵一下格式。

1
2
3
4
5
6
7
func 函数名(参数列表)(返回值列表){
}
//比如
func Compute(arg1 int,arg2 float64)(retVal float64,err error){
}

​ 在Go的函数中支持多个函数返回值,比如上面的例子中返回了retVal和error。但是并不是所有的返回值都需要赋值,如果函数返回值没有被明确的赋值都会被设置为对应的类型的零值,比如retVal被设置为0.0,err被设置为nil。

Go中的注释

Go的注释和C++保持一致,同时支持一下两种方式:

1
2
3
4
5
/*
快注释
*/
// 行注释

​ ⚠️

  1. 在Go源码文件中不能包含没有使用的包,否则会编译错误。

  2. 在Go源码文件中不能使用分号。

  3. 同时在含有括号的语句中,左括号({)不能另起一行。syntax error: unexpected semicolon or newline before {

    代码的编译与执行

    1
    2
    $ go run hello.go #直接执行
    Hello, world. 你好,世界!

    使用如上命令,会将编译、链接和运行3个步骤合并为一步。运行完成后在当前目录下也看不到中间文件和最终的可执行文件。如果需要生成编译结果,不需要自动执行,可以使用build命令:

    1
    2
    3
    $ go build hello.go #编译
    $ ./hello
    Hello, world. 你好,世界!

工程管理

​ 在实际的开发工作中,直接调用编译器进行编译和链接的场景是少而又少,因为在工程中不会简单到只有一个源代码文件,且源文件之间会有相互的依赖关系。如果这样一个文件一个文件逐步编译,那不亚于一场灾难。Go语言的设计者作为行业老将,自然不会忽略这一点。早期Go语言使用makefile作为临时方案,到了Go 1发布时引入了强大无比的Go命令行工具。

​ Go命令行工具的革命性之处在于彻底消除了工程文件的概念,完全用目录结构和包名来推导工程结构和构建顺序。针对只有一个源文件的情况讨论工程管理看起来会比较多余,因为这可以直接用go run和go build搞定。下面我们将用一个更接近现实的虚拟项目来展示Go语言的基本工程管理方法。

假设有这样一个场景:我们需要开发一个基于命令行的计算器程序。下面为此程序的基本用法:

1
2
3
4
5
6
7
8
9
10
11
$ cal help
Usage: calc cmmand [args]...
The cmmand are:
sqrt Square root of non-negative value.
add Add of two value
$ cal sqrt 4 # 开根号
2
$ cal add 1 2 # 加法
3

我们假设这个工程被分割为两个部分:
 可执行程序,名为calc,内部只包含一个calc.go文件;
 算法库,名为simplemath,每个command对应于一个同名的go文件,比如add.go。

1
则一个正常的工程目录组织应该如下所示:

在上面的结构里,带尖括号的名字表示其为目录。xxx_test.go表示的是一个对于xxx.go的单元测试,这也是Go工程里的命名规则。

为了让读者能够动手实践,这里我们会列出所有的源代码并以注释的方式解释关键内容,需要注意的是,本示例主要用于示范工程管理,并不保证代码达到产品级质量。

cal.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//calc.go
package main
import (
"fmt"
"gostudy/ch02/calc/simplemath"
"os" //用于获取命令行参数os.Args
"strconv"
)
func main() {
args := os.Args //获取控制台参数
argsLen := len(args)
if args == nil || argsLen < 3 {
Usage()
return
}
switch args[1] {
case "add":
if argsLen < 4 {
fmt.Println("Usage: calc add <integer1><integer2>")
return
}
v1, err1 := strconv.Atoi(args[2])
v2, err2 := strconv.Atoi(args[3])
if err1 != nil || err2 != nil {
fmt.Println("Usage: calc add <integer1><integer2>")
return
}
result := simplemath.Add(v1, v2)
fmt.Println("Result:", result)
case "sqrt":
v, err := strconv.Atoi(args[2])
if err != nil {
fmt.Println("Usage: calc sqrt <integer>")
return
}
result := simplemath.Sqrt(v)
fmt.Println("Result:", result)
default:
Usage()
}
}
func Usage() {
fmt.Println("Usage: calc command [args]...")
fmt.Println("The command are:")
fmt.Println("sqrt Square root of non-negative value.")
fmt.Println("add Add of two value")
}

add.go

1
2
3
4
5
6
//add.go
package simplemath
func Add(a int, b int) int {
return a + b
}

add_test.go

1
2
3
4
5
6
7
8
9
10
11
12
package simplemath
import (
"testing"
)
func TestAdd1(t *testing.T) {
r := Add(1, 2)
if r != 3 {
t.Errorf("Add(1,2) failed.Got %d,expected 3.", r)
}
}

sqrt.go

1
2
3
4
5
6
7
8
9
10
package simplemath
import (
"math"
)
func Sqrt(a int) int {
v := math.Sqrt(float64(a))
return int(v)
}

sort_test.go

1
2
3
4
5
6
7
8
9
10
11
12
package simplemath
import (
"testing"
)
func TestSqrt1(t *testing.T) {
v := Sqrt(16)
if v != 4 {
t.Errorf("Sqrt(16) failed. Got %v, expected 4.", v)
}
}

为了能够构建这个工程,需要先把这个工程的根目录加入到环境变量GOPATH中。

执行测试go test simplemath

生成可执行文件go install calc.go

进入bin ./calc add 1 2