Go语言学习笔记 - 第二章 程序结构(The Go Programming Language)

这一生的挚爱 提交于 2020-01-13 05:17:39

第二章 程序结构

2.1命名

划重点

  • 函数名、变量名、常量名、类型名、语句标号和包名
  • 以一个字母(Unicode字母)或下划线开头,跟任意数量的字母、数字或下划线,大写字母和小写字母是不同的。
  • 关键字(25个)
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
  • 预定义的名字 (30+个),这些不是关键字,可以再定义中重新使用它们
内建常量: true false iota nil
内建类型: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error
内建函数: make len cap new append copy close delete
complex real imag
panic recover
  • 作用域:内部定义内部有效;外部定义包内有效,名字开头大写可在包外部可见。比如:fmtPrint可以在fmt包外访问。
  • 名字长度:原则上没有规定,但是内部尽可能短;如果作用域大,生命周期比较长,长名字会更有意义。
  • 命名方式:使用 “驼峰式”命名。例如:标准库有QuoteRuneToASCIIparseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCIIparse_request_line这样的命名。而像ASCIIHTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscapeHTMLEscapeescapeHTML,但不会是escapeHtml

2.2声明

划重点

  • 类型声明:四种类型的声明语句:varconsttypefunc,分别对应变量、常量、类型和函数实体对象。
  • 包一级类型、变量、常量、函数的各种类型的声明语句的顺序无关紧要,函数内部的名字则必须先声明之后才能使用。
  • 函数声明:函数名字、参数列表、返回值列表、函数定义的函数体。

2.3变量

划重点

  • var 变量名字 类型 = 表达式,“类型”或“= 表达式”可省其一,未初始化,则初始化为零值
  • 零值:

数值-> 0
布尔->false
字符串->空字符串
接口或引用类型(包括slice、map、chan和函数)->nil
数组或结构体等聚合类型->各元素或字段对应的零值

  • 声明一组变量,或者由函数返回的多个返回值初始化
var i, j, k int // int, int, int
var b, f, s = true, 2.3, "four" // bool, float64, string
var f, err = os.Open(name) // os.Open returns a file and an error

2.3.1 简短变量声明

划重点

  • 示例,一个或一组变量:
i := 100
i, j := 0, 1
  • :=是一个变量声明语句,而=是一个变量赋值操作
  • 下面的代码先声明了in和err,则在第二次只声明out,已声明的err只是赋值; 简短变量声明中必须至少有一个新的变量;
编译通过
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)
编译失败
f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables

2.3.2指针

划重点

  • var x int &x表达式(取x变量的内存地址),指针对应的数据类型是 *int*p表达式对应p指针指向的变量的值
  • Go语言中,返回函数中局部变量的地址也是安全的,下面的代码是有效的,但是每次返回的地址是变化的,值不变
var p = f()
func f() *int {
   v := 1
   return &v
}
  • Go语言中不能对指针++--*p++只是只是增加p指向的变量的值,并不改变p指针!!!
  • 指针是实现标准库中flag包的关键技术

常用库及方法

  • flag flag.Bool() flag.String() flag.Parse() flag.Args()

2.3.3new函数

划重点

  • new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T
  • 用new创建变量和普通变量声明语句方式创建变量没有区别,下面是等价的:
func newInt() *int {
  return new(int)
}
func newInt() *int {
  var dummy int
  return &dummy
}
  • 如果两个类型都是空的,也就是说类型的大小是0,例如struct{}[0]int,有可能有相同的地址。(请谨慎使用大小为0的类型,因为如果类型的大小位0好话,可能导致Go语言的自动垃圾回收器有不同的行为,具体请查看 runtime.SetFinalizer 函数相关文档)
  • new为预定义的函数,不是关键字,将new重新定义
func delta(old, new int) int { 
   return new - old 
}

2.3.4变量的生命周期

划重点

  • 包级变量:程序运行周期;局部变量:声明开始,不再引用为止,入参和返回均为局部变量;
  • 函数右小括弧可以另起一行缩进,为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号,如下:
img.SetColorIndex(
size+int(x*size+0.5), size+int(y*size+0.5),
blackIndex, // 最后插入的逗号不会导致编译错误,这是Go编译器的一个>特性
) // 小括弧另起一行缩进,和大括弧的风格保存一致
  • Go编译器会自动选择在栈上还是在堆上分配局部变量的存储空间,但可能令人惊讶的是,这个选择并不是由用var还是new声明变量的方式决定
  • 变量的逃逸行为

2.4赋值

划重点

  • 自增和自减是语句,而不是表达式,因此x = i++之类的表达式是错误的

2.4.1元组赋值

划重点

  • 同时更新变量左边的值
x, y = y, x
a[i], a[j] = a[j], a[i]
  • 表达式会产生多个值,或者返回布尔值,通常被称为ok,map查找(§4.3)类型断言(§7.10)、或通道接收(§8.4.2) 出现在赋值语句的右边,它们都可能会产生两个结果,有一个额外的布尔结果表示操作是否成功:
f, err = os.Open("foo.txt") // function call returns two values
v, ok = m[key] // map lookup
v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive
  • :map查找(§4.3)、类型断言(§7.10)或通道接收(§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于值产生一个结果的情形,map查找失败时会返回零值,类型断言失败时会发送运行时panic异常,通道接收失败时会返回零值(阻塞不算是失败),例如:
v = m[key] // map查找,失败时返回零值
v = x.(T) // type断言,失败时panic异常
v = <-ch // 管道接收,失败时返回零值(阻塞不算是失败)
_, ok = m[key] // map返回2个值
_, ok = mm[""], false // map返回1个值
_ = mm[""] // map返回1个值

2.4.2可赋值性

划重点

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

2.5类型

划重点

  • type 类型名字 底层类型类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部包也可以使用
  • 每一个类型T,都有转换操作T(x),用于将x转为T类,如果T
    指针类型,可能会需要用小括弧包装T,比如 (*int)(0),当两个类型的底层基础类型相同时,才允许这种转型操作

2.6包和文件

划重点

  • 包所在目录路径的后缀是包的导入路径;例如包gopl.io/ch1/helloworld对应的目录路径是$GOPATH/src/gopl.io/ch1/helloworld
  • 每个包都对应一个独立的名字空间;image.Decodeutf16.Decode不同的形式
  • 如果一个名字是大写字母开头的,那么该名字是导出的
  • 包级别的名字,在一个文件声明的类型和常量,在同一个包的其他源文件也是可以直接访问的
  • 包声明前的注释是包注释(§10.7.4)。一个包通常只有一个源文件有包注释,如果有多个包注释,会将它们链接为一个包注释,如果很大,通常会放到独立的doc.go文件中

2.6.1导入包

划重点

  • 每个包还有一个包名,包名一般是短小的名字(并不要求包名是唯一的)
  • golang.org/x/tools/cmd/goimports导入工具,可以根据需要自动添加或删除导入的包

2.6.2包的初始化

划重点

  • 包的初始化解决包级变量的依赖顺序,并根据出现的顺序依次初始化
var a = b + c // a 第三个初始化, 为 3
var b = f() // b 第二个初始化, 为 2, 通过调用 f (依赖c)
var c = 1 // c 第一个初始化, 为 1
func f() int { return c + 1 }
  • 某些表格数据初始化不是简单的赋值过程,可以用一个特殊的init初始化函数来简化初始化工作,每个文件都可以包含多个init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用,每个包只会被初始化一次
func init() { /* ... */ }
  • range循环只使用了索引,则可以省略没有用到的值部分,下面等价:
for i := range pc {}
for i, _ := range pc { }

2.7作用域

划重点

  • 一个程序可能包含多个同名的声明,只要作用域不一样即可。例:你可以声明一个局部变量,和包级的变量同名;可以将一个函数参数的名字声明为new,虽然内置的new是全局作用域的。局部声明覆盖全局声明;
  • if,switch条件部分为一个隐式词法域,然后每个是每个分支的词法域
  • 在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。

常用库及方法

  • os os.Getwd
  • log log.Fatalf
  • unicode unicode.ToUpper
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!