Golang 程序结构
Go 和其他语言一样,程序是由很多小的基础构件组成的,变量存值,简单的加法和减法运算被组合成复杂的表达式,还有基础类型被聚合为数组或结构体,后面这些都会一一学习到,先从最简单的
命令
,声明
,变量
,赋值
,类型
,包和文件
,作用域
这些基础的概念学习
命名
在 Go 中
函数名
,变量名
,常量名
,类型名
,语句标识
和包名
, 遵循的规则为一个名字必须以一个字母或下划线开头,后面可以跟任意数量的字母,数字或下划线
,严格区分大小写
。
关键字
Go 的关键字并不是很多,有
25个
, 关键字不能用于自定义的名字
,在特定的语法结构中使用
1 | break default func interface select |
预定义名字
预定义名字并不是关键字
,可以在定义中重新使用他们, 但是要要注意避免过度使用而引起的语义混乱
, 在Go 中这样的预定义名字大约有 30 多个, 主要对应内建的常量
,类型
和函数
1 | // 内建常量: |
命名推荐
名字出现的位置, 影响到这个名字的有效使用范围,和作用域有关系,作用域这个后面会有讲到,名字在
函数内部定义, 就只在函数内部有效
,在函数外部定义, 当前包的所有文件中都可以访问
,名字的开头字母的大小决定了包外的可见性
,大写字母开头,是可以导出的, 可以被外部访问
,包名
采用小写字母
, Go语言名字的风格推荐短小的名字
,驼峰式命名
,多个单词单用大小写分割
声明
Go语言主要有四种类型声明的语句:
var
,const
,type
和func
, 分别对应变量
,常量
,类型
,函数实体对象
, 一个Go
语言程序对应一个或多个.go
为文件后缀名的源文件中,每个源文件以包的声明语句开始,说明该源文件属于那个包
1 | package main |
import
import语句导入依赖的其它包boilingF
变量是包一级声明语句声明, 可以在整个包对应的每个源文件中被访问
f
和c
变量是函数内部声明, 只能在函数内部被访问
变量
var
声明语句可以创建特定类型的变量
, 给变量附加一个名字,并且设置变量的初始值
语法格式
1 | var 变量名字 类型 = 表达式 |
表达式
和类型
两个可以省略一个, 省略类型信息
, 将根据初始化表达式推导变量的类型信息, 省略表达式
, 将用零值
初始化该变量, 这里要注意的是接口
或引用类型(包括 slice, 指针, map chan 和 函数)
变量对应的零值是nil
, 这样做的好处是可以确保每个声明的变量总有一个良好定义的值
,Go语言中不存在未初始化的变量
。
1 | var s string |
同时声明多个变量
1 | var i, j, k int // int, int, int |
简短变量
简短变量
声明语句的形式可用于声明和初始化局部变量, 形式为名字 := 表达式
, 变量的类型根据表达式来自动推导,多用与局部变量的声明和初始化,var
声明语句多用于需要显示指定变量类型的地方
,:=
是一个变量声明语句,=
是一个变量赋值操作。
1 | t := 0.0 |
:=是一个变量声明语句
1 | i, j = j, i //交换i和j的值 |
简短变量
声明左边的变量可能并不是全部都是刚刚声明的,如果有一些已经在相同的作用域
声明过了,那么简短变量声明语句对这些已经声明过的变量就只有赋值行为,简短变量声明语句中至少要声明一个新的变量
, 如果不在同一作用域中
,那么简短变量将会在当前作用域声明一个新的变量
1 | f, err := os.Open(infile) |
指针
变量对应一个保存了变量对应类型值的内存空间
, 普通变量在声明语句创建时被绑定到一个变量名, 还有很多变量是用表达式引入的,x[i]
或x.f
变量, 所有这些表达式一般都是读取一个变量的值。一个指针的值是另一个变量的地址, 一个指针对应变量在内存中的存储位置
, 并不是每个值都会有一个内存地址,但每一个变量都会有对应的内存地址,通过指针我们可以直接读或更新对应变量的值,而不需要知道该变量的名字
1 |
|
声明
x
变量,&x
表达式将产生一个指向该整数变量的指针, 指针对应的类型为*int
, 指针名为p
, 那么可以说p
指针指向变量x
,*p
表达式对应p指针指向的变量的值,*p
表达式读取指针指向变量的值
聚合类型
聚合类型
, 比如结构体的每个字段
,或者数组的每个元素
,也都是对应一个变量,所以可以被取地址,变量有时被称为可寻址的值,即使变量由表达式临时生成,那么表达式也必须能接受 & 取地址操作
,任何类型的指针零值都是 nil
。
1 | var x, y int |
返回局部变量地址
1 | var p = f() |
Go语言中, 返回函数中局部变量的地址也是安全的,局部变量地址被返回之后依然有效,因为指针
p
依然引用这个变量。
指针更新变量的值
1 | package main |
指针包含一个变量的地址,将
指针作为参数调用函数
,可以在函数中通过该指针来更新变量的值
, 然后返回更新后的值,对一个变量取地址,或者复制指针, 都是为原变量创建了新的别名
, *p 就是变量 v 的别名, 指针的特别价值在于我们不用名字而访问一个变量
new 函数
在Go中,还有一个创建变量的方法是调用内建
new
函数, 表达式new(T)
将创建一个T类型的匿名变量,初始化为 T 类型的零值,返回变量的地址
1 | package main |
new
创建变量和普通变量声明语句方式创建变量没有什么区别,new 函数类似一种语法糖,而不是一个新的基础概念
, 下面这两个函数具有相同的行为, 多数情况下每次调用 new 函数都是返回一个新的变量的地址
, 如果两个类型的大小都是0, 有可能有相同的地址
1 |
|
变量的生命周期
声明周期
是指在程序运行期间变量的有效存在的时间间隔
包一级声明的变量
: 生命周期和整个程序运行的周期是一致的局部变量的声明
: 声明周期是动态的,每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被收回,函数的参数变量和返回值变量都是局部变量
赋值
赋值语句
可以更新一个变量的值, 是将被赋值的变量放在 =
的左边,新值表达式放在 =
的右边
1 | x = 1 // 命名变量的赋值 |
元祖赋值
元祖赋值是另一种形式的赋值语句, 它允许同时更新多个变量的值
,赋值语句右边的所有表达式将会先进行求值,然后再同一更新左边对应变量的值, 元祖复制可以是一系列琐碎的赋值更加紧凑
1 | x, y = y, x |
计算斐波纳契数列的第N个数
1 | package main |
多返回值
有些表达式会产生多个值,一个函数可能返回多个值, 左边变量的数目必须和表达式返回的值的个数一致, 如果我们不需要的值, 可以使用下划线空白标识符
_
来丢弃。
可赋值性
赋值语句是显示的赋值形式,在程序中还有很多地方会发生隐式的赋值行为
,例如函数调用会隐式将调用参数的值赋值给函数的参数变量,一个返回语句会隐式的将返回操作的值赋值给结果变量, 符合类型的字面量也会产生赋值行为
1 | medals := []string{"gold", "silver", "bronze"} |
不管是隐式还是显示的赋值,赋值语句左边的变量和右边最终的求到的值必须有相同的数据类型
, 只有右边的值对于左边的变量是可赋值的,赋值语句才是允许的,