Skip to content

Latest commit

 

History

History
240 lines (151 loc) · 5.34 KB

ch2.md

File metadata and controls

240 lines (151 loc) · 5.34 KB

第二章 程序结构

2.1 名称

  • Go 有25个关键字。30多个预声明的常量,类型和函数(可以重声明,但是有风险)。(见书第20页)

  • 包级别的声明对包里的所有文件可见,大写字母开头表示它是导出的。

  • 包名本身用小写字母组成

  • 命名采用驼峰式

2.2 声明

  • 有4个主要的声明:变量(var),常量(const),类型(type),函数(func)

2.3 变量

// 类型和表达式可以省略其中一个
var name type = expression
  • Go中不存在未初始化变量,各种类型的0值:
类型 0值
数字 0
布尔值 false
字符串 ""
接口和引用类型(slice,指针,map,通道,函数) nil
复合类型(数组,结构体) 元素或成员的0值
  • 忽略类型允许声明多个不同类型的变量:
var b, f, s = true, 2.3, "four"
  • 包级别的变量初始化在 main 开始之前进行。函数级别的在函数执行期间进行。

2.3.1 短变量声明

  • := 表示声明,= 表示赋值

  • 局部 变量的声明和初始化中主要使用短声明

  • 使用 var 声明有两种情况:

    • 初始化表达式和类型不一致:

      var f float64 = 100
    • 初始值不重要时,后面才赋值:

      var names []string
      var p Point
  • 只有对可读性有帮助时才使用多个表达式初始化变量,例如 for 循环的初始化:

    for i, j := 0, 0; //...
  • 在多个左值的声明中,短变量声明至少声明一个新变量:

    in, err := os.Open(infile)
    // out为新变量
    out, err := os.Create(outfile)
  • 只有在同一个词法块中变量已经存在的情况下,短声明行为才和赋值一样。外层的声明将被忽略。

2.3.2 指针

  • 指针可以比较,两个指针指向同一变量或者都是 nil 时才相等

  • 函数返回局部变量的值的指针是安全的

    var p = f()
    
    func f() *int {
        v := 1
        return &v
    }
  • 指针产生别名。复制其他引用类型也产生别名。

2.3.3 new 函数

  • 表达式 new(T) 做三件事:

    • 创建一个未命名的 T 类型变量
    • 初始化为 T 类型的零值
    • 返回其地址 ( 类型为 *T )
    func newInt() *int {
        return new(int)
    }
    // 等同于
    func newInt() *int {
        var i int
        return &i
    }
  • 如果两个变量的类型不携带任何信息且都是零值,它们就具有相同的地址。例如类型为 struct{} 或 [0]int

  • new 不是关键字,可以重定义

2.3.4 变量的声明周期

  • 变量的声明周期是通过它是否可达确定的

  • 变量的逃逸

    var g *int
    
    func f() {
        var x int
        x = 1
        g = &x   // 变量x从f逃逸
    }
  • 合理控制变量的声明周期,提高垃圾回收效率

2.4 赋值

  • 用赋值操作符(+=, *=) 避免重复变量本身

  • 用++,--,递增或递减

2.4.1 多重赋值

  • 可以交换变量的值

  • 让赋值表达式变得紧凑

  • 同时返回操作结果:

    v, ok = mmap[key]
    v, ok = x.(T)
    v, ok = <-ch

2.4.2 可赋值性

  • 函数调用隐式地将参数赋值给参数变量

  • return 语句隐式地将 return 操作数赋值给结果

  • 可赋值性:左右值类型必须精准匹配

  • 可比较性:两个值如果可以使用 == 或 != 进行比较,第一个操作数相对于第二个操作数必须是可赋值的

2.5 类型声明

  • type 声明(通常在包级别)定义一个新的类型,和某个已有类型使用同样的底层类型

    type name underlyingType
  • 不同类型间可以相互转换的条件:

    • 具有相同的底层类型

    • 或指向相同底层类型变量的未命名指针类型

  • 不同命名类型的值不能比较

  • 命名类型可以与其底层类型相同类型的未命名类型比较

2.6 包和文件

  • 包用于支持模块化,封装,编译隔离和重用

  • 包给声明提供独立的命名空间

  • 包导出的标识符以大写字母开头

  • package 声明前紧挨着文档注释,对整个进行描述。一个包只有一个文件应包含文档注释。扩展文档注释通常放在 doc.go 中

2.6.1 导入

  • 包名是包导入路径的最后一段

  • 导入未使用的包会编译报错

2.6.2 包的初始化

  • 任何文件可以包含任意数量的 init 函数声明:

    func init() { /* ... */ }
  • init 函数不能被调用。程序启动时,init 函数按照声明的顺序执行

  • 包的初始化按照在程序中导入的顺序来进行,main 包最后初始化

2.7 作用域

  • 作用域是编译时属性,声明周期是运行时属性

  • 声明的词法块决定声明作用域的大小。不用的词法块可以包含同名的声明。内层声明将覆盖外层声明

  • 内置类型,函数,常量对整个程序可见

  • 包级别的声明对整个包可见

  • 导入的包,可见性是文件级别的

  • for, if, switch 都会创建隐式的词法块。注意在其中声明变量的生命周期。

  • 内层词法块中的声明可能屏蔽全局变量,避免使用 :=

    var cwd strings
    func init() {
        cwd, err := os.Getwd() // 错误
    }
    
    // 应该
    func init() {
        var err error
        cwd, err = os.Getwd()
    }