本文的内容建立在已对 C 和 C++ 熟练掌握的基础之上,主要给出 Go 与 C 和 C++ 之间的不同之处。
以下是一段 Go 语言的简单代码。
1 | package main |
第一段为包声明,说明这个文件属于哪个包。
第二段 import
作用是引入外部包,"fmt"
中有格式化 IO 函数 fmt.Println()
。
后面的 func main() {...}
为程序执行的入口函数,main()
函数是每一个可执行程序所必须包含的,且必须属于 package main
才可以执行。
import
导入包名(package
),Go 在引入包时首先会在 GOPATH/src
和 GOROOT/src
两个路径中查找。因此,建议 Go 的开发尽量放在这两个路径下以减少不必要的麻烦。
Go 的一个文件夹对应到一个包,包名是文件夹的路径名。同一个文件夹下的包名应该保持一致,并建议与文件夹名一致。同一个包中的函数、变量等不能出现重名。包中的函数、变量名需要首字母大写才能保证他是可以被其他程序调用的(可导出)。
有时会遇到同名包的情况,这个时候需要使用到包的别名对同名包进行区分。
1 | import ( |
在项目中,应将 package main
的 Go 程序放在项目的根目录,且 package main
中仅存在有一个 main()
函数作为执行入口。
假设某项目有以下目录结构及文件内容。
1 | DIR |
1 | /* hello.go */ |
这里的 init.go
文件和 main.go
文件在同一文件夹下,所以他们的包名应该相同。且由于 main.go
中有 main()
函数,所以其包名应为 package main
,package main
都应该放在项目根目录下。hello.go
则是属于 package greet
,放在了子文件夹中。
对于 main.go
,可以单独执行该文件,也可以将项目整体编译执行,两种情况下的执行结果不同,如下所示。
1 | go run main.go # -> Hello Go! |
Go 程序在启动后会先执行 init()
函数,再执行 main()
函数。整体执行项目会将 init.go
中的 init()
函数引入,运行时会先调用其中的 init()
函数,而单独执行 main.go
时则不会。
fmt.Printf()
支持格式化输出,使用方式类比于 C 的 printf()
。相较于 C,Go 中额外支持的格式有:
%v
,按本来值输出,会自动转换,支持输出结构体和数组等%+v
,在 %v
的基础上会展开结构体的字段名%#v
,在 %+v
的基础上再输出结构体名(类型)%T
,输出类型fmt.Sprintf()
支持格式化输出至字符串,使用方式类比于 C 的 sprintf()
。
fmt.Print()
无需格式化输出,其会默认将每个参数以 %v
输出。
fmt.Println()
在 fmt.Print()
基础上将内容输出并以空格分隔,默认在行尾加上换行。
示例如下。
1 | x, y := 1, 2.2 |
fmt.Scanf()
的使用方式类似于 C 中的 scanf()
,但是其在读入 \n
时就会直接结束,且不会丢弃 \n
。因此, fmt.Scanf()
在读入多行时需要注意换行的读入,示例如下。
1 | var x, y, z string // INPUT: "1\n2\n3\n" |
fmt.Scan()
使用方式为 fmt.Scan(&x, &y, &z)
,其会忽略 \n
,读到时不会结束,因此多行读入可以直接使用。
fmt.Scanln()
使用方式为类似于 fmt.Scan()
,但是会在读入 \n
时就结束,不同于 fmt.Scanf()
的是其会将缓冲区中的 \n
收走,示例如下。
1 | var x, y, z string // INPUT: "1\n2\n3\n" |
Go 中的数据类型主要有三种布尔型 bool
,数字类型(包括整型 int
以及浮点型 float32
/ float64
)和字符串类型 string
。
整型中除了有一般的 int
和 uint
外,还有基于架构的变量 int8
,int16
,int32
,int64
以及 uint8
,uint16
,uint32
,uint64
。
浮点型有 float32
,float64
以及复数 complex64
(32 位实数和虚数),complex128
(64 位实数和虚数)。
另外,还有 byte
类型,其等同于 uint8
,用于表示 ASCII 码的字符。以及 rune
类型,等同于 uint32
,用于表示 UTF-8 编码的字符。
变量的声明方式为 var x type
,示例如下。
1 | var x bool = true; |
变量可以声明但不赋初值,默认为 0
,空串或是 nil
(空)。
变量可以不指明类型,由编译器自行判断,示例如下。
1 | var x = true; |
但是变量不能既不指明类型,又不赋初值,例如 var x
。
另外,可以采用 :=
的简明方式声明并对变量赋初值,示例如下。
1 | x := 1 // 等同于 var x int = 1 |
多个变量可以同时声明,示例如下。
1 | var a, b, c int = 1, 2, 3 |
常量的声明方式为 const x type
,其定义方式与使用方式与变量 var
一致。
iota
为特殊的常量,其会在 const
出现时置为 0
,常用于枚举,示例如下。
1 | const ( |
在声明常量组时,如果未指定值,则会使用上一行的表达式。例如,上面例子中的 b
使用的是上一行的表达式 iota
。
数值类型之间的相互转换类似于 C,如下。
1 | var x float32 = 1.22 |
Go 不接受隐式类型转换。
可以使用 type
为类型定义一个别名,示例如下。
1 | type myint = int |
这里的 myint
本质上和 int
一致,并且可以直接和 int
进行运算。
Go 中的运算符规则与 C 和 C++ 基本一致,包含有算数运算符、关系运算符、逻辑运算符、位运算符和赋值运算符。仅有一处不同,自增运算符不支持赋值,示例如下。
1 | var x, y int = 10, 0 |
Go 也支持取地址 &
和指针变量 *
运算符。
1 | var x int = 10 |
init()
函数调用顺序:
init()
,调用顺序为文件名字符串比较由小到大调用。例如,同一个文件夹下有 A.go 和 B.go,均有 init()
函数,那么调用顺序为 A.go 中的 init()
到 B.go 中的 init()
。init()
,调用顺序为 import
顺序。例如,A.go 依次调用了 B.go 和 C.go,且 B.go 和 C.go 都有 init()
,那么调用顺序为 B.go 中的 init()
到 C.go 中的 init()
。init()
最后到当前包中的 init()
。例如,基于 2 中的例子,我们再假设 A.go 中也存在 init()
函数,那么调用顺序为 B.go 中的 init()
到 C.go 中的 init()
再到 A.go 中的 init()
。嵌套调用总是最深处的那个包的 init()
先调用。Go 中有空白标识符 _
,是一个只写变量。
不同于 C,Go 中 int
类型和 bool
类型无法直接相互转化,bool(int)
和 int(bool)
转换是不合法的。