Golang 语法速通指南(二)程序结构
让我们从最简单的 HelloWorld
开始理解
1 | package main |
包
声明包
每个 Go 程序都是由包构成的,每个项目都是从 main
包开始运行,最开头的 package main
声明了我们当前在 main
包中
导入包
使用 import
语句来导入包,例如这里导入了 fmt
包,并且在 main
函数中使用了它的函数 Println
在需要导入多个包,可以使用多个 import
语句
1 | import "fmt" |
也可以选择使用括号括起来
1 | import ( |
导出名
在语言中,常使用 public
和 private
关键字来说明一个对象是公用的还是私有的(对外是否可见)
而在 Go 中则简单地使用首字母大小写来表示这一点,如果一个名字以大写字母开头,那么它就是已导出的(外部可见),这一点在很多地方都能体现,例如包中的函数和结构体中的元素
这也解释了为什么Println
的首字母是大写的
让我们实操一下:
在项目中新建 .\sayhello\sayhello.go
1 | package sayhello |
然后回到 main.go
来调用它
1 | package main |
函数
这里只是简单地介绍一下函数,更为深入的内容会在后面讲
声明函数
基本的函数声明模板如下
1 | func 函数名 (参数列表) (返回值列表){ |
与 C 不同,Go 的参数是名称在前,类型在后,请看下面的例子
1 | func add(x int, y int) int { |
如果连续多个参数的类型相同,那么就可以写在一起
1 | func add(x, y int) int { |
多值返回
Go 扩展了 C 中的函数的返回值,现在函数可以返回任意多个参数
1 | func swap(x, y string) (string, string) { |
命名返回值
Go 的返回值可被命名,它们会被视作定义在函数顶部的变量
这样一来,在最后可以直接用一句 return
来结束,定义的返回值就会自动返回
1 | func add(x, y int) (ans int) { |
变量
声明及初始化
基本的变量声明模板如下
1 | var 变量名 类型 = 表达式 |
其中,类型或表达式可以省略一个:
- 若省略类型,就会按表达式自动推断类型
- 若省略表达式,就会自动初始化为该类型的零值
零值就是字面意思,就像你在 C 中对 int
类型总会手动初始化为 0,只是在 Go 中,这一切都是自动化的,这可以简化很多代码
多个变量也可以一起声明和初始化,若省略类型,则会分别自动推导类型
1 | var i, j, k int // int, int, int |
前面讲了函数可以同时返回多个参数,所以一组变量也可以这样初始化
1 | var f, err = os.Open(name) // os.Open returns a file and an error |
接收多个变量的时候,可以使用下划线(_
)丢弃不需要的值
1 | var f, _ = os.Open(name) // 不接收错误 |
var
和 import
一样,使用括号括起来
1 | var ( |
这种写法一般用于在函数外声明一堆包级变量
短变量声明
在函数中,简洁赋值语句 :=
可在类型明确的地方代替 var
声明
1 | 变量名 := 表达式 |
函数外的每个语句都必须以关键字开始(var
, func
等等),因此 :=
结构不能在函数外使用
上面的例子也可以这样写
1 | f, err := os.Open(name) // os.Open returns a file and an error |
赋值
Go 的赋值兼容了 C 中的所有操作,包括缩写运算符(如 +=
),还有自增自减(++
、--
)等
Go 在这方面创新了一个元组赋值,就像上面的多变量初始化一样,右边的几个变量分别赋值到左边去
这一特性最大的帮助就是使交换变量变得非常方便
1 | x, y = y, x |
在 C 中如果要交换两个变量的值,一般都要借助于一个临时变量,但 Go 使得这一操作变得非常优雅
来看看用 Go 来求最大公约数,是不是简洁了许多
1 | func gcd(x, y int) int { |
与初始变量时一样,你可以使用下划线来丢去部分值
1 | _, err = io.Copy(dst, src) // 丢弃字节数 |
关于这个下划线,我还想多说一句
众所周知如果你声明了一个变量但是没用它是会报错的,有红色波浪线,看得很烦心
这时,你可以接着下划线假装“使用”了这一变量
1 | _ = a |
指针
Go 的指针摒弃了 C 中的运算功能(加减法),其他地方可以认为是相同的
类型
基本类型
Go 中的基本类型有
1 | bool |
类型转换
这点与 C 有较大区别,使用 T(v)
将值 v
转换为类型 T
1 | var i int = 42 |
并且,Go 不提供自动转换,在 C 中你可以直接将一个 int
赋给 float
,但你不能在 Go 中做到这种事情
类型别名
1 | type mytype = string |
类似于 C 中的 define
,在编译时全部替换
没法自己添加方法,可以增强可读性
类型定义
1 | type mytype string |
基于旧类型生成新类型,相互可以强制转换,可以添加自己的方法
逻辑控制
for
Go 中的 for
,与 C 中的基本相同,但是少了小括号,并且大括号变成必须的了
1 | for i := 0; i < 10; i++ { |
与 C 一样,起始条件、结束条件和后置语句都是可选的
while
当你省略了起始条件和后置语句时,for
其实就变成了 while
,所以 Go 中是没有while
的
1 | sum := 1 |
而无限循环也就只剩下了一个 for
1 | for { |
if
类似地,无需小括号,而大括号是必须的
1 | func sqrt(x float64) string { |
Go 允许你在条件表达式前先执行一个语句,这可以简化代码
1 | func pow(x, n, lim float64) float64 { |
与你想的一样,这里的 v
的作用域仅限该 if
语句内
至于 else
和 else if
的用法,可以参考下面的例子
1 | var a int |
switch
对比 C 的 switch
,你只需要更新了以下几点:
-
case
现在不一定要是常量,也可以是表达式(如果是表达式,那么中途跳出后下面的表达式并不会执行) -
每个分支都是默认
break
的,如果你不想跳出,可以以fallthrough
语句结束 -
支持多条件匹配
1
2
3
4switch a {
case 1,2,3,4:
default:
}
来看几个例子
https://tour.go-zh.org/flowcontrol/9
1 | package main |
https://www.runoob.com/go/go-switch-statement.html
1 | package main |
defer
defer
语句会将函数推迟到外层函数返回之后执行
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用
1 | package main |
作用域
除下面的两点外,其他的都与 C 中的相同
- 在同一个包内的变量和函数,在整个包内的所有文件中都可用
import
语句只对当前文件有效