《Go语言圣经》学习笔记 第二章 程序结构

Go语言圣经学习笔记 第二章 程序结构


目录

  1. 命名
  2. 声明
  3. 变量
  4. 赋值
  5. 类型
  6. 包和文件
  7. 作用域

注:学习《Go语言圣经》笔记,PDF点击下载,建议看书。
Go语言小白学习笔记,几乎是书上的内容照搬,大佬看了勿喷,以后熟悉了会总结成自己的读书笔记。


1. 命名

  1. Go语言中的函数名、 变量名、 常量名、 类型名、 语句标号和包名等所有的命名, 都遵循一个简单的命名规则: 一个名字必须以一个字母( Unicode字母) 或下划线开头, 后面可以跟任意数量的字母、 数字或下划线。 大写字母和小写字母是不同的: heapSort和Heapsort是两个不同的名字。
  2. Go语言中类似if和switch的关键字有25个; 关键字不能用于自定义名字, 只能在特定语法结构中使用。
    《Go语言圣经》学习笔记 第二章 程序结构
  3. 这些内部预先定义的名字并不是关键字, 你可以再定义中重新使用它们。 在一些特殊的场景中重新定义它们也是有意义的, 但是也要注意避免过度而引起语义混乱。
  4. 如果一个名字是在函数内部定义, 那么它的就只在函数内部有效。 如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。 名字的开头字母的大小写决定了名字在包外的可见性。 如果一个名字是大写字母开头的( 译注: 必须是在函数外部定义的包级名字; 包级函数名本身也是包级名字) , 那么它将是导出的, 也就是说可以被外部的包访问, 例如fmt包的Printf函数就是导出的, 可以在fmt包外部访问。 包本身的名字一般总是用小写字母。
  5. 名字的长度没有逻辑限制, 但是Go语言的风格是尽量使用短小的名字, 对于局部变量尤其是这样; 你会经常看到i之类的短名字, 而不是冗长的theLoopIndex命名。 通常来说, 如果一个名字的作用域比较大, 生命周期也比较长, 那么用长的名字将会更有意义。
  6. 在习惯上, Go语言程序员推荐使用 驼峰式 命名, 当名字有几个单词组成的时优先使用大小写分隔, 而不是优先用下划线分隔。 因此, 在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名, 但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法, 它们可能被称为htmlEscape、 HTMLEscape或escapeHTML, 但不会是escapeHtml。

2. 声明

  1. 声明语句定义了程序的各种实体对象以及部分或全部的属性。 Go语言主要有四种类型的声明语句: var、 const、 type和func, 分别对应变量、 常量、 类型和函数实体对象的声明。 这一章我们重点讨论变量和类型的声明, 第三章将讨论常量的声明, 第五章将讨论函数的声明。

  2. 一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件中。 每个源文件以包的声明语句开始, 说明该源文件是属于哪个包。 包声明语句之后是import语句导入依赖的其它包,然后是包一级的类型、 变量、 常量、 函数的声明语句, 包一级的各种类型的声明语句的顺序无关紧要( 译注: 函数内部的名字则必须先声明之后才能使用) 。 例如, 下面的例子中声明了一个常量、 一个函数和两个变量:
    gopl.io/ch2/boiling

  3. 其中常量boilingF是在包一级范围声明语句声明的, 然后f和c两个变量是在main函数内部声明的声明语句声明的。 在包一级声明语句声明的名字可在整个包对应的每个源文件中访问, 而不是仅仅在其声明语句所在的源文件中访问。 相比之下, 局部声明的名字就只能在函数内部很小的范围被访问。

  4. 一个函数的声明由一个函数名字、 参数列表( 由函数的调用者提供参数变量的具体值) 、 一个可选的返回值列表和包含函数定义的函数体组成。 如果函数没有返回值, 那么返回值列表是省略的。 执行函数从函数的第一个语句开始, 依次顺序执行直到遇到renturn返回语句, 如果没有返回语句则是执行到函数末尾, 然后返回到函数调用者。

  5. 我们已经看到过很多函数声明和函数调用的例子了, 在第五章将深入讨论函数的相关细节,这里只简单解释下。 下面的fToC函数封装了温度转换的处理逻辑, 这样它只需要被定义一次, 就可以在多个地方多次被使用。 在这个例子中, main函数就调用了两次fToC函数, 分别是使用在局部定义的两个常量作为调用函数的参数。
    gopl.io/ch2/ftoc


3. 变量

  1. var声明语句可以创建一个特定类型的变量, 然后给变量附加一个名字, 并且设置变量的初始值。 变量声明的一般语法如下:
    《Go语言圣经》学习笔记 第二章 程序结构
  2. 这段代码将打印一个空字符串, 而不是导致错误或产生不可预知的行为。 Go语言程序员应该让一些聚合类型的零值也具有意义, 这样可以保证不管任何类型的变量总是有一个合理有效的零值状态。
  3. 也可以在一个声明语句中同时声明一组变量, 或用一组初始化表达式声明并初始化一组变量。 如果省略每个变量的类型, 将可以声明多个类型不同的变量( 类型由初始化表达式推导) :
    《Go语言圣经》学习笔记 第二章 程序结构

1. 简短变量声明

  1. 在函数内部, 有一种称为简短变量声明语句的形式可用于声明和初始化局部变量。 它以“名字:= 表达式”形式声明变量, 变量的类型根据表达式来自动推导。 下面是lissajous函数中的三个简短变量声明语句
    《Go语言圣经》学习笔记 第二章 程序结构
  2. 和var形式声明语句一样, 简短变量声明语句也可以用来声明和初始化一组变量:
    《Go语言圣经》学习笔记 第二章 程序结构
  3. 和普通var形式的变量声明语句一样, 简短变量声明语句也可以用函数的返回值来声明和初始化变量, 像下面的os.Open函数调用将返回两个值:
    《Go语言圣经》学习笔记 第二章 程序结构
  4. 简短变量声明语句中必须至少要声明一个新的变量, 下面的代码将不能编译通过:
    《Go语言圣经》学习笔记 第二章 程序结构
  5. 对于聚合类型每个成员——比如结构体的每个字段、 或者是数组的每个元素——也都是对应一个变量, 因此可以被取地址。

  6. 变量有时候被称为可寻址的值。 即使变量由表达式临时生成, 那么表达式也必须能接受 & 取地址操作。

  7. 任何类型的指针的零值都是nil。 如果 p != nil 测试为真, 那么p是指向某个有效变量。 指针之间也是可以进行相等测试的, 只有当它们指向同一个变量或全部是nil时才相等。

    《Go语言圣经》学习笔记 第二章 程序结构
  8. 每次调用f函数都将返回不同的结果(每次调用f函数返回的指针地址都是不一样的):

    《Go语言圣经》学习笔记 第二章 程序结构
  9. 因为指针包含了一个变量的地址, 因此如果将指针作为参数调用函数, 那将可以在函数中通过该指针来更新变量的值。 例如下面这个例子就是通过指针来更新变量的值, 然后返回更新后的值, 可用在一个表达式中( 译注: 这是对C语言中 ++v 操作的模拟, 这里只是为了说明指针的用法, incr函数模拟的做法并不推荐) :

    《Go语言圣经》学习笔记 第二章 程序结构
  10. 调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。 它有三个属性: 第一个是的命令行标志参数的名字“n”, 然后是该标志参数的默认值( 这里是false) , 最后是该标志参数对应的描述信息。 如果用户在命令行输入了一个无效的标志参数, 或者输入 -h 或 -help 参数, 那么将打印所有标志参数的名字、 默认值和描述信息。 类似的, 调用flag.String函数将于创建一个对应字符串类型的标志参数变量, 同样包含命令行标志参数对应的参数名、 默认值、 和描述信息。 程序中的 sep 和 n 变量分别是指向对应命令行标志参数变量的指针, 因此必须用 *sep 和 *n 形式的指针语法间接引用它们。

  11. 当程序运行时, 必须在使用标志参数对应的变量之前调用先flag.Parse函数, 用于更新每个标志参数对应变量的值( 之前是默认值) 。 对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问, 返回值对应对应一个字符串类型的slice。 如果在flag.Parse函数解析命令行参数时遇到错误, 默认将打印相关的提示信息, 然后调用os.Exit(2)终止程序。

  12. 让我们运行一些echo测试用例:

    《Go语言圣经》学习笔记 第二章 程序结构
  13. 用new创建变量和普通变量声明语句方式创建变量没有什么区别, 除了不需要声明一个临时变量的名字外, 我们还可以在表达式中使用new(T)。 换言之, new函数类似是一种语法糖, 而不是一个新的基础概念。
  14. 下面的两个newInt函数有着相同的行为:
    《Go语言圣经》学习笔记 第二章 程序结构
  15. 当然也可能有特殊情况: 如果两个类型都是空的, 也就是说类型的大小是0, 例如 struct{} 和 [0]int , 有可能有相同的地址( 依赖具体的语言实现) ( 译注: 请谨慎使用大小为0的类型, 因为如果类型的大小位0好话, 可能导致Go语言的自动垃圾回收器有不同的行为, 具体请查看 runtime.SetFinalizer 函数相关文档) 。
  16. new函数使用常见相对比较少, 因为对应结构体来说, 可以直接用字面量语法创建新变量的方法会更灵活。
  17. 由于new只是一个预定义的函数, 它并不是一个关键字, 因此我们可以将new名字重新定义为别的类型。 例如下面的例子:
    《Go语言圣经》学习笔记 第二章 程序结构
  18. 译注: 函数的有右小括弧也可以另起一行缩进, 同时为了防止编译器在行尾自动插入分号而导致的编译错误, 可以在末尾的参数变量后面显式插入逗号。 像下面这样:
    《Go语言圣经》学习笔记 第二章 程序结构
  19. f函数里的x变量必须在堆上分配, 因为它在函数退出后依然可以通过包一级的global变量找到, 虽然它是在函数内部定义的; 用Go语言的术语说, 这个x局部变量从函数f中逃逸了。 相反, 当g函数返回时, 变量 *y 将是不可达的, 也就是说可以马上被回收的。 因此, *y 并没有从函数g中逃逸, 编译器可以选择在栈上分配 *y 的存储空间( 译注: 也可以选择在堆上分配, 然后由Go语言的GC回收这个变量的内存空间) , 虽然这里用的是new方式。 其实在任何时候, 你并不需为了编写正确的代码而要考虑变量的逃逸行为, 要记住的是, 逃逸的变量需要额外分配内存, 同时对性能的优化可能会产生细微的影响。
  20. Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助, 但也并不是说你完全不用考虑内存了。 你虽然不需要显式地分配和释放内存, 但是要编写高效的程序你依然需要了解变量的生命周期。 例如, 如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时, 会阻止对短生命周期对象的垃圾回收( 从而可能影响程序的性能) 。

4. 赋值

  1. 使用赋值语句可以更新一个变量的值, 最简单的赋值语句是将要被赋值的变量放在=的左边,新值的表达式放在=的右边。
    《Go语言圣经》学习笔记 第二章 程序结构
  2. 这样可以省去对变量表达式的重复计算。
  3. 数值变量也可以支持 ++ 递增和 – 递减语句( 译注: 自增和自减是语句, 而不是表达式, 因此 x = i++ 之类的表达式是错误的) :
    《Go语言圣经》学习笔记 第二章 程序结构
  4. 或者是计算两个整数值的的最大公约数( GCD) ( 译注: GCD不是那个敏感字, 而是greatest common divisor的缩写, 欧几里德的GCD是最早的非平凡算法) :

  5. 或者是计算斐波纳契数列( Fibonacci) 的第N个数:

  6. 元组赋值也可以使一系列琐碎赋值更加紧凑( 译注: 特别是在for循环的初始化部分) ,

    《Go语言圣经》学习笔记 第二章 程序结构
  7. 通常, 这类函数会用额外的返回值来表达某种错误类型, 例如os.Open是用额外的返回值返回一个error类型的错误, 还有一些是用来返回布尔值, 通常被称为ok。 在稍后我们将看到的三个操作都是类似的用法。 如果map查找 、 类型断言 或通道接收出现在赋值语句的右边, 它们都可能会产生两个结果, 有一个额外的布尔结果表示操作是否成功:

    《Go语言圣经》学习笔记 第二章 程序结构
  8. 和变量声明一样, 我们可以用下划线空白标识符 _ 来丢弃不需要的值。

    《Go语言圣经》学习笔记 第二章 程序结构
  9. 隐式地对slice的每个元素进行赋值操作, 类似这样写的行为:
    《Go语言圣经》学习笔记 第二章 程序结构
  10. 类型声明语句一般出现在包一级, 因此如果新创建的类型名字的首字符大写, 则在外部包也可以使用。
  11. 译注: 对于中文汉字, Unicode标志都作为小写字母处理, 因此中文的命名默认不能导出; 不过国内的用户针对该问题提出了不同的看法, 根据RobPike的回复, 在Go2中有可能会将中日韩等字符当作大写字母处理。 下面是RobPik在 Issue763 的回复:
    《Go语言圣经》学习笔记 第二章 程序结构
  12. 比较运算符 == 和 《Go语言圣经》学习笔记 第二章 程序结构
  13. 许多类型都会定义一个String方法, 因为当使用fmt包的打印方法时, 将会优先使用该类型对应的String方法返回的结果打印, 我们将在7.1节讲述。
    《Go语言圣经》学习笔记 第二章 程序结构

6. 包和文件

  1. Go语言中的包和其他语言的库或模块的概念类似, 目的都是为了支持模块化、 封装、 单独编译和代码重用。 一个包的源代码保存在一个或多个以.go为文件后缀名的源文件中, 通常一个包所在目录路径的后缀是包的导入路径; 例如包gopl.io/ch1/helloworld对应的目录路径是$GOPATH/src/gopl.io/ch1/helloworld。

  2. 每个包都对应一个独立的名字空间。 例如, 在image包中的Decode函数和在unicode/utf16包中的 Decode函数是不同的。 要在外部引用该函数, 必须显式使用image.Decode或utf16.Decode形式访问。

  3. 包还可以让我们通过控制哪些名字是外部可见的来隐藏内部实现信息。 在Go语言中, 一个简单的规则是: 如果一个名字是大写字母开头的, 那么该名字是导出的( 译注: 因为汉字不区分大小写, 因此汉字开头的名字是没有导出的) 。

  4. 为了演示包基本的用法, 先假设我们的温度转换软件已经很流行, 我们希望到Go语言社区也能使用这个包。 我们该如何做呢/p>

  5. 让我们创建一个名为gopl.io/ch2/tempconv的包, 这是前面例子的一个改进版本。 ( 我们约定我们的例子都是以章节顺序来编号的, 这样的路径更容易阅读) 包代码存储在两个源文件中, 用来演示如何在一个源文件声明然后在其他的源文件访问; 虽然在现实中, 这样小的包一般只需要一个文件。

  6. 我们把变量的声明、 对应的常量, 还有方法都放到tempconv.go源文件中:

  7. gopl.io/ch2/tempconv

    // Package tempconv performs Celsius and Fahrenheit conversions.package tempconvimport "fmt"type Celsius float64type Fahrenheit 来源:Lumos`
                                                            

    声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年3月20日
下一篇 2020年3月20日

相关推荐