go语言圣经第二章(读书笔记)

第二章 程序结构

命名

  • 区分大小写
  • 开头字母或下划线
  • 在一个程序包中,函数外定义的,
    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)
	}
}
posted @ 2019-07-21 18:38  DickLai  阅读(273)  评论(0)    收藏  举报