第2章,部分字词修订。

This commit is contained in:
zhliner 2017-08-24 22:25:47 +08:00
parent 622f7fcc2c
commit 9f90d30fa7
9 changed files with 22 additions and 22 deletions

View File

@ -29,8 +29,8 @@ continue for import return var
这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。 这些内部预先定义的名字并不是关键字,你可以在定义中重新使用它们。在一些特殊的场景中重新定义它们也是有意义的,但是也要注意避免过度而引起语义混乱。
如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的译注必须是在函数外部定义的包级名字包级函数名本身也是包级名字那么它将是导出的也就是说可以被外部的包访问例如fmt包的Printf函数就是导出的可以在fmt包外部访问。包本身的名字一般总是用小写字母。 如果一个名字是在函数内部定义那么它就只在函数内部有效。如果是在函数外部定义那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的译注必须是在函数外部定义的包级名字包级函数名本身也是包级名字那么它将是导出的也就是说可以被外部的包访问例如fmt包的Printf函数就是导出的可以在fmt包外部访问。包本身的名字一般总是用小写字母。
名字的长度没有逻辑限制但是Go语言的风格是尽量使用短小的名字对于局部变量尤其是这样你会经常看到i之类的短名字而不是冗长的theLoopIndex命名。通常来说如果一个名字的作用域比较大生命周期也比较长那么用长的名字将会更有意义。 名字的长度没有逻辑限制但是Go语言的风格是尽量使用短小的名字对于局部变量尤其是这样你会经常看到i之类的短名字而不是冗长的theLoopIndex命名。通常来说如果一个名字的作用域比较大生命周期也比较长那么用长的名字将会更有意义。
在习惯上Go语言程序员推荐使用 **驼峰式** 命名,当名字有几个单词组成的时优先使用大小写分隔而不是优先用下划线分隔。因此在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法它们可能被称为htmlEscape、HTMLEscape或escapeHTML但不会是escapeHtml。 在习惯上Go语言程序员推荐使用 **驼峰式** 命名,当名字由几个单词组成时优先使用大小写分隔而不是优先用下划线分隔。因此在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法它们可能被称为htmlEscape、HTMLEscape或escapeHTML但不会是escapeHtml。

View File

@ -2,7 +2,7 @@
声明语句定义了程序的各种实体对象以及部分或全部的属性。Go语言主要有四种类型的声明语句var、const、type和func分别对应变量、常量、类型和函数实体对象的声明。这一章我们重点讨论变量和类型的声明第三章将讨论常量的声明第五章将讨论函数的声明。 声明语句定义了程序的各种实体对象以及部分或全部的属性。Go语言主要有四种类型的声明语句var、const、type和func分别对应变量、常量、类型和函数实体对象的声明。这一章我们重点讨论变量和类型的声明第三章将讨论常量的声明第五章将讨论函数的声明。
一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件。每个源文件以包的声明语句开始说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包然后是包一级的类型、变量、常量、函数的声明语句包一级的各种类型的声明语句的顺序无关紧要译注函数内部的名字则必须先声明之后才能使用。例如下面的例子中声明了一个常量、一个函数和两个变量 一个Go语言编写的程序对应一个或多个以.go为文件后缀名的源文件。每个源文件以包的声明语句开始说明该源文件是属于哪个包。包声明语句之后是import语句导入依赖的其它包然后是包一级的类型、变量、常量、函数的声明语句包一级的各种类型的声明语句的顺序无关紧要译注函数内部的名字则必须先声明之后才能使用。例如下面的例子中声明了一个常量、一个函数和两个变量
<u><i>gopl.io/ch2/boiling</i></u> <u><i>gopl.io/ch2/boiling</i></u>
```Go ```Go
@ -26,7 +26,7 @@ func main() {
一个函数的声明由一个函数名字、参数列表由函数的调用者提供参数变量的具体值、一个可选的返回值列表和包含函数定义的函数体组成。如果函数没有返回值那么返回值列表是省略的。执行函数从函数的第一个语句开始依次顺序执行直到遇到return返回语句如果没有返回语句则是执行到函数末尾然后返回到函数调用者。 一个函数的声明由一个函数名字、参数列表由函数的调用者提供参数变量的具体值、一个可选的返回值列表和包含函数定义的函数体组成。如果函数没有返回值那么返回值列表是省略的。执行函数从函数的第一个语句开始依次顺序执行直到遇到return返回语句如果没有返回语句则是执行到函数末尾然后返回到函数调用者。
我们已经看到过很多函数声明和函数调用的例子了在第五章将深入讨论函数的相关细节这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑这样它只需要被定义一次就可以在多个地方多次被使用。在这个例子中main函数就调用了两次fToC函数分别使用在局部定义的两个常量作为调用函数的参数。 我们已经看到过很多函数声明和函数调用的例子了在第五章将深入讨论函数的相关细节这里只简单解释下。下面的fToC函数封装了温度转换的处理逻辑这样它只需要被定义一次就可以在多个地方多次被使用。在这个例子中main函数就调用了两次fToC函数分别使用在局部定义的两个常量作为调用函数的参数。
<u><i>gopl.io/ch2/ftoc</i></u> <u><i>gopl.io/ch2/ftoc</i></u>
```Go ```Go

View File

@ -8,7 +8,7 @@ freq := rand.Float64() * 3.0
t := 0.0 t := 0.0
``` ```
因为简洁和灵活的特点简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型地方或者因为变量稍后会被重新赋值而初始值无关紧要的地方。 因为简洁和灵活的特点简短变量声明被广泛用于大部分的局部变量的声明和初始化。var形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。
```Go ```Go
i := 100 // an int i := 100 // an int
@ -26,7 +26,7 @@ i, j := 0, 1
但是这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用比如for语句的循环的初始化语句部分。 但是这种同时声明多个变量的方式应该限制只在可以提高代码可读性的地方使用比如for语句的循环的初始化语句部分。
请记住“:=”是一个变量声明语句,而“=”是一个变量赋值操作。也不要混淆多个变量的声明和元组的多重赋值§2.4.1),后者是将右边各个表达式值赋值给左边对应位置的各个变量: 请记住“:=”是一个变量声明语句,而“=”是一个变量赋值操作。也不要混淆多个变量的声明和元组的多重赋值§2.4.1),后者是将右边各个表达式值赋值给左边对应位置的各个变量:
```Go ```Go
i, j = j, i // 交换 i 和 j 的值 i, j = j, i // 交换 i 和 j 的值
@ -61,6 +61,6 @@ f, err := os.Open(infile)
f, err := os.Create(outfile) // compile error: no new variables f, err := os.Create(outfile) // compile error: no new variables
``` ```
解决的方法是第二个简短变量声明语句改用普通的多重赋值语 解决的方法是第二个简短变量声明语句改用普通的多重赋值语
简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。我们在本章后面将会看到类似的例子。 简短变量声明语句只有对已经在同级词法域声明过的变量才和赋值操作语句等价,如果变量是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量。我们在本章后面将会看到类似的例子。

View File

@ -55,7 +55,7 @@ incr(&v) // side effect: v is now 2
fmt.Println(incr(&v)) // "3" (and v is 3) fmt.Println(incr(&v)) // "3" (and v is 3)
``` ```
每次我们对一个变量取地址,或者复制指针,我们都是为原变量创建了新的别名。例如,`*p`就是变量v的别名。指针特别有价值的地方在于我们可以不用名字而访问一个变量但是这是一把双刃剑要找到一个变量的所有访问者并不容易我们必须知道变量全部的别名译注这是Go语言的垃圾回收器所做的工作。不仅仅是指针会创建别名很多其他引用类型也会创建别名例如slice、map和chan甚至结构体、数组和接口都会创建所引用变量的别名。 每次我们对一个变量取地址,或者复制指针,我们都是为原变量创建了新的别名。例如,`*p`就是变量v的别名。指针特别有价值的地方在于我们可以不用名字而访问一个变量但是这是一把双刃剑要找到一个变量的所有访问者并不容易我们必须知道变量全部的别名译注这是Go语言的垃圾回收器所做的工作。不仅仅是指针会创建别名很多其他引用类型也会创建别名例如slice、map和chan甚至结构体、数组和接口都会创建所引用变量的别名。
指针是实现标准库中flag包的关键技术它使用命令行参数来设置对应变量的值而这些对应命令行标志参数的变量可能会零散分布在整个程序中。为了说明这一点在早些的echo版本中就包含了两个可选的命令行参数`-n`用于忽略行尾的换行符,`-s sep`用于指定分隔字符默认是空格。下面这是第四个版本对应包路径为gopl.io/ch2/echo4。 指针是实现标准库中flag包的关键技术它使用命令行参数来设置对应变量的值而这些对应命令行标志参数的变量可能会零散分布在整个程序中。为了说明这一点在早些的echo版本中就包含了两个可选的命令行参数`-n`用于忽略行尾的换行符,`-s sep`用于指定分隔字符默认是空格。下面这是第四个版本对应包路径为gopl.io/ch2/echo4。
@ -82,9 +82,9 @@ func main() {
} }
``` ```
调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。它有三个属性第一个是命令行标志参数的名字“n”然后是该标志参数的默认值这里是false最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数或者输入`-h`或`-help`参数那么将打印所有标志参数的名字、默认值和描述信息。类似的调用flag.String函数将创建一个对应字符串类型的标志参数变量,同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的`sep`和`n`变量分别是指向对应命令行标志参数变量的指针,因此必须用`*sep`和`*n`形式的指针语法间接引用它们。 调用flag.Bool函数会创建一个新的对应布尔型标志参数的变量。它有三个属性第一个是命令行标志参数的名字“n”然后是该标志参数的默认值这里是false最后是该标志参数对应的描述信息。如果用户在命令行输入了一个无效的标志参数或者输入`-h`或`-help`参数那么将打印所有标志参数的名字、默认值和描述信息。类似的调用flag.String函数将创建一个对应字符串类型的标志参数变量同样包含命令行标志参数对应的参数名、默认值、和描述信息。程序中的`sep`和`n`变量分别是指向对应命令行标志参数变量的指针,因此必须用`*sep`和`*n`形式的指针语法间接引用它们。
当程序运行时必须在使用标志参数对应的变量之前先调用flag.Parse函数用于更新每个标志参数对应变量的值之前是默认值。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问,返回值对应对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误默认将打印相关的提示信息然后调用os.Exit(2)终止程序。 当程序运行时必须在使用标志参数对应的变量之前先调用flag.Parse函数用于更新每个标志参数对应变量的值之前是默认值。对于非标志参数的普通命令行参数可以通过调用flag.Args()函数来访问返回值对应一个字符串类型的slice。如果在flag.Parse函数解析命令行参数时遇到错误默认将打印相关的提示信息然后调用os.Exit(2)终止程序。
让我们运行一些echo测试用例 让我们运行一些echo测试用例

View File

@ -1,6 +1,6 @@
### 2.3.3. new函数 ### 2.3.3. new函数
另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个T类型的匿名变量初始化为T类型的零值然后返回变量地址返回的指针类型为`*T`。 另一个创建变量的方法是调用内建的new函数。表达式new(T)将创建一个T类型的匿名变量初始化为T类型的零值然后返回变量地址返回的指针类型为`*T`。
```Go ```Go
p := new(int) // p, *int 类型, 指向匿名的 int 变量 p := new(int) // p, *int 类型, 指向匿名的 int 变量

View File

@ -1,6 +1,6 @@
### 2.3.4. 变量的生命周期 ### 2.3.4. 变量的生命周期
变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,局部变量的声明周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。 变量的生命周期指的是在程序运行期间变量有效存在的时间。对于在包一级声明的变量来说,它们的生命周期和整个程序的运行周期是一致的。而相比之下,局部变量的生命周期则是动态的:每次从创建一个新变量的声明语句开始,直到该变量不再被引用为止,然后变量的存储空间可能被回收。函数的参数变量和返回值变量都是局部变量。它们在函数每次被调用的时候创建。
例如下面是从1.4节的Lissajous程序摘录的代码片段 例如下面是从1.4节的Lissajous程序摘录的代码片段
@ -13,7 +13,7 @@ for t := 0.0; t < cycles*2*math.Pi; t += res {
} }
``` ```
译注:函数的右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样: 译注:函数的右小括弧也可以另起一行缩进,同时为了防止编译器在行尾自动插入分号而导致的编译错误,可以在末尾的参数变量后面显式插入逗号。像下面这样:
```Go ```Go
for t := 0.0; t < cycles*2*math.Pi; t += res { for t := 0.0; t < cycles*2*math.Pi; t += res {

View File

@ -53,7 +53,7 @@ v, ok = x.(T) // type assertion
v, ok = <-ch // channel receive v, ok = <-ch // channel receive
``` ```
译注map查找§4.3、类型断言§7.10或通道接收§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于值产生一个结果的情形map查找失败时会返回零值类型断言失败时会发送运行时panic异常通道接收失败时会返回零值阻塞不算是失败。例如下面的例子 译注map查找§4.3、类型断言§7.10或通道接收§8.4.2)出现在赋值语句的右边时,并不一定是产生两个结果,也可能只产生一个结果。对于只产生一个结果的情形map查找失败时会返回零值类型断言失败时会发生运行时panic异常通道接收失败时会返回零值阻塞不算是失败。例如下面的例子
```Go ```Go
v = m[key] // map查找失败时返回零值 v = m[key] // map查找失败时返回零值

View File

@ -10,7 +10,7 @@
type 类型名字 底层类型 type 类型名字 底层类型
``` ```
类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部也可以使用。 类型声明语句一般出现在包一级,因此如果新创建的类型名字的首字符大写,则在外部也可以使用。
译注对于中文汉字Unicode标志都作为小写字母处理因此中文的命名默认不能导出不过国内的用户针对该问题提出了不同的看法根据RobPike的回复在Go2中有可能会将中日韩等字符当作大写字母处理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的回复: 译注对于中文汉字Unicode标志都作为小写字母处理因此中文的命名默认不能导出不过国内的用户针对该问题提出了不同的看法根据RobPike的回复在Go2中有可能会将中日韩等字符当作大写字母处理。下面是RobPik在 [Issue763](https://github.com/golang/go/issues/5763) 的回复:

View File

@ -4,9 +4,9 @@
不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。 不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
句法块是由花括弧所包含的一系列语句就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围。我们可以把块block的概念推广到包括其他声明的群组这些声明在代码中并未显式地使用花括号包裹起来我们称之为词法块。对全局的源代码来说存在一个整体的词法块称为全局词法块对于每个包每个for、if和switch语句也都对应词法块每个switch或select的分支也有独立的法块;当然也包括显式书写的词法块(花括弧包含的语句)。 句法块是由花括弧所包含的一系列语句就像函数体或循环体花括弧包裹的内容一样。句法块内部声明的名字是无法被外部块访问的。这个块决定了内部声明的名字的作用域范围。我们可以把块block的概念推广到包括其他声明的群组这些声明在代码中并未显式地使用花括号包裹起来我们称之为词法块。对全局的源代码来说存在一个整体的词法块称为全局词法块对于每个包每个for、if和switch语句也都对应词法块每个switch或select的分支也有独立的法块;当然也包括显式书写的词法块(花括弧包含的语句)。
声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量比如int、len和true等是在全局作用域的因此可以在整个程序中直接使用。任何在函数外部也就是包级语法域声明的名字可以在同一个包的任何源文件中访问的。对于导入的包例如tempconv导入的fmt包则是对应源文件级的作用域因此只能在当前的文件中访问导入的fmt包当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句比如tempconv.CToF函数中的变量c则是局部作用域的它只能在函数内部甚至只能是局部的某些部分访问。 声明语句对应的词法域决定了作用域范围的大小。对于内置的类型、函数和常量比如int、len和true等是在全局作用域的因此可以在整个程序中直接使用。任何在函数外部也就是包级语法域声明的名字可以在同一个包的任何源文件中访问的。对于导入的包例如tempconv导入的fmt包则是对应源文件级的作用域因此只能在当前的文件中访问导入的fmt包当前包的其它源文件无法访问在当前源文件导入的包。还有许多声明语句比如tempconv.CToF函数中的变量c则是局部作用域的它只能在函数内部甚至只能是局部的某些部分访问。
控制流标号就是break、continue或goto语句后面跟着的那种标号则是函数级的作用域。 控制流标号就是break、continue或goto语句后面跟着的那种标号则是函数级的作用域。
@ -44,7 +44,7 @@ func main() {
在`x[i]`和`x + 'A' - 'a'`声明语句的初始化的表达式中都引用了外部作用域声明的x变量稍后我们会解释这个。注意后面的表达式与unicode.ToUpper并不等价。 在`x[i]`和`x + 'A' - 'a'`声明语句的初始化的表达式中都引用了外部作用域声明的x变量稍后我们会解释这个。注意后面的表达式与unicode.ToUpper并不等价。
正如上面例子所示并不是所有的词法域都显式地对应到由花括弧包含的语句还有一些隐含的规则。上面的for语句创建了两个词法域花括弧包含的是显式的部分是for的循环体部分词法域另外一个隐式的部分则是循环的初始化部分比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分`i++`),当然也包含循环体词法域。 正如上面例子所示并不是所有的词法域都显式地对应到由花括弧包含的语句还有一些隐含的规则。上面的for语句创建了两个词法域花括弧包含的是显式的部分是for的循环体部分词法域另外一个隐式的部分则是循环的初始化部分比如用于迭代变量i的初始化。隐式的词法域部分的作用域还包含条件测试部分和循环后的迭代部分`i++`),当然也包含循环体词法域。
下面的例子同样有三个不同的x变量每个声明在不同的词法域一个在函数体词法域一个在for隐式的初始化词法域一个在for循环体词法域只有两个块是显式创建的 下面的例子同样有三个不同的x变量每个声明在不同的词法域一个在函数体词法域一个在for隐式的初始化词法域一个在for循环体词法域只有两个块是显式创建的
@ -71,7 +71,7 @@ if x := f(); x == 0 {
fmt.Println(x, y) // compile error: x and y are not visible here fmt.Println(x, y) // compile error: x and y are not visible here
``` ```
第二个if语句嵌套在第一个内部因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则条件部分为一个隐式词法域然后每个是每个分支的词法域。 第二个if语句嵌套在第一个内部因此第一个if语句条件初始化词法域声明的变量在第二个if中也可以访问。switch语句的每个分支也有类似的词法域规则条件部分为一个隐式词法域然后是每个分支的词法域。
在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。 在包级别,声明的顺序并不会影响作用域范围,因此一个先声明的可以引用它自身或者是引用后面的一个声明,这可以让我们定义一些相互嵌套或递归的类型或函数。但是如果一个变量或常量递归引用了自身,则会产生编译错误。
@ -85,7 +85,7 @@ f.ReadByte() // compile error: undefined f
f.Close() // compile error: undefined f f.Close() // compile error: undefined f
``` ```
变量f的作用域只在if语句内因此后面的语句将无法引入它这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示具体错误信息依赖编译器的实现。 变量f的作用域只在if语句内因此后面的语句将无法引入它这将导致编译错误。你可能会收到一个局部变量f没有声明的错误提示具体错误信息依赖编译器的实现。
通常需要在if之前声明变量这样可以确保后面的语句依然可以访问变量 通常需要在if之前声明变量这样可以确保后面的语句依然可以访问变量
@ -112,7 +112,7 @@ if f, err := os.Open(fname); err != nil {
但这不是Go语言推荐的做法Go语言的习惯是在if中处理错误然后直接返回这样可以确保正常执行的语句不需要代码缩进。 但这不是Go语言推荐的做法Go语言的习惯是在if中处理错误然后直接返回这样可以确保正常执行的语句不需要代码缩进。
要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。这可以本来通过直接调用os.Getwd完成但是将这个从主逻辑中分离出来可能会更好特别是在需要处理错误的时候。函数log.Fatalf用于打印日志信息然后调用os.Exit(1)终止程序。 要特别注意短变量声明语句的作用域范围,考虑下面的程序,它的目的是获取当前的工作目录然后保存到一个包级的变量中。这本来可以通过直接调用os.Getwd完成但是将这个从主逻辑中分离出来可能会更好特别是在需要处理错误的时候。函数log.Fatalf用于打印日志信息然后调用os.Exit(1)终止程序。
```Go ```Go
var cwd string var cwd string
@ -127,7 +127,7 @@ func init() {
虽然cwd在外部已经声明过但是`:=`语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明因此上面的代码并不会正确更新包级声明的cwd变量。 虽然cwd在外部已经声明过但是`:=`语句还是将cwd和err重新声明为新的局部变量。因为内部声明的cwd将屏蔽外部的声明因此上面的代码并不会正确更新包级声明的cwd变量。
由于当前的编译器会检测到局部声明的cwd并没有使用然后报告这可能是一个错误但是这种检测并不可靠。因为一些小的代码变更例如增加一个局部cwd的打印语句就可能导致这种检测失效。 由于当前的编译器会检测到局部声明的cwd并没有使用然后报告这可能是一个错误但是这种检测并不可靠。因为一些小的代码变更例如增加一个局部cwd的打印语句就可能导致这种检测失效。
```Go ```Go
var cwd string var cwd string