从零开始的Golang学习-学习笔记(4)

Go程序的基本结构和要素

先看一个示例,就是我们安装测试Go 的时候写的 hello_world.go ,不过稍微加了一点东西

package main
import "fmt"

func main(){
        fmt.Println("hello, world")
}

包的概念,导入和可见性

包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。

每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 .go 为扩展名的源文件组成,因此文件名和包名一般来说都是不相同的。

必须在源文件中非注释的第一行指明这个文件属于哪个包。

比如上例子 中的package mainpackage main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

一个应用程序可以包含不同的包,你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。如果你打算编译包名不是为 main 的源文件,如 pack1,编译后产生的对象文件将会是 pack1.a 而不是可执行程序。 所有的包名都应该使用小写字母

标准库

在 Windows 下的位置在 Go 根目录下的子目录 pkg\windows_386 中;

在 Linux 下的标准库在 Go 根目录下的子目录 pkg\linux_amd64 中(如果是安装的是 32 位,则在 linux_386 目录中)。

一般情况下,标准包会存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目录下。

如果想要构建一个程序,则包和包内的文件都必须以正确的顺序进行编译。包的依赖关系决定了其构建顺序。

属于同一个包的源文件必须全部被一起编译,一个包即是编译时的一个单元,因此根据惯例,每个目录都只包含一个包。

如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。

Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .o 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。

如果 A.go 依赖 B.go,而 B.go 又依赖 C.go

每一段代码只会被编译一次

一个 Go 程序是通过 import 关键字将一组包链接在一起。

import "fmt" 告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 "" 中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。

如果需要多个包,它们可以被分别导入:

import "fmt"
import "os"

或写到一行中

import "fmt"; import "os"

也可以这样优雅的书写

import (
   "fmt"
   "os"
)

同样可以写到一行中(像下面这样写了之后,使用 gofmt 后将会被强制换行)

import ("fmt"; "os")

习惯:导入多个包时,最好按照字母顺序排列包名,这样做更加清晰易读。

如果包名不是以 ./ 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找;如果包名以 ./ 开头,则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。

注: 以相对路径在GOPATH下导入包会产生报错信息 (报错信息:local import “./XXX” in non-local package)

除了符号 _,包中所有代码对象的标识符必须是唯一的,以避免名称冲突。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。

可见性规则

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

(注:大写字母可以使用任何 Unicode 编码的字符,比如希腊文,不仅仅是 ASCII 码中的大写字母)

举例:假设在包 pack1 中我们有一个变量或函数叫做 Thing(以 T 开头,所以它能够被导出),那么在当前包中导入 pack1 包,Thing 就可以像面向对象语言那样使用点标记来调用:pack1.Thing(pack1 在这里是不可以省略的)。因此包也可以作为命名空间使用,帮助避免命名冲突(名称冲突):例如 pack1.Thingpack2.Thing

对包使用 别名 ,如:import fm "fmt"

注意事项

如果你导入了一个包却没有使用它,则会在构建程序时引发错误,如 imported and not used: os

包的分级声明和初始化

你可以在使用 import 导入包之后定义或声明 0 个或多个常量(const)、变量(var)和类型(type),这些对象的作用域都是全局的(在本包范围内),所以可以被本包中所有的函数调用(如 gotemplate.go 源文件中的 c 和 v),然后声明一个或多个函数(func)。

函数

定义一个函数最简单的格式:

func functionName()

在括号 () 中写入 0 个或多个函数的参数(使用逗号 , 分隔),每个参数的名称后面必须紧跟着该参数的类型

main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数)。如果你的 main 包的源代码没有包含 main 函数,则会引发构建错误 undefined: main.main。main 函数既没有参数,也没有返回类型(与 C 家族中的其它语言恰好相反)。

如果你不小心为 main 函数添加了参数或者返回类型,将会引发构建错误:

func main must have no arguments and no return values results.

程序开始执行并完成初始化后,第一个调用(程序的入口点)的函数是 main.main()。该函数一旦返回就表示程序已成功执行并立即退出。

函数里的代码(函数体)使用大括号 {} 括起来。

左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会报错。

Go 语言虽然看起来不使用分号作为语句的结束,但实际上这一过程是由编译器自动完成,因此才会引发像上面这样的错误

右大括号 } 需要被放在紧接着函数体的下一行。如果你的函数非常简短,你也可以将它们放在同一行:

func Sum(a, b int) int { return a + b }

对于大括号 {} 的使用规则在任何时候都是相同的(如:if 语句等)。

因此符合规范的函数一般写成如下的形式:

func functionName(parameter_list) (return_value_list) {
   …
}

其中:

只有当某个函数需要被外部包调用的时候才使用大写字母开头,并遵循 Pascal 命名法;否则就遵循骆驼命名法,即第一个单词的首字母小写,其余单词的首字母大写。

下面这一行调用了 fmt 包中的 Println 函数,可以将字符串输出到控制台,并在最后自动增加换行字符 \n

fmt.Println("hello, world")

使用 fmt.Print("hello, world\n") 可以得到相同的结果。

PrintPrintln 这两个函数也支持使用变量。这些函数只可以用于调试阶段,在部署程序的时候务必将它们替换成 fmt 中的相关函数。

程序正常退出的代码为 0 即 Program exited with code 0;如果程序因为异常而被终止,则会返回非零值,如:1。这个数值可以用来测试是否成功执行一个程序。

注释

示例: hello_world2.go

package main

import "fmt" // Package implementing formatted I/O.

func main() {
   fmt.Printf("Καλημέρα κόσμε; or こんにちは 世界\n")
}

使用以 // 开头的单行注释。多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

注释不会被编译,但可以通过 godoc 来使用。

每一个包应该有相关注释,在 package 语句之前的块注释将被默认认为是这个包的文档说明,其中应该提供一些相关信息并对整体功能做简要的介绍。一个包可以分散在多个文件中,但是只需要在其中一个进行注释说明即可。

在首行的简要注释之后可以用成段的注释来进行更详细的说明,另外,在多段注释之间应以空行分隔加以区分。

示例:

// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman

几乎所有全局作用域的类型、常量、变量、函数和被导出的对象都应该有一个合理的注释。如果这种注释(称为文档注释)出现在函数前面,例如函数 Abcd,则要以 "Abcd..." 作为开头。

示例:

// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
   ...
}

类型

Golang的变量使用 var 声明的变量的值会自动初始化为该类型的零值。类型定义了某个变量的值的集合与可对其进行操作的集合。

函数也是一个确定的类型,以函数作为返回类型。需要写在函数名和可选的参数之后,例如

func FunctionName(a typea, b typeb) typeFunc

也可以返回多个,需要用小括号把它们括起来,并且使用逗号分隔

func FunctionName(a typea, b typeb) (t1 type1, t2 type2)

那么如上返回的形式为

return var1, var2

使用 type 关键字可以定义一个类型,它可以是结构体,也可以是一个已经存在的类型的别名

type IZ int

也可以同时定义多个类型

type (
    IZ int
    FZ float64
    STR string
)

go的编译器要求能够推断出所有值的对应类型出来,所以 Go 是一种强静态的语言。

类型转换

一个类型的值可以被转换为另一种类型的值。因为 Go 不存在隐式转换,所以转换必须显式说明(类型在这里的作用可以被看成是一个函数)

valueOfTypeB = typeB(valueOfTypeA)

类型 B 的值 = 类型 B(类型 A 的值) ,举个例子

a := 5.0
b := int(a)

var a IZ = 5
c := int(a)
d := IZ(c)

只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(例如将 int16 转换为 int32)。当从一个取值范围较大的转换到取值范围较小的类型时(例如将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。当编译器捕捉到非法的类型转换时会引发编译时错误,否则将引发运行时错误。

命名规范

通过 gofmt 来强制实现统一的代码风格。名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符。返回某个对象的函数或方法的名称一般都是使用名词,没有 Get... 之类的字符,如果是用于修改某个对象,则使用 SetName。有必须要的话可以使用大小写混合的方式,如 MixedCaps 或 mixedCaps,而不是使用下划线来分割多个名称。