本文的内容建立在已对 C 和 C++ 熟练掌握的基础之上,主要给出 Go 与 C 和 C++ 之间的不同之处。
以下是一段 Go 语言的简单代码。
1 | package main |
第一段为包声明,说明这个文件属于哪个包。
第二段 import 作用是引入外部包,"fmt" 中有格式化 IO 函数 fmt.Println()。
后面的 func main() {...} 为程序执行的入口函数,main() 函数是每一个可执行程序所必须包含的,且必须属于 package main 才可以执行。
import 导入包名(package)。
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) 转换是不合法的。