第二章 程序结构
命名
- 区分大小写
- 开头字母或下划线
- 在一个程序包中,函数外定义的,
1.开头大写的表示可导出的,
2.小写的表示不可导出的,
3.当然,下划线开头的变量是不可导出的
- 推荐风格:
1.尽量短小
2.驼峰式
3.专有名词,像ASCII,HTML这样的缩略词大小写不混用(也就是要么全部大写,要么全部小写)
声明
- 四种基本类型的声明语句:
1.var
2.const
3.type
4.func
- 包声明:import
- 声明的级别:包级>函数级
1.大范围的可以被小范围的访问
2.同级函数之间互相独立,但可以通过调用,传参等方式建立联系
变量
- 零值初始化规则:当变量被声明而没有明确初始化,将被使用零值初始化
- 各种类型的零值不一定相同,大体规则如下:
1.数值类型:0
2.布尔类型:false
3.字符串:空字符串,也就是""
4.接口或引用类型或“指针”(包括slice,map,chan和函数): nil
5.聚合类型(就是多种的基础类型被放在一个struct或者一种基础类型以数组形式组织):相应各个元素对应类型的零值
简短变量声明
- 名字:=表达式
- 简短变量声明并非所有的变量都是刚刚声明的。以下例子中的f和err不一定都是刚刚声明的, 但是肯定的是,其中一个是没有声明的
func dosomething() {
f, err := os.Open(name)
}
- 需要注意的是,在函数外不能使用简短变量声明,不然会报错
non-declaration statement outside function body
指针
- 一个变量对应了一个保存了变量对应类型值的内存空间(内存地址)
- 而指针的值就是另一个变量的内存地址
- 对于变量var x int, &x操作将会返回x的内存地址,对应的数据类型为*int,术语称之为“指向int类型的指针”
标准库flag包
- 使用命令行参数来设置对应变量的值
- 换句话说,使用flag包可以用来构建像godoc这样的程序
new函数
- 注意:new只是一个预定义的函数
- new(T)操作
1.创建一个T类型的匿名变量,
2.用零值初始化该匿名变量,
3.返回该匿名变量的地址,其类型就是T
4.总结:返回T类型的变量,并且其指向的空间已经使用对应类型的零值初始化了
- 以下是用指针变量声明和用new的区别
p := new(int)
var p2 *int
fmt.Println(p) //某个内存地址
fmt.Println(*p) // "0"
fmt.Println(p2) //nil
fmt.Println(*p2) //panic: runtime error: invalid memory address or nil pointer dereference
- 指针变量声明只用nil初始化指针变量
- new会初始化指针对应类型的匿名变量
变量的生命周期
- 在程序运行期间,变量有效存在的时间间隔
- 包级别的变量,生命周期和整个程序运行周期一致;局部变量(即函数内的)生命周期是动态的,该变量不再被引用才有可能被回收(自动垃圾收集器)
- “一个变量的有效周期只取决于是否可达”(gopl-zh.pdf 64页)
- 逃逸现象:结合以下例子说明,函数f()中的x变量地址传递给了包级别声明的变量global,这就是一种逃逸现象;通常的说法是“这个x局部变量从函数f中逃逸了”
var global *int
func f() {
var x int
x = 1
global = &x
}
- “要记住的是,逃逸的变量需要额外分配内存,同时对性能的优化可能会产生细微的影响”(gopl.pdf 64页)
赋值
元组赋值
- 在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值
可赋值性
- 隐式赋值行为的一些例子:
1.函数调用会将调用参数的值赋给函数的参数变量
2.函数中返回语句将返回操作中的值赋予给结果变量
3.复合类型的字面量也会产生赋值行为,以下的gold,silver,bronze就是字面量
medals := []string{"gold", "silver", "bronze"}
- 赋值语句中,左边的变量和右边最终求得的值必须有相同的数据类型(有些地方比较特殊,例如接口类型)
- nil可以赋值给任何指针或引用类型的变量
类型
- 类型定义了对应存储值的属性特征,例如:
1.在内存的存储大小
2.内部是如何表达
3.支持哪些操作符
4.它们关联的方法集
- 存在一些变量有着相同的内部结构(底层类型),但是却表达不同的概念(类型名字),可以使用type声明语句来标识
type 类型名字 底层类型
- 标识后的类型是有区别的,简单的判断方法就是类型名字相同,类型才相同;反之,类型名字不同,类型就不同
- 显式类型转换条件:两个类型的底层类型相同
- 在进行运算(数值或者逻辑)时,同样类型可以进行,但是字面量都是想通的,可以进行
type a1 int
type a2 int
func main() {
t1 := 1
t2 := a1(2)
t3 := a2(3)
fmt.Println(t2+2) //ok
fmt.Println(t2 > 0) //ok
fmt.Println(t2 > t1) //invalid operation: t2 > t1 (mismatched types a1 and int)
fmt.Println(t2 > t3) //invalid operation: t2 > t3 (mismatched types a1 and a2)
fmt.Println(t3 > t2) //invalid operation: t3 > t2 (mismatched types a2 and a1)
}
包和文件(详细的将在第十章找到)
- 通常一个包所在目录路径的后缀是包的导入路径
- 每一个包都是一个独立的名字空间
- 通过大小写的导出规则,实现外部的可见性
导入包
- 包的名字和包的导入路径的最后一个字段相同(一般情况下)
- 导入包却没有使用将被当作一个编译错误
包的初始化
- 首先解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化
- 如果包中含有多个.go文件,它们将按照发送给编译器的顺序进行初始化
- 可以使用Init函数做初始化工作,它们会按照声明顺序执行
作用域
- 声明语句的作用域是指源代码中可以有效使用这个名字的范围(静态概念)
- 生命周期是运行时的概念,是指当程序运行后,变量存在的有效时间段
- 语法块是由花括弧所包含的一系列语句。语法块内部声明的名字无法被外部访问,也就是说语法快决定了内部声明名字的作用域
- 对于内置的类型,函数,常量,是全局作用域的,可以在整个程序中直接使用
- 补充:
1.词法作用域,也叫静态作用域,它的作用域是指在词法分析阶段就确定了,不会改变。动态作用域是在运行时根据程序的流程信息来动态确定的,而不是在写代码时进行静态确定的。
简书 康斌:https://www.jianshu.com/p/70b38c7ab69c
2.动态作用域就是整个程序运行的时候只有一个env。什么是env呢?env就是一组binding。binding是什么呢?binding就是从identifer到value的映射。dynamic scope在每次函数求值的时候都会在这唯一的一个env里查询或更新。而static scope是每次函数求值的时候都创建一个新的env,包含了函数定义时候的所能访问到的各种binding。这个新的env连同那个函数一起,俗称闭包Closure。
作者:知乎用户
链接:https://www.zhihu.com/question/20032419/answer/49183240
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 需要注意的几个例子(非推荐写法,只是允许,应该尽量避免):
func main() {
x := "hello"
for _, x := range x {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" (one letter per iteration)
}
}
if x := f(); x == 0 {
fmt.Println(x)
} else if y := g(x); x == y {
fmt.Println(x, y)
} else {
fmt.Println(x, y)
}
fmt.Println(x, y) // compile error: x and y are not visible here
var cwd string
func init() {
cwd, err := os.Getwd() // compile error: unused: cwd
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}
var cwd string
func init() {
cwd, err := os.Getwd() // NOTE: wrong!
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
log.Printf("Working directory = %s", cwd)
}
var cwd string
func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("os.Getwd failed: %v", err)
}
}