3. Go语言基础知识

3.1. Hello Golang 实例

Golang1.14以后已经默认使用go modules 作为包依赖管理工具,也就不存在工作目录的说法 了,我们可以在随意指定的目录下创建我们的项目文件

3.1.1. 编写程序

package main

import "fmt"

func main() {
   /* 这是我的第一个简单的程序 */
   fmt.Println("Hello, World!")
}

让我们来看下以上程序的各个部分:

  1. 第一行代码 package main 定义了包名。你必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

  2. 下一行 import “fmt” 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。

  3. 下一行 func main() 是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。

  4. 下一行 /*...*/ 是注释,在程序执行时将被忽略。单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

  5. 下一行 fmt.Println(…) 可以将字符串输出到控制台,并在最后自动增加换行字符 \n。 使用 fmt.Print("hello, world\n") 可以得到相同的结果。 Print 和 Println 这两个函数也支持使用变量,如:fmt.Println(arr)。如果没有特别指定,它们会以默认的打印格式将变量 arr 输出到控制台。

  6. 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

3.1.2. 执行 Go 程序

让我们来看下如何编写 Go 代码并执行它。步骤如下:

  1. 打开编辑器如vscode,将以上代码添加到编辑器中。

  2. 将以上代码保存为 hello.go

  3. 打开命令行,并进入程序文件保存的目录中。

  4. 输入命令 go run hello.go 并按回车执行代码。

  5. 如果操作正确你将在屏幕上看到 “Hello World!” 字样的输出。

$ go run hello.go
Hello, World!

3.2. 行分隔符

行分隔符为; 但是我们写程序的时候往往都不需要写, 比如Hello world里面的打印语句

fmt.Println("Hello World!")

这是因为Go语言的编译器会自动为我们添加

如果你打算将多个语句写在同一行,它们则必须使用 ; 人为区分,但在实际开发中我们并不鼓励这种做法。

而且go fmt 会自动帮你分成2行!

fmt.Println("Hello World!");fmt.Println("Hello World!")

3.3. 注释

Go 支持两种注释方式,行注释块注释

  • 行注释:以//开头,例如: //我是行注释

  • 块注释:以/*开头,以*/结尾,例如:/*我是块注释*/

// 这是一个行注释

/*
这是一个
块注释
*/

3.4. 标识符

标识符用来命名变量、类型等程序实体,一个标识符实际上就是一个或是多个字母(A-Za-z)数字(0-9)、下划线_组成的序列,但是第一个字符必须是字母或下划线而不能是数字

Go 语言标识符的命名规则如下:

  • 只能由非空字母(Unicode)、数字、下划线_组成

  • 只能以字母或下划线开头

  • 不能 Go 语言关键字

  • 避免使用 Go 语言预定义标识符

  • 建议使用驼峰式

  • 标识符区分大小写

下面这些就是一些合法的标识符

username   xxx   M   user_name   user1
_temp   temp_   heelo1  MMXXX  中文

比如这段代码是合法的

package main

import (
	"fmt"
)

func main() {
	中文 := "你好,中文"
	fmt.Println(中文)
}

而下面这些就是一个非法的标识符

1user  // 数字打头
for    // 关键字不能作为标识符
m*m   // 运算符是不允许的
  // 有空格

下面这段代码就会报错

package main

import (
	"fmt"
)

func main() {
	m*2 := "stirng"
	fmt.Println(m*2)
}

3.5. 关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。

Go语言中有25个关键字:

    break        default      func         interface    select
    case         defer        go           map          struct
    chan         else         goto         package      switch
    const        fallthrough  if           range        type
    continue     for          import       return       var

此外,Go语言中还有37个保留字。

    Constants:    true  false  iota  nil

        Types:    int  int8  int16  int32  int64  
                  uint  uint8  uint16  uint32  uint64  uintptr
                  float32  float64  complex128  complex64
                  bool  byte  rune  string  error

    Functions:   make  len  cap  new  append  copy  close  delete
                 complex  real  imag
                 panic  recover

3.6. 变量

3.6.1. 变量的来历

程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。

3.6.2. 变量类型

变量(Variable)的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等(后续介绍)。

注解

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用

3.6.3. 变量声明

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。 并且Go语言的变量声明后必须使用。

3.6.3.1. 标准声明

Go语言的变量声明格式为:

var 变量名 变量类型

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号。 举个例子:

var name string
var age int     
var sex bool    

3.6.3.2. 批量声明

每声明一个变量就需要写var关键字会比较繁琐,go语言中还支持批量变量声明:

var (
		name1  string
		age1   int
		sex1   bool
		weight float32
	)

3.6.4. 变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil

当然我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:

var 变量名 类型 = 表达式

举个例子:

var name string = "xiaoming"
var age int = 18

或者一次初始化多个变量

var name, age = "xiaoming", 20

3.6.4.1. 类型推导

有时候我们会将变量的类型省略,这个时候编译器会根据等号右边的值来推导变量的类型完成初始化。

var name3 = "libai"
var age3, weight3 = 21, 183.5

3.6.4.2. 短变量声明

函数内部,可以使用更简略的 := 方式声明并初始化变量。

name4 := "alice"
age4 := 100
weight4, sex4 := 193.6, true

3.6.4.3. 匿名变量

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,例如:

func foo() (int, string) {
	return 10, "abc"
}
func main() {
	x, _ := foo()
	_, y := foo()
	fmt.Println("x=", x)
	fmt.Println("y=", y)
}

匿名变量不占用命名空间,不会分配内存,所以匿名变量之间不存在重复声明。 (在Lua等编程语言里,匿名变量也被叫做哑元变量。)

注意事项:

  1. 函数外的每个语句都必须以关键字开始(var、const、func等)

  2. :=不能使用在函数外。

  3. _多用于占位,表示忽略值。

3.6.5. Demo

3.6.5.1. 短变量声明

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	a := 10
 8	b := "golang"
 9	c := 4.17
10	d := true
11	e := "Hello"
12	f := `Do you like my hat?`
13	g := 'M'
14
15	fmt.Printf("%v \n", a)
16	fmt.Printf("%v \n", b)
17	fmt.Printf("%v \n", c)
18	fmt.Printf("%v \n", d)
19	fmt.Printf("%v \n", e)
20	fmt.Printf("%v \n", f)
21	fmt.Printf("%v \n", g)
22	
23	fmt.Printf("%T \n", a)
24	fmt.Printf("%T \n", b)
25	fmt.Printf("%T \n", c)
26	fmt.Printf("%T \n", d)
27	fmt.Printf("%T \n", e)
28	fmt.Printf("%T \n", f)
29	fmt.Printf("%T \n", g)
30}
10 
golang 
4.17
true
Hello
Do you like my hat?
77
int
string
float64
bool
string
string
int32

3.6.5.2. 变量零值

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	var a int
 8	var b string
 9	var c float64
10	var d bool
11
12	fmt.Printf("%#v \n", a)
13	fmt.Printf("%#v \n", b)
14	fmt.Printf("%#v \n", c)
15	fmt.Printf("%#v \n", d)
16
17	fmt.Println()
18}
0 
"" 
0
false

3.6.5.3. less emphasic

1package main
2
3import "fmt"
4
5func main() {
6	var message string
7	message = "Hello World."
8	fmt.Println(message)
9}
Hello World.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	var message string
 7	var a, b, c int
 8	a = 1
 9
10	message = "Hello World!"
11
12	fmt.Println(message, a, b, c)
13}
Hello World! 1 0 0

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	var message = "Hello World!"
 8	var a, b, c int = 1, 2, 3
 9
10	fmt.Println(message, a, b, c)
11}
Hello World! 1 2 3

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	var message = "Hello World!"
 8	var a, b, c = 1, 2, 3
 9
10	fmt.Println(message, a, b, c)
11}
Hello World! 1 2 3

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	var message = "Hello World!"
 8	var a, b, c = 1, false, 3
 9
10	fmt.Println(message, a, b, c)
11}
Hello World! 1 false 3

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	// you can only do this inside a func
 8	message := "Hello World!"
 9	a, b, c := 1, false, 3
10	d := 4
11	e := true
12
13	fmt.Println(message, a, b, c, d, e)
14}
Hello World! 1 false 3 4 true

 1package main
 2
 3import "fmt"
 4
 5var a = "this is stored in the variable a"     // package scope
 6var b, c string = "stored in b", "stored in c" // package scope
 7var d string                                   // package scope
 8
 9func main() {
10
11	d = "stored in d" // declaration above; assignment here; package scope
12	var e = 42        // function scope - subsequent variables have func scope:
13	f := 43
14	g := "stored in g"
15	h, i := "stored in h", "stored in i"
16	j, k, l, m := 44.7, true, false, 'm' // single quotes
17	n := "n"                             // double quotes
18	o := `o`                             // back ticks
19
20	fmt.Println("a - ", a)
21	fmt.Println("b - ", b)
22	fmt.Println("c - ", c)
23	fmt.Println("d - ", d)
24	fmt.Println("e - ", e)
25	fmt.Println("f - ", f)
26	fmt.Println("g - ", g)
27	fmt.Println("h - ", h)
28	fmt.Println("i - ", i)
29	fmt.Println("j - ", j)
30	fmt.Println("k - ", k)
31	fmt.Println("l - ", l)
32	fmt.Println("m - ", m)
33	fmt.Println("n - ", n)
34	fmt.Println("o - ", o)
35}
o
a -  this is stored in the variable a
b -  stored in b
c -  stored in c
d -  stored in d
e -  42
f -  43
g -  stored in g
h -  stored in h
i -  stored in i
j -  44.7
k -  true
l -  false
m -  109
n -  n
o -  o

3.7. 常量

相对于变量,常量是恒定不变的值,多用于定义程序运行期间不会改变的那些值。 常量的声明和变量声明非常类似,只是把var换成了const,常量在定义的时候必须赋值。

const pi = 3.1415
const e = 2.7182

声明了pie这两个常量之后,在整个程序运行期间它们的值都不能再发生变化了。

多个常量也可以一起声明:

const (
    pi = 3.1415
    e = 2.7182
)

const同时声明多个常量时,如果省略了值则表示和上面一行的值相同。 例如:

const (
    n1 = 100
    n2
    n3
)

上面示例中,常量n1n2n3的值都是100。

小技巧

常量更推荐使用大写来定义标识符

3.7.1. iota

iota是go语言的常量计数器,只能在常量的表达式中使用。

iota在const关键字出现时将被重置为0。const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。 使用iota能简化定义,在定义枚举时很有用。

举个例子:

const (
		n1 = iota //0
		n2        //1
		n3        //2
		n4        //3
	)

3.7.1.1. 几个常见的iota示例:

使用_跳过某些值

const (
		n1 = iota //0
		n2        //1
		_
		n4        //3
	)

iota声明中间插队

const (
		n1 = iota //0
		n2 = 100  //100
		n3 = iota //2
		n4        //3
	)
	const n5 = iota //0

定义数量级 (这里的<<表示左移操作,1<<10表示将1的二进制表示向左移10位,也就是由1变成了10000000000,也就是十进制的1024。同理2<<2表示将2的二进制表示向左移2位,也就是由10变成了1000,也就是十进制的8。)

const (
		_  = iota
		KB = 1 << (10 * iota)
		MB = 1 << (10 * iota)
		GB = 1 << (10 * iota)
		TB = 1 << (10 * iota)
		PB = 1 << (10 * iota)
	)

多个iota定义在一行

const (
		a, b = iota + 1, iota + 2 //1,2
		c, d                      //2,3
		e, f                      //3,4
	)

3.7.2. demo

package main

import "fmt"

func main() {

	// 定义了常量之后不能修改
	// 在程序运行期间不会改变的量
	const pi = 3.1415
	const e = 2.7182

	fmt.Println(pi, e)
	// pi = 100 会报错:cannot assign to pi (declared const)
	// 也可以批量赋值常量
	const (
		n1 = 100
		n2 // n2 =100
		n3 // n3 = 100
	)
	fmt.Println(n1, n2, n3)

	//计数器常量
	const (
		s1 = iota //0
		s2        //1
		s3        //2

	)
	fmt.Println(s1, s2, s3)
    
	//使用_跳过某些值
	const (
		a1 = iota
		_
		_
		a3
	)
	fmt.Println(a3)
	//中间插队
	const (
		a4 = iota //0
		a5        //1
		a6 = 100  //100
		a7 = iota ///3
		a8        //4
	)

	fmt.Println(a4, a5, a6, a7, a8)

	//定义数量级
	const (
		_  = iota //0
		KB = 1 << (10 * iota)
		MB = 1 << (10 * iota)
		GB = 1 << (10 * iota)
		TB = 1 << (10 * iota)
		PB = 1 << (10 * iota)
	)
	fmt.Println(KB, MB, GB, TB, PB)

	//多个iota定义在一行,每行的iota都是一样,不会自增
	const (
		a, b = iota + 1, iota + 2 //1,2
		c, d                      //2,3

	)
	fmt.Println(a, b, c, d)

}
3.1415 2.7182
100 100 100
0 1 2
3
0 1 100 3 4
1024 1048576 1073741824 1099511627776 1125899906842624
1 2 2 3

3.8. 扩展

进程内存结构:

mem_struct

  • 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中(new 出来的对象)

  • 堆: 存放用new产生的数据

3.8.1. 值类型和引用类型

  • 值类型: 这段内存里面存储的是基本类型的数据, 比如 a, 10, 0.01

  • 引用类型: 这段内存里面存储的是一个地址, 比如 0xc00011e370 0xc00011e380

3.8.2. 修改值

比如 下面这段代码中 a, b 10 存在在哪儿? 当执行赋值操作时 都发生了什么?

var a int 
a = 10

var b int 
b = 10

a = 20

var_ref

  • 编译器先处理 var a int, 首先它会在栈中创建一个变量为a的引用

  • a = 10,查找栈中是否有10这个值, 没找到,就将10存放进来, 然后让a指向10

  • var b int, 创建一个变量为b的引用

  • b = 10, 查找栈中是否有10这个值, 找到10, 让b指向10

  • a = 20, 查找栈中是否有20这个值, 为找到, 将20放进去, 然后让a指向20

3.8.3. 修改引用

看看下面这段代码, 修改了j的值,为啥没修改i的值

i := "this is i"
j := i
j := "this is j"

value_ref

下面我们修改了j的值, 为啥i的值也被修改了

i := "this is a"
j := &i
*j = "this is j"

ref_edit

3.9. 变量作用域

小技巧

变量作用域指变量可以使用范围

Go语言使用大括号显示的标识作用域范围,大括号{}内包含一连串的语句,叫做语句块。语句块可以嵌套,语句块内定义的变量不能在语句块外使用

常见隐式语句块:

  • 全语句块

  • 包语句块

  • 文件语句块

  • if、switch、for、select、case 语句块

我们将作用域分为如下几种:

  • 函数内定义的变量称为局部变量

  • 函数外定义的变量称为全局变量

  • 函数定义中的变量称为形式参数

注解

  • 全局变量如果是大写字母开头, 所有地方都可以访问这个变量,跨Package访问时需要带上Package名词,全 局变量相当于所有函数的父级变量, 所有函数都能访问

  • 全局变量如果是小写字母开头,只能在包内被访问到

  • 作用域内定义变量只能被声明一次且变量必须使用,否则编译错误。在不同作用域可定义相同的变量,此时局部将覆盖全局

  • 子作用域的优先级是高于父作用域的优先级的, 因此子作用域可以覆盖父作用域的变量的值, 但是同一个变量不能声明2次

3.9.1. Demo

package main

import "fmt"

func main() {
	var a string
    a = "local var"
    fmt.Println(a)
}

fmt.Println(a)
# command-line-arguments
.\main.go:11:1: syntax error: non-declaration statement outside function body

package main

import "fmt"

func main() {
	x := 42
	fmt.Println(x)
	{
		fmt.Println(x)
		y := "The credit belongs with the one who is in the ring."
		fmt.Println(y)
	}
	// fmt.Println(y) // outside scope of y
}
42
42
The credit belongs with the one who is in the ring.

package main

import "fmt"

func main() {
	x := 42
	fmt.Println(x)
	foo()
}

func foo() {
	// no access to x
	// this does not compile
	fmt.Println(x)
}
# command-line-arguments
.\main.go:14:14: undefined: x

package main

import "fmt"

func main() {
	fmt.Println(x)
	fmt.Println(y)
	x := 42
}

var y = 42
# command-line-arguments
.\main.go:6:14: undefined: x

package main

import "fmt"

var x = 42

func main() {
	fmt.Println(x)
	foo()
}

func foo() {
	fmt.Println(x)
}
42
42

package main

import "fmt"

func main() {
	fmt.Println(x)
}
package main

var x = 7
// IMPORTANT
// to run this code:
// go run *.go
// ---- OR ----
// go build
//./xxx

package main

import "fmt"

func wrapper() func() int {
	x := 0
	return func() int {
		x++
		return x
	}
}

func main() {
	increment := wrapper()
	fmt.Println(increment())
	fmt.Println(increment())
}

/*
closure helps us limit the scope of variables used by multiple functions
without closure, for two or more funcs to have access to the same variable,
that variable would need to be package scope
*/
1
2

3.10. Operater

运算符用于在程序运行时执行数学或逻辑运算。

Go 语言内置的运算符有:

  1. 算术运算符

  2. 关系运算符

  3. 逻辑运算符

  4. 位运算符

  5. 赋值运算符

3.10.1. 算术运算符

运算符

描述

+

相加

-

相减

*

相乘

/

相除

%

求余

注解

++(自增)和--(自减)在Go语言中是单独的语句,并不是运算符

3.10.2. 关系运算符

运算符

描述

==

检查两个值是否相等,如果相等返回 True 否则返回 False。

!=

检查两个值是否不相等,如果不相等返回 True 否则返回 False。

>

检查左边值是否大于右边值,如果是返回 True 否则返回 False。

>=

检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。

<

检查左边值是否小于右边值,如果是返回 True 否则返回 False。

<=

检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

3.10.3. 逻辑运算符

运算符

描述

&&

逻辑 AND 运算符。 如果两边的操作数都是 True,则为 True,否则为 False。

||

逻辑 OR 运算符。 如果两边的操作数有一个 True,则为 True,否则为 False。

!

逻辑 NOT 运算符。 如果条件为 True,则为 False,否则为 True。

3.10.4. 位运算符

位运算符对整数在内存中的二进制位进行操作。

运算符

描述

&

参与运算的两数各对应的二进位相与。 (两位均为1才为1)

|

参与运算的两数各对应的二进位相或。 (两位有一个为1就为1)

^

参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。 (两位不一样则为1)

<<

左移n位就是乘以2的n次方。 “a<<b”是把a的各二进位全部左移b位,高位丢弃,低位补0。

>>

右移n位就是除以2的n次方。 “a>>b”是把a的各二进位全部右移b位。

3.10.4.1. 位与运算&

  9 & 8 
= 1001 & 1000  // 0与任何数相乘都等于0
= 1000
= 8
---
  5 & 2 
= 0101 & 0010
= 0000
= 0

// The verification results
fmt.Println("9&8=", 9&8, "5&2=", 5&2)
9&8= 8 5&2= 0

3.10.4.2. 位或运算 |

 9 | 8
= 1001 | 1000  // 1与任何数相或都为1
= 1001
= 9
---
  7 | 8
= 0111 | 1000
= 1111
= 15

// The verification results
fmt.Println("9|8=", 9|8, "7|8=", 7|8)
9|8= 9 7|8= 15

3.10.4.3. 位异或运算 ^

  5 ^ 8
= 0101 ^ 1000  //相同为0,不同为1.也可用“进位加法”来理解
= 1101
= 13

// The verification results
fmt.Println("5^8", 5^8)
5^8 13

3.10.4.4. 位左移运算 <<

 8 << 2 
= 0000 1000 << 2 // 0000 1000' => 00'0010 0000
= 0010 0000
= 32

// The verification results
fmt.Println("8 << 2=", 8<<2)
8 << 2= 32 

3.10.4.5. 位右移运算 >>

---
8 >> 2= 2 
-8 >> 2= -2   //思考一下,为什么负数的位计算结果不同
  8 >> 2
= 0000 1000 >>2 // 0000 1000' =>0000 0010'00  
=  0000 0010  // 正数高位补0
= 2

  -8 >>2 
= 1000 1000 >> 2 // 1000 1000' => 1110 0010'00
= 1110 0010  // 复数高位补1
= -98

// The verification results
fmt.Println("8 >> 2=", 8>>2)
fmt.Println("-8 >> 2=", 8>>2)
8 >> 2= 2 
-8 >> 2= -2   //思考一下,为什么负数的位计算结果和推演不同

注解

后面单独开章节叙述为原码反码补码以及字节序,来解释这个问题

3.10.5. 赋值运算符

运算符

描述

=

简单的赋值运算符,将一个表达式的值赋给一个左值

+=

相加后再赋值

-=

相减后再赋值

*=

相乘后再赋值

/=

相除后再赋值

%=

求余后再赋值

<<=

左移后赋值

>>=

右移后赋值

&=

按位与后赋值

|=

按位或后赋值

^=

按位异或后赋值

3.10.6. 练习题

有一堆数字,如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?

func main() {
	item := 0
	dist := [...]int{1, 2, 3, 4, 5, 4, 3, 2, 1}
	for _, v := range dist {
		item ^= v
	}
	fmt.Println(item)
}

3.11. go doc

go doc 命令打印Go语言程序实体上的文档。可以使用参数来指定程序实体的标识符。

Go语言程序实体是指变量、常量、函数、结构体以及接口。

程序实体标识符就是程序实体的名称。

3.11.1. go doc 用法

go doc [-u] [-c] [package|[package.]symbol[.methodOrField]]

可用的标识:

标识

说明

-all

显示所有文档

-c

匹配程序实体时,大小写敏感

-cmd

将命令(main包)视为常规程序包,如果要显示main包的doc,请指定这个标识

-src

显示完整源代码

-u

显示未导出的程序实体

示例

输出指定 package ,指定类型,指定方法的注释

$ go doc sync.WaitGroup.Add

输出指定 package ,指定类型的所有程序实体,包括未导出的

$ go doc -u -all sync.WaitGroup

输出指定 package 的所有程序实体(非所有详细注释)

$ go doc -u sync

3.12. godoc

godoc命令主要用于在无法联网的环境下,以web形式,查看Go语言标准库和项目依赖库的文档。

go 1.12 之后的版本中,godoc不再做为go编译器的一部分存在。依然可以通过go get命令安装:

go get -u -v golang.org/x/tools/cmd/godoc

3.12.1. 国内的安装方法

mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/tools.git
cd tools/cmd/godoc
go install 
ls -alh $GOPATH/bin

3.12.2. 通过网页查看文档

  • godoc命令

    godoc -http=:6060
    

    godoc会监听6060端口,通过网页访问 http://127.0.0.1:6060,godoc基于GOROOT和GOPATH路径下的代码生成文档的。打开首页如下,我们自己项目工程文档和通过go get的代码文档都在Packages中的Third party里面。

3.12.2.1. 编写自己的文档

  • 1、设计接口函数代码

    创建documents/calc.go文件

    /*
    简易计算器计算自定义包
     */
    package documents
    // 一种实现两个整数相加的函数,
    // 返回值为两整数相加之和
    func Add(a, b int) int {
        return a + b
    }
    // 一种实现两个整数相减的函数,
    // 返回值为两整数相减之差
    func Sub(a, b int) int {
        return a - b
    }
    // 一种实现两个整数相乘的函数,
    // 返回值为两整数相乘之积
    func Mul(a, b int) int {
        return a * b
    }
    // 一种实现两个整数相除的函数,
    // 返回值为两整数相除之商
    func Div(a, b int) int {
        if b == 0 {
            panic("divide by zero")
        }
        return a / b
    }
    
  • 2、设计Example示例代码

    创建documents/calc_test.go文件,给calc.go中每个函数编写Example函数

    package documents
    import (
      "fmt"
    )
    func ExampleAdd() {
      result := Add(4, 2)
      fmt.Println("4 + 2 =", result)
      // Output:
      // 4 + 2 = 6
    }
    func ExampleSub() {
      result := Sub(4, 2)
      fmt.Println("4 - 2 =", result)
      // Output:
      // 4 - 2 = 2
    }
    func ExampleMul() {
      result := Mul(4, 2)
      fmt.Println("4 * 2 =", result)
      // Output:
      // 4 * 2 = 8
    }
    func ExampleDiv() {
      result := Div(4,2)
      fmt.Println("4 / 2 =", result)
      // Output:
      // 4 / 2 = 2
    }
    
  • 3、网页查看文档

    注意以上两个文件必须在$GOPATH/src路径下,使用godoc命令创建文档,用网页打开显示如下

    format_png

小技巧

1、在源码文件中,在package语句前做注释,在文档中看到的就是Overview部分, 注意:此注释必须紧挨package语句前一行,要作为Overview部分的,注释块中间不能有空行。 2、在函数、结构、变量等前做注释的,在文档中看到的就是该项详细描述。注释规则同上。 3、编写的Example程序,函数名必须以Example为前缀,可将测试的输出结果放在在函数尾部,以”// Output:”另起一行,然后将输出内容注释,并追加在后面。

3.13. Golds

Golds全称Go local docs server。顾名思义,这是一款Go本地文档服务器,可以认为是官方godoc程序的一款竟品。相对于godoc,它有以下特点:

  • 展示类型实现关系。这个对阅读和理解代码很有帮助。

  • 展示所有的因为内嵌而得到的提升字段。这个对于阅读使用大量内嵌字段的项目很有用,比如kubernetes项目。

  • 支持展示非导出资源。这对阅读理解其他人写的代码很有帮助。

  • 丰富的代码阅读体验(点击局部标识符高亮显示此标识符的所有使用;点击引入路径高亮显示被引入包的所有使用;点击包级标识符直接跳转到声明处)。在浏览器里阅读代码的体验有时比在IDE里更好。

  • 生成代码统计报告。支持生成静态HTML文档。

  • JavaScript关闭不影响阅读体验;JavaScript打开体验更佳。

关于更多细小的特性,请阅读项目首页(https://github.com/go101/golds)。

截图示例:

img

统计信息

img

实现关系

img

代码高亮

安装Golds很简单:

  • 如果你已经在使用官方工具链1.16+版本,则可运行 go install http://go101.org/golds@latest 安装Golds;

  • 如果你仍在在使用官方工具链1.15-版本,则可运行 go get -u http://go101.org/golds 安装Golds。

安装后,可以

  • 运行golds ./...来查看当前项目的文档和代码;

  • 运行golds toolchain来查看官方工具链的文档和代码;运行golds std来查看标准库的文档和代码;

  • 运行golds ./... std来查看当前项目和标准库的文档和代码;

  • 运行golds toolchain std来查看官方工具链和标准库的文档和代码。

生成的标准库文档展示:https://docs.go101.org/std/index.html