-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEffective Go - The Go Programming Language.html
2584 lines (2197 loc) · 122 KB
/
Effective Go - The Go Programming Language.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Effective Go - The Go Programming Language</title>
<link type="text/css" rel="stylesheet" href="Effective%20Go%20-%20The%20Go%20Programming%20Language_files/style.css">
<script type="text/javascript" async="" src="Effective%20Go%20-%20The%20Go%20Programming%20Language_files/platform_002.js"></script><script src="Effective%20Go%20-%20The%20Go%20Programming%20Language_files/cbgapi" async=""></script><script src="Effective%20Go%20-%20The%20Go%20Programming%20Language_files/cbgapi_002" async=""></script><script src="Effective%20Go%20-%20The%20Go%20Programming%20Language_files/platform.js" async="" type="text/javascript"></script><script type="text/javascript">window.initFuncs = [];</script>
</head>
<body>
<div id="topbar" class="wide"><div class="container">
<form method="GET" action="/search">
<div style="min-width: 710px;" id="menu">
<a href="http://localhost:6060/doc/">Documents</a>
<a href="http://localhost:6060/pkg/">Packages</a>
<a href="http://localhost:6060/project/">The Project</a>
<a href="http://localhost:6060/help/">Help</a>
<a href="http://localhost:6060/blog/">Blog</a>
<input id="search" name="q" class="inactive" value="Search" placeholder="Search" type="text">
</div>
<div id="heading"><a href="http://localhost:6060/">The Go Programming Language</a></div>
</form>
</div></div>
<div tabindex="-1" style="outline: 0px none;" id="page" class="wide">
<div class="container">
<div id="plusone"><div style="position: absolute; width: 450px; left: -10000px;" id="___plusone_0"><iframe hspace="0" marginheight="0" marginwidth="0" scrolling="no" style="position:absolute;top:-10000px;width:450px;margin:0px;border-style:none" tabindex="0" vspace="0" id="I0_1478063940638" name="I0_1478063940638" src="https://apis.google.com/u/0/_/+1/fastbutton?usegapi=1&bsv=o&size=small&annotation=none&origin=http%3A%2F%2Fwww.hellogcc.org&url=http%3A%2F%2Fwww.hellogcc.org%2Feffective_go.html&gsrc=3p&ic=1&jsh=m%3B%2F_%2Fscs%2Fapps-static%2F_%2Fjs%2Fk%3Doz.gapi.zh_CN.8XZSeOvksVk.O%2Fm%3D__features__%2Fam%3DIQ%2Frt%3Dj%2Fd%3D1%2Ft%3Dzcms%2Frs%3DAItRSTNwjb_pve5n6TpFKpNTk0XnS1SS7g#_methods=onPlusOne%2C_ready%2C_close%2C_open%2C_resizeMe%2C_renderstart%2Concircled%2Cdrefresh%2Cerefresh&id=I0_1478063940638&parent=http%3A%2F%2Fwww.hellogcc.org&pfname=&rpctoken=37400968" width="100%" frameborder="0"></iframe></div><g:plusone size="small" annotation="none" data-gapiscan="true" data-onload="true" data-gapistub="true"></g:plusone></div>
<h1>Effective Go</h1>
<div id="nav"><table class="unruled"><tbody><tr><td class="first"><dl><dt><a href="#introduction">简介</a></dt><dd><a href="#examples">例子</a></dd><dt><a href="#formatting">格式</a></dt><dt><a href="#commentary">注释</a></dt><dt><a href="#names">名字</a></dt><dd><a href="#package-names">程序包名</a></dd><dd><a href="#Getters">Get方法</a></dd><dd><a href="#interface-names">接口名</a></dd><dd><a href="#mixed-caps">混合大小写</a></dd><dt><a href="#semicolons">分号</a></dt><dt><a href="#control-structures">控制结构</a></dt><dd><a href="#if">If</a></dd><dd><a href="#redeclaration">重新声明和重新赋值</a></dd><dd><a href="#for">For</a></dd><dd><a href="#switch">Switch</a></dd><dd><a href="#type_switch">类型switch</a></dd><dt><a href="#functions">函数</a></dt><dd><a href="#multiple-returns">多个返回值</a></dd><dd><a href="#named-results">命名的结果参数</a></dd><dd><a href="#defer">延期执行</a></dd><dt><a href="#data">数据</a></dt><dd><a href="#allocation_new">使用new进行分配</a></dd><dd><a href="#composite_literals">构造器和复合文字</a></dd><dd><a href="#allocation_make">使用make进行分配</a></dd><dd><a href="#arrays">数组</a></dd><dd><a href="#slices">切片</a></dd><dd><a href="#two_dimensional_slices">二维切片</a></dd><dd><a href="#maps">Maps</a></dd><dd><a href="#printing">打印输出</a></dd><dd><a href="#append">append内建函数</a></dd><dt><a href="#initialization">初始化</a></dt></dl></td><td><dl><dd><a href="#constants">常量</a></dd><dd><a href="#variables">变量</a></dd><dd><a href="#init">init函数</a></dd><dt><a href="#methods">方法</a></dt><dd><a href="#pointers_vs_values">指针 vs. 值</a></dd><dt><a href="#interfaces_and_types">接口和其它类型</a></dt><dd><a href="#interfaces">接口</a></dd><dd><a href="#conversions">转换</a></dd><dd><a href="#interface_conversions">接口转换和类型断言</a></dd><dd><a href="#generality">概述</a></dd><dd><a href="#interface_methods">接口和方法</a></dd><dt><a href="#blank">空白标识符</a></dt><dd><a href="#blank_assign">空白标识符在多赋值语句中的使用</a></dd><dd><a href="#blank_unused">未使用的导入和变量</a></dd><dd><a href="#blank_import">副作用式导入</a></dd><dd><a href="#blank_implements">接口检查</a></dd><dt><a href="#embedding">内嵌(Embedding)</a></dt><dt><a href="#concurrency">并发</a></dt><dd><a href="#sharing">以通信实现共享</a></dd><dd><a href="#goroutines">Goroutines</a></dd><dd><a href="#channels">Channels</a></dd><dd><a href="#chan_of_chan">Channel类型的Channel</a></dd><dd><a href="#parallel">并行</a></dd><dd><a href="#leaky_buffer">一个“Leaky Buffer”的示例</a></dd><dt><a href="#errors">错误</a></dt><dd><a href="#panic">严重故障(Panic)</a></dd><dd><a href="#recover">恢复(Recover)</a></dd><dt><a href="#web_server">一个web服务示例</a></dt><dt><a href="#translator">中文译者</a></dt></dl></td></tr></tbody></table></div>
<h2 id="introduction">简介</h2>
<p>
Go是一个新的语言。虽然是借鉴了现有的语言,但是它独有的特性可以使得高效的Go程序,与其它语言编写的程序相比,大不相同。直接将C++或者Java
程序转换为Go程序,是不可能产生令人满意的结果—Java程序是使用Java编写的,而不是Go。另一方面,从Go的角度考虑问题则会产生成功的,而且
大不相同的程序。换句话说,想要编写好的Go程序,理解它的特性和风格是非常重要的。了解Go语言编程中已有的约定也非常重要,例如命名,格式,程序结
构,等等。这会使得其他Go程序员容易理解你编写的程序。
</p>
<p>
该文档对如何编写清晰,符合语言规范的Go代码,给出了一些建议。你应该先阅读<a href="http://localhost:6060/ref/spec">language specification</a>,<a href="http://tour.golang.org/">Tour of Go</a>和<a href="http://localhost:6060/doc/code.html">How to Write Go Code</a>,然后将该文档作为扩展阅读。
</p>
<h3 id="examples">例子</h3>
<p>
<a href="http://localhost:6060/src/pkg/">Go package sources</a>旨在不仅作为核心库来使用,而且还可以作为如何使用语言的例子。此外,许多程序包都包含了可以在<a href="http://golang.org/">golang.org</a>网站上独立执行的例子,例如<a href="http://golang.org/pkg/strings/#example_Map">这一个</a>(如果需要,点击单词"Example"来打开)。如果你对如何处理一个问题,或者如何进行实现有疑问,那么库中的文档,代码和例子可以提供答案,概念和背景。
</p>
<h2 id="formatting">格式</h2>
<p>
格式化是一个最具争议,但又无关紧要的问题。人们可以习惯于不同的格式风格。但是,最好不必这样,这就不用在每个人是否遵守相同风格的话题上花费时间了。问题是在没有一个长效的风格指导下,如何达到这样美好的乌托邦。
</p>
<p>
对于Go,我们采取了不同寻常的方式,让机器来处理大多数的格式问题。程序<code>gofmt</code>(也可以用<code>go fmt</code>,其操作于程序包的级别,而不是源文件级别),读入一个Go程序,然后输出按照标准风格缩进和垂直对齐的源码,并且保留了根据需要进行重新格式化的注释。如果你想知道如何处理某种新的布局情况,可以运行<code>gofmt</code>;如果答案看起来不正确,则需要重新组织你的程序(或者提交一个关于<code>gofmt</code>的bug),不要把问题绕过去。
</p>
<p>
举个例子,不需要花费时间对结构体中每个域的注释进行排列。<code>Gofmt</code>将会替你完成这些。给定一个声明
</p>
<pre>type T struct {
name string // name of the object
value int // its value
}
</pre>
<p>
<code>gofmt</code>将会按列进行排列:
</p>
<pre>type T struct {
name string // name of the object
value int // its value
}
</pre>
<p>
标准程序包中的所有Go代码,都已经使用<code>gofmt</code>进行了格式化。
</p>
<p>
还是有一些格式化的细节的。非常简短:
</p>
<dl>
<dt>缩进</dt>
<dd>我们使用tab进行缩进,这是<code>gofmt</code>的缺省输出。只有在你必须的时候才使用空格。
</dd>
<dt>行长度</dt>
<dd>
Go没有行长度限制。不必担心会有打孔卡片溢出。如果感觉一行太长,可以折成几行,并额外使用一个tab进行缩进。
</dd>
<dt>括号</dt>
<dd>
Go相比C和Java,很少需要括号:控制结构(<code>if</code>,<code>for</code>,<code>switch</code>)的语法不需要括号。而且,操作符优先级更短,更清晰。这样,
<pre>x<<8 + y<<16
</pre>
的含义就已经由空格表明了。这不像其它语言。
</dd>
</dl>
<h2 id="commentary">注释</h2>
<p>
Go提供了C风格的块注释<code>/* */</code>和C++风格的行注释<code>//</code>。通常为行注释;块注释大多数作为程序包的注释,但也可以用于一个表达式中,或者用来注释掉一大片代码。
</p>
<p>
程序—同时又是网络服务器—<code>godoc</code>,用来处理Go源文件,抽取有关程序包内容的文档。在顶层声明之前出现,并且中间没有换行的注释,会随着声明一起被抽取,作为该项的解释性文本。这些注释的本质和风格决定了<code>godoc</code>所产生文档的质量。
</p>
<p>
每个程序包都应该有一个<i>包注释</i>,一个位于package子句之前的块注释。对于有多个文件的程序包,包注释只需要出现在一个文件中,任何一个文件都可以。包注释应该用来介绍该程序包,并且提供与整个程序包相关的信息。它将会首先出现在<code>godoc</code>页面上,并会建立后续的详细文档。
</p>
<pre>/*
Package regexp implements a simple library for regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
concatenation:
{ closure }
closure:
term [ '*' | '+' | '?' ]
term:
'^'
'$'
'.'
character
'[' [ '^' ] character-ranges ']'
'(' regexp ')'
*/
package regexp
</pre>
<p>
如果程序包很简单,则包注释可以非常简短。
</p>
<pre>// Package path implements utility routines for
// manipulating slash-separated filename paths.
</pre>
<p>
注释不需要额外的格式,例如星号横幅。生成的输出甚至可能会不按照固定宽度的字体进行展现,所以不要依靠用空格进行对齐—<code>godoc</code>,就像<code>gofmt</code>,会处理这些事情。注释是不作解析的普通文本,所以HTML和其它注解,例如<code>_this_</code>,将会<i>逐字的</i>被复制。对于缩进的文本,<code>godoc</code>确实会进行调整,来按照固定宽度的字体进行显示,这适合于程序片段。<a href="http://golang.org/pkg/fmt/"><code>fmt</code> package</a>的包注释使用了这种方式来获得良好的效果。
</p>
<p>
根据上下文,<code>godoc</code>甚至可能不会重新格式化注释,所以要确保它们看起来非常直接:使用正确的拼写,标点,以及语句结构,将较长的行进行折叠,等等。
</p>
<p>
在程序包里面,任何直接位于顶层声明之前的注释,都会作为该声明的<i>文档注释</i>。程序中每一个被导出的(大写的)名字,都应该有一个文档注释。
</p>
<p>
文档注释作为完整的语句可以工作的最好,可以允许各种自动化的展现。第一条语句应该为一条概括语句,并且使用被声明的名字作为开头。
</p>
<pre>// Compile parses a regular expression and returns, if successful, a Regexp
// object that can be used to match against text.
func Compile(str string) (regexp *Regexp, err error) {
</pre>
<p>
如果都是使用名字来起始一个注释,那么就可以通过<code>grep</code>来处理<code>godoc</code>的输出。设想你正在查找正规表达式的解析函数,但是不记得名字“Compile”了,那么,你运行了命令
</p>
<pre>$ godoc regexp | grep parse
</pre>
<p>
如果程序包中所有的文档注释都起始于"This function...",那么<code>grep</code>将无法帮助你想起这个名字。但是,因为程序包是使用名字来起始每个文档注释,所以你将会看到类似这样的信息,这将使你想起你要查找的单词。
</p>
<pre>$ godoc regexp | grep parse
Compile parses a regular expression and returns, if successful, a Regexp
parsed. It simplifies safe initialization of global variables holding
cannot be parsed. It simplifies safe initialization of global variables
$
</pre>
<p>
Go的声明语法允许对声明进行组合。单个的文档注释可以用来介绍一组相关的常量或者变量。由于展现的是整个声明,这样的注释通常非常肤浅。
</p>
<pre>// Error codes returned by failures to parse an expression.
var (
ErrInternal = errors.New("regexp: internal error")
ErrUnmatchedLpar = errors.New("regexp: unmatched '('")
ErrUnmatchedRpar = errors.New("regexp: unmatched ')'")
...
)
</pre>
<p>
分组还可以用来指示各项之间的关系,例如一组实际上由一个互斥进行保护的变量。
</p>
<pre>var (
countLock sync.Mutex
inputCount uint32
outputCount uint32
errorCount uint32
)
</pre>
<h2 id="names">名字</h2>
<p>
和其它语言一样,名字在Go中是非常重要的。它们甚至还具有语义的效果:一个名字在程序包之外的可见性是由它的首字符是否为大写来确定的。因此,值得花费一些时间来讨论Go程序中的命名约定。
</p>
<h3 id="package-names">程序包名</h3>
<p>
当一个程序包被导入,程序包名便可以用来访问它的内容。在
</p>
<pre>import "bytes"
</pre>
<p>
之后,导入的程序包便可以谈到<code>bytes.Buffer</code>。如果每个使用程序包的人都可以使用相同的名字来引用它的内容,这会是
很有帮助的。这意味着程序包名要很好:简短,简明,能引起共鸣的。按照惯例,程序包使用小写,一个单词的名字;不需要使用下划线或者混合大小写。要力求简
短,因为每个使用你的程序包的人都将敲入那个名字。不用担心会与<i>先前的</i>有冲突。程序包名只是导入的缺省名字;其不需要在所有源代码中是唯一的。对于很少出现的冲突情况下,导入的程序包可以选择一个不同的名字在本地使用。不管怎样,冲突是很少的,因为导入的文件名确定了所要使用的程序包。
</p>
<p>
另一种约定是,程序包名为其源目录的基础名;在<code>src/pkg/encoding/base64</code>中的程序包,是作为<code>"encoding/base64"</code>来导入的,但是名字为<code>base64</code>,而不是<code>encoding_base64</code>或<code>encodingBase64</code>。
</p>
<p>
程序包的导入者将使用名字来引用其内容,因此在程序包中被导出的名字可以利用这个事实来避免口吃现象。(不要使用<code>import .</code>标记,这将会简化那些必须在程序包之外运行,本不应该避免的测试)例如,在<code>bufio</code>程序包中的带缓冲的读入类型叫做<code>Reader</code>,而不是<code>BufReader</code>,因为用户看到的是<code>bufio.Reader</code>,一个清晰,简明的名字。而且,因为被导入的实体总是通过它们的程序包名来寻址,所以<code>bufio.Reader</code>和<code>io.Reader</code>并不冲突。类似的,为<code>ring.Ring</code>创建一个新实例的函数—在Go中是定义一个<em>构造器</em>—通常会被叫做<code>NewRing</code>,但是由于<code>Ring</code>是程序包导出的唯一类型,由于程序包叫做<code>ring</code>,所以它只叫做<code>New</code>。这样,程序包的客户将会看到<code>ring.New</code>。使用程序包结构可以帮助你选择好的名字。
</p>
<p>
另一个小例子是<code>once.Do</code>;<code>once.Do(setup)</code>很好读,写成<code>once.DoOrWaitUntilDone(setup)</code>并不会有所改善。长名字并不会自动使得事物更易读。具有帮助性的文档注释往往会比格外长的名字更有用。
</p>
<h3 id="Getters">Get方法</h3>
<p>
Go不提供对Get方法和Set方法的自动支持。你自己提供Get方法和Set方法是没有错的,通常这么做是合适的。但是,在Get方法的名字中加上<code>Get</code>,是不符合语言习惯的,并且也没有必要。如果你有一个域叫做<code>owner</code>(小写,不被导出),则Get方法应该叫做<code>Owner</code>(大写,被导出),而不是<code>GetOwner</code>。对于要导出的,使用大写名字,提供了区别域和方法的钩子。Set方法,如果需要,则可以叫做<code>SetOwner</code>。这些名字在实际中都很好读:
</p>
<pre>owner := obj.Owner()
if owner != user {
obj.SetOwner(user)
}
</pre>
<h3 id="interface-names">接口名</h3>
<p>
按照约定,单个方法的接口使用方法名加上“er”后缀来命名,或者类似的修改来构造一个施动者名词:<code>Reader</code>,<code>Writer</code>,<code>Formatter</code>,<code>CloseNotifier</code>等。
</p>
<p>
有许多这样的名字,最有效的方式就是尊重它们,以及它们所体现的函数名字。<code>Read</code>,<code>Write</code>,<code>Close</code>,<code>Flush</code>,<code>String</code>等,都具有规范的签名和含义。为了避免混淆,不要为你的方法使用这些名字,除非其具有相同的签名和含义。反过来讲,如果你的类型实现了一个和众所周知的类型具有相同含义的方法,那么就使用相同的名字和签名;例如,为你的字符串转换方法起名为<code>String</code>,而不是<code>ToString</code>。
</p>
<h3 id="mixed-caps">混合大小写</h3>
<p>
最后,Go约定使用<code>MixedCaps</code>或者<code>mixedCaps</code>的形式,而不是下划线来书写多个单词的名字。
</p>
<h2 id="semicolons">分号</h2>
<p>
类似于C,Go的规范语法是使用分号来终结语句的。但是于C不同的是,这些分号并不在源码中出现。词法分析器会在扫描时,使用简单的规则自动插入分号,因此输入文本中大部分是没有分号的。
</p>
<p>
规则是这样的,如果在换行之前的最后一个符号为一个标识符(包括像<code>int</code>和<code>float64</code>这样的单词),一个基本的文字,例如数字或者字符串常量,或者如下的一个符号
</p>
<pre>break continue fallthrough return ++ -- ) }
</pre>
<p>
则词法分析器总是会在符号之后插入一个分号。这可以总结为“如果换行出现在可以结束一条语句的符号之后,则插入一个分号”。
</p>
<p>
紧挨着右大括号之前的分号也可以省略掉,这样,语句
</p>
<pre> go func() { for { dst <- <-src } }()
</pre>
<p>
就不需要分号。地道的Go程序只在<code>for</code>循环子句中使用分号,来分开初始化,条件和继续执行,这些元素。分号也用于在一行中分开多条语句,这也是你编写代码应该采用的方式。
</p>
<p>
分号插入规则所导致的一个结果是,你不能将控制结构(<code>if</code>,<code>for</code>,<code>switch</code>或<code>select</code>)的左大括号放在下一行。如果这样做,则会在大括号之前插入一个分号,这将会带来不是想要的效果。应该这样编写
</p>
<pre>if i < f() {
g()
}
</pre>
<p>
而不是这样
</p>
<pre>if i < f() // wrong!
{ // wrong!
g()
}
</pre>
<h2 id="control-structures">控制结构</h2>
<p>
Go的控制结构与C的相关,但是有重要的区别。没有<code>do</code>或者<code>while</code>循环,只有一个稍微广义的<code>for</code>;<code>switch</code>更加灵活;<code>if</code>和<code>switch</code>接受一个像<code>for</code>那样可选的初始化语句;<code>break</code>和<code>continue</code>语句接受一个可选的标号来指定中断或继续什么;还有一些新的控制结构,包括类型switch和多路通信复用器(multiway communications multiplexer),<code>select</code>。语句也稍微有些不同:没有圆括号,并且控制结构体必须总是由大括号包裹。
</p>
<h3 id="if">If</h3>
<p>
Go中,简单的<code>if</code>看起来是这样的:
</p>
<pre>if x > 0 {
return y
}
</pre>
<p>
强制的大括号可以鼓励大家在多行中编写简单的<code>if</code>语句。不管怎样,这是一个好的风格,特别是当控制结构体包含了一条控制语句,例如<code>return</code>或者<code>break</code>。
</p>
<p>
既然<code>if</code>和<code>switch</code>接受一个初始化语句,那么常见的方式是用来建立一个局部变量。
</p>
<pre>if err := file.Chmod(0664); err != nil {
log.Print(err)
return err
}
</pre>
<p id="else">
在Go的库中,你会发现当<code>if</code>语句不会流向下一条语句时—也就是说,控制结构体结束于<code>break</code>,<code>continue</code>,<code>goto</code>或者<code>return</code>—则不必要的<code>else</code>会被省略掉。
</p>
<pre>f, err := os.Open(name)
if err != nil {
return err
}
codeUsing(f)
</pre>
<p>
这个例子是一种常见的情况,代码必须防范一系列的错误条件。如果成功的控制流是沿着页面往下走,来消除它们引起的错误情况,那么代码会非常易读。由于错误情况往往会结束于<code>return</code>语句,因此代码不需要有<code>else</code>语句。
</p>
<pre>f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)
</pre>
<h3 id="redeclaration">重新声明和重新赋值</h3>
<p>
另外:上一章节的最后一个例子,展示了<code>:=</code>短声明形式的工作细节。该声明调用了<code>os.Open</code>进行读取,
</p>
<pre>f, err := os.Open(name)
</pre>
<p>
该语句声明了两个变量,<code>f</code>和<code>err</code>。几行之后,又调用了<code>f.Stat</code>进行读取,
</p>
<pre>d, err := f.Stat()
</pre>
<p>
这看起来像是又声明了<code>d</code>和<code>err</code>。但是,注意<code>err</code>在两条语句中都出现了。这种重复是合法的:<code>err</code>是在第一条语句中被声明,而在第二条语句中只是被<em>重新赋值</em>。这意味着使用之前已经声明过的<code>err</code>变量调用<code>f.Stat</code>,只会是赋给其一个新的值。
</p>
<p>
在<code>:=</code>声明中,变量<code>v</code>即使已经被声明过,也可以出现,前提是:
</p>
<ul>
<li>该声明和<code>v</code>已有的声明在相同的作用域中(如果<code>v</code>已经在外面的作用域里被声明了,则该声明将会创建一个新的变量 §)</li>
<li>初始化中相应的值是可以被赋给<code>v</code>的</li>
<li>并且,声明中至少有其它一个变量将被声明为一个新的变量</li>
</ul>
<p>
这种不寻常的属性纯粹是从实用主义方面来考虑的。例如,这会使得在一个长的<code>if-else</code>链中,很容易地使用单个<code>err</code>值。你会经常看到这种用法。
</p>
<p>
§ 值得一提的是,在Go中,函数参数和返回值的作用域与函数体的作用域是相同的,虽然它们在词法上是出现在包裹函数体的大括号外面。
</p>
<h3 id="for">For</h3>
<p>
Go的<code>for</code>循环类似于—但又不等同于—C的。它统一了<code>for</code>和<code>while</code>,并且没有<code>do-while</code>。有三种形式,其中只有一个具有分号。
</p>
<pre>// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
</pre>
<p>
短声明使得在循环中很容易正确的声明索引变量。
</p>
<pre>sum := 0
for i := 0; i < 10; i++ {
sum += i
}
</pre>
<p>
如果你是在数组,切片,字符串或者map上进行循环,或者从channel中进行读取,则可以使用<code>range</code>子句来管理循环。
</p>
<pre>for key, value := range oldMap {
newMap[key] = value
}
</pre>
<p>
如果你只需要range中的第一项(key或者index),则可以丢弃第二个:
</p>
<pre>for key := range m {
if key.expired() {
delete(m, key)
}
}
</pre>
<p>
如果你只需要range中的第二项(value),则可以使用<em>空白标识符</em>,一个下划线,来丢弃第一个:
</p>
<pre>sum := 0
for _, value := range array {
sum += value
}
</pre>
<p>
空白标识符有许多用途,这在<a href="#blank">后面的章节</a>中会有介绍。
</p>
<p>
对于字符串,<code>range</code>会做更多的事情,通过解析UTF-8来拆分出单个的Unicode编码点。错误的编码会消耗一个字节,产生一个替代的符文(rune)U+FFFD。(名字(与内建类型相关联的)<code>rune</code>是Go的术语,用于指定一个单独的Unicode编码点。详情参见<a href="http://golang.org/ref/spec#Rune_literals">the language specification</a>)循环
</p>
<pre>for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
</pre>
<p>
会打印出
</p>
<pre>character U+65E5 '日' starts at byte position 0
character U+672C '本' starts at byte position 3
character U+FFFD '�' starts at byte position 6
character U+8A9E '語' starts at byte position 7
</pre>
<p>
最后,Go没有逗号操作符,并且<code>++</code>和<code>--</code>是语句而不是表达式。因此,如果你想在<code>for</code>中运行多个变量,你需要使用并行赋值(尽管这样会阻碍使用<code>++</code>和<code>--</code>)。
</p>
<pre>// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
</pre>
<h3 id="switch">Switch</h3>
<p>
Go的<code>switch</code>要比C的更加通用。表达式不需要为常量,甚至不需要为整数,case是按照从上到下的顺序进行求值,直到找到匹配的。如果<code>switch</code>没有表达式,则对<code>true</code>进行匹配。因此,可以—按照语言习惯—将<code>if</code>-<code>else</code>-<code>if</code>-<code>else</code>链写成一个<code>switch</code>。
</p>
<pre>func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
</pre>
<p>
switch不会自动从一个case子句跌落到下一个case子句。但是case可以使用逗号分隔的列表。
</p>
<pre>func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}
</pre>
<p>
虽然和其它类C的语言一样,使用<code>break</code>语句来提前中止<code>switch</code>在Go中几乎不怎么常见。不过,有时候是需要中断包含它的循环,而不是switch。在Go中,可以通过在循环上加一个标号,然后“breaking”到那个标号来达到目的。该例子展示了这些用法。
</p>
<pre>Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
</pre>
<p>
当然,<code>continue</code>语句也可以接受一个可选的标号,但是只能用于循环。
</p>
<p>
作为这个章节的结束,这里有一个对字节切片进行比较的程序,使用了两个<code>switch</code>语句:
</p>
<pre>// Compare returns an integer comparing the two byte slices,
// lexicographically.
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b
func Compare(a, b []byte) int {
for i := 0; i < len(a) && i < len(b); i++ {
switch {
case a[i] > b[i]:
return 1
case a[i] < b[i]:
return -1
}
}
switch {
case len(a) > len(b):
return 1
case len(a) < len(b):
return -1
}
return 0
}
</pre>
<h3 id="type_switch">类型switch</h3>
<p>
switch还可用于获得一个接口变量的动态类型。这种<em>类型switch</em>使用类型断言的语法,在括号中使用关键字<code>type</code>。如果switch
在表达式中声明了一个变量,则变量会在每个子句中具有对应的类型。比较符合语言习惯的方式是在这些case里重用一个名字,实际上是在每个case里声名一个新的变量,其具有相同的名字,但是不同的类型。
</p>
<pre>var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
</pre>
<h2 id="functions">函数</h2>
<h3 id="multiple-returns">多个返回值</h3>
<p>
Go的其中一个不同寻常的特点是,函数和方法可以返回多个值。这种形式可以用来改进C程序中几个笨拙的语言风格:返回一个错误,例如<code>-1</code>对应于<code>EOF</code>,同时修改一个由地址传递的参数。
</p>
<p>
在C中,一个写错误是由一个负的计数和一个隐藏在易变位置(a volatile location)的错误代码来表示的。在Go中,<code>Write</code>可以返回一个计数<i>和</i>一个错误:“是的,你写了一些字节,但并没有全部写完,由于设备已经被填满了”。在程序包<code>os</code>的文件中,<code>Write</code>方法的签名是:
</p>
<pre>func (file *File) Write(b []byte) (n int, err error)
</pre>
<p>
正如文档所言,其返回写入的字节数和一个非零的<code>error</code>,当<code>n</code><code>!=</code> <code>len(b)</code>的时候。这是一种常见的风格;更多的例子可以参见错误处理章节。
</p>
<p>
类似的方法使得不再需要传递一个返回值指针来模拟一个引用参数。这里有一个非常简单的函数,用来从字节切片中的一个位置抓取一个数,返回该数和下一个位置。
</p>
<pre>func nextInt(b []byte, i int) (int, int) {
for ; i < len(b) && !isDigit(b[i]); i++ {
}
x := 0
for ; i < len(b) && isDigit(b[i]); i++ {
x = x*10 + int(b[i]) - '0'
}
return x, i
}
</pre>
<p>
你可以使用它来扫描输入切片<code>b</code>中的数字,如:
</p>
<pre> for i := 0; i < len(b); {
x, i = nextInt(b, i)
fmt.Println(x)
}
</pre>
<h3 id="named-results">命名的结果参数</h3>
<p>
Go函数的返回或者结果“参数”可以给定一个名字,并作为一个普通变量来使用,就像是输入参数一样。当被命名时,它们在函数起始处被初始化为对应类型的零值;如果函数执行了没有参数的<code>return</code>语句,则结果参数的当前值便被作为要返回的值。
</p>
<p>
名字并不是强制的,但是可以使代码更加简短清晰:它们也是文档。如果我们将<code>nextInt</code>的结果进行命名,则其要返回的<code>int</code>是对应的哪一个就很显然了。
</p>
<pre>func nextInt(b []byte, pos int) (value, nextPos int) {
</pre>
<p>
因为命名结果是被初始化的,并且与没有参数的return绑定在一起,所以它们即简单又清晰。这里是一个<code>io.ReadFull</code>的版本,很好地使用了这些特性:
</p>
<pre>func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}
</pre>
<h3 id="defer">延期执行</h3>
<p>
Go的<code>defer</code>语句用来调度一个函数调用(<i>被延期的</i>函数),使其在执行<code>defer</code>的函数即将返回之前才被运行。这是一种不寻常但又很有效的方法,用于处理类似于不管函数通过哪个执行路径返回,资源都必须要被释放的情况。典型的例子是对一个互斥解锁,或者关闭一个文件。
</p>
<pre>// Contents returns the file's contents as a string.
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
var result []byte
buf := make([]byte, 100)
for {
n, err := f.Read(buf[0:])
result = append(result, buf[0:n]...) // append is discussed later.
if err != nil {
if err == io.EOF {
break
}
return "", err // f will be closed if we return here.
}
}
return string(result), nil // f will be closed if we return here.
}
</pre>
<p>
对像<code>Close</code>这样的函数调用进行延期,有两个好处。首先,其确保了你不会忘记关闭文件,如果你之后修改了函数增加一个新的返回路径,会很容易犯这样的错。其次,这意味着关闭操作紧挨着打开操作,这比将其放在函数结尾更加清晰。
</p>
<p>
被延期执行的函数,它的参数(包括接收者,如果函数是一个方法)是在<i>defer</i>执行的时候被求值的,而不是在<i>调用</i>执行的时候。这样除了不用担心变量随着函数的执行值会改变,这还意味着单个被延期执行的调用点可以延期多个函数执行。这里有一个简单的例子。
</p>
<pre>for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
</pre>
<p>
被延期的函数按照LIFO的顺序执行,所以这段代码会导致在函数返回时打印出<code>4 3 2 1 0</code>。一个更加真实的例子,这是一个跟踪程序中函数执行的简单方法。我们可以编写几个类似这样的,简单的跟踪程序:
</p>
<pre>func trace(s string) { fmt.Println("entering:", s) }
func untrace(s string) { fmt.Println("leaving:", s) }
// Use them like this:
func a() {
trace("a")
defer untrace("a")
// do something....
}
</pre>
<p>
利用被延期的函数的参数是在<code>defer</code>执行的时候被求值这个事实,我们可以做的更好些。trace程序可以为untrace程序建立参数。这个例子:
</p>
<pre>func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
}
func main() {
b()
}
</pre>
<p>
会打印出
</p>
<pre>entering: b
in b
entering: a
in a
leaving: a
leaving: b
</pre>
<p>
对于习惯于其它语言中的块级别资源管理的程序员,<code>defer</code>可能看起来很奇怪,但是它最有趣和强大的应用正是来自于这样的事实,这是基于函数的而不是基于块的。我们将会在<code>panic</code>和<code>recover</code>章节中看到它另一个可能的例子。
</p>
<h2 id="data">数据</h2>
<h3 id="allocation_new">使用<code>new</code>进行分配</h3>
<p>
Go有两个分配原语,内建函数<code>new</code>和<code>make</code>。它们所做的事情有所不同,并且用于不同的类型。这会有些令人混淆,但规则其实很简单。我们先讲下<code>new</code>。这是一个用来分配内存的内建函数,但是不像在其它语言中,它并不<em>初始化</em>内存,只是将其<em>置零</em>。也就是说,<code>new(T)</code>会为<code>T</code>类型的新项目,分配被置零的存储,并且返回它的地址,一个类型为<code>*T</code>的值。在Go的术语中,其返回一个指向新分配的类型为<code>T</code>,值为零的指针。
</p>
<p>
由于<code>new</code>返回的内存是被置零的,这会有助于你将数据结构设计成,每个类型的零值都可以使用,而不需要进一步初始化。这意味着,数据结构的用户可以使用<code>new</code>来创建数据,并正确使用。例如,<code>bytes.Buffer</code>的文档说道,"<code>Buffer</code>的零值是一个可以使用的空缓冲"。类似的,<code>sync.Mutex</code>没有显式的构造器和<code>Init</code>方法。相反的,<code>sync.Mutex</code>的零值被定义为一个未加锁的互斥。
</p>
<p>
“零值可用”的属性是可以传递的。考虑这个类型声明。
</p>
<pre>type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
</pre>
<p>
<code>SyncedBuffer</code>类型的值也可以在分配或者声明之后直接使用。在下一个片段中,<code>p</code>和<code>v</code>都不需要进一步的处理便可以正确地工作。
</p>
<pre>p := new(SyncedBuffer) // type *SyncedBuffer
var v SyncedBuffer // type SyncedBuffer
</pre>
<h3 id="composite_literals">构造器和复合文字</h3>
<p>
有时候零值并不够好,需要一个初始化构造器(constructor),正如这个源自程序包<code>os</code>的例子。
</p>
<pre>func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
</pre>
<p>
有许多这样的模版。我们可以使用<i>复合文字(composite literal)</i>进行简化,其为一个表达式,在每次求值的时候会创建一个新实例。
</p>
<pre>func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := File{fd, name, nil, 0}
return &f
}
</pre>
<p>
注意,不像C,返回一个局部变量的地址是绝对没有问题的;变量关联的存储在函数返回之后依然存在。实际上,使用复合文字的地址也会在每次求值时分配一个新的实例,所以,我们可以将最后两行合并起来。
</p>
<pre> return &File{fd, name, nil, 0}
</pre>
<p>
复合文字的域按顺序排列,并且必须都存在。然而,通过<i>field</i><code>:</code><i>value</i>显式地为元素添加标号,则初始化可以按任何顺序出现,没有出现的则对应为零值。因此,我们可以写成
</p>
<pre> return &File{fd: fd, name: name}
</pre>
<p>
作为一种极端情况,如果复合文字根本不包含域,则会为该类型创建一个零值。表达式<code>new(File)</code>和<code>&File{}</code>是等价的。
</p>
<p>
复合文字还可用于arrays,slices和maps,域标号使用适当的索引或者map key。下面的例子中,不管<code>Enone</code>,<code>Eio</code>和<code>Einval</code>的值是什么,只要它们不同,初始化就可以工作。
</p>
<pre>a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
m := map[int]string{Enone: "no error", Eio: "Eio", Einval: "invalid argument"}
</pre>
<h3 id="allocation_make">使用<code>make</code>进行分配</h3>
<p>
回到分配的话题。内建函数<code>make(T, </code><i>args</i><code>)</code>与<code>new(T)</code>的用途不一样。它只用来创建slice,map和channel,并且返回一个<em>初始化的</em>(而不是<em>置零</em>),类型为<code>T</code>的值(而不是<code>*T</code>)。之所以有所不同,是因为这三个类型的背后是象征着,对使用前必须初始化的数据结构的引用。例如,slice是一个三项描述符,包含一个指向数据(在数组中)的指针,长度,以及容量,在这些项被初始化之前,slice都是<code>nil</code>的。对于slice,map和channel,<code>make</code>初始化内部数据结构,并准备好可用的值。例如,
</p>
<pre>make([]int, 10, 100)
</pre>
<p>
分配一个有100个int的数组,然后创建一个长度为10,容量为100的slice结构,并指向数组前10个元素上。(当创建slice时,容量可以省略掉,更多信息参见slice章节。)对应的,<code>new([]int)</code>返回一个指向新分配的,被置零的slice结构体的指针,即指向<code>nil</code>slice值的指针。
</p>
<p>
这些例子阐释了<code>new</code>和<code>make</code>之间的差别。
</p>
<pre>var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints
// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)
// Idiomatic:
v := make([]int, 100)
</pre>
<p>
记住<code>make</code>只用于map,slice和channel,并且不返回指针。要获得一个显式的指针,使用<code>new</code>进行分配,或者显式地使用一个变量的地址。
</p>
<h3 id="arrays">数组</h3>
<p>
数组可以用于规划内存的精细布局,有时利于避免分配,不过从根本上讲,它们是切片的基本构件,这是下一章节的话题。作为铺垫,这里介绍一下数组。
</p>
<p>
在Go和C中,数组的工作方式有几个重要的差别。在Go中,
</p>
<ul>
<li>
数组是值。将一个数组赋值给另一个,会拷贝所有的元素。
</li>
<li>
特别是,如果你给函数传递一个数组,其将收到一个数组的<i>拷贝</i>,而不是它的指针。
</li><li>
数组的大小是其类型的一部分。类型<code>[10]int</code>和<code>[20]int</code>是不同的。
</li>
</ul>
<p>
数组为值这样的属性,可以很有用处,不过也会有代价;如果你希望类C的行为和效率,可以传递一个数组的指针。
</p>
<pre>func Sum(a *[3]float64) (sum float64) {
for _, v := range *a {
sum += v
}
return
}
array := [...]float64{7.0, 8.5, 9.1}
x := Sum(&array) // Note the explicit address-of operator
</pre>
<p>
不过,这种风格并不符合Go的语言习惯。相反的,应该使用切片。
</p>
<h3 id="slices">切片</h3>
<p>
切片(slice)对数组进行封装,提供了一个针对串行数据,更加通用,强大和方便的接口。除了像转换矩阵这样具有显式维度的项,Go中大多数的数组编程都是通过切片完成,而不是简单数组。
</p>
<p>
切片持有对底层数组的引用,如果你将一个切片赋值给另一个,二者都将引用同一个数组。如果函数接受一个切片参数,那么其对切片的元素所做的改动,对于调用者是可见的,好比是传递了一个底层数组的指针。因此,<code>Read</code>函数可以接受一个切片参数,而不是一个指针和一个计数;切片中的长度已经设定了要读取的数据的上限。这是程序包<code>os</code>中,<code>File</code>类型的<code>Read</code>方法的签名:
</p>
<pre>func (file *File) Read(buf []byte) (n int, err error)
</pre>
<p>
该方法返回读取的字节数和一个错误值,如果存在的话。要读取一个大缓冲<code>b</code>中的前32个字节,可以将缓冲进行<i>切片</i>(这里是动词)。
</p>
<pre> n, err := f.Read(buf[0:32])
</pre>
<p>
这种切片很常见,而且有效。实际上,如果先不考虑效率,下面的片段也可以读取缓冲的前32个字节。
</p>
<pre> var n int
var err error
for i := 0; i < 32; i++ {
nbytes, e := f.Read(buf[i:i+1]) // Read one byte.
if nbytes == 0 || e != nil {
err = e
break
}
n += nbytes
}
</pre>
<p>
只要还符合底层数组的限制,切片的长度就可以进行改变;直接将其赋值给它自己的切片。切片的<i>容量</i>,可以通过内建函数<code>cap</code>访问,告知切片可以获得的最大长度。这里有一个函数可以为切片增加数据。如果数据超出了容量,则切片会被重新分配,然后返回新产生的切片。该函数利用了一个事实,即当用于<code>nil</code>切片时,<code>len</code>和<code>cap</code>是合法的,并且返回0.
</p>
<pre>func Append(slice, data[]byte) []byte {
l := len(slice)
if l + len(data) > cap(slice) { // reallocate
// Allocate double what's needed, for future growth.
newSlice := make([]byte, (l+len(data))*2)
// The copy function is predeclared and works for any slice type.
copy(newSlice, slice)
slice = newSlice
}
slice = slice[0:l+len(data)]
for i, c := range data {
slice[l+i] = c
}
return slice
}