/ 读书笔记

Go语言圣经 学习笔记 第二章

第二章 程序结构

2.1 命名

  • 简单的命名规则:一个名字必须以一个字母( Unicode字母) 或下划线开头,后面可以跟任意数量的字母、数字或下划线。大写字母和小写字母是不同的:heapSort和Heapsort是两个不
    同的名字
  • Go语言中有关键字和预定义名字,关键字不能用于自定义名字,而预定义名字可以再次定义使用它们.
  • 如果一个名字是在函数内部定义,那么它的就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性.如果一个名字是大写字母开头的,那么它将是导出的,也就是说可以被外部的包访问

2.2 声明

  • Go语言主要有四种类型的声明语句:var、const、type和func,分别对应变量、常量、类型和函数实体对象的声明。

2.3 变量

  • 变量声明的一般语法如下:
var 变量名字 类型 = 表达式

其中“类型”或“= 表达式”两个部分可以省略其中的一个

  • 零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。

2.3.1 简短变量生命

  • “名字:= 表达式”形式声明变量,变量的类型根据表达式来自动推导
  • 简短变量声明左边的变量可能并不是全部都是刚刚声明的。如
    果有一些已经在相同的词法域声明过了(§2.7),那么简短变量声明语句对这些已经声明过的变量就只有赋值行为了
  • 简短变量声明语句中必须至少要声明一个新的变量

2.3.2 指针

  • 一个指针的值是另一个变量的地址。一个指针对应变量在内存中的存储位置。
  • 在Go语言中,返回函数中局部变量的地址也是安全的。
  • 指针的取地址及取值操作跟C语言指针类似分别使用&和*号操作,但是GO语言中指针不能进行加减操作(详见第一章 第八节)

2.3.3 new函数

  • 表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T 。
p := new(int) // p, *int 类型, 指向匿名的 int 变量
*p = 2 // 设置 int 匿名变量的值为 2
fmt.Println(*p) // "2"
  • 由于new只是一个预定义的函数,它并不是一个关键字,因此我们可以将new名字重新定义为别的类型。例如下面的例子:
func delta(old, new int) int { return new - old }

由于new被定义为int类型的变量名,因此在delta函数内部是无法使用内置的new函数的。

2.3.4 变量生命周期

  • 对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,在局部变量的声明周期则是动态的:从每次创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。

2.4 赋值

2.4.1 元组赋值

  • 元组赋值是另一种形式的赋值语句,它允许同时更新多个变量的值。在赋值之前,赋值语句右边的所有表达式将会先进行求值,然后再统一更新左边对应变量的值
x, y = y, x
a[i], a[j] = a[j], a[i]

2.4.2 可赋值性

  • 类型必须完全匹配,nil可以赋值给任何指针或引用类型的变量。
  • 对于两个值是否可以用==或!=进行相等比较的能力也和可赋值能力有关系:对于任何类型的值的相等比较,第二个值必须是对第一个值类型对应的变量是可赋值的,反之依然

2.5类型

  • 一个类型声明语句创建了一个新的类型名称,和现有类型具有相同的底层结构。新命名的类型提供了一个方法,用来分隔不同概念的类型,这样即使它们底层类型相同也是不兼容的
type 类型名字 底层类型
  • 对于每一个类型T,都有一个对应的类型转换操作T(x),用于将x转为T类型(如果T是指针类型,可能会需要用小括弧包装T,比如 (*int)(0) )
  • 数值类型之间的转型也是允许的,并且在字符串和一些特定类型的slice之间也是可以转换的,在下一章我们会看到这样的例子。这类转换可能改变值的表现。例如,将一个浮点数转为整数将丢弃小数部分,将一个字符串转为[]byte类型的slice将拷贝一个字符串数据的副本。在任何情况下,运行时不会发生转换失败的错误(译注:错误只会发生在编译阶段)
  • 底层数据类型决定了内部结构和表达方式,也决定是否可以像底层类型一样对内置运算符的支持。
  • 比较运算符==和<也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直接进行比较
var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // "true"
fmt.Println(f >= 0) // "true"
fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f))

2.6 包和文件

  • 在Go语言中,一个简单的规则是:如果一个名字是大写字母开头的,那么该名字是导出的

2.6.2 包的初始化

  • 包的初始化首先是解决包级变量的依赖顺序,然后安照包级变量声明出现的顺序依次初始化:
  • 如果包中含有多个.go源文件,它们将按照发给编译器的顺序进行初始化,Go语言的构建工具首先会将.go文件根据文件名排序,然后依次调用编译器编译
  • 每个文件都可以包含多个init初始化函数
func init() { /* ... */ }

这样的init初始化函数除了不能被调用或引用外,其他行为和普通函数类似。在每个文件中的
init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用。
每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次。因此,如果一个p包导入了q包,那么在p包初始化的时候可以认为q包必然已经初始化过了。初始化工作是自下而上进行的,main包最后被初始化。以这种方式,可以确保在main函数执行之前,所有依然的包都已经完成初始化工作了

2.7 作用域

  • 声明语句的作用域是指源代码中可以有效使用这个名字的范围
  • 声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时
    间区域内它可以被程序的其他部分引用;是一个运行时的概念