Go 语言,由 Google 开发并在 2009 年公开发布,已成为现代软件开发领域中的一颗明星,尤其受到云服务和高性能应用开发者的青睐。作为一种静态类型的编译语言,Go 结合了简洁的语法结构、强大的并发支持和高效的性能表现,提供了一个既强大又易于使用的编程环境。
在本文中,我们将深入探讨 Go 程序的四个基础组成部分:文件名、标识符、关键字和包。这些元素是理解和有效编写 Go 程序的基石,每一个部分都承载着特定的规则和功能,它们共同构建了 Go 语言的结构框架
文章目录
-
-
- 1、文件名、关键字与标识符
- [2、Go 包](#2、Go 包)
-
- [2.1、Go 语言包的概念](#2.1、Go 语言包的概念)
- [2.2、Go 语言的标准库](#2.2、Go 语言的标准库)
- [2.3、Go 语言包依赖与编译策略](#2.3、Go 语言包依赖与编译策略)
- 2.4、每一段代码只会被编译一次
- [2.5、Go 语言的标识符可见性与包管理](#2.5、Go 语言的标识符可见性与包管理)
- 2.6、使用包的别名
- 2.7、包的声明和初始化
-
1、文件名、关键字与标识符
1.1、名称与标识符
Go 语言中的源文件通常以 .go
为扩展名,并存储在计算机上。这些文件的命名完全由小写字母构成,例如 scanner.go
。对于包含多个词的文件名,通常使用下划线 _
来连接各部分,如 scanner_test.go
,并且文件名中不会包含空格或其他特殊字符。
在 Go 语言的环境中,源文件的大小并没有限制,一个文件可以包含从几行到数千行的代码。
在 Go 代码中,几乎所有的元素都需要用到名称或标识符,而且这些标识符对大小写敏感,这一点与 C 系列语言相似。有效的标识符应该以字母(支持任何 UTF-8 编码的字符)或下划线 _
开头,后接任意数量的字母、数字或下划线,例如:X56
、group1
、_x23
、i
、өԑ12
。
不过,有些标识符是无效的,包括:
1ab
:以数字开头是不允许的。case
:因为它是 Go 语言的关键字。a+b
:包含运算符,这是不被允许的。
特别地,下划线 _
被称为空白标识符,它在 Go 语言中有特殊用途。可以将任何类型的值赋给它,但赋值后的结果不会被使用,也不能通过它来进行进一步的变量赋值或计算。
此外,在 Go 代码中,有时候你会遇到没有明确名称的变量、类型或方法,这些通常被称为匿名变量。使用匿名变量可以在某些场景下提升代码的灵活性,尽管它们不是编写 Go 程序的必需元素。
1.2、关键字
Go 语言定义了 25 个关键字或保留字,这些关键字在编写 Go 代码时具有特殊的语义,不能用作标识符。这些关键字涵盖了控制结构、数据类型定义、以及包的管理等多个方面。下面是 Go 语言中所有的关键字列表:
控制流关键字:
break
: 用于中断当前循环;case
: 在switch
语句中,用于定义一个分支;continue
: 跳过当前循环的剩余部分,继续执行下一次循环;default
: 在switch
或select
语句中,定义默认分支;defer
: 延迟执行一个函数直到包含它的函数执行结束;else
: 与if
一起使用,定义一个条件不满足时执行的代码块;fallthrough
: 在switch
中强制执行下一个case
;for
: 定义循环;goto
: 跳转到指定的标签;if
: 条件语句;return
: 从函数中返回值;select
: 用于处理多个通道的接收操作;switch
: 条件分支语句;
数据类型及结构关键字:
chan
: 用于定义通道类型;const
: 定义常量;func
: 定义函数;interface
: 定义接口;map
: 定义映射类型;package
: 定义包名;range
: 用于迭代数组、切片、字符串、映射或通道;struct
: 定义结构体;type
: 定义一个新的类型;var
: 定义变量。
这些关键字是 Go 语言的基础组成部分,理解它们的用途和语义对于编写有效和高效的 Go 程序至关重要。
之所以刻意地将 Go 代码中的关键字保持的这么少,是为了简化在编译过程第一步中的代码解析。和其它语言一样,关键字不能够作标识符使用。
Go 语言除了有25个关键字外,还提供了36个预定义的标识符,这些包括了一系列基本数据类型的名称和一些重要的内置函数。这些预定义标识符是 Go 语言标准库的一部分,它们提供了操作基本数据类型和执行关键操作的能力。
这里是 Go 语言中的 36 个预定义标识符:append
、bool
、byte
、cap
、close
、complex
、complex64
、complex128
、copy
、false
、float32
、float64
、imag
、int
、int8
、int16
、int32
、int64
、iota
、len
、make
、new
、nil
、panic
、print
、println
、real
、recover
、string
、true
、uint
、uint8
、uint16
、uint32
、uint64
、uintptr
。
1.3、符号
程序一般由关键字、常量、变量、运算符、类型和函数组成。
程序中可能会使用到这些分隔符:括号 ()
,中括号 []
和大括号 {}
。
程序中可能会使用到这些标点符号:.
、,
、;
、:
和 ...
。
程序的代码通过语句来实现结构化。每个语句不需要像 C 家族中的其它语言一样以分号 ;
结尾,因为这些工作都将由 Go 编译器自动完成。
如果你打算将多个语句写在同一行,它们则必须使用 ;
人为区分,但在实际开发中我们并不鼓励这种做法。
2、Go 包
2.1、Go 语言包的概念
包在 Go 语言中用于结构化代码,类似于其他编程语言中的类库或命名空间。每个 Go 源文件都属于一个特定的包,并且一个包可以由多个 .go
扩展名的源文件组成。通常情况下,文件名和包名不相同。
在源文件的非注释第一行,必须明确指出该文件属于哪个包,例如 package main
。package main
定义了一个可独立执行的程序,每个 Go 应用程序必须包含一个名为 main
的包。
尽管一个应用程序可能包含多个不同的包,但即使只使用 main
包,也不必将所有代码写在一个庞大的文件中。可以将代码分散到几个较小的文件中,每个文件的第一行使用 package main
表明它们都属于 main
包。如果编译的包名不是 main
,如 pack1
,则编译产生的是 pack1.a
对象文件而不是可执行程序。值得注意的是,所有包名应使用小写字母。
2.2、Go 语言的标准库
Go 语言的安装包中包括了一个丰富的标准库,这是一组预先编译好的包,可以直接在你的程序中使用。标准库的具体位置取决于操作系统和处理器架构。
例如,在 Windows 系统上,标准库位于 Go 安装根目录下的 pkg\windows_386
子目录中;在 Linux 系统上,如果是 64 位,则在 pkg\linux_amd64
子目录中,32 位系统则在 linux_386
目录中。通常情况下,标准库存放在 $GOROOT/pkg/$GOOS_$GOARCH/
目录下。
标准库中包含了许多实用的包,如 fmt
和 os
。除了使用这些现成的包,你也可以创建自己的包以满足特定的功能需求。
为了构建程序,所有相关的包和文件必须按照正确的顺序编译,这个顺序通常由包的依赖关系决定。在 Go 语言中,属于同一个包的所有源文件必须一起编译,因为一个包在编译时被视为一个单独的编译单元。按照惯例,每个目录中通常只包含一个包,以保持结构的清晰和管理的简单。
2.3、Go 语言包依赖与编译策略
在 Go 语言的包模型中,对某个包进行更改或重新编译后,所有引用该包的客户端程序必须重新编译,以确保所有的依赖都是最新的。Go 的编译器采用了一种显式依赖关系的机制,这种机制极大地加快了编译速度。
编译过程中,Go 编译器从后缀名为 .o
的对象文件中提取传递依赖类型的信息。这意味着,编译器在编译一个文件时,只需引用直接依赖的 .o
文件,而不是所有相关的源文件。例如:
- 假设有三个文件:
A.go
、B.go
和C.go
,其中A.go
依赖于B.go
,而B.go
依赖于C.go
。 - 编译顺序应为先编译
C.go
,再编译B.go
,最后编译A.go
。 - 在编译
A.go
时,编译器只需读取B.o
文件。尽管B.go
依赖C.go
,编译A.go
并不直接需要C.o
,因为所有必要的信息都已经在B.o
中。
这种编译机制不仅简化了依赖管理,也使得编译大型项目更加高效。通过这种方式,Go 语言能够提供快速的编译速度,同时保持代码的模块化和高度组织化。
2.4、每一段代码只会被编译一次
一个 Go 程序是通过 import
关键字将一组包链接在一起。
import "fmt"
告诉 Go 编译器这个程序需要使用 fmt
包(的函数,或其他元素),fmt
包实现了格式化 IO(输入/输出)的函数。包名被封闭在半角双引号 ""
中。如果你打算从已编译的包中导入并加载公开声明的方法,不需要插入已编译包的源代码。
如果需要多个包,它们可以被分别导入:
go
import "fmt"
import "os"
或:
import "fmt"; import "os"
但是还有更短且更优雅的方法(被称为因式分解关键字,该方法同样适用于 const
、var
和 type
的声明或定义):
go
import (
"fmt"
"os"
)
它甚至还可以更短的形式,但使用 gofmt 后将会被强制换行:
go
import ("fmt"; "os")
当你导入多个包时,最好按照字母顺序排列包名,这样做更加清晰易读。
如果包名不是以 .
或 /
开头,如 "fmt"
或者 "container/list"
,则 Go 会在全局文件进行查找;如果包名以 ./
开头,则 Go 会在相对目录中查找;如果包名以 /
开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
导入包即等同于包含了这个包的所有的代码对象。
除了符号 _
,包中所有代码对象的标识符必须是唯一的,以避免名称冲突。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。
包通过下面这个被编译器强制执行的规则来决定是否将自身的代码对象暴露给外部文件:
2.5、Go 语言的标识符可见性与包管理
在 Go 语言中,标识符(包括常量、变量、类型、函数名、结构字段等)的首字母大小写决定了其可见性和可访问性。如果标识符以大写字母开头(如 Group1
),则该标识符的对象可以被外部包访问,这相当于其他面向对象语言中的 public
。这种机制被称为导出。值得注意的是,大写字母可以是任何 Unicode 编码的字符,不限于 ASCII 码。
相反,如果标识符以小写字母开头,则它对包外是不可见的,类似于面向对象语言中的 private
。这种标识符仅在其所属的包内部可见并可用。
例如,假设在包 pack1
中有一个变量或函数名为 Thing
(以大写 T
开头,因此可被导出),在其他包中通过导入 pack1
可以访问 Thing
,使用方式如下:pack1.Thing
。在这里,pack1
作为命名空间,是不可以省略的。
包的这种命名空间功能有助于避免名称冲突。例如,pack1.Thing
和 pack2.Thing
可以清晰地区分属于不同包的同名变量或函数。
2.6、使用包的别名
在 Go 中,你可以通过为包指定别名来解决包名冲突或简化代码。例如,你可以这样导入 fmt
包并为其设置别名:
go
package main
import fm "fmt"
func main() {
fm.Println("hello, world")
}
2.7、包的声明和初始化
在 Go 中,你可以在导入包之后,在包级别定义或声明常量(const
)、变量(var
)和类型(type
)。这些定义的作用域是全局的,可在整个包中被访问。之后,可以声明一个或多个函数(func
)来进行具体的逻辑操作。这样的结构使得代码既清晰又易于管理。
Ps:如果你导入了一个包却没有在代码中使用它,Go 编译器会抛出错误,例如 imported and not used: os
。这反映了 Go 的设计哲学:"没有不必要的代码!"