标签 Dr.Dobb’s 下的文章

给新手程序员的建议

本文翻译自Dr. Dobb’s杂志主编Andrew Binstock的"Advice to a new programmer"一文

总是有太多的建议摆在新手程序员面前,以致他们难于选择从何处开始。然而,所有这些建议都是建构在下面这五条实践的基础之上的。

每隔几个月,我就会收到一些勤奋有加的新手程序员的求助,他们希望知道如何才能成为一名真正优秀的程序员。在一些程序员论坛上,我也能看到为数不 少的类似问题,这是一个令人鼓舞的趋势。一些最周全的答案往往与我对这个问题的看法相似,这表明在基础的最佳实践上确实存在着某种一致。因此我下 面的建议并非原创,不过也许我的这些补充会提供给你更进一步的理解。

我印象中的新手程序员基本了解编程的原理,写过程序,但大多规模较小且复杂度不一,致力于某个领域的工作或自己或他人个人项目。

编程工作只有一种真正的基础活动,那就是写代码。要想擅于编程,你就必须编写大量的代码。 大量的工作可能成为一种促进你成长的工具,也可能是一些有限技能的重复练习。为了避免成为后者,你应该做到:

阅读大量代码。尤其是阅读大量由卓越程序员编写的代码。记住:不仅读那些坐在大厅里的优秀程序员的代码,更要读那些卓越程序员的。 在开源软件大行其道的今天,做到这些十分容易。当我学习Java时,我读了Tomcat的代码,读了Cruise Control CI服务器的代码。自从那时起,我已经读了大量优秀的代码。

阅读代码时很容易从main函数开始,但这样一来,你很可能会在初始化以及命令行解析代码上花费大量时间。我更喜欢根据源文件名寻找一些令我感兴 趣的功能实现,然后深入阅读这些源文件。理解整个项目或整体设计的来龙去脉并不是关键,做这些会让你感觉筋疲力尽的。阅读代码。查看注释,弄清楚 作者在做什么以及是如何着手做的。

彻底了解你的工具。我认为损耗编程时间最多的不是调试或重写代码,而是因开发人员对其所使用工具的不熟悉而导致的无数碎片时间的损 耗。我所指的工具包括:集成开发环境(IDE)、编程语言、构建系统以及版本控制系统。其中,集成开发环境和编程语言是到目前为止最为重要的。经 过几个星期的练习,你应该知道集成开发环境中的几乎每个按键组合,这样你只有在为了节省按键时间时才使用鼠标。如果你知道按键组合,你就知道了这 些命令。但如果你只使用鼠标,你只会知道菜单,菜单上面有你倾向于点击的相同的一个或两个条目。因此了解集成开发环境是一种不折不扣的纪律。

了解大型编程语言,诸如Java或C++,需要的不仅仅是纪律。它们自身规模庞大,它们的库亦规模庞大。阅读代码是我认为的了解编程语言最好的方式,阅读 那些使用了你所未知特性的代码并寻找机会使用它们。书籍(而不是博客)是另外一个极好的资料来源。了解你目前正在使用的特性的外围,很快你就会发现外围扩 大了。了解版本控制系统和构建系统将让你成为一个理想的团队成员 — 不会因为对重要操作的无知而浪费时间。

动手编码前先规划好你的代码。我认为这是建议列表中最难做的一项,但同时它也可能给你换来最多的益处。我所指的并不是正式的设计 – 在这个阶段正式设计一般不是必要的。不过你确实应该用一种其他方式精心策划一下代码,而不仅仅是将思路放在脑子里。最简单的方法就是编写一个小文档(我经 常使用的是思维导图):代码的需求是什么?你打算如何实现它?还有哪些目前未知的事情需要去了解?我需要或需要创建什么对象?将这些都写出来。只有在这样 之后开始编码,你才会发现代码变得更加容易编写了,更容易形成文档了,也更容易修改了。将你的笔记保存下来 – 它们将是很好的参考资料。

大量编写代码并进行代码评审。如果你那里不做代码评审,那你就自己来做。找出那些最好的程序员,并且你可以通过某种方式听 到和理解他们给你的有用的建议。别做令人讨厌的人,但也不能因为你害羞,忙碌或自负而回避这个过程。代码评审应该成为你编程人生的一部分。要有创造性。试 试在某个下午与比你更牛的程序员一起做结对编程。重要的是你需要反馈,而这些反馈是你自己无法给予自己的。

编码与写测试并驾齐驱。这也许是这里唯一有争议的一条。它并非是对TDD的认可(译注:测试驱动开发)。但这里要认可的是你的代码 在大多数要面对的场合里都是可以工作的。开始单元测试,并用边缘值测试新代码。例如,当传入一个负数或者是整型数最大值时,你的函数是否还能正常工作?如 果不能,你的函数是否抛出了一个信息详实的异常或只是崩溃退出?如果不是异常,你是否使用了断言(assert)来缩小输入的范围?如果这么做了,那就测 试这个断言。利用之前做的规划编写一些模拟(mock)测试,接下来用这些模拟对象去开始测试你的新代码。这将有助于阐明你当前代码中的设计问题以及即将 实现的对象。保存你的测试代码,在每次签入代码前都运行它们,这些测试将成为后续那些破坏你当前代码的新代码的早期预警系统。

还有许多建议和至理名言可以添加到这个列表中。但这本身就是问题的一部分:过多的建议将导致新手难于知道到底从何处开始。因此,我故意将我的建议缩减到仅 剩五点。如果你能勤奋地运用这五点建议实践,你会很快发现两件事情:你将可以逐步应付更大更重要的任务了,并且当你回首翻看你几个月之前编写的代码时,你 会觉得尴尬。

毫无疑问这两种感受都是你进步的标志。祝你好运!

开始学Go

本文翻译自Dr.Dobb's的"Getting Going with Go"。

本文是有关Google新的系统原生语言的五周教程的第一部分,这里将先向大家展示如何建立Go语言开发环境以及构建程序,然后带领大家浏览 一些代码范例来着重了解一下这门语言的一些有趣的特性。

这个教程系列将连续刊登五周。在今天这一部分中,Go语言专家Mark Summerfield将讲解如何建立Go语言开发环境,提供两个Go语言范例并给予深度解析。这些样例程序会向大家局部地展示了Go语言的一些关键特性 以及包。接下来几周将展示其余的关键特性,并特别为C、C++和Java程序员们深入研究那些Go语言独有的特性。

正如本周主编文章中所解释的那样,Go语言拥有许多独一无二的特性,因此它也许可以被称为二十一世纪的C语言。而且考虑到Ken Thompson也是该语言的设计者之一,这两种语言的确是有共同的祖先。

开始

Go是编译型语言,而不是解释型的。Go的编译速度非常快– 甚至远远快过其他同类语言- 知名的如C和C++。

标准Go语言编译器被称为gc,与其相关的工具链包括用于编译的5g、6g和8g;用于链接的5l、6l和8l以及用于查看Go语言文档的 godoc(在Windows平台上这些程序为5g.exe、6g.exe等等诸如此类)。这些奇怪的名字遵循了Plan 9操作系统编译器的命名方式,即用数字表示处理器体系("5"代表ARM,"6"代表AMD64-包括Intel 64位处理器- "8"代表Intel 386)。幸运的是,我们无需对此产生忧虑,Go语言提供了一个更高级别的Go语言构建工具,这个工具可以为我们处理编译和链接任务。

本文中的所有代码使用的都是Go 1版本语法,并在Linux、Mac OS X以及Windows上用gc测试通过了。Go语言的开发者计划让随后所有Go 1.x版本支持Go 1向后兼容,因此这里的代码和例子将适用于所有1.x系列版本。

要下载和安装Go,请访问golang.org/doc/install.html,那里提供了下载链接与安装指令。Go 1为FreeBSD 7+、Linux 2.6+、Mac OS X (Snow Leopard和Lion)以及Windows 2000+提供源码包以及二进制形式安装包,可以支持所有Intel 32位和AMD 64位处理器体系。Go还支持ARM处理器版本的Linux,为Ubuntu Linux发布版提供预建go包。当你读到这里时,也许已经有其他Linux发布版的Go安装包了。

使用gc编译器的程序使用了一种特定的调用惯例(call convention)。这意味着使用gc编译的程序只可以与使用同样调用惯例编译的外部库进行链接 – 除非使用某适合的工具消除这些差异。使用cgo工具Go可以支持在Go程序中使用外部C代码,并且至少在Linux和BSD系统上,通过SWIG工具我们 可以将C和C++代码用于Go程序中。

除了gc,还有一种编译器称为gccgo。它是Gcc的一个Go特定前端,Gcc 4.6及以后版本才能支持。像gc一样,gccgo也许内建在一些Linux发行版中。构建和安装gccgo的指令在Go主站点上可以找到。

Go文档

Go的官方站点上维护着一份最新的Go文档。"Packages"链接提供了有关所有Go标准库包的访问方式- 以及它们的源码,这些源码在文档还很稀缺时十分有用。通过"Commands"链接你可以找到与Go一起发布的相关程序的文档(诸如编译器,构建工具 等)。通过"Specification"链接,你可以找到一份非正式,但很全面的Go语言规范。通过"Effective Go"链接,你可以找到一份介绍Go最佳实践的文档。

该站点还提供了一个沙箱特性,用于在线编写、编译以及运行Go小程序(稍有限制)。这个特性十分有用,便于初学者试验一些古怪的语法。Go站点上 的搜索框只能用于在Go文档中搜索;如果要对Go的资源进行全面搜索,请访问http://go-lang.cat-v.org/go- search。

Go的文档也可以在本地浏览,例如在Web浏览器中。如果要这样做,可运行Go的godoc工具,并通过传入命令行参数告知它以一个web服务器 的方式运行。下面是在一个Unix或Windows控制台中进行这个操作的方法,假设PATH环境变量中已经设置了godoc:

$ godoc -http=:8000

这个例子中的端口号可以是任意的- 如果它与一个已存在的服务器端口冲突,可以使用其他任一个端口号。

要想查看文档,打开一个浏览器,输入地址http://localhost:8000。一个类似golang.org首页的页面将会呈现在你的面 前。"Package"链接将指向Go标准库以及安装在GOROOT环境变量下的第三方包的文档。如果你定义了GOPATH环境变量(比如,为本 地程序和包),一个链接将会出现在"Packages"链接旁,通过这个链接你可以访问其他相关文档。(本文后续会讨论GOROOT和 GOPATH环境变量)

编辑,编译和运行

Go程序用UTF-8编码的普通的Unicode文本编写。绝大多数现代文本编辑器都可以自动处理这些代码,并且一些最流行的编辑器可以支持Go 源码的语法色彩高亮以及自动缩进。如果你的编辑器不支持Go,可以尝试在Go搜索引擎中输入你的编辑器的名字,查看一下是否有合适的插件。作为编 辑惯例,Go所有的关键字和操作符都使用ASCII字母;然而,Go的标识符可以以任意Unicode字母作为起始,后面可以跟着任意 Unicode字母或数字,因此Go开发者可以自由使用他们的母语。

为了掌握如何编辑、编译以及运行一个Go程序,我开始会用经典的"Hello World"程序作为例子- 然而我编写这个程序比寻常的稍复杂些。

如果你已经用二进制包或通过源码安装了Go,并且是以root或管理员权限安装的,你应该至少设置一个环境变量:GOROOT,该变量指示Go的 安装路径信息,你的PATH变量现在应该包含$GOROOT/bin或%GOROOT%\bin。为了检查Go安装地是否正确,可在控制台下输入 下面命令:

$ go version

如果你得到"command not found"或"'go' is not recognized…"的错误信息,那就意味着在PATH变量配置的路径下没有Go。

编译与链接

构建一个Go程序需要两步:编译和链接。(由于我们假设使用gc编译器,使用gccgo编译器的读者需要遵循golang.org/doc /gccgo_install.html中描述的编译和链接过程,同样,使用其他编译器的读者需要根据其编译器的指令进行编译和链接)。编译和链 接过程都由工具go处理,它不仅可以构建本地程序和包,还能够获取、构建以及安装第三方程序和包。

要想go能够编译本地程序和包,有三个要求。第一,Go的bin目录($GOROOT/bin或%GOROOT%\bin)必须在PATH环境变 量下。第二,必须存在一个目录,该目录下包含一个src目录,本地程序和包的源码就驻留在src目录下。例如,例子代码会解包到goeg/src /hello、goeg/src/bigdigits下等。最后,包含src的那个目录必须在GOPATH环境变量中设置。例如,要使用go工具 构建hello这个例子,我们必须这么做:

$ export GOPATH=$HOME/goeg
$ cd $GOPATH/src/hello
$ go build

两个例子中,我们都假设PATH环境变量中包含了$GOROOT/bin或%GOROOT%\bin。一旦go编译程序完毕,我们就可以运行这个 程序了。默认情况下,go会用可执行文件所在目录的名字来命名该文件(例如,在类Unix系统上是hello,而在Windows上为 hello.exe)。一旦构建完毕,我们就可以按常规方式运行它。

$ ./hello
Hello World!

注意,我们不需要编译或显式地链接其他包(即便后续我们将看到,hello.go使用了三个标准库的包)。这也是Go程序编译如此之快的一个原 因。

如果我们有多个Go程序,若他们的可执行文件能够放在同一个目录下将会非常方便,后续只需将这个目录加入到PATH环境变量中。幸运地是go支持 这样的情况:

$ export  GOPATH=$HOME/goeg
$ cd  $GOPATH/src/hello
$ go install

go install命令除了做了go build所做的事情之外,还将可执行文件放在标准位置($GOPATH/bin或%GOPATH%\bin)。这意味着将一个单一路径($GOPATH /bin或%GOPATH>%\bin)加入到PATH环境变量中,我们安装的所有Go程序就可以方便地被加入到PATH中。

除了这里给出的例子外,我们很可能想要在我们自己的目录下开发我们自己的Go程序和包。通过为GOPATH环境变量设置两个(或更多)冒号分隔的 路径(在Windows上用分号分隔)我们可以很容易解决这个问题。

虽然Go使用go工具作为标准构建工具,但你仍然可以使用make或其他现代构建工具,或使用其他Go专用的构建工具,或一些流行IDE的插件。

和谁打招呼(Hello)?

既然我们已经看到了如何构建一个Hello程序,接下来我们来看看其源代码。下面是hello程序的完整源码(在文件 hello/hello.go中):

// hello.go
package main
import (
    "fmt"
    "os"
    "strings"
)
func main() {
    who := "World!"
    if len(os.Args) > 1 { /* os.Args[0] is "hello" or "hello.exe" */
        who = strings.Join(os.Args[1:], " ")
    }
    fmt.Println("Hello", who)
}

Go用C++风格的注释符号//作为单行注释,用/* … */作为多行注释符号。依照惯例,Go中多使用单行注释,多行注释常用于在开发中注释掉代码块。

每段Go代码都存在于一个包内,并且每个Go程序必须具有一个包含main()函数的main包,其中main函数会作为程序执行的入口点,即这 个函数首先执行。事实上,Go包也可以定义在main函数之前执行的init函数。值得注意的是包名和函数名之间不会存在冲突的情况。

Go的操作是以包为单位的,而不是文件。这意味着我们可以根据需要任意地将一个包拆分到多个文件中。如果多个文件具有相同的包声明,Go语言认为 这些文件都是同一个包的组成部分,与所有内容在单一文件中无异。当然,我们也可以将应用的功能分解到许多本地包中,这样可以保持代码整洁地模块 化。

import语句从标准库导入三个包。fmt包提供格式化文本以及读取格式化文本的函数;os包提供平台无关的操作系统变量以及函 数;strings包提供操作字符串的函数。

Go的基本类型支持普通操作符(例如,+可用于数值加法以及字符串连接),Go的标准库通过提供操作基本类型的函数包补充功能,例如这里导入的 strings包。我们可以在基本类型的基础上创建我们自定义的类型并为它们定义相关方法- 自定义操作特性类型的函数。

读者也许已经注意到了Go源码中没有分号、import的包无需逗号分隔以及if条件语句不需要括号。在Go中,块(block),包括函数体以 及控制结构体(如for、if语句以及for循环),使用括号界定。缩进只是单纯用于提高代码的可读性。技术上而言,Go语句是用分号分隔的,但 这些分号由编译器插入,我们自己无需关心,除非我们要将多个语句放在同一行中时。没有分号、很少的逗号以及括号让Go程序看起来更简洁,需要的输 入也更少。

Go使用func关键字定义函数(function)和方法(method)。main包的main()函数总是具有相同的函数签名 – 没有参数、没有返回值。当main.main()结束时,程序将终止并返回0给操作系统。当然,我们可以在任意时刻返回并选择我们自己的返回值。

main()函数中的第一个语句(使用:=操作符)在Go的术语里被称为一个短变量声明。这个语句在同一时间声明并初始化一个变量。此外,我们无 需指定变量的类型,因为Go可根据初始值推导出变量的类型。因此在这个例子中,我们声明了一个string类型的变量who,感谢Go的强类型机 制,我们只需将字符串赋值给who即可。

os.Args变量是字符串的一个slice(片段)。Go使用array(数组)、slice和其他集合数据类型,但在这些例子中,知道下面这 些即可:使用内置的len()函数获取一个slice的长度以及通过[]下标操作符访问其中的元素。特别是,slice[n]返回slice的第 n个元素(从0起始),slice[:n]返回另外一个slice,这个新slice由原slice的第n个到最后一个之间的元素组成。在集合一 章,我们将看到有关这方面的一般性的Go语法。就这里的os.Args来说,这个slice总是应该至少在位置0处具有一个字符串(程序的名 字)。(所有Go的下标索引都是从0开始)

如果输入一个或更多命令行参数,if条件将被满足,我们将所有参数拼接成一个单一的字符串并赋值给who。在这里例子中,我们使用赋值操作符 (=),如果我们使用:=,我们将声明和初始化一个新的who变量,其影响范围将局限在if语句块中。strings.Join函数接受一个字符 串slice和一个分割符(可以为空,即"")作为参数,并返回一个由所有slice的字符串元素组成的,由分隔符分隔的单一字符串。这里我们用 空格分隔这些字符串。

最后,在最后一个语句中,我们输出Hello,一个空格,who变量中的字符串以及一个新行(newline)。fmt包中拥有许多不同的打印输 出变体,一些像fmt.Println()的将整齐地输出任何传入的值,其他的诸如fmt.Printf将使用占位符以提供更佳的格式控制。

另外一个例子 – 二维slices

下一个例子bigdigits程序从命令行(以一个字符串形式)读取一个数,并在控制台上使用"大号字体"输出这个数。早在二十世纪,在很多用户 共享一台高速行打印机的场合,按惯例将使用这种技术在每个用户的打印工作之前放置一个封面,该封面可以展示用于识别的细节,诸如用户名以及将被打 印的文件的名字。

我将分三段回顾这个程序的代码:首先是import部分,然后是静态数据,最后是处理过程。不过现在让我们来看看一个样例,了解一下这个程序是如 何工作的吧:

$ ./bigdigits  290175493

每位数字都由一个字符串slice表示,所有数字一起由一个元素为字符串slice的slice表示。在查看数据之前,这里我们展示一下如何声明 和初始化一个一维的字符串和数字slice:

longWeekend := []string{"Friday", "Saturday", "Sunday", "Monday"}
var lowPrimes = []int{2, 3, 5, 7, 11, 13, 17, 19}

slice用[]Type表示,如果我们要初始化它们,我们可以直接在后面跟上一个用大括号包裹、逗号分隔的对应类型的元素列表。我们本可以用同 样的变量声明语法来声明这两个变量,但在这里我们为展示两种语法的差异以及一个稍后即将说明的原因,lowPrimes slice使用了一个更长形式的声明。由于slice类型可以作为slice类型的元素类型,因此我们可以很容易地创建多维集合(slice的slice 等)。

bigdigits程序只需要导入四个包。

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

fmt包提供了用于文本格式化和读取格式化文本的函数。log包提供了日志记录函数。os包提供了平台无关的操作系统变量以及函数,其中就包括持 有命令行参数值的[]string类型的os.Args变量。path包下面的filepath包提供跨平台操作文件名和路径的相关函数。注意对 于逻辑上存在包含关系的包(译注:如path/filepath)来说,我们在代码中使用时只指明其名字的最后部分(这里是filepath)。

对于bigdigits这个程序,我们需要一个二维的数据(一个字符串slice的slice)。下面就是创建它的方法,通过代表数字0的字符串 布局我们可以看出这个数字对应的字符串在输出时相应的行。3到8的数字对应的字符串这里省略了。

var bigDigits = [][]string{
   {"  000  ",
    " 0   0 ",
    "0     0",
    "0     0",
    "0     0",
     "0  0",
     " 000 "},
    {" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},
    {"222","2  2","  2","  2 ","2  ","2  ","22222"},
    // … 3 to 8 …
    {" 9999", "9  9", "9  9", " 9999", "  9", "  9", "  9"},

在函数或方法外面声明的变量不可以使用:=操作符,但我们可以使用长声明形式(使用关键字var)以及赋值操作符(=)来得到同样的效果,就如我 们这里为bigDigits程序中的变量所做的那样(前面对lowPrime变量的声明)。我们仍然无需指定bigDigits变量的类型,Go 可以从赋值中推断出其类型。

我们把计算工作留给了Go编译器,因此也没有必要指出slice的维数。Go的一个方便之处就是它对使用了括号的符合字面值的良好支持,这样我们 无需在一个地方声明一个数据变量,然后在另外一个地方用数据给它赋值了。

main()函数读取命令行,并使用这些数据产生输出,这个函数只有20行。

func main() {
    if len(os.Args) == 1 {
        fmt.Printf("usage: %s <whole-number>\n", filepath.Base(os.Args[0]))
        os.Exit(1)
    }
 
    stringOfDigits := os.Args[1]
    for row := range bigDigits[0] {
        line := ""
        for column := range stringOfDigits {
            digit := stringOfDigits[column] – '0'
            if 0 <= digit && digit <= 9 {
                line += bigDigits[digit][row] + " "
            } else {
                log.Fatal("invalid whole number")
            }
        }
        fmt.Println(line)
    }
}

程序一开始检查是否有任何命令行参数。如果没有,len(os.Args)的值将为1(回忆一下,os.Args[0]中存放的是程序名,因此这 个slice的长度至少是1)。如果这个if语句条件成立,我们将使用fmt.Printf输出一条适当程序用法信息,该Printf函数使用类 似C/C++中printf()或Python中%操作符的%占位符。

path/filepath包提供路径操作函数- 比如,filepath.Base()函数返回给定路径的基本名(basename,即文件名)。在输出这条信息后,程序使用os.Exit()函数结束 程序,并返回1给操作系统。在类Unix系统中,一个值为0的返回值用于表示成功,非0值标识用法错误或失败。

filepath.Base()函数的使用向我们说明了Go的一个美妙的特性:当一个包被导入时,无论它是顶层的包还是逻辑上内置于其他包中的包 (例如:path/filepath),我们总是可以只通过其名字的最后部分(即filepath)来引用它。我们还可以给包赋予本地名字以避免 名字冲突。

如果至少传入了一个命令行参数,第一个参数将被拷贝到stringOfDigits变量(string类型)中。要想将用户输入的数字转换成大数 字,我们必须迭代处理bigDigits slice的每一行,即每个数字的第一行(最上面的一行),接下来第二行,依次类推。我们假设所有bigDigits的slice都具有相同数量的行,这 样我们可以从第一个slice那里得到行数。Go的for循环对不同场景有不同的应对语法;在这个例子中,for…range循环返回 slice中每个元素的索引位置信息。

行和列的循环部分的代码可以这样来写:

for row := 0; row < len(bigDigits[0]); row++ {
    line := ""
    for column := 0; column < len(stringOfDigits); column++ {
        …

这是一个C、C++和Java程序员都熟悉的语法形式,在Go中它也是有效的。(与C、C++和Java不同在于,在Go中,++和–操作符只 能用作语句,而不能用作表达式。此外,它们只能被用作后缀操作符,而不能作为前缀操作符。这意味着求值顺序导致的相关问题在Go中不会发生- 谢天谢地,像f(i++)和a[i] = b[++i]这样的表达式在Go中是非法的。) 然而,for…range语法更加短小,也更加方便。

在每次行迭代时,代码会将行的line赋值为空字符串。接下来,我们做迭代处理从用户那里获取的stringOFDigits中的列(即,字 符)。Go的字符串使用UTF-8字符,因此本质上一个字符很可能用两个或更多字节表示。但这不是这里要讨论的话题,因为我们只关心数值0、 1、…、9,这些数值用UTF-8字符表示时只需一个字节,与用7比特ASCII字符表示所使用的字节值相同。

当我们索引字符串中的某个特定位置时,我们获取了那个位置的字节值。(在Go中byte类型是uint8类型的同义词。)因此我们获取到命令行字 符串特定列上的字节值,减去数字0的字节值后,得到它表示的数字。在UTF-8(以及7比特ASCII)中,字符'0'是码点(字符)十进制值 48,字符'1'是码点十进制值49,依次类推。这样举例,如果我们有字符'3'(码点1),我们可以通过做减法'3' – '0'(即51-48)的结果得到其整型值3。

Go使用单引号表示字符字面值,一个字符字面值是一个与Go任何整型类型都兼容的整数。Go的强类型意味着如果不进行显式转型,我们无法将一个 int32类型的数与一个int16类型的数相加,不过,Go中的数值常量和字面值自适应于其上下文,这样一来,这里的'0'被认为是一个字节。

如果这个数字(byte类型)在范围内,我们会将对应的字符串加到line变量中。(在if语句中,常量0和9被认为是byte类型,因为它们是 数值类型,但如果数值是一个不同的类型,比如说,int,它们将会被当作新类型对待。)虽然Go中的字符串是不可改变的,Go仍然支持+=附加操 作符以提供一个便于使用的语法。(它通过在后台替换掉原先的字符串。)Go同样支持+字符串连接操作符,该操作将返回一个由左右字符串操作数连接 而成的新字符串。

为了获取对应的字符串,我们根据这个数值访问bigDigits slice,然后访问其中我们需要的行(字符串)。

如果数值超出了范围(比如,由于stringOfDigits包含了一个非数值),我们调用log.Fatal()函数记录一条错误信息。如果没有显式指 定其他日志输出目标,这个函数会在os.Stderr中记录下日期、时间和错误信息。然后该函数调用os.Exit(1)结束程序。还有 一个名为log.Fatalf()的函数可以做同样的事情,但它接受%占位符。我们没有在第一个if语句中使用log.Fatal()函数,因为 我们想输出程序的使用方法信息,但不要log.Fatal()默认输出的日期和时间信息。

一旦给定行的所有数字的字符串都累加完毕,完整的一行就被输出。在这里,我们输出了7行,因为每个bigDigits slice中的数字由七个字符串表示。

最后一点是声明和定义的顺序无关紧要。因此在bigdigits/bigdigits.go文件中,我们可在main()函数前声明bigDigits变 量,也可在后面声明。在这个例子里,我们将main()函数放在前面,对于这篇文章中的例子,我们通常更倾向于自顶向下的排序。

这里的两个例子已经涵盖了大量特性,但它们所展示的资料与其他主流语言甚为相似,即便语法稍有不同。下周的文章将检视Go语言的其余特性,包含一些高级方面的特性。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats