diff --git a/.gitignore b/.gitignore index 1a366fb..c13ccf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,30 @@ -# Node rules: -## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt -## Dependency directory -## Commenting this out is preferred by some people, see -## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git -node_modules +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release -# Book build output -_book +# Dependency directory +# Deployed apps should consider commenting this line out: +# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git +node_modules -# eBook build output -*.epub -*.mobi -*.pdf \ No newline at end of file +_book/ +book.pdf +book.epub +book.mobi diff --git a/11-hello.md b/11-hello.md new file mode 100644 index 0000000..fde2784 --- /dev/null +++ b/11-hello.md @@ -0,0 +1,162 @@ +我们从经典的Hello Word程序开始,1978年《C语言编程》出版的时候也是这么干的。C应该说是对Go语言产生直接影响最大的语言之一,从Hello World这个例子我们就能看出一些端倪 + +![](/assets/helloworld.png) + +Go是一个编译语言。Go工具链会把源代码以及相关依赖包转换成为计算机底层原生机器语言的指令。这些工具链只通过一个命令来管理:go。go命令有一系列的子命令,对应各个工具集操作。最简单的子命令就是run,该命令可以编译一个或多个命名为.go的源代码文件,链接依赖库,然后运行编译结果(但是不会留下编译好的可执行文件)。我们在本书中用$符号表示命令提示符。 + +$ go run hellowolrd.go + +毫无悬念,屏幕上将会得到一下输出: + +Hello,世界 + +Go原生自带Unicode的处理,所以可以处理世界上各种语言的文本。 + +如果程序只实现性的运行一次无法满足需求,那么你应该会想要保存编译出的结果已被将来调用。那么这时就应该使用go build命令: + +$ go build helloworld.go + +然后一个名为helloworld的可执行的二进制文件就编译出来了,下次直接运行可执行文件即可,不用在进行重复的编译过程。 + +$ .\/helloworld + +Hello, 世界 + +为了方便使用,本书的代码都标注好了名字,你可以在gopl.io\/ch1\/helloworld下获得刚才的源码。 + +如果你运行go get gopl.io\/ch1\/helloworld, 该命令就会从网络上对应地址获取源码,存放到本地的相应目录中。在2.6节和10.7节中会有更详细的描述。 + +现在我们来仔细看下这个程序本身。Go代码以包的形式来管理,包的概念和其他一些语言里库的概念或是模块的概念相似。一个包由一个或多个.go源文件组成,这些源文件放在同一个目录里,里面定义了这个包的功能实现。每一个源文件开头都是一个包的声明,在这个例子里导入声明是pacakage main, 说明了该文件所属的包叫做main。后面跟着该文件需要导入的其他包的名字,然后是代码文件本身实现的程序的声明。 + +Go标准库有超过100个包,涵盖各种常见任务的实现, 包括输入和输出,排序,以及文本处理。比如说fmt包里面有实现格式化打印输出的函数和扫描输入的函数。Println是fmt中的一个基本输出函数。它打印出一个或多个值,用空格隔开,在行尾会加一个换行符,这样每一个值打印出来后都会新起一行。 + +Package main是一个特殊的包,它定义了一个独立的可执行程序,而不是一个library。在package main 中同样有一个特殊的函数,main函数,这是一个可执行程序的入口。main所做的事情就是这个程序做的事情。当然,main通常是靠调用其他包里面的其它函数来完成这些事情的,比如调用fmt.Println. + +我们必须告诉编译器当前源文件到底需要哪些package的依赖,这正是import 声明的使命,import声明跟在package 声明之后。hello world程序只是用了外部的一个包中的一个函数,但是大多数程序会依赖更多的package。 + +你必须精确的进行包引用。如果少了一个包,或是多饮用了一些没有用到的包,编译的时候都会出错。这个严格要求避免了程序演进的过程中越来越多的引用一些不必要的包而使得程序整体变得不必要的臃肿。 + +import声明必须跟在package声明的后面。在那之后会是程序的其他组成部分:函数声明, 变量, 常量, 还有类型定义(分别有各自的关键字定义: func, var, const, 还有type\);对于大多数组成部分而言,声明的顺序并不重要。示例程序很简短,只定义了一个函数,而这个函数也正好只调用了一个别的函数。为了节省空间,我们在演示时有时会隐藏package和import声明,但是源文件中肯定是有这些声明的,否则无法编译。 + +一个函数声明包括函数关键字func,函数名,参数列表(此例中main函数的函数列表是空列表),一个结果列表(这里也是空列表),还有函数体,也就是定义函数行为的语句们。函数体用花括号包起来。我们会在第五章仔细研究函数相关问题。 + +Go并不要求每一个语句后面加一个分号,除非一行写多个语句,那么前几个语句之间必须用分号隔开。事实上在编译之前带有特殊符号的换行符会被转化尾分号,所以换行符的位置在Go代码中是有讲究的。比如,开始的花括号{必须和函数声明在同一行,不能单独起一行,而在表达式x+y中,+号后面可以有换行,但是+号前面不可以(否则会把+y认成一个新的变量)。 + +Go在代码格式上的要求非常严格。gofmt工具会把代码转换成标准go代码格式的写法,go的fmt 子命令会给指定包中的所有源码文件进行gofmt格式化,默认值是给当前目录格式化。本书所有Go源文件都已经公国gofmt进行了格式化,你在编写自己的代码时也应该养成使用gofmt的习惯。强制使用标准格式减少了很多关于细节的无意义讨论,并且更重要的是使得自动化的代码转换变为了可能,而这在允许随意的编码格式的语言中是不可实现的。 + +许多文本编辑器可以配置为每次保存的时候自动运行gofmt, 这样你的代码就会永远保持标准格式了。还有一个相关的工具叫做goimports,会自动的管理引用的package,不足缺失的包,删掉多余的包。它并没有集成在标准发布版当中,不过你可以通过以下命令来获得这个命令工具: + +$ go get golang.org\/x\/tools\/cmd\/goimports + +对大多数用户来说,通用的操作都离不开go 工具,比如下载或是编译package,测试包,查看包的文档等等。我们会在10.7节详细看看go工具的使用。 + +### 1.2. 命令行参数 + +大多数程序本质上都是处理某种输入,然后产出某种输出。这可以看成是计算的一种定义。那么问题来了,程序是如何获得需要处理的输入数据的呢?有些程序自己产生输入数据,但是更常见的是通过一个外部的源来获取数据: 比如通过文件,通过网络连接,通过其他程序的输出,用户的键盘输入,以及命令行参数等等。接下俩的几个例子会讨论这几种不同的方式,我们先从命令行参数说起。 + +os包提供了和操作系统交互的函数,还可以直接获得某些和系统相关的值。这些函数和变量都是非平台相关的,也就是说在使用的时候可以不考虑所在操作系统,用法都是相同的。命令行参数可以通过os包中一个名为Args的变量获得,因此在os包之外的任何地方我们都用os.Args来访问该变量。 + +os.Arg是一个string组成的slice。slice是Go的一种基本类型。我们很快会详细谈到他。现在先吧slice看成是一个大小动态变化的数组s, 每一个数组元素可以通过下角标s\[i\]访问,也可以通过子序列s\[m:n\]来获取其中的一段生成一个新的slice。元素的个数通过len\(s\)获得。和大多数其他编程语言一样,Go的index也是用左闭右开区间, 这样可以简化逻辑。比如,slice s\[m:n\]包含n-m个元素。从s\[m\]到s\[n-1\] + +os.Args的第一个元素是os.Args\[0\], 就是命令名本身。os.Args剩下的其他元素就是开始执行是传入的参数。表达式s\[m:n\]表示从第m个元素到第n-1个元素组成的一个新slice。如果m或是n省略掉的话,表达式就表示从0开始或是直到len\(s\)的slice。所以,我们可以把想要的slice简记为os.Args\[1:\]。 + +这里我们来看一个Unix系统里 echo命令的简单实现。该命令可以把其参数用一行给打印出来。这里导入了两个包,注意不是用独立的import语句依次导入,而是用花括号包起来用一个import语句导入的。这两种写法都是对的,不过一般都用花括号的方式更方便一些。导入的顺序无关紧要,gofmt会把这些包名按照首字母顺序重排的。(对于有多个版本的示例程序,我们会用后面的数字来方读者区分) + +TODO1 p5 + +注释以双斜杠\/\/开头。\/\/后面到行尾的所有文本都会被编译器忽略掉,只是供程序员注释使用。习惯上我们会在包声明语句的前面一句用注释说明包的用途。对于main package,这句注释会是描述整个程序功能的一两句话。 + +关键字var声明了连个变量s还有sep,都是string类型。变量可以在声明的时候就初始化。如果没有进行显式的初始化,那该变量就会被隐式的初始化为对应类型的零值。比如数字类型就会被初始化为0, string类型就会被初始化为空字符串""。因此在本例中,s和sep都被隐式的初始化为空字符串。我们在第二章中会详细讨论变量声明。 + +对于数字类型,Go提供了通用的数学运算符和逻辑运算符。不过+号运算符用于string的时候表示连接操作。 + +所以sep+os.Args\[i\]这个表达式表示把sep和os.Args\[i\]连接起来,程序中的语句 + +s+= sep+os.Args\[i\] + +是一个赋值语句,把运算前的s,sep,还有os.Args\[i\]连接起来,然后赋值给s。该语句等价为 + +s = s+sep+os.Args\[i\] + ++=运算符是一个赋值运算符。每一个像+或是\*这样的数学运算符和逻辑运算符都有一个类似这样的赋值运算符。 + +echo程序其实可以在每一次循环中打印出循环对应的那一部分内容。不过这个版本选用的方式是 每次把新的文本添加到string的末尾,把所有结果拼到一个string中然后一次打印出来。string s一开始是空字符串“”, 然后每一次循环都会在末尾加入一些文本,并且每次迭代都插入一个空格,这样最后循环结束时参数之间会有空格分隔。这是以一个二元的过程,当参数比较多的时候会有性能问题。不过对于echo来说这种情况不太会发生。我们会在本章演示其他版本的echo,解决性能低下的问题。 + +循环的索引值i实在for循环的第一部分声明的。:=符号表明一个简短的变量声明,所在语句声明一个或多个变量,然后初始化它们,下一章会详细的讨论变量声明。 + +自增语句i++每次给i上加1, 等价于i+=1, 也等价于i=i+1;对应的还有一个自减运算i--,每次给i减1。自增和自减都是语句,不是表达式。这一点和C语言家族不一样,所以j=i++这样的写法是不允许的,并且也只能有后缀形式,所以--i这种写法也是非法的。 + +for循环是Go语言唯一支持的循环语句。有好几种形式,其中的一种如下: + +for initialization; condition; post{ + +\/\/ zero or more statements + +} + +for循环的三个部分都不需要括号,但是循环体必须用花括号括起来,而且开头的花括号必须和post语句在同一行。 + +可选的initialization语句在循环开始之前被执行,如果有这句的话必须是一个简单语句,可以使一个简短的变量声明,一个自增或是赋值语句,或是一个函数调用。condition是一个boolean表达式,每次循环迭代之初都会判断一下condition是否为true, 如果是true则循环迭代一次。post语句则是在每次循环迭代完成之后被调用,然后开始下一轮,condition又开始判断。当condition编程false的时候,循环结束。 + +这三部分都可以被省略。如果initialization和post都没有,那么分号也可以省略: + +\/\/传统的while也就是这个玩意儿 + +for condition{ + +\/\/... + +} + +如果condition被省略掉了,不管另两部分怎么样,都会成为一个死循环,比如 + +\/\/相当于传统的死循环 + +for{ + +\/\/... + +} + +这个循环本身是无限循环下去的,不过这种形式的循环语句可以用其他方式结束,比如通过break或是return语句; + +for循环的另一种形式是在一个区间上循环执行,比如string的一段或是slice的子slice。为了说明这一点我们来看看第二版echo的代码 + +\/\/TODO 2 page 25 + +在每一个循环迭代中,range产生一组值:index,还有slice对应index位置上的值。在这个例子种,我们不需要索引值,不过range循环的语法要求我们必须成对处理index和对应value。一个自然地想法是把index赋给一个临时变量temp然后忽略这个值,不过Go的优雅不允许存在没使用过的局部变量,这会导致一个编译错误。 + +解决方案是使用一个下划线表示空标识符。空标识符可以用在任何一个语法要求变量名但是逻辑上并不使用该变量的场合。比如在你只需要元素值的地方来处理语法上龟毛强制获得的index。大多数Go码农都会像上面那样使用range和\_来写echo程序,因为os.Args的索引值其实是隐式存在的,并没有显式的声明出来,因此这么写更不容易出错。 + +这一版的程序使用了变量的简短声明来声明和初始化s还有sep, 不过我们也可以分开声明他们。这里有几种完全等价的string声明方式: + +s := "" + +var s string + +var s = "" + +var s string = "" + +那么应该怎么选择呢?第一种写法是简明变量声明法,最为简洁,不过只能在函数内部使用,不能用于声明package级别的变量。第二种写法依赖了隐式的默认零值初始化,把string初始化为"".第三种写法很少用到,除非是在声明多个变量的时候。第四种写法显式的声明了变量类型,这在初始化值为同样类型时显得有点多余,不过在二者类型不同的时候就显得很必要了。在实际工作中,通常你应该使用第一种和第二种写法,显式初始化用于表明该变量的初始值很重要,而隐式初始化表明该变量的初始值并不重要。 + +正如上面所述,每一次循环迭代,string s都会得到全新的内容。+=语句通过拼接原来的string,一个空格字符,还有下一个参数文本,得到一个全新的string, 然后赋值给s。原来s的内容不再被使用,所以在一定的时候会被GC回收掉。 + +如果涉及的数据比较大,GC的操作就会开销比较大。更简单且效率更高的方案是使用strings包的Join函数,一句就搞定: + +gopl.io\/ch1\/echo3 + +func main\(\){ + +fmt.Println\(strings.Join\(os.Args\[1:\]," "\) + +} + +最后,如果我们不太关心显示的格式,而只是想看一下值,比如是为了调试,那么我们可以让Println来为我们格式化结果数据: + +fmt.Println\(os.Args\[1:\]\) + +这句语句的输出和我们从strings.Join得到的差不多,只不过多了一个中括号。所有的slice都可以这样打印。 + + + diff --git a/README.md b/README.md index f35b4c5..94473c1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,106 @@ -# My Awesome Book +# The Go Programming Language + +“Go 是一个开源的编程语言,它使得人们可以很容易的创建简洁、可靠、且高效的软件” ---摘自golang.org官网 + +``` +2007年九月, Google的三个在计算机界举足轻重的人物,Robert Griesemer\(Java HotSpot虚拟机和Google V8 JavaScript engine的核心开发者之一\)、Ken Thompson\(Unix操作系统的联合开发者,B语言之父,C语言联合创造者\)、以及Rob Pike\(Plan9分布式操作系统作者,同Ken Thompson一起创造了UTF-8编码\) , 开始构想Go语言的蓝图。2009年11月,Go的第一个版本发布。Go语言及其配套的一系列工具集的主要设计目标是要兼具足够的表达力和高效性。这里高效性值得是同时在编译效率和执行效率方面都高效,并且可以帮助程序员高效的写出可靠稳定的程序。 + + Go猛一看长得比较像C语言。和C语言一样,Go面向的是专业的程序设计人员,玩的溜的老鸟可以利用Go在最小的开销代价下获得极大的性能。不过并不能说Go只是一个升级版的C。Go语言在设计的时候借鉴移植了不少其他语言的有点,同时也吸取其他语言的教训避开了一些容易导致复杂性和不可靠性的坑。Go语言在处理并发性问题方面有着过人的天资,并且在处理数据抽象和面向对象编程问题时灵活得令人发指。此外,Go语言拥有程序员们喜闻乐见的GC机制,可以自动管理内存 +``` + +``` +Go语言特别适合拿来做网络服务器,或是一些工具软件。不过作为一门通用编程语言,Go当然也可以拿来做一些其他领域的事情,比如图像处理,移动应用,或是机器学习等等。Go语言正在逐步取代一些弱类型的脚本语言,这得益于Go很好的平衡了表达性和安全性。Go语言通常可以获得比动态语言更高的执行速度,同时不会因为未知类型之类的错误导致程序挂掉。 + +Go是一个开源醒目,这意味着任何人只要感兴趣都可以自由的研究Go的编译器,标准库以及相关工具库代码。来自全世界的爱好者们活跃在社区里,一起为Go项目提交着各种各样的代码。Go语言可以运行于任何类Unix操作系统,包括Linux,FreeBSD,OpenBSD,Mac OS X,甚至还包括Plan9 系统,当然微软的Windows也在内。在这些操作系统上写的Go程序不需要修改就可以移植到其他操作系统上运行。 +``` + +本书主要目的在于帮助你尽快的开始上手Go语言,让你高效的掌握Go语言的特性,能够充分利用Go语言及其标准库的强大之处,写出清晰、地道、高效的程序。 + +Go语言的身世 + +和生物物种的进化类似,优秀的编程语言也会在一起杂交出新的编程语言。这些新语言不但兼具其祖先的优势,有时因为杂交优势还能获得出乎意料的惊喜。从语言演变的影响我们可以了解很多关于语言设计时的考虑,为什么语言要设计成这个样子,又到底是为了适应什么样的应用环境,等等等等。下图就展现了Go语言设计时所考虑到的早期其他编程语言的影响: + +![](/assets/ancestors.png) + +Go 有时候会被描述为“和C长得很像的语言”,或者说成是“21世纪的C语言”。 从C语言那里,Go继承了表达式语法,控制流语句,基本数据类型,call-by-value参数传递机制,指针,还有最重要的一个特性:也就是C语言所强调的,把程序编译成有效的机器编码,原生的同目标操作系统配合高效工作。 + +``` + 不过Go的家庭族谱上还有其他的祖先。其中一个主要的分支来自于Niklaus Wirth创造的一系列语言。这一支的影响始于Pascal语言,然后从Modula-2语言引入了package包的概念,借鉴Oberon语言去掉了module interface稳健和module implementation +``` + +稳健的区别,而Oberon-2语言则影响了Go的package语法,import语法,和declaration语法, Object Oberon则提供了函数声明的语法格式参考。 + +``` + 另一支Go的祖先则是来自于贝尔实验室的一系列小众编程语言。这一支祖先虽然小众,但是对Go的特性却影响重大,正是由于该分支对Go的影响,才使得Go能够明显的同其他现代编程语言区分开来。这一系列语言的原型都来自于1978年Tony Hoare关于并发编程原理的论文。Tony提出了communicating sequential processes\(CSP\)的概念。在CSP模型中,一个程序实际上是一堆并行进程的组合,进程间没有共享的状态,进程间相互通讯通过频道(channel)来进行。不过Hoare的CSP描述只是一个概念上的语言,重点是说明并发性的一些基础概念。这个时候的CSP并不是一个用来可以编写可执行程序的编程语言实现。 +``` + +Rob Pike和他的小伙伴们则开始动手进行一些实验性工作,把CSP理论落实到实际的编程语言上来。最早的实验产物被命名为Squeak(英文“吱吱声”之意,意为用于和老鼠交流的语言)。这玩儿真是用来和老鼠交流的——该语言主要用于处理鼠标和键盘的事件信息,用的是静态创建的channels。然后这伙人又捣鼓出来了升级版Newsqueak,加入了类似C语言的语句和表达式语法,以及类似Pascal语言的类型标注。Newsqueak已经是一个支持垃圾回收的纯功能性语言了,虽然用途仍然是管理键盘、鼠标,以及一些窗口消息事件。此时的Channels作为最高类型,可以支持动态创建,也可以以变量的形式存储。 + +Plan9操作系统在设计的时候也传承了这些成果,弄出来一个Alef语言。 Alef尝试把Newsqueak变成一种可视的系统编程语言,不过Alef去掉了GC的特性,这导致其在并发方面相当的挫。 + +Go的其他一些特性也分散的体现出一些不在族谱上的其他语言的特性。比如iota或多或少受到A语言的影响,而嵌套函数中的语义范围则来自于Scheme语言(大多数在Scheme之后诞生的编程语言都这么干)。当然也会有一些突变的新特性,比如Go创造性的slice概念,既提供了可以高效随机访问的动态数组,又不像传统的链表那样要考虑一些链接的复杂性。另外defer语句也是Go的首创。 + +Go语言计划 + +所有的编程语言都反映了其创造者的编程哲学,这其中通常会包含对现有编程语言的某些弱点的加强和改进。Go语言项目诞生之时Google正饱受与日俱增的系统复杂性困扰,而这并不只是Google一家会面临的问题,几乎所有的软件系统都会随着时间推移和功能增加,变得越来越无法维护。 + +正如Rob Pike指出的那样,复杂性是乘法属性的问题。修复系统中某一部分的问题会导致该部分慢慢变得更复杂,并且这一定会导致系统其他部分的复杂度增加。虽然从长期来看保持系统简洁对于一个好的软件来说至关重要,但是在日常开发的时候系统简洁性通常会让位于日益增加的新功能和新的配置属性,在要求出活儿越快越好的压力下经常会选择忽视系统的简洁性需求。 + +系统简洁性要想做好,就必须在项目开始之初就投入更多的精力来深刻理解待解决问题的本质,并且要求在整个项目生命周期中都严格遵守规则,认真区分哪些改进是好的,哪些改动是不好的,哪些改动是有害的会带来问题的。足够的精力投入下去,才能做到所谓好的改进,才能毫不妥协的实现Fred Brooks所说的设计上的“概念完整性”。糟糕的改动可达不到这一点,而有害的改动会牺牲系统简洁性来换取一时的方便。只有设计时充分考虑系统简洁性的需求,才可能做出一个可靠,安全得系统,才能保证系统演进的过程中保持一致性。 + +Go项目包括Go语言本身,相关工具集,标准库,还有最重要的一点:根生的简洁性思想。作为一门最新的高级编程语言,Go得益于不少前人淌出来的经验。在基本功能方面Go做的很好, Go拥有GC,软件包package,最高级函数,语义范围,系统调用接口,还有通常以UTF-8编码的不可变String。 但是相比其他一些语言来说Go的功能要少一些,而且并没有计划增多。比如说,Go没有隐式的数字转换,没有构造函数和析构函数,没有运算符重载,没有缺省的参数值,没有继承,没有泛型,没有异常,没有宏,没有函数标注,也没有线程本地存储。Go语言是成熟、稳定的,并且后向兼容,老版本的Go写的程序可以被新版编译器编译,也可以和新版标准库一起工作。 + +Go的类型系统对于避免绝大多数困扰程序员的粗心问题来说已经足够,不过比起其他可比的强类型语言来说要更简单一些。这在一个类型框架更广泛的架构中有时可能会导致一些孤立的无类型编程,Go程序员也无法像C++程序员和Haskell程序员那样深入的使用类型检查的证据来保证类型安全。不过在实战中Go给程序员提供了足够的安全性和运行时性能,这归功于一个相对强壮却并不复杂的类型系统。 + +Go鼓励使用现代计算机系统设计的一些先进理念,尤其是对于局部性原理的重要性。其內建的数据类型和大多数标准库的数据结构都是可以即开即用的,不需要显示的初始化或是隐式的构造函数,因此相对就少了一些隐藏的内存分配和写入。Go的聚合类型(Structs和arrays)直接保存数据元素,这就比使用非直接字段的语言消耗更少的存储和分配开销,也没有那么多的指针跳转。而且因为现代计算机是一个并行的机器系统,Go实现了基于CSP的并发机制。Go轻量级的线程(或者称为goroutines)的栈大小很小,以至于创建一个goroutine的开销小到实践中你可以同时创建上百万个goroutine一起跑。 + +Go标准库经常被描述为“自带电池”, 提供了干净的构件块,还有各种API: I\/O , 文本处理,图片,加密, 网络, 分布式应用,支持各种标准文件格式和协议。标准库和工具集通过广泛使用约定俗成,减少了配置和解释的需求,因此简化了程序逻辑,是的不同的Go程序之间在处理相似问题时更为相似,因此更容易被其他人所理解。用go tool编译的项目只需要使用文件名,标识名,还有一个可选的特殊注释就可以决定项目的所有库,可执行文件,测试,跑分,示例,平台特殊的变量,还有文档。Go源码本身包含了编译规范。 + +本书结构: + +我们假定读者已经使用过一种或多种编程语言码过代码,比如C,C++,Java等编译性语言,或是解释型语言比如Python,Ruby,JavaScript等,所以我们不会像对待全新菜鸟那样掰碎了一口一口喂。其实语法看上去是相近的,变量、常量,表达式,控制流,还有函数的概念也都差不多。 + +第一章是一个基本概念的引导。这都是日常工作中经常用的东西,比如读写文件,格式化文本,创建图片,在server和client之间通信等等。 + +第二章描述了一个Go程序的结构组件——声明,变量,新类型,包,还有文件, 作用范围。第三章讨论numbers, booleans, strings, 还有常量,并且解释了如何用Unicode编码。第四章描述了复合类型,也就是用简单基本类型拼出来的新类型,比如数组,map,struct,还有slice,也就是Go的动态链表。第五章讨论了函数,还有错误处理,panic,recover,还有defer语句。 + +第一到第五章都是基础概念,这些都是主流编程语言都有的东西。Go的语法和风格有时候会和其他语言有些不同,不过大多数程序员都可以快速上手。剩下的章节集中讨论Go语言和其他语言不太一样的地方: 函数,接口,并发,包,测试,还有反射。 + +Go的面向对象编程方法也很特别。在Go的世界里没有类继承,事实上也没有class的概念。复杂的对象行为是通过简单行为的组合来完成的,木有继承。函数方法可以同任何用户定义的类型相关联,并不限于Struct,另外具体类型和抽象类型(接口)的关系是隐含的,所以一个具体类型可能会对应一个设计者在编程时并不知道的接口。在第六章第七章中我们进一步讨论函数。 + +第八章讲的是Go的并发,这是基于通信序列过程模型\(CSP\)实现的。有两个重要的组成部分: goroutines和channels。第九章解释了传统的基于共享变量的并发集中的各个方面。 + +第十章描述了包,也就是管理libraries的机制。这一章里也会说一下如何搞笑的使用go tool, 包括在编译,测试,跑分,格式化代码,文档以及其他任务。这都是一个命令搞定的。 + +第11章重点处理测试问题。这方面Go有个轻量级的方案,是一个可圈可点的亮点。得益于简洁的标准库和工具集,Go没有过于抽象的测试架构。测试库提供了一个基础架构,在其之上可以根据实际项目需要构建更加复杂的抽象。 + +第12章讨论了反射。就是程序在运行时检查自己的表述的能力。反射是一个有力的工具,不过用的时候要小心。这一章通过介绍一些Go的标准库是如何通过反射来实现的,以求解释清楚如何正确的权衡反射的应用。第13章解释了使用不安全的包绕过Go的类型系统进行底层编程的血淋淋的细节,也说明了什么时候这么做是合适的。 + +每一章都有一些练习来帮助检验对本章的理解是否深刻,同时这些练习也是对正文内容很好的补充和扩展。 + +几乎所有的详细代码都可以在gopl.io的git库中下载。每个例子都标识有包导入路径可以很方便的通过go get命令获取,编译,安装。你需要选择一个目录作为你的Go workspace, 然后把GOPATH环境变量指到该目录。如果需要也可以用go tool来创建这个目录,例如: + +![](/assets/1.png) + +运行这些示例代码需要你的Go版本在1.5以上。 + +![](/assets/1`2.png) + +如果你的Go版本较低或是没有安装Go环境,请参考官方的安装指南安装新版本 https:\/\/golang.org\/doc\/install + +**到哪儿去找更多的信息** + +关于Go的最好的信息源应该是官网https:\/\/golang.org,上面有很多文档,包括Go语言规范,标准包等等。官网上还有引导教程告诉你如何写以及如何写出好的Go程序,还有大量不错的文章和视频资源,可以作为本书很好的补充内容。还有个Go语言博客,blog.golang.org上面会有一些关于Go语言的不错的文章,主题涵盖Go语言的现状,未来的计划,研讨会报告,还有一些Go相关话题的深入讨论。 + +线上Go语言资源有一个纸质书无法达到的有点就是可以在描述语言特性的网页上马上跑一下相关的Go程序实际体验一把。这个功能可以在play.golang.org上通过GoPlayground实现,也可以集成到其他网页,比如golang.org的主页,或是集成了godoc工具的文档里。 + +Playground技术使得简单的代码实验非常的方便,可以随时随地的写一个小程序来检验关于语法,语义,或是标准库的理解。这种思想完全可以取代其他编程语言的REPL循环,即先读代码,然后评估,然后打印的循环。固定的URL方便交流Go代码,也方便报告bug或是提建议。 + +在Playground之上建立的Go教程tour.golang.org是一系列简短的交互课程,讲述了Go语言的基本思想和组件,带领读者一步一步的了解Go语言。 + +Playground和线上教程的主要缺点是他们只能导入标准库,很多库的功能还被限制了,比如networking库。这当然是出于安全的考虑。另外每一次编译和运行都要求网络连接,所以要想进行更复杂一些的实验,你还是必须在自己的电脑上运行Go语言。幸运的是下载过程很直接,从golang.org获取并安装Go环境应该是分分钟的事情。 + +既然Go是一个开源项目,你当然可以阅读标准库中任意类型或是函数的实现源码,网址是https:\/\/golang\/org\/pkg。下载的环境里也有。阅读源码可以深刻的理解整个机制是如何运行的,了解更多语言的细节,最起码可以学学专家们是如何写地道的Go程序的。 + + -This file file serves as your book's preface, a great place to describe your book's content and ideas. diff --git a/SUMMARY.md b/SUMMARY.md index 84616cd..44e00e4 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,3 +1,6 @@ # Summary -* [First Chapter](chapter1.md) +* [Introduction](README.md) +* [第一章 导论](chapter1.md) +* [1.1 Hello,World](11-hello.md) + diff --git a/assets/1.png b/assets/1.png new file mode 100644 index 0000000..5f0735f Binary files /dev/null and b/assets/1.png differ diff --git a/assets/1`2.png b/assets/1`2.png new file mode 100644 index 0000000..55d1e07 Binary files /dev/null and b/assets/1`2.png differ diff --git a/assets/ancestors.png b/assets/ancestors.png new file mode 100644 index 0000000..c33a058 Binary files /dev/null and b/assets/ancestors.png differ diff --git a/assets/helloworld.png b/assets/helloworld.png new file mode 100644 index 0000000..a16fa59 Binary files /dev/null and b/assets/helloworld.png differ diff --git a/chapter1.md b/chapter1.md index c0e22c6..d3323d8 100644 --- a/chapter1.md +++ b/chapter1.md @@ -1,3 +1,8 @@ -# First Chapter +# 第一章 导论 + +本章将会带领大家浏览一下Go语言的基础模块。我们希望通过足够的信息和例子带领大家迅速开始动手做一些实际的实验。这里以及全书的例子都是针对真实世界中的典型开发任务设计的。本章主要是让大家感受一下Go语言写出来的程序是什么感觉,包括简单的文件处理,一点点图片相关的处理,还有网络并发的client和server。当然我们不会在第一章里就展开所有的细节,不过学习一门新的编程语言的时候,研究如何用新语言来写这几类基础的程序是一个非常好的开始。 + +当你学习一门新语言的时候,潜意识很自然的就会引导你像写以往熟悉的语言那样去写新的程序。在学习Go的时候应该有意识的避免这样做,我们会在书中演示和解释如何写地道的Go语言程序,所以在写你自己的代码的时候应该以本书的示例代码作为指导。 + + -GitBook allows you to organize your book into chapters, each chapter is stored in a separate file like this one.