-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
312 lines (150 loc) · 492 KB
/
search.xml
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>GO基础篇-函数</title>
<link href="/article/82e629cf.html"/>
<url>/article/82e629cf.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_function.png" width="100%" alt="图片名称" align="center"/>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 函数 </tag>
</tags>
</entry>
<entry>
<title>GO基础篇-包</title>
<link href="/article/b6073d0a.html"/>
<url>/article/b6073d0a.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><p>任何包系统设计的目的都是为了简化大型程序的设计和维护工作,通过将一组相关的特性放进一个独立的单元以便于理解和更新,在每个单元更新的同时保持和程序中其它单元的相对独立性。这种模块化的特性允许每个包可以被其它的不同项目共享和重用,在项目范围内、甚至全球范围统一的分发和复用。</p><p>Golang中的包(Package)是类型、函数、常量和变量的集合,它将相关特性的函数和数据放在统一的文件或文件夹中进行管理,Golang中的包是一种组织代码的机制,它有助于将相关的代码组织在一起,使代码具有更好的可复用性、可维护性以及可读性。在Golang中,包是代码的基本单元,一个程序可以由多个包组成,每个包都有独立的命名空间。</p><p>每个包一般都定义了一个不同的名字空间用于它内部的每个标识符的访问。每个名字空间关联到一个特定的包,让我们给类型、函数等选择简短明了的名字,这样可以避免在我们使用它们的时候减少和其它部分名字的冲突。每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。</p><h2 id="2-包的分类"><a href="#2-包的分类" class="headerlink" title="2 包的分类"></a>2 包的分类</h2><h3 id="2-1-按文件类型"><a href="#2-1-按文件类型" class="headerlink" title="2.1 按文件类型"></a>2.1 按文件类型</h3><p>按照文件类型来分,一般情况下源码文件分为三类,分别是命令源码文件、库源码文件和测试源码文件,而三类文件对应所在的两种包。</p><ul><li>main包:命令源码文件所在包就是main包,即<code>package main</code>是主函数(可运行的程序)所在的包,main包也是代码人口包;</li><li>普通包:我们自定义包以及第三方库源码文件和测试源码文件所在包;</li></ul><h3 id="2-2-按文件范围"><a href="#2-2-按文件范围" class="headerlink" title="2.2 按文件范围"></a>2.2 按文件范围</h3><p>按照文件范围,Golang中的包可以分为三种:系统内置包、自定义包和第三方包。</p><ul><li>系统内置包:Golang语言给我们提供的内置包,引入后可以直接使用,比如<code>fmt、strconv、strings、sort、errors、time、encoding/json、os、io</code>等。</li><li>自定义包:开发者自己写的包。</li><li>第三方包:它也属于自定义包的一种,只不过是其他开发者开发的自定义包,需要下载安装到本地后才可以使用,比如前面章节介绍的<code>github.com/jmoiron/sqlx</code>包。</li></ul><h2 id="3-包声明"><a href="#3-包声明" class="headerlink" title="3 包声明"></a>3 包声明</h2><p>在每个Golang源文件的开头都必须有包声明语句,它的大致格式为<code>package <包路径></code>,包声明语句的主要目的是<strong>确定当前包被其它包导入时默认的标识符(也称为包名)</strong>,例如,<code>math/rand</code>包的每个源文件的开头都包含<code>package rand</code>包声明语句,所以当你导入这个包,你就可以用<code>rand.Int</code>、<code>rand.Float64</code>类似的方式访问包的成员。</p><h3 id="3-1-包的声明规则"><a href="#3-1-包的声明规则" class="headerlink" title="3.1 包的声明规则"></a>3.1 包的声明规则</h3><ol><li><p>同一文件目录下直接包含的文件只能归属一个<code>package</code>,同样一个<code>package</code>的文件不能在多个文件夹下,简单来讲就是,同一文件目录下你可以定义无数个Go文件,但是文件里面声明的包名必须是同一个,否则编译无法通过。</p></li><li><p>包名可以不和文件夹的名字一样,但是最好是保持一致。</p></li><li><p>包的声明语句package必须位于Go文件的第一行,否则编译错误。</p></li><li><p>Golang规定,<code>package main</code>是主函数(可运行的程序)所在的包,其他的均为库文件的形式存在。</p></li></ol><p>包名由小写字母、数字和下划线_组成,不能包含其他特殊符号。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"math/rand"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 比如,包路径为"math/rand",我们使用直接使用包路径的最后一段rand即可</span></span><br><span class="line"> fmt.Println(rand.Int())</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通常来说,默认的包名就是包导入路径名的最后一段,因此即使两个包的导入路径不同,它们依然可能有一个相同的包名;例如<code>math/rand</code>包和<code>crypto/rand</code>包的包名都是rand,稍后我们将看到如何同时导入两个有相同包名的包。</p><p>关于默认包名一般采用导入路径名的最后一段的约定也有三种例外情况:</p><ul><li>第一个例外,包对应一个可执行程序,也就是main包,这时候main包本身的导入路径是无关紧要的。名字为main的包是给<code>go build</code>构建命令一个信息,这个包编译完之后必须调用连接器生成一个可执行程序。</li><li>第二个例外,包所在的目录中可能有一些文件名是以<code>_test.go</code>为后缀的Go源文件,并且这些源文件声明的包名也是以<code>_test</code>为后缀名的。这种目录可以包含两种包:一种普通包,另外一种则是测试的外部扩展包。所有以_test为后缀包名的测试外部扩展包都由<code>go test</code>命令独立编译,普通包和测试的外部扩展包是相互独立的。测试的外部扩展包一般用来避免测试代码中的循环导入依赖,</li><li>第三个例外,一些依赖版本号的管理工具会在导入路径后追加版本号信息,例如<code>gopkg.in/yaml.v2</code>。这种情况下包的名字并不包含版本号后缀,而是yaml。</li></ul><h3 id="3-2-包名的规则"><a href="#3-2-包名的规则" class="headerlink" title="3.2 包名的规则"></a>3.2 包名的规则</h3><p>包名和导入路径都是包的重要标识符,代表包包含的所有内容。规范地命名包不仅可以提高代码质量,还可以提高用户的质量,糟糕的包名使代码难以导航和维护。这里有一些识别和纠正不规范包的指导方针。</p><h4 id="3-2-1-包名小写"><a href="#3-2-1-包名小写" class="headerlink" title="3.2.1 包名小写"></a>3.2.1 包名小写</h4><p>包名其实可以使用大小写、数字和下划线_,但是我们仍然建议包名只要小写字母的,且不要在包名中使用蛇形或驼峰形式,至于为什么如此,你可以认为这是一种规范。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> demo_pacakge <span class="comment">// 反例:蛇形</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> demoPakcage <span class="comment">// 反例:驼峰</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> demopackage <span class="comment">// 正例 </span></span><br></pre></td></tr></table></figure><h4 id="3-2-2-简短且具有代表性"><a href="#3-2-2-简短且具有代表性" class="headerlink" title="3.2.2 简短且具有代表性"></a>3.2.2 简短且具有代表性</h4><p>避免无意义的包名,比如名为util、common或misc的包让工程师不知道包中包含什么,这使得客户端更难以使用包,也使得维护人员更难以保持包的重点。随着时间的推移,它们积累的依赖关系会使编译显著地、不必要地变慢,尤其是在大型程序中。由于这样的包名是通用的,它们更有可能与客户端代码导入的其他包发生冲突,迫使客户端发明名称来区分它们,所以<strong>包名要简短且是唯一并具有代表性</strong>。</p><h4 id="3-2-3-避免为所有API使用一个包"><a href="#3-2-3-避免为所有API使用一个包" class="headerlink" title="3.2.3 避免为所有API使用一个包"></a>3.2.3 避免为所有API使用一个包</h4><p>许多工程师将程序公开的所有接口放入一个名为api、types或interfaces的包中,认为这样更容易找到代码库的入口点。毫无疑问这是错误的做法,这样的包与那些名为util或common的包一样,存在同样的问题: <strong>不受约束地增长,不向用户提供指导,积累依赖,并与其他导入发生冲突</strong>。将它们分开,也许可以使用目录将公共包与实现分开。</p><h4 id="3-2-4-避免不必要的包名冲突"><a href="#3-2-4-避免不必要的包名冲突" class="headerlink" title="3.2.4 避免不必要的包名冲突"></a>3.2.4 避免不必要的包名冲突</h4><p>虽然不同目录中的包可能具有相同的名称,但经常一起使用的包应该具有不同的名称。这减少了混淆和在客户端代码中进行本地重命名的需要。出于同样的原因,避免使用与流行的标准包(如io或http)相同的名称。</p><h2 id="4-包导入"><a href="#4-包导入" class="headerlink" title="4 包导入"></a>4 包导入</h2><p>我们可以可以在一个Go语言源文件包声明语句之后,其它非导入声明语句之前,导入包含零到多个导入包声明语句。每个导入声明可以单独指定一个导入路径,也可以通过圆括号同时导入多个导入路径。下面两个导入形式是等价的,但是第二种形式更为常见。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 第一种导入方式</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">"os"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 第二种导入方式</span></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"os"</span></span><br><span class="line">)</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>导入的包之间可以通过添加空行来分组;通常将来自不同组织的包独自分组,包的导入顺序无关紧要,但是在每个分组中一般会根据字符串顺序排列,gofmt和goimports工具都可以将不同分组导入的包独立排序。</p><h3 id="4-1-下划线导入-匿名导入"><a href="#4-1-下划线导入-匿名导入" class="headerlink" title="4.1 下划线导入(匿名导入)"></a>4.1 下划线导入(匿名导入)</h3><p>下划线导入也叫<strong>匿名导入</strong>,它的格式类似<code>import _ <包路径></code>,通常引入某个包,但不直接使用包里的函数,而是调用该包里面的init函数,比如下面的mysql包的导入,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> _ <span class="string">"github.com/go-sql-driver/mysql"</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>当通过匿名方式导入一个包时,它所有的init()函数就会被执行,但有些时候并非真的需要使用这些包,仅仅是希望它的init()函数被执行而已,这个时候就可以使用_操作引用该包了。即:使用_操作引用包是无法通过包名来调用包中的导出函数,而是只是为了简单的调用其init函数()。</p><p>通常情况下,这些init函数里面是注册自己包里面的引擎,让外部可以方便的使用,例如实现<code>database/sql</code>的包,在init函数里面都是调用了<code>sql.Register(name string, driver driver.Driver)</code>注册自己,然后外部就可以使用了。</p><h3 id="4-2-点导入"><a href="#4-2-点导入" class="headerlink" title="4.2 点导入"></a>4.2 点导入</h3><p>使用点导入,它的格式如下: <code>import . <包路径></code>,其目的作用就是为了在使用包内的方法、属性以及其他对象时,可以省略包名,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> . <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 原来通过fmt.Println("Hello world")调用的包现在可以直接省略包名</span></span><br><span class="line"> Println(<span class="string">"Hello world"</span>) <span class="comment">//Output: Hello world</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>另外我们可以使用 <strong>.</strong> 导入多个包,但是多个包最好不能有相同的函数或者其他对象,否则会编译错误。</p></blockquote><p>这里我在另外的db目录中定义了一个Go文件,并在其中定义了一个<code>Abs()</code>方法,方法定义如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Abs</span><span class="params">(num <span class="type">float32</span>)</span></span> <span class="type">float32</span> {</span><br><span class="line"> <span class="keyword">if</span> num >= <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">return</span> num</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> -num</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而Golang内置的math包中也有<code>Abs()</code>方法, 其方法签名如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Copyright 2009 The Go Authors. All rights reserved.</span></span><br><span class="line"><span class="comment">// Use of this source code is governed by a BSD-style</span></span><br><span class="line"><span class="comment">// license that can be found in the LICENSE file.</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">package</span> math</span><br><span class="line"></span><br><span class="line"><span class="comment">// Abs returns the absolute value of x.</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Special cases are:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// Abs(±Inf) = +Inf</span></span><br><span class="line"><span class="comment">// Abs(NaN) = NaN</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Abs</span><span class="params">(x <span class="type">float64</span>)</span></span> <span class="type">float64</span> {</span><br><span class="line"> <span class="keyword">return</span> Float64frombits(Float64bits(x) &^ (<span class="number">1</span> << <span class="number">63</span>))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这时候我们同时我们同时通过 <strong>.</strong> 引入这两个包,然后调用<code>Abs()</code>,运行就会发生编译错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> . <span class="string">"fmt"</span></span><br><span class="line"> . <span class="string">"math"</span></span><br><span class="line"></span><br><span class="line"> . <span class="string">"ratel.com/go-tutorial/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> Println(Abs(<span class="number">-12.0</span>))</span><br><span class="line"> Println(Abs(<span class="number">13.0</span>))</span><br><span class="line"> <span class="comment">// compile error: main.go:7:2: Abs redeclared in this block, $GOROOT/src/math/abs.go:13:6: other declaration of Abs</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据错误信息可以看出,在main()函数中第7行第2列也即是<code>ratel.com/go-tutorial/db</code>包,重复定义了Abs函数,因为<code>$GOROOT/src/math/abs.go</code>的第13行第6列已经定义了<code>Abs</code>函数,同时我们发现无论两个函数的签名是否一样,只要函数名称相同,那就会出现重复定义的错误。 </p><p>那么应该怎么解决呢?</p><p>其中一种最最简单的解决办法就是其中一个包不要使用 <strong>.</strong> 导入,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> . <span class="string">"fmt"</span></span><br><span class="line"> . <span class="string">"math"</span></span><br><span class="line"> <span class="string">"ratel.com/go-tutorial/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> Println(Abs(<span class="number">-12.0</span>))</span><br><span class="line"> Println(db.Abs(<span class="number">13.0</span>))</span><br><span class="line"> <span class="comment">// Output: 12</span></span><br><span class="line"> <span class="comment">// 13</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>还有一种解决办法就是别名导入。</p><h3 id="4-3-别名导入"><a href="#4-3-别名导入" class="headerlink" title="4.3 别名导入"></a>4.3 别名导入</h3><p>别名导入,顾名思义就是给导入的包起一个别名,他的语法格式如下:<code>import alias_name <包路径></code>,声明了别名后,我们就可以通过别名来使用包内的函数、对象来构建我们的程序。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> . <span class="string">"math"</span></span><br><span class="line"> c_math <span class="string">"ratel.com/go-tutorial/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(Abs(<span class="number">-12.0</span>))</span><br><span class="line"> fmt.Println(c_math.Abs(<span class="number">13.0</span>))</span><br><span class="line"> <span class="comment">// Output: 12 </span></span><br><span class="line"> <span class="comment">// 13</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过上例,我们发现通过别名可以解决4.4的问题。</p><p>如果我们导入的两个包路径的包名是一样的,会发生怎样的后果呢?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"math/rand"</span></span><br><span class="line"> <span class="string">"crypto/rand"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(rand.Int31())</span><br><span class="line"> <span class="comment">// compile error: main.go:7:2: rand redeclared in this block, main.go.go:6:2: rand redeclared in this block</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据错误信息可以看出,在main()函数中第7行第2列也即是<code>math/rand</code>包,重复声明了rand包,因为在main()函数中第7行第2列也即是<code>crypto/rand</code>包已经声明了rand包。</p><p>所以这种情况,我们可以给其中一个包起一个别名即可解决,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"math/rand"</span></span><br><span class="line"> crand <span class="string">"crypto/rand"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(rand.Intn(<span class="number">100</span>))</span><br><span class="line"> fmt.Println(crand.Reader)</span><br><span class="line"> <span class="comment">// Output: 52</span></span><br><span class="line"> <span class="comment">// &{}</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>还有种情况就是,如果引入的包名非常长,在后面的使用过程中非常不方便,这时候可以使用别名, 例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> pn <span class="string">"ratel.com/go-tutorial/this_is_my_longest_package_name"</span></span><br></pre></td></tr></table></figure><h3 id="4-4-相对路径-绝对路径导入-模块导入"><a href="#4-4-相对路径-绝对路径导入-模块导入" class="headerlink" title="4.4 相对路径|绝对路径导入|模块导入"></a>4.4 相对路径|绝对路径导入|模块导入</h3><p>绝对路径导入和相对路径导入是Golang早期版本中导包的两种方式,但是随着Golang版本升级到1.11并且你的项目开启了模块支持的情况下,就不再支持相对路径和绝对路径这两种方式导入包,接下里看看关闭模块支持后相对路径和绝对路径如何导包。</p><p>首先我们通过<code>go env -w GO111MODULE="off"</code>先关闭模块支持打开<code>GOPATH</code>模式,然后在<code>$GOPATH</code>之外的目录创建名称为<code>go-samples</code>工程,并在其目录下创建两个子目录<code>db</code>和<code>main</code>,并基于这两个目录分别创建<code>db.go</code>和<code>main.go</code>两个文件,文件内容如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewDb</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"New db function"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4-4-1-相对路径导入"><a href="#4-4-1-相对路径导入" class="headerlink" title="4.4.1 相对路径导入"></a>4.4.1 相对路径导入</h4><p>所谓相对路径导入是相当于当前<code>main.go</code>文件时db包所在目录,根据当前目录结构来看,<code>db</code>相对于<code>main.go</code>的相对目录即为<code>../db</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"../db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line"> <span class="comment">// Output:New db function</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>对于Golang来说,相对路径的包引入,并不是一个好的方案,首先会与官方标准包的导入相混淆,同时增加相对导入包的软件管理难度,因此不建议使用相对路径导入。</p><h4 id="4-4-2-绝对路径导入"><a href="#4-4-2-绝对路径导入" class="headerlink" title="4.4.2 绝对路径导入"></a>4.4.2 绝对路径导入</h4><p>在<code>GOPATH</code>模式下,绝对路径导包会去<code>$GOPATH/src</code>目录下去找当前包,如果找不到则去<code>$GOROOT/src</code>目录下去找当前包,都找不到运行则会出现错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"go-samples/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: cannot find package "go-samples/db" in any of:</span></span><br><span class="line"><span class="comment">// D:\Go\src\go-samples\db (from $GOROOT)</span></span><br><span class="line"><span class="comment">// D:\GoProjects\src\go-samples\db (from $GOPATH) (exit status 1)</span></span><br></pre></td></tr></table></figure><p>这里找不到<code>go-samples/db</code>包,原因就是<code>$GOPATH/src</code>和<code>$GOROOT/src</code>目录都没有目标包,所以我们有两种方式可以解决:</p><ol><li>可以把要导入的<code>go-samples/db</code>包目录复制一份到<code>$GOPATH/src</code>下即可运行成功。</li><li>可以把直接把<code>go-samples</code>工程迁移到<code>$GOPATH/src</code>下即可运行成功。</li></ol><h4 id="4-4-3-模块导入"><a href="#4-4-3-模块导入" class="headerlink" title="4.4.3 模块导入"></a>4.4.3 模块导入</h4><p>随着Golang版本升级到1.11支持模块开发后,就不支持相对路径和绝对路径这两种方式导入包,因为模块支持后,会以模块为根目录然后导其他包。</p><p>同样首先通过<code>go env -w GO111MODULE="on"</code>先开启模块支持,然后创建<code>go-samples</code>目录,进入该目录通过<code>go mod init "ratel.com/go-samples"</code>初始化一个模块,这里对于模块名称的定义其实没有做严格控制,字母大小写、数字、 <strong>-</strong> 和 <strong>_</strong> 都可以,执行完毕后会生成一个go.mod文件,内容如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">module ratel.com/<span class="keyword">go</span>-samples</span><br><span class="line"></span><br><span class="line"><span class="keyword">go</span> <span class="number">1.21</span><span class="number">.3</span></span><br></pre></td></tr></table></figure><p>其次在其目录下创建<code>db</code>和<code>main</code>两个子目录,并基于这两个目录分别创建<code>db.go</code>和<code>main.go</code>两个文件,<code>db.go</code>文件内容同上,接下来重点看如何在<code>main.go</code>文件中导入db包。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"../db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: main.go:4:2: "../db" is relative, but relative import paths are not supported in module mode</span></span><br></pre></td></tr></table></figure><p>我们先尝试使用相对路径导入,则报<code>"../db"</code>是相对路径且相对路径导入在模块模式下不支持的错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"go-samples/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: main.go:4:2: package go-samples/db is not in std (D:\Go\src\go-samples\db) (exit status 1)</span></span><br></pre></td></tr></table></figure><p>如果我们尝试使用<code>go-samples/db</code>绝对路径导入,则报<code>go-samples/db</code>不在标准包(即<code>$GOROOT/src</code>目录)下的错误,为什么会如此呢?</p><p>当开启模块支持后,当<code>import "go-samples/db"</code>导入包后:</p><ol><li>首先会从当前模块内搜索该包,如果找不到</li><li>其次会到<code>$GOPATH/pkg/mod</code>目录搜索该包,还是找不到</li><li>最后去<code>$GOROOT/src</code>目录搜索该包,还是找不到则报错<code>go-samples/db is not in std</code>(std是Golang标准库的包)。</li></ol><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"ratel.com/go-samples/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: New db function</span></span><br></pre></td></tr></table></figure><p>可以看到当开启模块支持后,同模块内的其他包导入包名全路径为<code>模块名称/包名</code>。</p><h3 id="4-5-导入第三方包"><a href="#4-5-导入第三方包" class="headerlink" title="4.5 导入第三方包"></a>4.5 导入第三方包</h3><p>这些三方包从哪里获取,我们可以通过官网(<a href="https://pkg.go.dev/">https://pkg.go.dev/</a>) 搜索关键字来找到我们想要的包。</p><img src="/img/go/dev-mode/go_package_search_index.png" width="100%" alt="图片名称" align="center"/><p>这里我们搜索一个实现MySQL数据库数据查询逻辑的sql包并实现一个例子,如下图:<br><img src="/img/go/dev-mode/go_package_search_result.png" width="100%" alt="图片名称" align="center"/></p><blockquote><p>Go语言中的<code>database/sql</code>包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动,使用<code>database/sql</code>包时必须注入至少一个数据库驱动, 比如<code>github.com/go-sql-driver/mysql</code>就是MySQL数据库的驱动实现包。</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"database/sql"</span></span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"> _ <span class="string">"github.com/go-sql-driver/mysql"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> TenantInfo <span class="keyword">struct</span> {</span><br><span class="line"> id <span class="type">int</span></span><br><span class="line"> Tenant_code <span class="type">string</span></span><br><span class="line"> Tenant_name <span class="type">string</span></span><br><span class="line"> deleted <span class="type">int</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> database, err := sql.Open(<span class="string">"mysql"</span>, <span class="string">"admin:123456@tcp(127.0.0.0:3306)/test"</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="built_in">panic</span>(err.Error())</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">defer</span> database.Close()</span><br><span class="line"></span><br><span class="line"> results, err := database.Query(<span class="string">"SELECT id, tenant_code, tenant_name, deleted FROM tenant_info"</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="built_in">panic</span>(err.Error())</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">defer</span> results.Close()</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> results.Next() {</span><br><span class="line"> <span class="keyword">var</span> tenantInfo TenantInfo</span><br><span class="line"> err := results.Scan(&tenantInfo.id, &tenantInfo.Tenant_code, &tenantInfo.Tenant_name, &tenantInfo.deleted)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="built_in">panic</span>(err)</span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"%v\n"</span>, tenantInfo)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: </span></span><br><span class="line"><span class="comment">// {1 100001 租户001 0}</span></span><br><span class="line"><span class="comment">// {2 100002 租户002 0}</span></span><br></pre></td></tr></table></figure><h2 id="5-包的初始化"><a href="#5-包的初始化" class="headerlink" title="5 包的初始化"></a>5 包的初始化</h2><p>在初始化Go包时,Go会按照一定的顺序,逐一地调用这个包的init函数,初始化函数会按照它们在源文件中的顺序被调用,从而确定了包的初始化顺序,每个包都允许有零个、一个或者多个<code>init</code>函数,当这个包被导入时,会执行该包的这个<code>init</code>函数,做一些初始化任务;比如数据库连接池的建立。 </p><h3 id="5-1-初始化包"><a href="#5-1-初始化包" class="headerlink" title="5.1 初始化包"></a>5.1 初始化包</h3><p>在Golang语言程序执行时导入包语句会自动触发包内部init函数的调用,<code>init()</code>函数是Golang内置函数,用来初始化包使用,每个包可以包含一个或者多个初始化函数,这些初始化函数会在程序启动时按照它们在源文件中的顺序被调用。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> x = <span class="number">10</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Main function"</span>)</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: x = 10</span></span><br><span class="line"><span class="comment">// Main function</span></span><br></pre></td></tr></table></figure><p>根据结果显示,初始化包有优先级:<code>全局声明 --> init() --> main() </code></p><blockquote><p>需要注意的是: <code>init</code>函数没有返回值、没有参数,且不能在代码中主动调用它,违背此原则将会编译错误。</p></blockquote><h3 id="5-2-init函数执行顺序"><a href="#5-2-init函数执行顺序" class="headerlink" title="5.2 init函数执行顺序"></a>5.2 <code>init</code>函数执行顺序</h3><p>任何一个包都可以定义一个或者多个初始化函数,假设我们在main包定义了多个<code>init</code>函数,大致内容如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"First init function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Second init function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Main function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// First init function</span></span><br><span class="line"><span class="comment">// Second init function</span></span><br><span class="line"><span class="comment">// Main function</span></span><br></pre></td></tr></table></figure><p>在这个示例中,<code>main</code>包中包含了两个初始化函数,当程序启动时,这两个初始化函数会按照它们在源文件中的顺序被调用,然后才会执行<code>main</code>函数。</p><p>如果main包导入了其他包(三方包或者自定义包),而其他包也声明了初始化函数,那么执行顺序会是怎么的呢?</p><p>这里我在另外的db目录中定义了一个go文件,并在其中定义了一个<code>Abs()</code>方法,方法定义如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Db init function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Abs</span><span class="params">(num <span class="type">float32</span>)</span></span> <span class="type">float32</span> {</span><br><span class="line"> <span class="keyword">if</span> num >= <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">return</span> num</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> -num</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后在当前main包引入<code>ratel.com/go-tutorial/db</code>自定义包, 然后运行工程。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"ratel.com/go-tutorial/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">init</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Init function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Main function"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// Db init function</span></span><br><span class="line"><span class="comment">// Init function</span></span><br><span class="line"><span class="comment">// Main function</span></span><br></pre></td></tr></table></figure><p>从示例中可以看到执行顺序为:<code>db</code>包的<code>init()</code>函数 –> <code>main</code>包的<code>init()</code>函数 –> <code>main()</code>函数。</p><p>依次类推,假设如果db包中引入了其他带有<code>init()</code>函数的<code>demo</code>包,那么执行顺序会是怎么的呢?</p><p>Go语言包会从main包开始检查其导入的所有包,每个包中又可能导入了其他的包,Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码,在运行时,被最后导入的包会最先初始化并调用其<code>init()</code>函数,如下图示:</p><img src="/img/go/go-package/go_package_init2.png" width="100%" alt="图片名称" align="center"/><blockquote><p>简单来说,就是被依赖包的init函数优先执行。</p></blockquote><h2 id="6-包成员可见性"><a href="#6-包成员可见性" class="headerlink" title="6 包成员可见性"></a>6 包成员可见性</h2><p>Golang在控制包内函数、常量、变量和结构体的访问权限时,对关键字的使用非常吝啬,它甚至都没有类似<code>private和public</code>这些的关键字,他比较简单地使用名称首字母大小写判断对应(函数、常量、变量、结构体等)的访问权限,这是一个全新的权限控制方式,使用这种方式可以省去额外的、繁琐的关键字,让代码变得更加简洁。</p><ul><li>首字母大写:包外可见,类似于Java类中的public关键字</li><li>首字母小写:包外不可见,类似于Java类中的private关键字</li></ul><p>我们先来看一个例子,首先声明一个db包,然后在包内创建一些有首字母大小写的函数、常量、变量、结构体等变量,如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 常量</span></span><br><span class="line"><span class="keyword">const</span> JdbcUrlTemplate <span class="type">string</span> = <span class="string">"jdbc:mysql://%s:%d/%s"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> host <span class="type">string</span> = <span class="string">"localhost"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> port <span class="type">int</span> = <span class="number">3306</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 变量</span></span><br><span class="line"><span class="keyword">var</span> databaseName <span class="type">string</span> = <span class="string">"test_db"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> JdbcUrl, er = fmt.Printf(JdbcUrlTemplate, host, port, databaseName)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数据库配置结构体</span></span><br><span class="line"><span class="keyword">type</span> DBConfig <span class="keyword">struct</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 用户名</span></span><br><span class="line"> Username <span class="type">string</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 密码</span></span><br><span class="line"> password <span class="type">string</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 编码</span></span><br><span class="line"> encoding <span class="type">string</span></span><br><span class="line"></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数据库链接</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetConnection</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Get connection"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取数据库链接</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">getConnection</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"get connection"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关闭数据库链接</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">CloseConnection</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Close connection"</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关闭数据库链接</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">closeConnection</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"close connection"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>然后在当前main包引入<code>ratel.com/go-tutorial/db</code>自定义包, 然后我们观察可以使用db包哪些对象。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"ratel.com/go-tutorial/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.GetConnection()</span><br><span class="line"> db.getConnection() <span class="comment">// undefined: db.getConnection compiler</span></span><br><span class="line"> db.CloseConnection()</span><br><span class="line"> db.closeConnection() <span class="comment">// undefined: db.closeConnection compiler</span></span><br><span class="line"> fmt.Println(db.JdbcUrlTemplate)</span><br><span class="line"> fmt.Println(db.host) <span class="comment">// undefined: db.host compiler</span></span><br><span class="line"> fmt.Println(db.port) <span class="comment">// undefined: db.port compiler</span></span><br><span class="line"> fmt.Println(db.JdbcUrl)</span><br><span class="line"> fmt.Println(db.databaseName) <span class="comment">// undefined: db.databaseName compiler</span></span><br><span class="line"> fmt.Println(db.DBConfig{Username: <span class="string">"Ratel"</span>})</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据示例可以看出,无论是常量、变量、结构体、函数,只要其名称首字母是大写的,我们都是可以导出使用,而首字母小写则会编译出错。</p><h2 id="7-包可见性"><a href="#7-包可见性" class="headerlink" title="7 包可见性"></a>7 包可见性</h2><p>从标准的Go工程结构章节中,我们发现<code>internal</code>包是私有应用程序代码包,即<code>internal</code>包不能其他应用程序或者库中导入该包的代码,这里我们试验一下是否真的不能被导入。</p><p>我们首先构建一个工程,模块名称为<code>ratel.com/go-samples</code>,其目录如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">go-samples</span><br><span class="line">├──db</span><br><span class="line"> ├──db.go</span><br><span class="line"> ├──export</span><br><span class="line"> └──export.go</span><br><span class="line"> └──internal</span><br><span class="line"> ├──internal.go</span><br><span class="line"> ├──fa</span><br><span class="line"> └──fa.go</span><br><span class="line"> └──fb</span><br><span class="line"> └──fb.go</span><br><span class="line">└──main</span><br><span class="line"> └──main.go</span><br></pre></td></tr></table></figure><p><code>db.go、export.go、internal.go、fa.go、fb.go</code>这些Go文件都定了一个简单的函数,接下来我们来看main.go如何导包。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"ratel.com/go-samples/db"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/export"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line"> export.NewExport()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Oputput:</span></span><br><span class="line"><span class="comment">// New db function</span></span><br><span class="line"><span class="comment">// New export function</span></span><br></pre></td></tr></table></figure><p>先尝试导入其他非<code>internal</code>包,运行工程发现打印结果非常正常。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"ratel.com/go-samples/db"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/export"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/internal"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line"> export.NewExport()</span><br><span class="line"> internal.NewInternal()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// use of internal package ratel.com/go-samples/db/internal not allowed (exit status 1)</span></span><br></pre></td></tr></table></figure><p>导入<code>internal</code>包直接编译错误,报内部包<code>ratel.com/go-samples/db/internal</code>不允许被用在这里的错误。</p><p>根据<code>internal</code>包的导入规则,<code>internal</code>包的父包下面的其他目录下的Go文件都可以导入<code>internal</code>包,也就是说<code>db</code>包下的<code>db.go</code>文件或者<code>export</code>包下的<code>export.go</code>文件可以导入<code>internal</code>包,来尝试一下在<code>db.go</code>导入<code>internal</code>包和<code>internal</code>包下的<code>fa</code>和<code>fb</code>包。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> db</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/internal"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/internal/fa"</span></span><br><span class="line"> <span class="string">"ratel.com/go-samples/db/internal/fb"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">NewDb</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"New db function"</span>)</span><br><span class="line"> internal.NewInternal()</span><br><span class="line"> fa.NewFa()</span><br><span class="line"> fa.NewFb()</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们在<code>main.go</code>文件中只导入<code>ratel.com/go-samples/db</code>包</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"ratel.com/go-samples/db"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> db.NewDb()</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// New db function</span></span><br><span class="line"><span class="comment">// New internal function</span></span><br><span class="line"><span class="comment">// New fa function</span></span><br><span class="line"><span class="comment">// New fb function</span></span><br></pre></td></tr></table></figure><p>从结果输出来看,<code>db</code>包下的Go文件都是可以导入<code>internal</code>包以及<code>internal</code>包下面的子包,而不能被<code>db</code>包同级的或者父级的包下面的Go文件导入,而<code>export.go</code>文件也可以导入<code>internal</code>包以及<code>internal</code>包下面的子包,这里不再演示。</p><blockquote><p>简单来说,<code>internal</code>包以及<code>internal</code>包下面的子包能被<code>internal</code>包的父级包下面Go文件导入,而不能被父包之外的其他包下面的Go文件导入。</p></blockquote><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8 总结"></a>8 总结</h2><p>总体来说,Golang的<code>Package</code>机制是一种简单而强大的代码组织方式,它有助于构建清晰、模块化、可维护且易于理解的代码结构,<code>Package</code>的导入机制使得我们可以在不同的项目中重用代码,提高了代码的可重用性,同时<code>Package</code>提供了一种简单而有效的封装机制,使得代码的实现细节对外部<code>Package</code>不可见,增强了代码的安全性和可维护性,下一章节我们将介绍函数。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 包 </tag>
</tags>
</entry>
<entry>
<title>Go基础篇-流程控制</title>
<link href="/article/386bbd98.html"/>
<url>/article/386bbd98.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_flow_control.png" width="100%" alt="图片名称" align="center"/>众所周知,任何一门程序设计语言的执行顺序都依赖于流程控制,流程控制语句,用于设定程序执行的次序,建立程序的逻辑结构。可以说,流程控制语句是整个程序的骨架,如果没有任何控制的话,那么程序会按照代码的一行行地顺序执行。<p>Golang是一门注重简洁、高效、并发编程的编程语言。在编写任何程序时,掌握流程控制是至关重要的,它决定了程序执行的顺序和条件。同时提供了一组清晰而灵活的流程控制结构,使得开发者能够轻松地编写可读性高且高效的代码。</p><h2 id="2-Overview"><a href="#2-Overview" class="headerlink" title="2 Overview"></a>2 Overview</h2><h3 id="2-1-顺序执行"><a href="#2-1-顺序执行" class="headerlink" title="2.1 顺序执行"></a>2.1 顺序执行</h3><p>在Golang中,程序按照代码的顺序逐行执行。这是最基本的流程控制形式,是任何编程语言的基础。通过顺序执行,我们能够在程序中定义一系列的操作,实现所需的功能。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> fmt.Println(<span class="string">"Hello, World!"</span>)</span><br><span class="line"> fmt.Println(<span class="string">"Welcome to Golang!"</span>)</span><br><span class="line"> <span class="comment">// Output: Hello, World!</span></span><br><span class="line"> <span class="comment">// Output: Welcome to Golang!</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="2-2-控制语句"><a href="#2-2-控制语句" class="headerlink" title="2.2 控制语句"></a>2.2 控制语句</h3><p>在Golang中,从根本上讲,流程控制只是为了控制程序语句的执行顺序,一般需要与各种条件配合,因此,在各种流程中,会加入条件判断语句。流程控制语句一般起以下3个作用:</p><ul><li>选择,即根据条件跳转到不同的执行序列;</li><li>循环,即根据条件反复执行某个序列,当然每一次循环执行的输入输出可能会发生变化;</li><li>跳转,即根据条件返回到某执行序列。</li></ul><p>Go语言支持如下的几种流程控制语句:</p><ul><li>条件语句,对应的关键字为if、else和else if;</li><li>选择语句,对应的关键字为switch、case和select(将在介绍channel的时候细说);</li><li>循环语句,对应的关键字为for和range;</li><li>跳转语句,对应的关键字为goto。<br>在具体的应用场景中,为了满足更丰富的控制需求,Go语言还添加了如下关键字:<code>break</code>、<code>continue</code>和<code>fallthrough</code>,在实际的使用中,需要根据具体的逻辑目标、程序执行的时间和空间限制、代码的可读性、编译器的代码优化设定等多种因素,灵活组合。</li></ul><p>接下来简要介绍一下各种流程控制功能的用法以及需要注意的要点。</p><h2 id="3-条件控制"><a href="#3-条件控制" class="headerlink" title="3 条件控制"></a>3 条件控制</h2><p>条件语句在编程中经常被使用,Golang提供了简单而强大的<code>if、else-if、else</code>结构。这使得我们能够根据不同的条件执行不同的代码块。</p><h3 id="3-1-基本语法"><a href="#3-1-基本语法" class="headerlink" title="3.1 基本语法"></a>3.1 基本语法</h3><p>if是用于测试某个条件(布尔型或逻辑型)的语句,如果该条件成立,则会执行if后由大括号括起来的代码块,否则就忽略该代码块继续执行后续的代码。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果存在第二个分支,则可以在上面代码的基础上添加else关键字以及另一代码块,这个代码块中的代码只有在条件不满足时才会执行。if和else后的两个代码块是相互独立的分支,只可能执行其中一个。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果存在第三个分支,则可以使用下面这种三个独立分支的形式:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> condition1 {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> condition2 {</span><br><span class="line"> <span class="comment">// do something else</span></span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// catch-all or default</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注:<code>else-if</code>分支的数量是没有限制的,但是为了代码的可读性,还是不要在if后面加入太多的else-if结构。如果你必须使用这种形式,则把尽可能先满足的条件放在前面。</p></blockquote><h3 id="3-2-注意点"><a href="#3-2-注意点" class="headerlink" title="3.2 注意点"></a>3.2 注意点</h3><p>1.条件语句不需要使用括号将条件包含起来()。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> x <span class="type">int</span> = <span class="number">20</span></span><br><span class="line"> <span class="comment">// x > 10无需()括起来</span></span><br><span class="line"> <span class="keyword">if</span> x > <span class="number">10</span> {</span><br><span class="line"> fmt.Println(<span class="string">"x is greater than 10"</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fmt.Println(<span class="string">"x is less than 10"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>2.无论语句体内有几条语句,花括号{}都是必须存在的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> x <span class="type">int</span> = <span class="number">20</span></span><br><span class="line"> <span class="keyword">if</span> x > <span class="number">10</span> <span class="comment">// expected '{', found newline syntax</span></span><br><span class="line"> fmt.Println(<span class="string">"x is greater than 10"</span>)</span><br><span class="line"> <span class="comment">// 编译错误</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>3.右花括号}必须与if或者else处于同一行,即:<code>else</code>和<code>else if</code>不能另起一行,否则编译错误。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> x <span class="type">int</span> = <span class="number">20</span></span><br><span class="line"> <span class="keyword">if</span> x > <span class="number">10</span> {</span><br><span class="line"> fmt.Println(<span class="string">"x is greater than 10"</span>)</span><br><span class="line"> } </span><br><span class="line"> <span class="keyword">else</span> { <span class="comment">// syntax error: unexpected else, expected }</span></span><br><span class="line"> fmt.Println(<span class="string">"x is less than 10"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 编译错误</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>4.在if条件判断语句内可以添加变量初始化语句,使用 <strong>;</strong> 间隔,这个变量地作用域只能在该条件逻辑块内,其他地方则不起作用。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">if</span> x := random(<span class="number">50</span>); x > <span class="number">10</span> {</span><br><span class="line"> fmt.Println(<span class="string">"x is greater than 10"</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fmt.Println(<span class="string">"x is less than 10"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 超出条件逻辑块作用域,则会编译错误</span></span><br><span class="line"> fmt.Println(x) <span class="comment">// undefined: x compiler (UndeclaredName) </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="4-选择语句"><a href="#4-选择语句" class="headerlink" title="4 选择语句"></a>4 选择语句</h2><p>如果你的分支条件越来越多,你需要写很多的<code>if-else</code>来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候<code>switch</code>就能很好的解决这个问题,相比较C和Java等其它语言而言,Go语言中的switch结构使用上更加灵活。它接受任意形式的表达式,他的语法大致如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> var1 {</span><br><span class="line"> <span class="keyword">case</span> expr1:</span><br><span class="line"> <span class="comment">// do something1</span></span><br><span class="line"> <span class="keyword">case</span> expr2:</span><br><span class="line"> <span class="comment">// do something2</span></span><br><span class="line"> <span class="keyword">case</span> expr3:</span><br><span class="line"> <span class="comment">// do something3</span></span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="comment">// do something4</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-1-switch变量"><a href="#4-1-switch变量" class="headerlink" title="4.1 switch变量"></a>4.1 switch变量</h3><p>变量var1可以是任何类型,同时也可以是表达式,而expr1和expr2可以是同类型的任意值,也可以是表达式。类型不被局限于常量或整数,但必须是相同的类型,或者最终结果为相同类型的表达式,然后另外前花括号{必须和switch关键字在同一行。 </p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> x := <span class="number">10</span></span><br><span class="line"> <span class="keyword">switch</span> x {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span> - x:</span><br><span class="line"> fmt.Println(<span class="string">"20 - x = "</span>, x)</span><br><span class="line"> <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> fmt.Println(<span class="string">"default x value"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: 20 - x = 10</span></span><br></pre></td></tr></table></figure><p>经过输出你会发现,条件满足第一个case,故输出<code>20 - x = 10</code>,这看起来很正常,但你仔细一看,实际上你会发现当x=10的时候,<code>case 10</code>和<code>case 20 - x</code>都满足条件,但是只输出了<code>20 - x = 10</code>,只是为什么?</p><blockquote><p>因为Go语言使用快速的查找算法从上往下依次来测试switch条件与case分支的匹配情况,直到算法匹配到某个case或者进入default条件为止,一旦成功地匹配到某个分支,在执行完相应代码后就会退出整个switch代码块,也就是说您不需要特别使用break语句来表示结束。这就导致同样满足条件的<code>case 10</code>的条件并没有执行到,所以case分支应该是唯一且互斥的,从而避免这样的情况发生。</p></blockquote><p>另外需要注意的是:多个相同条件的case一定不能是常量,否则会发生编译错误:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> x := <span class="number">10</span></span><br><span class="line"> <span class="keyword">switch</span> x {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span> - <span class="number">10</span>:</span><br><span class="line"> fmt.Println(<span class="string">"20 - 10 = "</span>, x)</span><br><span class="line"> <span class="keyword">case</span> <span class="number">10</span>: <span class="comment">// duplicate case 10 (constant of type int) in expression switch</span></span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> fmt.Println(<span class="string">"default x value"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>该例中,如果case中无论是10还是经过计算表达式20 - 10得到的10都是相同条件的case,这样编译器会直接报重复的<code>case 10</code>,除非像上例中的<code>case 20 - x</code>这样, 在编译期间,编译器无法判断<code>20 - x = 10</code>一定成立,只有在运行期间才会知道,所以编译期间并不会编译错误,但是即便如此,我们也应该避免写出这样的代码。</p><h3 id="4-2-case多个条件"><a href="#4-2-case多个条件" class="headerlink" title="4.2 case多个条件"></a>4.2 case多个条件</h3><p>另外同一个case可以同时测试多个可能符合条件的值,使用逗号分割它们,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> x := <span class="number">10</span></span><br><span class="line"> <span class="keyword">switch</span> x {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span> - x, x, x * <span class="number">1</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line"> <span class="keyword">case</span> <span class="number">15</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = 15"</span>)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> fmt.Println(<span class="string">"default x value"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Output: x = 10</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-3-switch初始化变量"><a href="#4-3-switch初始化变量" class="headerlink" title="4.3 switch初始化变量"></a>4.3 switch初始化变量</h3><p>和if一样,swith后也是可以初始化变量,然后再进行判断,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">switch</span> x := random(<span class="number">50</span>); {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span> - x:</span><br><span class="line"> fmt.Println(<span class="string">"20 - x = "</span>, x)</span><br><span class="line"> <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line"> <span class="keyword">case</span> <span class="number">15</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = 15"</span>, x)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> fmt.Println(<span class="string">"default x value"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="4-4-fallthrough关键字"><a href="#4-4-fallthrough关键字" class="headerlink" title="4.4 fallthrough关键字"></a>4.4 fallthrough关键字</h3><p>如果想要执行完第一个case后,继续执行下一个case,我们可以使用<code>fallthrough</code>关键字,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> x := <span class="number">10</span></span><br><span class="line"> <span class="keyword">switch</span> x {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span> - x:</span><br><span class="line"> fmt.Println(<span class="string">"20 - x = "</span>, x)</span><br><span class="line"> <span class="keyword">fallthrough</span></span><br><span class="line"> <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = "</span>, x)</span><br><span class="line"> <span class="keyword">fallthrough</span></span><br><span class="line"> <span class="keyword">case</span> <span class="number">15</span>:</span><br><span class="line"> fmt.Println(<span class="string">"x = 15"</span>, x)</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> fmt.Println(<span class="string">"default x value"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output: </span></span><br><span class="line"><span class="comment">// 20 - x = 10</span></span><br><span class="line"><span class="comment">// x = 10</span></span><br><span class="line"><span class="comment">// x = 15</span></span><br></pre></td></tr></table></figure><p>该示例很好演示了<code>fallthrough</code>关键字的作用,但你仔细发现x=10并不满足<code>case 15</code>的条件,但是仍然会执行,这是为什么?</p><blockquote><p>注意: 程序执行到fallthrough的时候,无论下一个case是否满足条件,都会直接执行case后的代码。</p></blockquote><h3 id="4-5-switch表达式"><a href="#4-5-switch表达式" class="headerlink" title="4.5 switch表达式"></a>4.5 switch表达式</h3><p>当switch跟一个表达式的时候,表达式返回的类型也要和case的类型保持一致,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> age := <span class="number">20</span></span><br><span class="line"> <span class="keyword">switch</span> age > <span class="number">0</span> {</span><br><span class="line"> <span class="keyword">case</span> age <= <span class="number">1</span>:</span><br><span class="line"> fmt.Println(<span class="string">"you are just a baby"</span>)</span><br><span class="line"> <span class="keyword">case</span> age > <span class="number">1</span> && age <= <span class="number">18</span>:</span><br><span class="line"> fmt.Println(<span class="string">"you are still a teenager"</span>)</span><br><span class="line"> <span class="keyword">case</span> age > <span class="number">18</span>:</span><br><span class="line"> fmt.Println(<span class="string">"you are an adult"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Output: you are an adult</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="5-循环语句"><a href="#5-循环语句" class="headerlink" title="5 循环语句"></a>5 循环语句</h2><p>与多数语言不同的是,Go语言中的循环语句只支持for和range关键字,而不支持<code>while</code>和<code>do-while</code>结构。关键字for的基本使用方法与C和C++中非常接近。</p><h3 id="5-1-基于数值的for循环"><a href="#5-1-基于数值的for循环" class="headerlink" title="5.1 基于数值的for循环"></a>5.1 基于数值的for循环</h3><p>基于数值的for循环基本语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> expression1; expression2; expression3 {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>其中<code>expression1</code> 、 <code>expression2</code>和<code>expression3</code>都是表达式,其中<code>expression1</code>和<code>expression3</code>是变<br>量声明或者函数调用返回值之类的,<code>expression2</code>是用来条件判断,<code>expression1</code>在循环开始之前调用,<code>expression3</code>在每轮循环结束之时调用。</p><p>使用for循环需要注意两点:</p><ul><li>同if判断一样,条件语句不需要使用括号将条件包含起来()</li><li>左花括号必须和for处同一行,否则编译错误</li><li>Go语言的for循环同样支持continue和break来控制循环,但是它提供了一个更高级的break,可以选择中断哪一个循环。</li></ul><p>让我们来看一个例子:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> sum := <span class="number">0</span> </span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">10</span>; i++ { </span><br><span class="line"> sum += i </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="5-2-基于条件的for循环"><a href="#5-2-基于条件的for循环" class="headerlink" title="5.2 基于条件的for循环"></a>5.2 基于条件的for循环</h3><p>for循环的第二种形式是没有头部的条件判断迭代(类似其它语言中的<code>while</code>循环),基本语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> condition {</span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>您可以认为这是没有初始化语句和修饰语句的for结构,因此<code>;;</code>便是多余的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> i <span class="type">int</span> = <span class="number">5</span></span><br><span class="line"> <span class="keyword">for</span> i >= <span class="number">0</span> {</span><br><span class="line"> i = i - <span class="number">1</span></span><br><span class="line"> fmt.Printf(<span class="string">"The variable i is now: %d\n"</span>, i)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// The variable i is now: 4</span></span><br><span class="line"><span class="comment">// The variable i is now: 3</span></span><br><span class="line"><span class="comment">// The variable i is now: 2</span></span><br><span class="line"><span class="comment">// The variable i is now: 1</span></span><br><span class="line"><span class="comment">// The variable i is now: 0</span></span><br><span class="line"><span class="comment">// The variable i is now: -1</span></span><br></pre></td></tr></table></figure><h3 id="5-3-无限for循环"><a href="#5-3-无限for循环" class="headerlink" title="5.3 无限for循环"></a>5.3 无限for循环</h3><p>条件语句是可以被省略的,如<code>i:=0; ; i++</code>或<code>for { }</code>或<code>for ;; { }</code> 其中<code>;;</code>会在使用<code>gofmt</code>时被移除,这些循环的本质就是无限循环,最后一个形式也可以被改写为<code>for true { }</code>,但一般情况下都会直接写<code>for { }</code>。<br>如果for循环的头部没有条件语句,那么就会认为条件永远为true,因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> i <span class="type">int</span> = <span class="number">15</span></span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> <span class="keyword">if</span> i < <span class="number">10</span> {</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"The variable i is now: %d\n"</span>, i)</span><br><span class="line"> i--</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// The variable i is now: 15</span></span><br><span class="line"><span class="comment">// The variable i is now: 14</span></span><br><span class="line"><span class="comment">// The variable i is now: 13</span></span><br><span class="line"><span class="comment">// The variable i is now: 12</span></span><br><span class="line"><span class="comment">// The variable i is now: 11</span></span><br><span class="line"><span class="comment">// The variable i is now: 10</span></span><br></pre></td></tr></table></figure><h3 id="5-4-for-range循环"><a href="#5-4-for-range循环" class="headerlink" title="5.4 for-range循环"></a>5.4 for-range循环</h3><p>这是Golang特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合(包括数组、切片和map)。语法上很类似其它语言中<code>foreach</code>语句,但您依旧可以获得每次迭代所对应的索引,当迭代map时没有索引idx,取而代之的时key,一般语法格式如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> idx|key, value := <span class="keyword">range</span> collection</span><br><span class="line">{ </span><br><span class="line"> <span class="comment">// do something</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>要注意的是,<code>value</code>始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(译者注:如果val为指针,则会产生指针的拷贝,依旧可以修改集合中的原值),一个字符串是<code>Unicode</code>编码的字符(或称之为<code>rune</code>)集合,因此您也可以用它迭代字符串。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 迭代数组和切片类似,这里以数组为例</span></span><br><span class="line"> array := [...]<span class="type">int</span>{<span class="number">0</span>:<span class="number">1</span>, <span class="number">2</span>:<span class="number">3</span>, <span class="number">4</span>} </span><br><span class="line"> <span class="keyword">for</span> idx, value := <span class="keyword">range</span> array {</span><br><span class="line"> fmt.Printf(<span class="string">"index = %d, value = %d\n"</span>, idx, value)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Output:</span></span><br><span class="line"> <span class="comment">// index = 0, value = 1</span></span><br><span class="line"> <span class="comment">// index = 1, value = 0</span></span><br><span class="line"> <span class="comment">// index = 2, value = 3</span></span><br><span class="line"> <span class="comment">// index = 3, value = 4</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 迭代map</span></span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>,<span class="string">"age"</span>, <span class="string">"20"</span>} </span><br><span class="line"> <span class="keyword">for</span> key, value := <span class="keyword">range</span> mapping {</span><br><span class="line"> fmt.Printf(<span class="string">"key = %s, value = %s\n"</span>, key, value)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Output:</span></span><br><span class="line"> <span class="comment">// index = name, value = Ratel</span></span><br><span class="line"> <span class="comment">// index = age, value = 20</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 迭代字符串</span></span><br><span class="line"> str := <span class="string">"Ratel"</span></span><br><span class="line"> <span class="keyword">for</span> idx, value := <span class="keyword">range</span> str {</span><br><span class="line"> fmt.Printf(<span class="string">"idx = %d, value = %c\n"</span>, idx, value)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Output:</span></span><br><span class="line"> <span class="comment">// idx = 0, value = R</span></span><br><span class="line"> <span class="comment">// idx = 1, value = a</span></span><br><span class="line"> <span class="comment">// idx = 2, value = t</span></span><br><span class="line"> <span class="comment">// idx = 3, value = e</span></span><br><span class="line"> <span class="comment">// idx = 4, value = l</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="6-跳转语句"><a href="#6-跳转语句" class="headerlink" title="6 跳转语句"></a>6 跳转语句</h2><p>for、switch或select语句都可以配合标签[label]形式的标识符使用,即某一行第一个以冒号 <strong>:</strong> 结尾的单词,其中标签标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母。</p><h3 id="6-1-标签定义后必须使用"><a href="#6-1-标签定义后必须使用" class="headerlink" title="6.1 标签定义后必须使用"></a>6.1 标签定义后必须使用</h3><p>标签定义了就必须要使用,定义但未使用标签会导致编译错误,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line">START: <span class="comment">//label START declared and not used compiler (UnusedLabel)</span></span><br><span class="line"> <span class="keyword">if</span> i < <span class="number">3</span> {</span><br><span class="line"> fmt.Println(i)</span><br><span class="line"> i++</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="6-2-goto语句和标签之间不能定义其他变量"><a href="#6-2-goto语句和标签之间不能定义其他变量" class="headerlink" title="6.2 goto语句和标签之间不能定义其他变量"></a>6.2 goto语句和标签之间不能定义其他变量</h3><p>当goto后面的标签出现的位置在goto语句之后时,两者之间不能有新的变量定义,否则将导致编译错误,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> a := <span class="number">1</span></span><br><span class="line"> <span class="keyword">goto</span> TARGET <span class="comment">// goto TARGET jumps over variable declaration at line 11 compiler</span></span><br><span class="line"> b := <span class="number">9</span></span><br><span class="line">TARGET:</span><br><span class="line"> b += a</span><br><span class="line"> fmt.Printf(<span class="string">"a is %v, b is %v"</span>, a, b)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="6-3-goto语句与标签"><a href="#6-3-goto语句与标签" class="headerlink" title="6.3 goto语句与标签"></a>6.3 goto语句与标签</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> i := <span class="number">0</span></span><br><span class="line">START:</span><br><span class="line"> <span class="keyword">if</span> i < <span class="number">3</span> {</span><br><span class="line"> fmt.Println(i)</span><br><span class="line"> i++</span><br><span class="line"> <span class="keyword">goto</span> START</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Output:</span></span><br><span class="line"> <span class="comment">// 0</span></span><br><span class="line"> <span class="comment">// 1</span></span><br><span class="line"> <span class="comment">// 2</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="6-4-continue语句与标签"><a href="#6-4-continue语句与标签" class="headerlink" title="6.4 continue语句与标签"></a>6.4 continue语句与标签</h3><p>continue语句也可以和标签配置使用,我们知道continue语句只能在循环中使用,所以结合continue结合标签也只能在循环中使用,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i <= <span class="number">3</span>; i++ {</span><br><span class="line"> LABEL:</span><br><span class="line"> <span class="keyword">for</span> j := <span class="number">0</span>; j <= <span class="number">3</span>; j++ {</span><br><span class="line"> <span class="keyword">if</span> j == <span class="number">2</span> {</span><br><span class="line"> <span class="keyword">continue</span> LABEL</span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"i is: %d, and j is: %d\n"</span>, i, j)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 3</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 3</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 3</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 3</span></span><br></pre></td></tr></table></figure><p>该示例展示了<code>continue LABEL</code>的使用,根据输出结果来看,该示例是否使用标签结果都是一样,原因是continue本身功能就是退出本次循环,从下一次开始,而LABEL标签恰好在下一次的执行位置,所以要不要标签上面的结果都是一样。</p><p>接下来我们把标签LABEL移到外层循环呢?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">LABEL:</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i <= <span class="number">3</span>; i++ {</span><br><span class="line"> <span class="keyword">for</span> j := <span class="number">0</span>; j <= <span class="number">3</span>; j++ {</span><br><span class="line"> <span class="keyword">if</span> j == <span class="number">2</span> {</span><br><span class="line"> <span class="keyword">continue</span> LABEL</span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"i is: %d, and j is: %d\n"</span>, i, j)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 1</span></span><br></pre></td></tr></table></figure><p>根据结果我们看到,<code>continue LABEL</code>有两层意思,一是退出当前本次循环,二是跳转到标签处执行代码,当前i=0时,该函数执行到在j==2时退出了本次循环,如果没有标签LABEL或者标签LABEL在内层循环,则会输出<code>i is: 0, and j is 3</code>,现在LABEL在外层,则直接跳转到外层,从i=1开始重新执行,j=3则不会执行。</p><h3 id="6-5-break语句与标签"><a href="#6-5-break语句与标签" class="headerlink" title="6.5 break语句与标签"></a>6.5 break语句与标签</h3><p>break语句也可以和标签配置使用,我们知道break语句只能在循环中使用,所以结合break结合标签也只能在循环中使用,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i <= <span class="number">3</span>; i++ {</span><br><span class="line"> LABEL:</span><br><span class="line"> <span class="keyword">for</span> j := <span class="number">0</span>; j <= <span class="number">3</span>; j++ {</span><br><span class="line"> <span class="keyword">if</span> j == <span class="number">2</span> {</span><br><span class="line"> <span class="keyword">break</span> LABEL</span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"i is: %d, and j is: %d\n"</span>, i, j)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 1, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 2, and j is: 1</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 3, and j is: 1</span></span><br></pre></td></tr></table></figure><p>该示例展示了<code>break LABEL</code>的使用,根据输出结果来看,该示例是否使用标签结果都是一样,原因是break本身功能就是退出本层循环,从外层循环开始,而LABEL标签加在内层循环毫无意义,根本不会执行,所以要不要标签上面的结果都是一样。</p><p>接下来我们把标签LABEL移到外层循环呢?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line">LABEL:</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i <= <span class="number">3</span>; i++ {</span><br><span class="line"> <span class="keyword">for</span> j := <span class="number">0</span>; j <= <span class="number">3</span>; j++ {</span><br><span class="line"> <span class="keyword">if</span> j == <span class="number">2</span> {</span><br><span class="line"> <span class="keyword">break</span> LABEL</span><br><span class="line"> }</span><br><span class="line"> fmt.Printf(<span class="string">"i is: %d, and j is: %d\n"</span>, i, j)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Output:</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 0</span></span><br><span class="line"><span class="comment">// i is: 0, and j is: 1</span></span><br></pre></td></tr></table></figure><p>根据结果我们看到,<code>break LABEL</code>执行完成后,不仅会退出内层循环,同时也会退出外层循环,即标签在哪一层就会退出到哪一层。</p><h3 id="6-6-goto注意事项"><a href="#6-6-goto注意事项" class="headerlink" title="6.6 goto注意事项"></a>6.6 goto注意事项</h3><ul><li><p>避免滥用:在绝大多数情况下,可以通过更好的代码结构和控制流语句来代替goto。只在极少数情况下,例如处理错误跳转等,才会考虑使用 goto。</p></li><li><p>跳转范围:<code>goto</code>只能在同一个函数内部进行跳转,不能跨越函数。</p></li><li><p>不同分支中的标签:标签不能在不同的分支之间重复使用,例如一个标签不能同时在<code>if</code>和<code>switch</code>语句中。</p></li><li><p>避免形成死循环:使用<code>goto</code>时要小心,确保不会形成死循环,否则会导致程序无法正常退出。</p></li></ul><blockquote><p>特别注意:使用标签和<code>goto</code>语句是不被鼓励的,因为它们会很快导致非常糟糕的程序设计,而且总有更加可读的替代方<br>案来实现相同的需求。</p></blockquote><h2 id="7-总结"><a href="#7-总结" class="headerlink" title="7 总结"></a>7 总结</h2><p>到此相信你已经了解了流程控制,流程控制是编程之路上的重要一步,Golang提供了简单而强大的工具,使得我们能够清晰地表达程序的逻辑,并以高效的方式实现所需的功能。通过深入理解和灵活运用流程控制,您将能够编写出可读性强、可维护性高的Golang代码,下一章节我们将介绍<code>Package</code>包。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 流程控制 </tag>
</tags>
</entry>
<entry>
<title>Go基础篇-映射</title>
<link href="/article/a1137c81.html"/>
<url>/article/a1137c81.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_map.png" width="100%" alt="图片名称" align="center"/>通过上一章对切片的介绍,相信大家已经对切片有了深刻了解,现在我们将介绍一种非常熟悉的数据结构:映射(map),类似与其他语言的字典和Hash表。<p>在Golang中,map是一个内建的引用类型,一种无序的键值对集合,类似类似于其他语言中的字典或哈希表。它允许我们使用键来存储和检索值。同时map提供了一种高效的方式来存储、组织和检索数据。map的键必须是唯一的,而值可以是任意类型:数字、字符串、布尔值、切片、映射或其他自定义类型,与数组和切片不同,map的大小是动态的,可以根据需要自动增长和收缩。</p><p>Map提供了一些常用的方法来操作键值对,如Get用于获取指定键的值,Set用于设置键值对,Delete用于删除键值对,以及Len用于获取map中的键值对数量。此外,还有一些有用的方法如Range可以遍历map中的所有键值对。</p><p>Map在Golang中广泛应用于各种场景,如数据存储、缓存、配置管理、日志记录等。由于其高效、灵活和易于使用的特性,map成为了Golang中不可或缺的数据结构之一。</p><h2 id="2-Map特点"><a href="#2-Map特点" class="headerlink" title="2 Map特点"></a>2 Map特点</h2><ul><li>无序、无重复<ul><li>存储无序、遍历无序</li><li>key不可重复</li></ul></li><li>动态长度<ul><li>map长度可以动态增长或缩减</li></ul></li><li>查询快<ul><li>通过key查询快,正常情况O(1)的时间复杂度</li><li>极端情况,hash极不均匀,时间复杂度退化为O(n)</li></ul></li><li>线程不安全<ul><li>map本身不是线程安全的,高并发场景需要加锁</li></ul></li><li>内置、使用方便<ul><li>直接使用无需引入其他库</li></ul></li></ul><h2 id="3-Map底层原理"><a href="#3-Map底层原理" class="headerlink" title="3 Map底层原理"></a>3 Map底层原理</h2><p>Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个bucket就保存了map中的一个或一组键值对。</p><h3 id="3-1-核心数据结构"><a href="#3-1-核心数据结构" class="headerlink" title="3.1 核心数据结构"></a>3.1 核心数据结构</h3><p>Map主要有两个核心结构,基础结构和桶结构:</p><ul><li>hmap:Map的基础结构。</li><li>bmap:存放key-value的桶结构。严格来说hmap.buckets指向桶组成的数组,每个桶的头部是bmap,之后是8个key,再是8个value,最后是1个溢出桶指针,指向额外的桶链表,用于存储溢出的元素。</li></ul><p>Map的数据结构定义于<code>src/runtime/map.go</code>中,首先我们看下相关常量、hmap的定义。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A header for a Go map.</span></span><br><span class="line"><span class="keyword">type</span> hmap <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.</span></span><br><span class="line"> <span class="comment">// Make sure this stays in sync with the compiler's definition.</span></span><br><span class="line"> count <span class="type">int</span> <span class="comment">// # live cells == size of map. Must be first (used by len() builtin)</span></span><br><span class="line"> flags <span class="type">uint8</span> </span><br><span class="line"> B <span class="type">uint8</span> <span class="comment">// log_2 of # of buckets (can hold up to loadFactor * 2^B items)</span></span><br><span class="line"> noverflow <span class="type">uint16</span> <span class="comment">// approximate number of overflow buckets; see incrnoverflow for details</span></span><br><span class="line"> hash0 <span class="type">uint32</span> <span class="comment">// hash seed</span></span><br><span class="line"></span><br><span class="line"> buckets unsafe.Pointer <span class="comment">// array of 2^B Buckets. may be nil if count==0.</span></span><br><span class="line"> oldbuckets unsafe.Pointer <span class="comment">// previous bucket array of half the size, non-nil only when growing</span></span><br><span class="line"> nevacuate <span class="type">uintptr</span> <span class="comment">// progress counter for evacuation (buckets less than this have been evacuated)</span></span><br><span class="line"> </span><br><span class="line"> extra *mapextra <span class="comment">// optional fields</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// mapextra holds fields that are not present on all maps.</span></span><br><span class="line"><span class="keyword">type</span> mapextra <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// If both key and elem do not contain pointers and are inline, then we mark bucket</span></span><br><span class="line"> <span class="comment">// type as containing no pointers. This avoids scanning such maps.</span></span><br><span class="line"> <span class="comment">// However, bmap.overflow is a pointer. In order to keep overflow buckets</span></span><br><span class="line"> <span class="comment">// alive, we store pointers to all overflow buckets in hmap.extra.overflow and hmap.extra.oldoverflow.</span></span><br><span class="line"> <span class="comment">// overflow and oldoverflow are only used if key and elem do not contain pointers.</span></span><br><span class="line"> <span class="comment">// overflow contains overflow buckets for hmap.buckets.</span></span><br><span class="line"> <span class="comment">// oldoverflow contains overflow buckets for hmap.oldbuckets.</span></span><br><span class="line"> <span class="comment">// The indirection allows to store a pointer to the slice in hiter.</span></span><br><span class="line"> overflow *[]*bmap</span><br><span class="line"> oldoverflow *[]*bmap</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// nextOverflow holds a pointer to a free overflow bucket.</span></span><br><span class="line"> nextOverflow *bmap</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>hmap结构体字段说明:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">count:元素的个数。len()函数返回的就是这个值</span><br><span class="line">flags:状态标记位。如是否被多线程读写、迭代器在使用新桶、迭代器在使用旧桶等</span><br><span class="line">B:桶指数,表示hash数组中桶数量为2^B(不包括溢出桶)。最大可存储元素数量为loadFactor * 2^B</span><br><span class="line">noverflow:溢出桶的数量的近似值。详见函数incrnoverflow()</span><br><span class="line">hash0:hash种子</span><br><span class="line">buckets:指向2^B个桶组成的数组的指针。可能是nil如果count为0</span><br><span class="line">oldbuckets:指向长度为新桶数组一半的旧桶数组,仅在增长时为非零</span><br><span class="line">nevacuate:进度计数器,表示扩容后搬迁的进度(小于该数值的桶已迁移)</span><br><span class="line">extra.overflow:保存溢出桶链表</span><br><span class="line">extra.oldoverflow:保存旧溢出桶链表</span><br><span class="line">extra.nextOverflow:下一个空闲溢出桶地址</span><br></pre></td></tr></table></figure><p>再来看bmap的定义:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// A bucket for a Go map.</span></span><br><span class="line"><span class="keyword">type</span> bmap <span class="keyword">struct</span> {</span><br><span class="line"> <span class="comment">// tophash generally contains the top byte of the hash value</span></span><br><span class="line"> <span class="comment">// for each key in this bucket. If tophash[0] < minTopHash,</span></span><br><span class="line"> <span class="comment">// tophash[0] is a bucket evacuation state instead.</span></span><br><span class="line"> tophash [bucketCnt]<span class="type">uint8</span> <span class="comment">//存储桶内8个key的hash值的高字节。tophash[0] < minTopHash表示桶处于扩容迁移状态。</span></span><br><span class="line"> <span class="comment">// Followed by bucketCnt keys and then bucketCnt elems.</span></span><br><span class="line"> <span class="comment">// <span class="doctag">NOTE:</span> packing all the keys together and then all the elems together makes the</span></span><br><span class="line"> <span class="comment">// code a bit more complicated than alternating key/elem/key/elem/... but it allows</span></span><br><span class="line"> <span class="comment">// us to eliminate padding which would be needed for, e.g., map[int64]int8.</span></span><br><span class="line"> <span class="comment">// Followed by an overflow pointer.</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>特别注意:实际分配内存时会申请一个更大的内存空间A,A的前8字节为bmap,后面依次跟8个key、8个 value、1个溢出指针。把所有的key排在一起和所有的 value排列在一起,而不是交替排列(key/elem/key/elem/…),这样可以填充空白字节,例如 map[int64]int8。map 的桶结构实际指的是内存空间A。</p></blockquote><h3 id="3-2-数据结构图"><a href="#3-2-数据结构图" class="headerlink" title="3.2 数据结构图"></a>3.2 数据结构图</h3><p>创建Map时,会初始化一个hmap结构体,同时分配一个足够大的内存空间A。其中A的前段用于hash数组,A的后段预留给溢出的桶。于是hmap.buckets指向hash数组,即A的首地址;<code>hmap.extra.nextOverflow</code>初始时指向内存A中的后段,即hash数组结尾的下一个桶,也即第1个预留的溢出桶。所以当hash冲突需要使用到新的溢出桶时,会优先使用上述预留的溢出桶。<code>hmap.extra.nextOverflow</code>依次往后偏移直到用完所有的溢出桶,才有可能会申请新的溢出桶空间。<br><img src="/img/go/go_map_struct_overview.png" alt="数据结构"></p><h2 id="4-定义Map"><a href="#4-定义Map" class="headerlink" title="4 定义Map"></a>4 定义Map</h2><p>在Go中,映射的声明、定义和初始化是相对简单的操作。映射可以直接通过字面量声明、也可以使用<code>make()</code>函数创建。</p><blockquote><p>在具体声明定义map之前,先思考一个问题,哪些数据类型可以用来做map的key呢?</p></blockquote><h3 id="4-1-key的数据类型"><a href="#4-1-key的数据类型" class="headerlink" title="4.1 key的数据类型"></a>4.1 key的数据类型</h3><p>根据<a href="https://go.dev/ref/spec#Map_types">Golang编程规范</a>一文中可以看到这么一句话:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. </span><br><span class="line">If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic.</span><br></pre></td></tr></table></figure><p>根据原文意思,只有可比较的数据类型才能作为map的key,即能够通过<code>==</code>和<code>!=</code>进行比较的类型可以做为map的key。</p><h4 id="4-1-1-可比较的数据类型"><a href="#4-1-1-可比较的数据类型" class="headerlink" title="4.1.1 可比较的数据类型"></a>4.1.1 可比较的数据类型</h4><ul><li>布尔值 可比较,如果两个布尔值都为真或者都为假则他们是相等的。</li><li>整型 可比较且有序。</li><li>浮点型 可比较。如果两个浮点型值一样 (由 IEEE-754 标准定义),则两者相等。</li><li>复数型 可比较。如果两个复数型值的<code>real()</code>方法和<code>imag()</code>方法都相等,则两者相等。</li><li>字符串 可比较。</li><li>指针 可比较。如果两个指针指向相同的地址或者两者都为nil,则两者相等,但是指向不同的零大小变量的指针可能不相等。</li><li>通道 可比较。如果两个通道是由同一个<code>make</code>创建的 (引用的是同一个<code>channel</code>指针),或者两者都为nil, 则两者相等。</li><li>接口 可比较。<code>interface</code>的内部实现包含了2个字段,类型T和值V。如果两个接口具有相同的动态类型和动态值,或者两者都为nil, 则两者相等。</li><li>结构体 如果两个结构体的所有字段都是可比较的,则结构体是是比较的。如果两个结构体对应的非空白字段相等,则两者相等</li><li>数组 如果两个数组的所有元素都是可比较的则数组是可比较。如果两个数组的所有对应元素相等,则两者相等。</li></ul><h4 id="4-1-2-不可比较的数据类型"><a href="#4-1-2-不可比较的数据类型" class="headerlink" title="4.1.2 不可比较的数据类型"></a>4.1.2 不可比较的数据类型</h4><p>Golang中有3种数据类型不能比较,分别是slice、map、func,如果要比较这3种类型,使用<code>reflect.DeepEqual</code>函数。</p><p>具体参考:<a href="https://go.dev/ref/spec#Comparison_operators">Golang编程规范-比较操作符</a></p><h3 id="4-2-var关键字声明"><a href="#4-2-var关键字声明" class="headerlink" title="4.2 var关键字声明"></a>4.2 var关键字声明</h3><p>在Golang中,可以使用var关键字来声明一个映射。映射的声明语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mapName <span class="keyword">map</span>[keyType]valueType</span><br></pre></td></tr></table></figure><p>其中,mapName是映射的名称,keyType是映射中key的数据类型,valueType是映射中value的数据类型。</p><blockquote><p>key可以是任意可以用 == 或者 != 操作符比较的类型,比如string、int、float。 所以数组、切片和结构体不能作为key (译者注:含有数组切片的结构体不能作为key,只包含内建类型的 struct是可以作为key的),但是指针和接口类型可以。如果要用结构体作为key可以提供Key()和Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的key。 而valueType可以是任意类型。</p></blockquote><p>例如,声明一个key是字符串类型,value是整数类型的映射:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> mapping [<span class="type">string</span>]<span class="type">int</span></span><br></pre></td></tr></table></figure><h4 id="4-2-1-nil映射"><a href="#4-2-1-nil映射" class="headerlink" title="4.2.1 nil映射"></a>4.2.1 nil映射</h4><p>nil映射被用在很多标准库和内置函数中,描述一个不存在的映射的时候,就需要用到nil映射。比如函数在发生异常的时候,返回的映射就是nil映射,或者声明了变量没有初始化的Map的值是nil。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> mapping <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span></span><br><span class="line"> <span class="comment">// 尝试赋值</span></span><br><span class="line"> mapping[<span class="string">"age"</span>] = <span class="number">20</span></span><br><span class="line"> <span class="comment">// 尝试赋值编译不会报错,但是运行会报错:panic: assignment to entry in nil map</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由此可见,如果只是声明了映射,不为它初始化值,则它只是一个nil映射,后续无法进行增加键值对。</p><h3 id="4-3-使用字面量定义Map"><a href="#4-3-使用字面量定义Map" class="headerlink" title="4.3 使用字面量定义Map"></a>4.3 使用字面量定义Map</h3><p>我们可以通过var关键字和:=直接声明和初始化映射,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// var关键字</span></span><br><span class="line"> <span class="keyword">var</span> mapping1 = <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%s,Length=%d\n"</span>, mapping1, <span class="built_in">len</span>(mapping1))</span><br><span class="line"> <span class="comment">// Ouptput: Value=map[age:20 name:Ratel],Length=2</span></span><br><span class="line"></span><br><span class="line"> mapping2 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%s,Length=%d\n"</span>, mapping2, <span class="built_in">len</span>(mapping2))</span><br><span class="line"> <span class="comment">// Ouptput: Value=map[age:20 name:Ratel],Length=2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4-3-1-空Map"><a href="#4-3-1-空Map" class="headerlink" title="4.3.1 空Map"></a>4.3.1 空Map</h4><p>空映射一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空映射。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> mapping = <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>{}</span><br><span class="line"> <span class="comment">// 赋值</span></span><br><span class="line"> mapping[<span class="string">"age"</span>] = <span class="number">20</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping))</span><br><span class="line"> <span class="comment">// Ouptput: Value=map[age:20],Length=1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-3-通过make关键定义Map"><a href="#4-3-通过make关键定义Map" class="headerlink" title="4.3 通过make关键定义Map"></a>4.3 通过<code>make</code>关键定义Map</h3><p>我们知道Golang内置的<code>make()</code>函数可以实现创建切片、映射和通道,我们先看<code>make()</code>的方法签名:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The make built-in function allocates and initializes an object of type</span></span><br><span class="line"><span class="comment">// slice, map, or chan (only). Like new, the first argument is a type, not a</span></span><br><span class="line"><span class="comment">// value. Unlike new, make's return type is the same as the type of its</span></span><br><span class="line"><span class="comment">// argument, not a pointer to it. The specification of the result depends on</span></span><br><span class="line"><span class="comment">// the type:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//Slice: The size specifies the length. The capacity of the slice is</span></span><br><span class="line"><span class="comment">//equal to its length. A second integer argument may be provided to</span></span><br><span class="line"><span class="comment">//specify a different capacity; it must be no smaller than the</span></span><br><span class="line"><span class="comment">//length. For example, make([]int, 0, 10) allocates an underlying array</span></span><br><span class="line"><span class="comment">//of size 10 and returns a slice of length 0 and capacity 10 that is</span></span><br><span class="line"><span class="comment">//backed by this underlying array.</span></span><br><span class="line"><span class="comment">//Map: An empty map is allocated with enough space to hold the</span></span><br><span class="line"><span class="comment">//specified number of elements. The size may be omitted, in which case</span></span><br><span class="line"><span class="comment">//a small starting size is allocated.</span></span><br><span class="line"><span class="comment">//Channel: The channel's buffer is initialized with the specified</span></span><br><span class="line"><span class="comment">//buffer capacity. If zero, or the size is omitted, the channel is</span></span><br><span class="line"><span class="comment">//unbuffered.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">make</span><span class="params">(t Type, size ...IntegerType)</span></span> Type</span><br></pre></td></tr></table></figure><p>通过make()函数不仅会声明映射变量,同时还会对它做初始化操作,在底层开辟好一定容量的空间方便后续做添加元素操作,它的大致格式如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// var关键字</span></span><br><span class="line"><span class="keyword">var</span> mapName = <span class="built_in">make</span>(<span class="keyword">map</span>[keyType]valueType, capacity)</span><br><span class="line"></span><br><span class="line"><span class="comment">// :=符号</span></span><br><span class="line">mapName := <span class="built_in">make</span>(<span class="keyword">map</span>[keyType]valueType, capacity)</span><br></pre></td></tr></table></figure><p>通过<code>make()</code>函数声明映射时,<code>capacity</code>参数可以不指定,如不指定时,其容量默认等于0,但是如果指定容量,那容量一定不能小于0,即<code>0 <= capacity</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 不指定容量</span></span><br><span class="line"> <span class="keyword">var</span> mapping1 = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping1, <span class="built_in">len</span>(mapping1))</span><br><span class="line"> <span class="comment">// Ouptput: Value=map[],Length=0</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 指定容量为5</span></span><br><span class="line"> <span class="keyword">var</span> mapping2 = <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">int</span>, <span class="number">5</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping2, <span class="built_in">len</span>(mapping2))</span><br><span class="line"> <span class="comment">// Ouptput: Value=map[],Length=0</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上例子可以看到,通过<code>make()</code>函数创建的映射,即使指定了容量,Map也是空的,其长度仍为0,因为映射中没有具体元素。只有当添加了键值对后,Map的长度才会相应增加。容量参数主要是为了提前分配底层数组的大小,以减少Map在容量不足时的重新分配,从而提高性能。</p><h2 id="5-访问Map元素"><a href="#5-访问Map元素" class="headerlink" title="5 访问Map元素"></a>5 访问Map元素</h2><p>由于Map是无序的,每次打印出来的map都会不一样,因此它不能通过index获取,而必须通过key获取。在Golang中,要从Map中查找一个特定的key,可以通过下面的代码来实现:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> value, ok := mapping[<span class="string">"name"</span>]</span><br><span class="line"> <span class="keyword">if</span> ok {</span><br><span class="line"> fmt.Println(<span class="string">"value: "</span>, value) <span class="comment">// Output: value: Ratel</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fmt.Println(<span class="string">"not found"</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>判断是否成功找到特定的key,不需要检查取到的值是否为零值,只需查看第二个返回值ok,如果ok为true,表示map中含有该key,否则不含有,看起来非常清晰易懂。</p><p>如果我们不用ok来判断,我们直接获取一个明显不存在的key, 会是怎么样的结果呢?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> value := mapping[<span class="string">"address"</span>]</span><br><span class="line"> fmt.Println(<span class="string">"value: "</span>, value) <span class="comment">// Output: value: </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上示例我们发现,如果address不存在,则返回空字符串。 </p><blockquote><p>如果key值在映射中不存在,则<code>map[key]</code>返回的值就是valueType的零值。因此我们不能用value是否为空来判断key在映射中是否存在。</p></blockquote><h2 id="6-Map遍历"><a href="#6-Map遍历" class="headerlink" title="6 Map遍历"></a>6 Map遍历</h2><p>由于映射无序,所以不可能通过索引去取值,所以只能使用<code>range</code>关键字遍历。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> <span class="keyword">for</span> key, value := <span class="keyword">range</span> mapping {</span><br><span class="line"> fmt.Printf(<span class="string">"Key: %s, Value: %s\n"</span>, key, value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果不需要使用value,可以直接省略:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> <span class="keyword">for</span> key := <span class="keyword">range</span> mapping {</span><br><span class="line"> fmt.Println(key)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果不需要使用key,可以通过下划线’_’代替:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> <span class="keyword">for</span> _, value := <span class="keyword">range</span> mapping {</span><br><span class="line"> fmt.Println(value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注意: Map的迭代顺序是不确定的,并且不同的哈希函数实现可能导致不同的遍历顺序。在实践中,遍历的顺序是随机的,每一次遍历的顺序都不相同。 设计就是如此,每次都使用随机的遍历顺序可以强制要求程序不会依赖具体的哈希函数实现。如果要按顺序遍历key/value对,我们必须显式地对key进行排序,可以使用sort包的Strings函数对字符串slice进行排序。</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"sort"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> <span class="comment">// 声明一个切片保存map数据</span></span><br><span class="line"> <span class="keyword">var</span> slice []<span class="type">string</span></span><br><span class="line"> <span class="comment">// 将map数据遍历复制到切片中</span></span><br><span class="line"> <span class="keyword">for</span> key := <span class="keyword">range</span> mapping {</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, key)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 对切片进行排序</span></span><br><span class="line"> sort.Strings(slice)</span><br><span class="line"> <span class="comment">// 输出</span></span><br><span class="line"> fmt.Println(slice)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-修改Map元素"><a href="#7-修改Map元素" class="headerlink" title="7 修改Map元素"></a>7 修改Map元素</h2><p>由于映射无序,所以不可能通过索引修改值,可以key修改对应value值,具体语法为:<code>mapping[key] = value</code>,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping[<span class="string">"name"</span>] = <span class="string">"RatelWu"</span></span><br><span class="line"> fmt.Println(mapping) <span class="comment">// Output: map[age:20 name:Ratel]</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个示例中,<code>mapping["name"] = "RatelWu"</code>将映射mapping中key为name的value修改为RatelWu。</p><p>映射是引用类型,所以在函数中传递<strong>映射本身</strong>或者<strong>映射的指针</strong>都可以修改映射中的键值对,例如:</p><h3 id="7-1-传递Map"><a href="#7-1-传递Map" class="headerlink" title="7.1 传递Map"></a>7.1 传递Map</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := changeElement(mapping1, <span class="string">"name"</span>, <span class="string">"RatelWu"</span>) <span class="comment">// 修改键值对</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping1, <span class="built_in">len</span>(mapping1)) <span class="comment">// Output: Value=map[age:20 name:RatelWu],Length=2</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping2, <span class="built_in">len</span>(mapping2)) <span class="comment">// Output: Value=map[age:20 name:RatelWu],Length=2</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改指定key的元素为value, 如果key不能在则会新增键值对</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeElement</span><span class="params">(mapping <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>, key, value <span class="type">string</span>)</span></span> <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span> {</span><br><span class="line"> mapping[key] = value</span><br><span class="line"> <span class="keyword">return</span> mapping</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="7-2-传递Map指针"><a href="#7-2-传递Map指针" class="headerlink" title="7.2 传递Map指针"></a>7.2 传递Map指针</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := changeElement(&mapping1, <span class="string">"name"</span>, <span class="string">"RatelWu"</span>) <span class="comment">// 修改键值对</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping1, <span class="built_in">len</span>(mapping1)) <span class="comment">// Output: Value=map[age:20 name:RatelWu],Length=2</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping2, <span class="built_in">len</span>(mapping2)) <span class="comment">// Output: Value=map[age:20 name:RatelWu],Length=2</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改指定key的元素为value, 如果key不能在则会新增键值对</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeElement</span><span class="params">(mapping *<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>, key, value <span class="type">string</span>)</span></span> <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span> {</span><br><span class="line"> (*mapping)[key] = value</span><br><span class="line"> <span class="keyword">return</span> *mapping</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,当传递map的指针时,要确保map不为nil。在使用指针之前,通常需要先创建一个非空的map。如果map为nil,则对其进行解引用会导致运行时错误。</p></blockquote><h2 id="8-新增Map元素"><a href="#8-新增Map元素" class="headerlink" title="8 新增Map元素"></a>8 新增Map元素</h2><p>新增映射键值对非常简单,语法和修改键值对一样,<code>map[key] = value</code> 即可实现添加元素,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br><span class="line"> mapping[<span class="string">"name"</span>] = <span class="string">"RatelWu"</span></span><br><span class="line"> mapping[<span class="string">"age"</span>] = <span class="string">"20"</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping)) </span><br><span class="line"> <span class="comment">// Output: Value=map[age:20 name:RatelWu],Length=2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="9-Map扩容机制"><a href="#9-Map扩容机制" class="headerlink" title="9 Map扩容机制"></a>9 Map扩容机制</h2><p>众所周知,我们使用Map的目的就是要根据目标key快速找到value,然而,随着向map中添加的key越来越多,key发生碰撞的概率也越来越大。bucket中的8个cell会被逐渐塞满,查找、插入、删除key的效率也会越来越低。最理想的情况是一个bucket只装一个key,这样,就能达到<code>O(1)</code>的效率,但这样空间消耗太大,用空间换时间的代价太高。</p><p>Go语言采用一个bucket里装载8个key,定位到某个bucket后,还需要再定位到具体的key,这实际上又用了时间换空间,当然这样做也要有一个度,不然所有的key都落在了同一个bucket里,直接退化成了链表,各种操作的效率直接降为<code>O(n)</code>,这样做效率太低,对Map来说肯定是不行的。</p><p>所以为了平衡空间和时间,则有一个负载因子loadFactor, 源码中默认负载因子为6.5,Golang中Map的扩容缩容都是grow相关的函数来完成的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Like mapaccess, but allocates a slot for the key if it is not present in the map.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">mapassign</span><span class="params">(t *maptype, h *hmap, key unsafe.Pointer)</span></span> unsafe.Pointer {</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// If we hit the max load factor or we have too many overflow buckets,</span></span><br><span class="line"> <span class="comment">// and we're not already in the middle of growing, start growing.</span></span><br><span class="line"> <span class="comment">// 触发扩容时机的判断</span></span><br><span class="line"> <span class="keyword">if</span> !h.growing() && (overLoadFactor(h.count+<span class="number">1</span>, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {</span><br><span class="line"> hashGrow(t, h)</span><br><span class="line"> <span class="keyword">goto</span> again <span class="comment">// Growing the table invalidates everything, so try again</span></span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// growing reports whether h is growing. The growth may be to the same size or bigger.</span></span><br><span class="line"><span class="comment">// 判断当前map是否在扩容, 如果oldbuckets不为空,说明老的buckects还有还元素没有搬迁完,也说明当前正在进行扩容操作。</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(h *hmap)</span></span> growing() <span class="type">bool</span> {</span><br><span class="line"><span class="keyword">return</span> h.oldbuckets != <span class="literal">nil</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.</span></span><br><span class="line"><span class="comment">// 负载因子超过6.5</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">overLoadFactor</span><span class="params">(count <span class="type">int</span>, B <span class="type">uint8</span>)</span></span> <span class="type">bool</span> {</span><br><span class="line"> <span class="keyword">return</span> count > bucketCnt && <span class="type">uintptr</span>(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.</span></span><br><span class="line"><span class="comment">// Note that most of these overflow buckets must be in sparse use;</span></span><br><span class="line"><span class="comment">// if use was dense, then we'd have already triggered regular map growth.</span></span><br><span class="line"><span class="comment">// overflow buckets太多</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">tooManyOverflowBuckets</span><span class="params">(noverflow <span class="type">uint16</span>, B <span class="type">uint8</span>)</span></span> <span class="type">bool</span> {</span><br><span class="line"> <span class="comment">// If the threshold is too low, we do extraneous work.</span></span><br><span class="line"> <span class="comment">// If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory.</span></span><br><span class="line"> <span class="comment">// "too many" means (approximately) as many overflow buckets as regular buckets.</span></span><br><span class="line"> <span class="comment">// See incrnoverflow for more details.</span></span><br><span class="line"> <span class="keyword">if</span> B > <span class="number">15</span> {</span><br><span class="line"> B = <span class="number">15</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// The compiler doesn't see here that B < 16; mask B to generate shorter shift code.</span></span><br><span class="line"> <span class="keyword">return</span> noverflow >= <span class="type">uint16</span>(<span class="number">1</span>)<<(B&<span class="number">15</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>从赋值函数<code>mapassign()</code>可以看出,触发扩容有两个条件:</p><ul><li>当前不处在growing状态,即不在扩容状态;</li><li>且两个(或)关系的条件;<ul><li>条件1:元素个数count大于hash桶数量<code>(2^B)*6.5</code>。注意这里的hash桶指的是hash数组中的桶,不包括溢出的桶;</li><li>条件2:溢出的桶数量<code>noverflow>=32768(1<<15)</code>或者<code>noverflow>=hash</code>数组中桶数量。</li></ul></li></ul><p>对于命中条件1,2的限制,都会发生扩容。但是扩容的策略并不相同,毕竟两种条件应对的场景不同。</p><p>对于条件1,元素太多而bucket数量太少,这个很简单:直接将B加1,bucket最大数量(2^B)直接变成原来bucket数量的2倍。于是,就有新老bucket了。注意:这时候元素都在老bucket里,还没迁移到新的bucket来。而且新bucket只是最大数量变为原来最大数量(2^B)的2倍(2^B * 2)。</p><p>对于条件2,其实元素没那么多,但是<code>overflow buckets</code>数特别多,说明很多bucket都没装满。解决办法就是开辟一个新bucket空间,将老bucket中的元素移动到新 bucket,使得同一个bucket中的key排列地更紧密。这样原来在<code>overflow bucket</code>中的key可以移动到bucket中来。结果是节省空间,提高bucket利用率,map的查找和插入效率自然就会提升。</p><p>我们先看<code>hashGrow()</code>函数所做的工作,再来看具体的搬迁buckets是如何进行的。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">hashGrow</span><span class="params">(t *maptype, h *hmap)</span></span> {</span><br><span class="line"> <span class="comment">// If we've hit the load factor, get bigger.</span></span><br><span class="line"> <span class="comment">// Otherwise, there are too many overflow buckets,</span></span><br><span class="line"> <span class="comment">// so keep the same number of buckets and "grow" laterally.</span></span><br><span class="line"> <span class="comment">// B+1 相当于是原来2倍的空间</span></span><br><span class="line"> bigger := <span class="type">uint8</span>(<span class="number">1</span>)</span><br><span class="line"> <span class="comment">// 对应条件1</span></span><br><span class="line"> <span class="keyword">if</span> !overLoadFactor(h.count+<span class="number">1</span>, h.B) {</span><br><span class="line"> <span class="comment">// 进行等量的内存扩容,所以B不变</span></span><br><span class="line"> bigger = <span class="number">0</span></span><br><span class="line"> h.flags |= sameSizeGrow</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 将老buckets挂到buckets上</span></span><br><span class="line"> oldbuckets := h.buckets</span><br><span class="line"> <span class="comment">// 申请新的buckets空间</span></span><br><span class="line"> newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, <span class="literal">nil</span>)</span><br><span class="line"></span><br><span class="line"> flags := h.flags &^ (iterator | oldIterator)</span><br><span class="line"> <span class="keyword">if</span> h.flags&iterator != <span class="number">0</span> {</span><br><span class="line"> flags |= oldIterator</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// commit the grow (atomic wrt gc)</span></span><br><span class="line"> <span class="comment">// 提交grow的动作</span></span><br><span class="line"> h.B += bigger</span><br><span class="line"> h.flags = flags</span><br><span class="line"> h.oldbuckets = oldbuckets</span><br><span class="line"> h.buckets = newbuckets</span><br><span class="line"> <span class="comment">// 搬迁进度为0</span></span><br><span class="line"> h.nevacuate = <span class="number">0</span></span><br><span class="line"> <span class="comment">// overflow buckets数为0</span></span><br><span class="line"> h.noverflow = <span class="number">0</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> h.extra != <span class="literal">nil</span> && h.extra.overflow != <span class="literal">nil</span> {</span><br><span class="line"> <span class="comment">// Promote current overflow buckets to the old generation.</span></span><br><span class="line"> <span class="keyword">if</span> h.extra.oldoverflow != <span class="literal">nil</span> {</span><br><span class="line"> throw(<span class="string">"oldoverflow is not nil"</span>)</span><br><span class="line"> }</span><br><span class="line"> h.extra.oldoverflow = h.extra.overflow</span><br><span class="line"> h.extra.overflow = <span class="literal">nil</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> nextOverflow != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">if</span> h.extra == <span class="literal">nil</span> {</span><br><span class="line"> h.extra = <span class="built_in">new</span>(mapextra)</span><br><span class="line"> }</span><br><span class="line"> h.extra.nextOverflow = nextOverflow</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// the actual copying of the hash table data is done incrementally</span></span><br><span class="line"> <span class="comment">// by growWork() and evacuate().</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于map扩容需要将原有的key/value重新搬迁到新的内存地址,如果有大量的key/value需要搬迁,会非常影响性能。因此map的扩容采取了一种称为“渐进式”地方式,原有的key并不会一次性搬迁完毕,每次最多只会搬迁2个bucket。</p><p>上面说的<code>hashGrow()</code>函数实际上并没有真正地“搬迁”,它只是分配好了新的buckets,并将老的buckets挂到了oldbuckets字段上。真正搬迁buckets的动作在<code>growWork()</code>函数中,而调用<code>growWork()</code>函数的动作是在<code>mapassign</code>和<code>mapdelete</code>函数中。也就是插入或修改、删除key的时候,都会尝试进行搬迁buckets的工作。先检查 oldbuckets是否搬迁完毕,具体来说就是检查oldbuckets是否为nil。</p><p>扩容的预处理工作已经完成,接下来就是怎么把元素搬迁到新hash表里了。如果现在就一次全量搬迁过去,显然接下来会有比较长的一段时间map被占用(不支持并发)。所以搬迁的工作是增量搬迁的,这个从<code>hashGrow()</code>函数尾部的注释也可以看出。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">growWork</span><span class="params">(t *maptype, h *hmap, bucket <span class="type">uintptr</span>)</span></span> {</span><br><span class="line"><span class="comment">// make sure we evacuate the oldbucket corresponding</span></span><br><span class="line"><span class="comment">// to the bucket we're about to use</span></span><br><span class="line"> <span class="comment">// 为了确认搬迁的bucket是我们正在使用的bucket。evacuate就是具体搬迁bucket的代码。</span></span><br><span class="line">evacuate(t, h, bucket&h.oldbucketmask())</span><br><span class="line"></span><br><span class="line"><span class="comment">// evacuate one more oldbucket to make progress on growing</span></span><br><span class="line"> <span class="comment">// 再搬迁一个bucket,以加快搬迁进程</span></span><br><span class="line"><span class="keyword">if</span> h.growing() {</span><br><span class="line">evacuate(t, h, h.nevacuate)</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>bucket&h.oldbucketmask()</code>这行代码,如源码注释里说的,是为了确认搬迁的bucket是我们正在使用的bucket。<code>oldbucketmask()</code>函数返回扩容前的map的 bucketmask。而bucketmask的作用就是将key计算出来的哈希值与bucketmask相与,得到的结果就是key应该落入的桶。比如B=5,那么bucketmask的低5位是11111,其余位是0,hash值与其相与的意思是,只有hash值的低5位决定key到底落入哪个bucket,<code>evacuate()</code>就是具体搬迁bucket的代码,大家可自行查看。</p><ul><li>每执行一次插入或删除,都会调用<code>growWork()</code>函数搬迁0~2个hash桶(有可能这次需要搬迁的2个桶在此之前都被搬过了);</li><li>搬迁是以hash桶为单位的,包含对应的hash桶和这个桶的溢出链表;</li><li>被delete掉的元素(emptyone标志)会被舍弃不进行搬迁。</li></ul><blockquote><p>需要注意的是,由于哈希表的特性,map在扩容过程中可能会导致元素的顺序发生变化。因此,在迭代map的过程中进行插入、删除等操作可能导致意外的结果。如果需要稳定的顺序,建议在迭代过程中不进行修改操作,或者重新构建一个新的map。</p></blockquote><h2 id="10-删除Map元素"><a href="#10-删除Map元素" class="headerlink" title="10 删除Map元素"></a>10 删除Map元素</h2><p>从映射map中删除元素的语法为<code>delete(map, key)</code>,让我们看下<code>delete</code>函数的方法签名。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The delete built-in function deletes the element with the specified key</span></span><br><span class="line"><span class="comment">// (m[key]) from the map. If m is nil or there is no such element, delete</span></span><br><span class="line"><span class="comment">// is a no-op.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">delete</span><span class="params">(m <span class="keyword">map</span>[Type]Type1, key Type)</span></span></span><br></pre></td></tr></table></figure><p>通过函数介绍可知,delete方法用于删除map的键值对,入参为map和key且方法没有返回值,如果map映射为空或者map没有指定元素,则删除操作就相当于没执行。 例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> <span class="built_in">delete</span>(mapping, <span class="string">"age"</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping)) <span class="comment">// Output: Value=map[name:Ratel],Length=1</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 如果key在mapping中不存在,则相当于没有执行该方法。</span></span><br><span class="line"> <span class="built_in">delete</span>(mapping, <span class="string">"address"</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping)) <span class="comment">// Output: Value=map[name:Ratel],Length=1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="11-清空Map元素"><a href="#11-清空Map元素" class="headerlink" title="11 清空Map元素"></a>11 清空Map元素</h2><p>Golang中,我们可以通过内置的clear()函数进行Map的清空,具体语法为<code>clear(map)</code>,我们来看看<code>clear</code>函数的方法签名。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The clear built-in function clears maps and slices.</span></span><br><span class="line"><span class="comment">// For maps, clear deletes all entries, resulting in an empty map.</span></span><br><span class="line"><span class="comment">// For slices, clear sets all elements up to the length of the slice</span></span><br><span class="line"><span class="comment">// to the zero value of the respective element type. If the argument</span></span><br><span class="line"><span class="comment">// type is a type parameter, the type parameter's type set must</span></span><br><span class="line"><span class="comment">// contain only map or slice types, and clear performs the operation</span></span><br><span class="line"><span class="comment">// implied by the type argument.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">clear</span>[<span class="title">T</span> ~[]<span class="title">Type</span> | ~<span class="title">map</span>[<span class="title">Type</span>]<span class="title">Type1</span>]<span class="params">(t T)</span></span></span><br></pre></td></tr></table></figure><p>通过函数介绍可知,clear方法用于清空map的键值对或者清空切片元素,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping)) <span class="comment">// Output: Value=map[name:Ratel],Length=1</span></span><br><span class="line"> <span class="comment">// 清空map</span></span><br><span class="line"> clear(mapping)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Length=%d\n"</span>, mapping, <span class="built_in">len</span>(mapping)) <span class="comment">// Output: Value=map[name:Ratel],Length=1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="12-Map复制"><a href="#12-Map复制" class="headerlink" title="12 Map复制"></a>12 Map复制</h2><p>因为映射是引用类型,当你将一个映射赋值给另一个变量时,会复制对映射的引用,而不是复制整个映射的内容。</p><h3 id="12-1-复制"><a href="#12-1-复制" class="headerlink" title="12.1 =复制"></a>12.1 =复制</h3><p>当我们使用=复制时,这是一种浅拷贝,这意味着两个不同的映射共享一个底层存储空间,那么对一个映射的修改就会影响到另一个映射,例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := mapping1 <span class="comment">// 复制mapping2到mapping1</span></span><br><span class="line"> mapping1[<span class="string">"name"</span>] = <span class="string">"RatelWu"</span> <span class="comment">// 修改mapping1的第一个元素</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Pointer=%p, Length=%d\n"</span>, mapping1, &mapping1, <span class="built_in">len</span>(mapping1))</span><br><span class="line"> <span class="comment">// Output: Value=map[age:20 name:RatelWu],Pointer=0xc000042020, Length=2</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Pointer=%p, Length=%d\n"</span>, mapping2, &mapping2, <span class="built_in">len</span>(mapping2))</span><br><span class="line"> <span class="comment">// Output: Value=map[age:20 name:RatelWu],Pointer=0xc000042028, Length=2</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个例子中,修改了mapping1的key=name对应value为RatelWu,mapping2的key=name对应的value也变成了RatelWu,因为在赋值时是将mapping1的引用复制给了mapping2,它们是共享同一个底层存储结构。所以当你修改mapping1中该元素值的时候,mapping2也跟着修改了。</p><h3 id="12-2-自定义函数"><a href="#12-2-自定义函数" class="headerlink" title="12.2 自定义函数"></a>12.2 自定义函数</h3><p>在Go中,没有内置的函数来复制Map,如果想要拷贝一个Map,只有一种办法就是循环赋值,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := deepCopy(mapping1)</span><br><span class="line"> mapping1[<span class="string">"name"</span>] = <span class="string">"RatelWu"</span> <span class="comment">// 修改mapping1的第一个元素</span></span><br><span class="line"></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Pointer=%p, Length=%d\n"</span>, mapping1, &mapping1, <span class="built_in">len</span>(mapping1))</span><br><span class="line"> <span class="comment">// Output: Value=map[age:20 name:RatelWu],Pointer=0xc000042020, Length=2</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%v,Pointer=%p, Length=%d\n"</span>, mapping2, &mapping2, <span class="built_in">len</span>(mapping2))</span><br><span class="line"> <span class="comment">// Output: Value=map[age:20 name:Ratel],Pointer=0xc000042028, Length=2</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 深拷贝函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">deepCopy</span><span class="params">(mapping <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span></span> <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span> {</span><br><span class="line"> newmapping := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>, <span class="built_in">len</span>(mapping)) <span class="comment">// 创建一个与mapping容量相同的空映射</span></span><br><span class="line"> <span class="comment">// Copy from the original map to the target map</span></span><br><span class="line"> <span class="keyword">for</span> key, value := <span class="keyword">range</span> mapping {</span><br><span class="line"> newmapping[key] = value</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> newmapping</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上示例可知,通过深拷贝复制的映射是相互独立的,底层各自拥有一片存储空间。</p><h2 id="12-Map比较"><a href="#12-Map比较" class="headerlink" title="12 Map比较"></a>12 Map比较</h2><p>在Go中,由于映射map是引用类型,所以不能直接使用==运算符进行比较,映射只有和nil比较才能使用 <strong>==</strong> 符号比较判断。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br><span class="line"> mapping1 == mapping2 <span class="comment">// invalid operation: mapping1 == mapping2 (map can only be compared to nil)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>要比较两个映射是否相等,你需要逐个比较它们的元素。可以自定义函数循环来比较映射中的每个键值对,或者使用<code>reflect.DeepEqual()</code>函数进行比较。 </p><h3 id="12-1-自定义函数"><a href="#12-1-自定义函数" class="headerlink" title="12.1 自定义函数"></a>12.1 自定义函数</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 映射比较函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">mappingEqual</span><span class="params">(mapping1, mapping2 <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span></span> <span class="type">bool</span> {</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(mapping1) != <span class="built_in">len</span>(mapping2) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> key, value := <span class="keyword">range</span> mapping1 {</span><br><span class="line"> <span class="keyword">if</span> mapping2[key] != value {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br><span class="line"> mapping2[<span class="string">"name"</span>] = <span class="string">"Ratel"</span></span><br><span class="line"> mapping2[<span class="string">"age"</span>] = <span class="string">"20"</span></span><br><span class="line"></span><br><span class="line"> result := mappingEqual(mapping1, mapping2)</span><br><span class="line"> fmt.Println(<span class="string">"Mappings are equal:"</span>, result) <span class="comment">// Output: Mappings are equal: true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="12-2-reflect-DeepEqual-函数"><a href="#12-2-reflect-DeepEqual-函数" class="headerlink" title="12.2 reflect.DeepEqual()函数"></a>12.2 <code>reflect.DeepEqual()</code>函数</h3><p><code>reflect.DeepEqual()</code>函数可以比较两个接口类型的值,包括映射。但请注意,这种方法有一些限制,不适用于所有类型的映射,且在性能上可能不如手动比较。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"reflect"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> mapping1 := <span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>{<span class="string">"name"</span>: <span class="string">"Ratel"</span>, <span class="string">"age"</span>: <span class="string">"20"</span>}</span><br><span class="line"> mapping2 := <span class="built_in">make</span>(<span class="keyword">map</span>[<span class="type">string</span>]<span class="type">string</span>)</span><br><span class="line"> mapping2[<span class="string">"name"</span>] = <span class="string">"Ratel"</span></span><br><span class="line"> mapping2[<span class="string">"age"</span>] = <span class="string">"20"</span></span><br><span class="line"></span><br><span class="line"> result := reflect.DeepEqual(mapping1, mapping2)</span><br><span class="line"> fmt.Println(<span class="string">"Mappings are equal:"</span>, result) <span class="comment">// Output: Mappings are equal: true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="12-3-注意事项"><a href="#12-3-注意事项" class="headerlink" title="12.3 注意事项"></a>12.3 注意事项</h3><p>使用<code>reflect.DeepEqual()</code>函数比较两个map有以下一些注意事项:</p><ul><li>不可比较类型的限制:<ul><li>如果map中包含不可比较的类型,<code>DeepEqual()</code>可能无法正常工作,因为不可比较的类型不支持直接的深度比较。</li><li>在map的值类型中使用不可比较的类型可能导致不准确的比较结果。</li></ul></li><li>不同类型的map:<ul><li>对于不同类型的map,<code>DeepEqual()</code>会返回false,即使它们的内容相同。</li><li>比较map时,要确保它们的类型相同。</li></ul></li><li>顺序敏感性:<ul><li><code>DeepEqual()</code>会递归比较map中的元素,但不会关心元素的顺序。如果你需要对map中的元素的顺序敏感,可能需要手动遍历map进行比较。</li></ul></li><li>性能影响:<ul><li><code>DeepEqual()</code>在比较大型map或深层次嵌套的map时,可能会产生较高的性能开销。</li><li>对于大型数据集,手动比较map中的元素可能更有效。</li></ul></li></ul><h2 id="13-总结"><a href="#13-总结" class="headerlink" title="13 总结"></a>13 总结</h2><p>经过这一章节,相信大家对映射Map已经比较了解,并知道针对map的常规操作以及它的底层原理,下一章节我们将继续Golang中的流程控制。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 复合类型 </tag>
<tag> 映射 </tag>
</tags>
</entry>
<entry>
<title>Go基础篇-切片</title>
<link href="/article/ee8b0644.html"/>
<url>/article/ee8b0644.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_slice.png" width="100%" alt="图片名称" align="center"/>书接上回,我们在上一章里了解数组的基本定义,并熟知了数组的底层原理,同时也熟悉了数组的一些基本操作,但是Go语言中数组的使用并不多,其根本原因就是数组不够灵活,但是切片使用非常广泛。<p>在Go语言中,切片是一个拥有相同类型元素动态长度的序列。本质是一个数组的引用,但是与数组不同的是切片是动态的,长度可以在运行时改变,切片的使用更加灵活,通常在实际开发中更常用。 本章节我们将详细介绍切片,并深入探讨它的特性、用法和常见操作。</p><h2 id="2-切片底层原理"><a href="#2-切片底层原理" class="headerlink" title="2 切片底层原理"></a>2 切片底层原理</h2><p>切片的底层原理是基于数组的一种数据结构。切片是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。</p><p>在Go语言中,切片是对数组的抽象,它提供了更强大的能力和便捷性。切片本身并不是动态数组或者数组指针,而是通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。你可以把它当作类似下面的一个结构体,内部包含三个元素分别是:指向底层数组的指针、切片的长度和切片的容量。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> slice <span class="keyword">struct</span> {</span><br><span class="line"> arrayPtr unsafe.Pointer <span class="comment">// 指向底层数组的指针</span></span><br><span class="line"> <span class="built_in">len</span> <span class="type">int</span> <span class="comment">// 切片的长度</span></span><br><span class="line"> <span class="built_in">cap</span> <span class="type">int</span> <span class="comment">// 切片的容量</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当对切片进行操作时,实际上是在操作底层数组。例如,对切片进行追加操作时,会先判断切片的容量是否足够,如果不够则进行扩容操作,将底层数组的长度增加一倍(或根据cap参数指定的值进行扩容),并将切片的指针指向新的数组。</p><p>总之,切片的底层原理是基于数组的一种数据结构,通过指针引用底层数组,并提供了更强大的能力和便捷性。</p><h2 id="3-切片特点"><a href="#3-切片特点" class="headerlink" title="3 切片特点"></a>3 切片特点</h2><ul><li>动态长度<ul><li>切片长度可以动态增长或缩减,而不需要提前声明容量。</li><li>使用<code>append()</code>函数可以向切片追加元素,自动扩容切片。</li></ul></li><li>引用底层数组<ul><li>切片是对底层数组的一个引用,多个切片可以共享相同的底层数组。</li><li>修改切片中的元素会影响到底层数组中的对应元素。</li></ul></li><li>灵活的操作<ul><li>可以通过切片进行切割(slice)、追加(append)和复制(copy)等操作。</li><li>切片提供了便捷的方式来处理数组,规避了数组固定长度的限制。</li></ul></li><li>高效的内存管理<ul><li>切片是一个轻量级的数据结构,只是包含了指向底层数组的指针、长度和容量等信息。</li><li>动态扩容时,Go会自动处理底层数组的重新分配和拷贝,使其更高效。</li></ul></li><li>传递切片:<ul><li>传递切片时,不会复制整个切片的内容,而是复制切片的引用。</li><li>这意味着不同的切片可能共享相同的底层数组,但修改其中一个切片不会影响另一个切片的长度或容量。</li></ul></li><li>不需要声明大小:<ul><li>与数组不同,切片不需要提前声明大小。它可以根据需要动态调整大小。</li><li>理解这些切片的特点可以帮助你更好地利用切片进行数据处理和管理。它们提供了一种便捷且高效的方式来操作数据集合,特别是当处理可变长度数据集合时。</li></ul></li></ul><h2 id="4-定义切片"><a href="#4-定义切片" class="headerlink" title="4 定义切片"></a>4 定义切片</h2><p>在Go中,切片的声明、定义和初始化是相对简单的操作。切片可以直接通过字面量声明、也可以由数组或者另一个切片生成、还可以使用<code>make()</code>函数创建。</p><h3 id="4-1-var关键字声明"><a href="#4-1-var关键字声明" class="headerlink" title="4.1 var关键字声明"></a>4.1 var关键字声明</h3><p>在Go语言中,可以使用var关键字来声明一个切片。切片的声明语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> sliceName []dataType</span><br></pre></td></tr></table></figure><p>其中,sliceName是切片的名称,dataType是切片中元素的数据类型,从声明方式来讲切片和数组最大的区别就是:数组会指定size大小而切片不需要指定。</p><p>例如,声明一个整数类型的切片:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> slice []<span class="type">int</span></span><br></pre></td></tr></table></figure><h3 id="4-2-使用字面量定义切片"><a href="#4-2-使用字面量定义切片" class="headerlink" title="4.2 使用字面量定义切片"></a>4.2 使用字面量定义切片</h3><p>我们可以通过var关键字和:=直接声明和初始化切片。这里看示例之前,我们先引出一个字面量的问题。</p><h4 id="4-2-1-什么叫字面量"><a href="#4-2-1-什么叫字面量" class="headerlink" title="4.2.1 什么叫字面量"></a>4.2.1 什么叫字面量</h4><p>在编程中,字面量(literal)是指表示自己的值的符号或语法表示。字面量直接表示固定的值,而不是表示一个变量或者存储在变量中的值。字面量提供了数据的直接表示方式。</p><p>字面量的概念用于表示代码中直接提供常量值的语法结构。在切片字面量中,它允许程序员直接提供切片的内容,以便在程序中直接使用。</p><p>在<strong>切片 (slice)</strong> 的语境中,切片字面量是一种用于直接创建切片的表示方式。切片字面量使用数组或者其他切片作为基础来创建新的切片。在不同的编程语言中,切片字面量的语法可能有所不同,但其基本原理是相似的,可以直接定义一个切片的初始内容。</p><p>简单总结来就一句话,字面量定义切片<strong>就是直接通过一个常量值的来定义一个切片变量,而不是通过索引依次去赋值</strong>。我们来看如何使用字面量定义切片。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> slice1 []<span class="type">int</span> = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1)) </span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4 5],Length=5,Capacity=5</span></span><br><span class="line"> </span><br><span class="line"> slice2 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4 5],Length=5,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们还可以在{}中使用<strong>index:value</strong>去指定索引和对应的值,意思就是我们可以选择初始化部分数据,如果中间没有指定索引和值则会设置为各数据类型的零值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{<span class="number">0</span>:<span class="number">1</span>, <span class="number">2</span>:<span class="number">3</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 0 3 3 4 5],Length=6,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 上一章节忘记说明其实数组也是可以这样声明的</span></span><br><span class="line"> array := [<span class="number">6</span>]<span class="type">int</span>{<span class="number">0</span>:<span class="number">1</span>, <span class="number">2</span>:<span class="number">3</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, array, <span class="built_in">len</span>(array), <span class="built_in">cap</span>(array))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 0 3 3 4 5],Length=6,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据上面的例子,我们可以看出虽然索引为1的位置没有赋值,但是Go编译器自动为我们设置了零值,因为int类型的零值就是0,另外说明一点,通过字面量声明的切片它的Length和Capacity都是一样。</p><blockquote><p>需要注意的是:使用字面量声明切片时,有两种特殊情况:一是nil切片,二是空切片。这两种情况创建出来的切片,其长度为0,是不能直接通过下标的方式来赋值的。</p></blockquote><h4 id="4-2-2-nil切片"><a href="#4-2-2-nil切片" class="headerlink" title="4.2.2 nil切片"></a>4.2.2 nil切片</h4><p>nil切片被用在很多标准库和内置函数中,描述一个不存在的切片的时候,就需要用到nil切片。比如函数在发生异常的时候,返回的切片就是nil切片。nil切片的指针指向nil。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> slice []<span class="type">int</span></span><br><span class="line"></span><br><span class="line">slice[<span class="number">0</span>] = <span class="number">1</span></span><br><span class="line"><span class="comment">// 尝试赋值编译不会报错,但是运行会报错:runtime error: index out of range [0] with length 0</span></span><br></pre></td></tr></table></figure><h4 id="4-2-3-空切片"><a href="#4-2-3-空切片" class="headerlink" title="4.2.3 空切片"></a>4.2.3 空切片</h4><p>空切片一般会用来表示一个空的集合。比如数据库查询,一条结果也没有查到,那么就可以返回一个空切片。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 切片的元素值是切片类型的零值,即 int 0, string '', 引用类型 nil</span></span><br><span class="line"><span class="keyword">var</span> slice = []<span class="type">int</span>{}</span><br><span class="line">slice := []{}</span><br><span class="line"><span class="comment">// 尝试赋值编译不会报错,但是运行会报错:runtime error: index out of range [0] with length 0</span></span><br><span class="line">slice[<span class="number">0</span>] = <span class="number">1</span></span><br></pre></td></tr></table></figure><p>空切片和nil切片的区别在于,空切片指向的地址不是nil,指向的是一个内存地址,但是它没有分配任何内存空间,即底层元素包含0个元素。</p><p>最后需要说明的一点是。不管是使用nil切片还是空切片,对其调用内置函数<code>append()</code>,<code>len()</code>和<code>cap()</code>的效果都是一样的。</p><h3 id="4-3-基于数组或切片定义切片"><a href="#4-3-基于数组或切片定义切片" class="headerlink" title="4.3 基于数组或切片定义切片"></a>4.3 基于数组或切片定义切片</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sliceName := arrayName[start:end:capEnd]</span><br></pre></td></tr></table></figure><p>我们可以通过以上这样形式的语法在数组的基础上生成一个切片,其中<code>start</code>就是开始的索引位置,<code>end</code>就是结束的索引位置,<code>capEnd</code>就是切片的容量结束位置,但不是新切片的容量。</p><blockquote><p><strong>注意:</strong></p><blockquote><ol><li>start、end、capEnd默认都是可以省略的,如果三个值都省略的话,那么[]中的符号 <strong>:</strong> 就不能省略,否则编译错误,但是符号 <strong>:</strong> 也只能有一个,否则编译也会报错。 </li><li>start、end、capEnd三者之间满足一个不等式关系(<code>0 <= start <= end <= capEnd <= len(array) = cap(array)</code>) 。</li></ol></blockquote></blockquote><p>接下来我们通过具体示例来说明。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 定义一个数组</span></span><br><span class="line"> array := [<span class="number">6</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}</span><br><span class="line"> <span class="comment">// 通过array[1:4:6]创建一个切片</span></span><br><span class="line"> slice1 := array[<span class="number">1</span>:<span class="number">4</span>:<span class="number">6</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[2 3 4],Length=3,Capacity=5</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// capEnd是可以省略的, capEnd不指定默认是数组容量len(array),两种效果其实是一样的</span></span><br><span class="line"> slice2 := array[<span class="number">1</span>:<span class="number">4</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[2 3 4],Length=3,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过以上操作我们确定,切片的长度和容量的计算公式:</p><blockquote><p> Length = end - start<br> Capacity = capEnd - start</p></blockquote><p>从上面的例子我们可以看到,<strong>从数组切出来的切片不包含结束的索引位置对应的元素</strong>,当然我们也可以不指定<code>end</code>。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 定义一个数组</span></span><br><span class="line"> array := [<span class="number">6</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}</span><br><span class="line"> <span class="comment">// 通过array[1:]创建一个切片, end是可以省略的,end不指定默认为数组容量len(array)</span></span><br><span class="line"> slice1 := array[<span class="number">1</span>:]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1)) </span><br><span class="line"> <span class="comment">// Ouptput: Value=[2 3 4 5 6],Length=5,Capacity=5</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等价于下面这两个表达</span></span><br><span class="line"> slice2 := array[<span class="number">1</span>:<span class="number">6</span>]</span><br><span class="line"> <span class="comment">// 等价于 slice2 := array[1:6:6]</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2)) </span><br><span class="line"> <span class="comment">// Ouptput: Value=[2 3 4 5 6],Length=5,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你想从第1个元素开始截取,可以不指定<code>start</code>或者<code>start</code>设置为0。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 定义一个数组</span></span><br><span class="line"> array := [<span class="number">6</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}</span><br><span class="line"> <span class="comment">// 通过array[0:]创建一个切片</span></span><br><span class="line"> slice1 := array[<span class="number">0</span>:<span class="number">4</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1)) </span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4],Length=4,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等价于下面的表达</span></span><br><span class="line"> slice2 := array[:<span class="number">4</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4],Length=4,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果你想创建一个包含整个数组元素的切片,可以使用不指定前后索引位置。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 定义一个数组</span></span><br><span class="line"> array := [<span class="number">6</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>}</span><br><span class="line"> <span class="comment">// 通过array[:]创建一个切片</span></span><br><span class="line"> slice1 := array[:]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4 5 6],Length=6,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 等价于下面这两个表达</span></span><br><span class="line"> slice2 := array[<span class="number">0</span>:<span class="number">6</span>]</span><br><span class="line"> <span class="comment">// 等价于 slice2 := array[0:6:6]</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2)) </span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 4 5 6],Length=6,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="4-4-通过make关键定义切片"><a href="#4-4-通过make关键定义切片" class="headerlink" title="4.4 通过make关键定义切片"></a>4.4 通过<code>make</code>关键定义切片</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// var关键字</span></span><br><span class="line"><span class="keyword">var</span> sliceName = <span class="built_in">make</span>([]dataType, length, capacity)</span><br><span class="line"></span><br><span class="line"><span class="comment">// :=符号</span></span><br><span class="line">sliceName := <span class="built_in">make</span>([]dataType, length, capacity)</span><br></pre></td></tr></table></figure><p>我们可以通过<code>make()</code>函数声明切片时,<code>length</code>参数必填,但<code>capacity</code>可以不指定,如不指定时,其容量默认等于长度值,但是如果指定容量,那容量一定不能小于长度,即<code>0 <= length <= capacity</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 以下两种声明方式是等价的</span></span><br><span class="line"><span class="comment">// 方式1</span></span><br><span class="line"><span class="keyword">var</span> slice1 = <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>)</span><br><span class="line">fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"><span class="comment">// Ouptput: Value=[0 0 0 0 0],Length=5,Capacity=5</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式2</span></span><br><span class="line"><span class="keyword">var</span> slice2 = <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>, <span class="number">5</span>)</span><br><span class="line">fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"><span class="comment">// Ouptput: Value=[0 0 0 0 0],Length=5,Capacity=5</span></span><br></pre></td></tr></table></figure><p>通过以上例子可以看到,通过<code>make</code>关键字创建的切片,切片中的元素值都是零值,要想改变元素值只有后续对它就行重新赋值。</p><h2 id="5-访问切片元素"><a href="#5-访问切片元素" class="headerlink" title="5 访问切片元素"></a>5 访问切片元素</h2><p>可以通过索引来访问切片中的元素。索引从0开始,逐个递增。例如,要访问上面声明的切片slice的第一个元素,可以使用<code>slice[0]</code>。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> first := slice[<span class="number">0</span>]</span><br><span class="line"> fmt.Println(<span class="string">"First element: "</span>, first) <span class="comment">// Output: First element: 1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="6-切片遍历"><a href="#6-切片遍历" class="headerlink" title="6 切片遍历"></a>6 切片遍历</h2><p>和数组一样,Go中的切片也有两种方式遍历。你可以使用传统的<code>for</code>循环,也可以使用<code>range</code>关键字。</p><h3 id="6-1-For关键字遍历"><a href="#6-1-For关键字遍历" class="headerlink" title="6.1 For关键字遍历"></a>6.1 For关键字遍历</h3><p>首先可以通过for关键字遍历,其中需要借助<code>len()</code>函数计算切片长度。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="built_in">len</span>(slice); i++ {</span><br><span class="line"> fmt.Println(slice[i])</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="6-2-Range关键字遍历"><a href="#6-2-Range关键字遍历" class="headerlink" title="6.2 Range关键字遍历"></a>6.2 Range关键字遍历</h3><p>也可以通过range关键字遍历切片,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> index, value := <span class="keyword">range</span> slice {</span><br><span class="line"> fmt.Printf(<span class="string">"Index: %d, Value: %d\n"</span>, index, value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果不需要使用索引,可以通过下划线’_’代替:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> _, value := <span class="keyword">range</span> slice {</span><br><span class="line"> fmt.Println(value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通常来说,我们使用<code>for range</code>方式迭代可能会好一点,因为这种迭代可以保证不会出现数组越界的情况,每次迭代对数组的访问可以省略对下标越界判断,当然具体使用,因实际情况不同而不同。</p><h2 id="7-修改切片元素"><a href="#7-修改切片元素" class="headerlink" title="7 修改切片元素"></a>7 修改切片元素</h2><p>可以通过索引来修改切片中的元素。例如,要将上面声明的切片slice的第一个元素修改为0,可以使用<code>slice[0] = 0</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> slice[<span class="number">0</span>] = <span class="number">0</span></span><br><span class="line"> fmt.Println(slice) <span class="comment">// Output: [0,2,3,4,5]</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个示例中,slice[0] = 0将切片slice中索引为0的元素修改为0。直接通过索引即可修改切片中特定位置的元素值。</p><blockquote><p>要注意确保索引不超出切片的范围,否则会导致运行时错误。Go中的切片索引从0开始,到<code>len(slice) - 1</code>,超出这个范围会导致越界错误。</p></blockquote><p>切片是引用类型,所以在函数中传递<strong>切片本身</strong>或者<strong>切片的指针</strong>都可以修改切片中特定索引的元素值。这是因为切片本身包含了对底层数组的引用,而不是数组的副本,例如。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice1 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> slice2 := changeElement(slice1) <span class="comment">// 修改数组元素</span></span><br><span class="line"> fmt.Println(slice1) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line"> fmt.Println(slice2) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改切片第一个元素的值为10,并返回修改后的切皮</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeElement</span><span class="params">(slice []<span class="type">int</span>)</span></span> []<span class="type">int</span> {</span><br><span class="line"> slice[<span class="number">0</span>] = <span class="number">10</span></span><br><span class="line"> <span class="keyword">return</span> slice</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="8-新增切片元素"><a href="#8-新增切片元素" class="headerlink" title="8 新增切片元素"></a>8 新增切片元素</h2><p>在上一章节中我们知道,数组一旦声明,其大小是固定的,无法新增删除元素,但是切片是可以,Go语言提供了内置函数<code>append()</code>来实现给切片增加元素,来看一下<code>append()</code>的方法签名。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// The append built-in function appends elements to the end of a slice. If</span></span><br><span class="line"><span class="comment">// it has sufficient capacity, the destination is resliced to accommodate the</span></span><br><span class="line"><span class="comment">// new elements. If it does not, a new underlying array will be allocated.</span></span><br><span class="line"><span class="comment">// Append returns the updated slice. It is therefore necessary to store the</span></span><br><span class="line"><span class="comment">// result of append, often in the variable holding the slice itself:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//slice = append(slice, elem1, elem2)</span></span><br><span class="line"><span class="comment">//slice = append(slice, anotherSlice...)</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">// As a special case, it is legal to append a string to a byte slice, like this:</span></span><br><span class="line"><span class="comment">//</span></span><br><span class="line"><span class="comment">//slice = append([]byte("hello "), "world"...)</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">append</span><span class="params">(slice []Type, elems ...Type)</span></span> []Type</span><br></pre></td></tr></table></figure><p>方法签名:该方法接受两个参数,第一个参数是一个切片,第二个参数是一个对应类型的可变参数。<br>方法说明:该方法用于将单个或多个元素添加到切片的尾部,如果切片的容量足够容纳新增的元素,则追加元素到原来切片,如果不够,底层将会重新申请一个新数组,然后复制原来的数据到新数组,然后返回新切片。</p><p>切片新增元素需要考虑在切片哪个位置新增元素,这里有三种情况分别是切片尾部、切片首部和切片中间新增元素,我们分别看看到底如何新增。</p><h3 id="8-1-切片首部新增"><a href="#8-1-切片首部新增" class="headerlink" title="8.1 切片首部新增"></a>8.1 切片首部新增</h3><p>由于<code>append()</code>函数只能向切片尾部追加元素,所以我们只能先创建一个包含一个或者多个元素的切片,然后利用append方法将原来的切片传入追加到新切片上即可,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 切片首部增加元素</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice, &slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2],Pointer=0xc000008048, Length=2,Capacity=2</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 首部新增一个元素</span></span><br><span class="line"> slice1 := <span class="built_in">append</span>([]<span class="type">int</span>{<span class="number">5</span>}, slice...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Output: Value=[5 1 2],Pointer=0xc000008078, Length=3,Capacity=3</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 首部新增多个元素</span></span><br><span class="line"> <span class="keyword">var</span> newSlice = []<span class="type">int</span>{<span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>}</span><br><span class="line"> slice2 := <span class="built_in">append</span>(newSlice, slice...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Output: Value=[5 6 7 1 2],Pointer=0xc0000080a8, Length=5,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="8-2-切片尾部新增"><a href="#8-2-切片尾部新增" class="headerlink" title="8.2 切片尾部新增"></a>8.2 切片尾部新增</h3><p>切片尾部系只能一个或者多个元素是比较方便的,直接通过<code>append()</code>函数追加即可,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 定义切片</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice, &slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 3],Pointer=0xc000008048, Length=3,Capacity=3</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 切片尾部增加元素</span></span><br><span class="line"> <span class="comment">// 新增一个元素</span></span><br><span class="line"> slice1 := <span class="built_in">append</span>(slice, <span class="number">1</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 3 1],Pointer=0xc000008078, Length=4,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 新增多个元素</span></span><br><span class="line"> slice2 := <span class="built_in">append</span>(slice, <span class="number">1</span>, <span class="number">2</span>)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 3 1 2],Pointer=0xc0000080a8, Length=5,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 新增多个元素, 切片作为参数</span></span><br><span class="line"> <span class="keyword">var</span> newSlice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> slice3 := <span class="built_in">append</span>(slice, newSlice...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice3, &slice3, <span class="built_in">len</span>(slice3), <span class="built_in">cap</span>(slice3))</span><br><span class="line"> <span class="comment">// Ouptput: Value=[1 2 3 1 2 3],Pointer=0xc0000080d8, Length=6,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>需要说明的是,当前传入的第二个参数为切片时,需要使用 <strong>…</strong> 运算符来辅助解构切片,否则编译错误。</p></blockquote><p>如果切片的容量不足以容纳新的元素,<code>append()</code>方法会创建一个新的数组,并将原始数组的内容复制到新数组中。</p><h3 id="8-3-切片中间新增"><a href="#8-3-切片中间新增" class="headerlink" title="8.3 切片中间新增"></a>8.3 切片中间新增</h3><p>切片中间指定位置插入一个或者多个元素,相对要麻烦一点,首先要将切片在指定索引位置把切片分成两部分,再将插入元素和分开的两部分切片通过<code>append()</code>函数拼接起接口。</p><p>比如需要插入到元素索引i后,则先以i+1为切割点,把slice切割成两半,索引i前数据: slice[:i+1], 索引i后的数据: slice[i+1:],然后再把索引i后的数据: slice[i:]合并到需要插入的元素切片中如:append([]int{6, 7}, slice[i:]…),最后再把合并后的切片合并到索引i前数据: slice[:i]</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 切片中间某个位置插入元素</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> <span class="comment">// 比如在元素索引1后增加元素,首先分成两部分</span></span><br><span class="line"> slice1 := slice[:<span class="number">2</span>]</span><br><span class="line"> slice2 := slice[<span class="number">2</span>:]</span><br><span class="line"> <span class="comment">// 要插入的切片数据</span></span><br><span class="line"> slice3 := []<span class="type">int</span>{<span class="number">6</span>, <span class="number">7</span>}</span><br><span class="line"> <span class="comment">// 然后拼接三部分切片数据即可, 以下两种方式都可以。</span></span><br><span class="line"> slice4 := <span class="built_in">append</span>(slice1, <span class="built_in">append</span>(slice3, slice2...)...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice4, &slice4, <span class="built_in">len</span>(slice4), <span class="built_in">cap</span>(slice4))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 6 7 3],Pointer=0xc000008048, Length=5,Capacity=6</span></span><br><span class="line"></span><br><span class="line"> slice5 := <span class="built_in">append</span>(<span class="built_in">append</span>(slice1, slice3...), slice2...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice5, &slice5, <span class="built_in">len</span>(slice5), <span class="built_in">cap</span>(slice5))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 6 7 3],Pointer=0xc000008078, Length=5,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="9-切片扩容机制"><a href="#9-切片扩容机制" class="headerlink" title="9 切片扩容机制"></a>9 切片扩容机制</h2><h3 id="9-1-切片扩容对底层数组的影响"><a href="#9-1-切片扩容对底层数组的影响" class="headerlink" title="9.1 切片扩容对底层数组的影响"></a>9.1 切片扩容对底层数组的影响</h3><p>当对切片进行<code>append</code>操作,导致长度超出容量时,就会创建新的数组,这会导致和原有切片的分离。 例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>)</span><br><span class="line"> slice1 := slice[<span class="number">0</span>:<span class="number">4</span>]</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, <span class="number">1</span>)</span><br><span class="line"> slice[<span class="number">1</span>] = <span class="number">5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Value=[0 0 0 0],Pointer=0xc000008060, Length=4,Capacity=5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice, &slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice))</span><br><span class="line"> <span class="comment">// Value=[0 5 0 0 0 1],Pointer=0xc000008048, Length=6,Capacity=10</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于slice的长度超出了容量,所以切片slice指向了一个增长后的新数组,而slice1仍然指向原来的老数组,所以之后对slice进行的操作,对slice1不会产生影响。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="number">5</span>, <span class="number">6</span>)</span><br><span class="line"> slice1 := slice[<span class="number">0</span>:<span class="number">4</span>]</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, <span class="number">1</span>)</span><br><span class="line"> slice[<span class="number">1</span>] = <span class="number">5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Value=[0 5 0 0],Pointer=0xc000008060, Length=4,Capacity=6</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice, &slice, <span class="built_in">len</span>(slice), <span class="built_in">cap</span>(slice))</span><br><span class="line"> <span class="comment">// Value=[0 5 0 0 0 1],Pointer=0xc000008048, Length=6,Capacity=6</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>本例中,slice的容量为6,因此在<code>append</code>后并未超出容量,所以并不会重新创建新数组,即两切片还是共用一个底层数组。因此,对slice进行的操作,对slice1同样产生了影响。</p><h3 id="9-2-扩容探讨"><a href="#9-2-扩容探讨" class="headerlink" title="9.2 扩容探讨"></a>9.2 扩容探讨</h3><p>我们先定义一个空切片,然后依次通过<code>append()</code>方法追加元素来看看切片的容量变化。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, i)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 1 2 4 4 8 8 8 8 16 16 16 16 16 16 16 16</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过示例,可以看到,空切片的初始容量为0,但后面向切片中添加元素时,并不是每次添加元素切片的容量都发生了变化。这是因为如果增大容量,也即需要创建新数组,同时还需要将原数组中的所有元素复制到新数组中,开销很大,所以GoLang设计了一套扩容机制,以减少需要创建新数组的次数。</p><p>如果我们尝试添加多个元素呢?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice := []<span class="type">int</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 6 12 24 24 48 48 48 48 48 96 96 96 96 96 96 96</span></span><br><span class="line">}</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>通过示例看起来,当向一个空切片中插入2n-1个元素时,容量是不是就会被设置为2n呢?<br>我们来试试其他的数据类型?</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// int8</span></span><br><span class="line"> slice1 := []<span class="type">int8</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice1), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice1 = <span class="built_in">append</span>(slice1, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice1), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 8 16 16 32 32 32 64 64 64 64 64 64 128 128 128 128</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// int16</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice2 := []<span class="type">int16</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice2), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice2 = <span class="built_in">append</span>(slice2, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice2), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 8 16 16 32 32 32 64 64 64 64 64 64 128 128 128 128</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// bool</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice3 := []<span class="type">bool</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice3), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice3 = <span class="built_in">append</span>(slice3, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">true</span>, <span class="literal">false</span>, <span class="literal">false</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice3), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 8 16 16 32 32 32 64 64 64 64 64 64 128 128 128 128</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// float32</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice4 := []<span class="type">float32</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice4), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice4 = <span class="built_in">append</span>(slice4, <span class="number">1.1</span>, <span class="number">2.2</span>, <span class="number">3.3</span>, <span class="number">4.4</span>, <span class="number">5.5</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice4), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 6 12 24 24 48 48 48 48 48 96 96 96 96 96 96 96 </span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// float64</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice5 := []<span class="type">float64</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice5), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice5 = <span class="built_in">append</span>(slice5, <span class="number">1.1</span>, <span class="number">2.2</span>, <span class="number">3.3</span>, <span class="number">4.4</span>, <span class="number">5.5</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice5), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 6 12 24 24 48 48 48 48 48 96 96 96 96 96 96 96 </span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//0 string</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice6 := []<span class="type">string</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice6), <span class="string">" "</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice6 = <span class="built_in">append</span>(slice6, <span class="string">"1.1"</span>, <span class="string">"2.2"</span>, <span class="string">"3.3"</span>, <span class="string">"4.4"</span>, <span class="string">"5.5"</span>)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice6), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 5 10 20 20 40 40 40 40 80 80 80 80 80 80 80 80</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// []int</span></span><br><span class="line"> fmt.Println()</span><br><span class="line"> slice7 := [][]<span class="type">int</span>{}</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice7), <span class="string">" "</span>)</span><br><span class="line"> temp := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">16</span>; i++ {</span><br><span class="line"> slice7 = <span class="built_in">append</span>(slice7, temp, temp, temp, temp, temp)</span><br><span class="line"> fmt.Print(<span class="built_in">cap</span>(slice7), <span class="string">" "</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//0 5 10 20 20 42 42 42 42 85 85 85 85 85 85 85 85</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到,根据切片对应数据类型的不同,切片扩容的方式也有很大的区别。</p><h3 id="9-3-源码分析"><a href="#9-3-源码分析" class="headerlink" title="9.3 源码分析"></a>9.3 源码分析</h3><p>具体为什么会是这样的变化过程,还需要从源码中寻找答案,下面是<code>src/runtime/slice.go</code>中的<code>growslice()</code>函数中的核心部分。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">growslice</span><span class="params">(oldPtr unsafe.Pointer, newLen, oldCap, num <span class="type">int</span>, et *_type)</span></span> slice {</span><br><span class="line"> <span class="comment">//......省略</span></span><br><span class="line"> newcap := oldCap</span><br><span class="line"> doublecap := newcap + newcap</span><br><span class="line"> <span class="keyword">if</span> newLen > doublecap {</span><br><span class="line"> newcap = newLen</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">const</span> threshold = <span class="number">256</span></span><br><span class="line"> <span class="keyword">if</span> oldCap < threshold {</span><br><span class="line"> newcap = doublecap</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// Check 0 < newcap to detect overflow</span></span><br><span class="line"> <span class="comment">// and prevent an infinite loop.</span></span><br><span class="line"> <span class="keyword">for</span> <span class="number">0</span> < newcap && newcap < newLen {</span><br><span class="line"> <span class="comment">// Transition from growing 2x for small slices</span></span><br><span class="line"> <span class="comment">// to growing 1.25x for large slices. This formula</span></span><br><span class="line"> <span class="comment">// gives a smooth-ish transition between the two.</span></span><br><span class="line"> newcap += (newcap + <span class="number">3</span> * threshold) / <span class="number">4</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// Set newcap to the requested cap when</span></span><br><span class="line"> <span class="comment">// the newcap calculation overflowed.</span></span><br><span class="line"> <span class="keyword">if</span> newcap <= <span class="number">0</span> {</span><br><span class="line"> newcap = newLen</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//......省略</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据源码我们得出:</p><ul><li>当需要的容量超过原切片容量的两倍时,会使用需要的容量作为新容量。</li><li>当原切片容量小于256时,新切片的容量会直接增加到源容量的2倍。</li><li>当原切片的容量大于等于256时,会以原容量的1.25倍增加,直到新容量超过所需要的容量。</li></ul><p>总之,GoLang中的切片扩容机制,与切片的数据类型、原本切片的容量、所需要的容量都有关系,其过程比较复杂。 要想具体分析还是要看<code>src/runtime/</code>下的<code>slice.go</code>和<code>sizeclasses.go</code>源码研究。</p><h2 id="10-删除切片元素"><a href="#10-删除切片元素" class="headerlink" title="10 删除切片元素"></a>10 删除切片元素</h2><p>Go没有为切片提供删除元素的方法,不过我们可以使用**sliceName1 := sliceName[start:end:capEnd]**删除元素或者使用内置函数<code>append()</code>来实现给切片删除元素。</p><p>和新增切片元素一样,删除也需要考虑在切片哪个位置删除元素,这里有三种情况分别是切片尾部、切片首部和切片中间删除元素,我们依次来看。</p><h3 id="10-1-切片首部删除"><a href="#10-1-切片首部删除" class="headerlink" title="10.1 切片首部删除"></a>10.1 切片首部删除</h3><p>在切片首部删除元素,我们可以直接通过<code>slice[start:end:capEnd]</code>实现,从而生成一个新的切片,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 切片首部删除元素</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 首部删除一个元素</span></span><br><span class="line"> slice1 := slice[<span class="number">1</span>:]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 首部删除多个元素</span></span><br><span class="line"> slice2 := slice[<span class="number">2</span>:]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="10-2-切片尾部删除"><a href="#10-2-切片尾部删除" class="headerlink" title="10.2 切片尾部删除"></a>10.2 切片尾部删除</h3><p>在切片尾部删除元素,我们可以直接通过<code>slice[start:end:capEnd]</code>实现,从而生成一个新的切片,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 切片尾部删除元素</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 尾部删除一个元素</span></span><br><span class="line"> slice1 := slice[:<span class="built_in">len</span>(slice) - <span class="number">1</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 尾部删除多个元素</span></span><br><span class="line"> slice2 := slice[:<span class="built_in">len</span>(slice) - <span class="number">2</span>]</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="10-3-切片中间删除"><a href="#10-3-切片中间删除" class="headerlink" title="10.3 切片中间删除"></a>10.3 切片中间删除</h3><p>切片中间指定位置删除一个或者多个元素,相对要麻烦一点,首先要将切片在指定索引位置把切片分成两部分,再将删除元素和分开的两部分切片通过<code>append()</code>函数拼接起接口。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 切片中间位置删除元素</span></span><br><span class="line"> <span class="keyword">var</span> slice = []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>}</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从切片中间删除, 如从索引为i,删除2个元素(i+2)</span></span><br><span class="line"> slice1 := <span class="built_in">append</span>(slice[:<span class="number">1</span>], slice[<span class="number">3</span>:]...)</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="11-切片复制"><a href="#11-切片复制" class="headerlink" title="11 切片复制"></a>11 切片复制</h2><p>因为切片是引用类型,当你将一个切片赋值给另一个变量时,会复制对切片的引用,而不是复制整个切片的内容。</p><h3 id="11-1-复制"><a href="#11-1-复制" class="headerlink" title="11.1 =复制"></a>11.1 =复制</h3><p>当我们使用=复制时,这意味着两个个不同的切片共享一个底层数组,那么对一个切片的修改就会影响到另一个切片,例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice1 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> slice2 := slice1 <span class="comment">// 复制slice1到slice1</span></span><br><span class="line"> slice1[<span class="number">0</span>] = <span class="number">10</span> <span class="comment">// 修改slice1的第一个元素</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000110041, Length=3,Capacity=3</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000110048, Length=3,Capacity=3</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个例子中,修改了slice1的第一个元素为10,slice2的第一个元素也变成了10,因为在赋值时是将slice1的引用复制给了slice2,它们是共享同一个底层数组。所以当你修改slice1中该元素值的时候,slice2也跟着修改了。<br>如果这个例子对底层数组的修改不是很明显,那我们可以显示地声明一个数组,然后基于数组生成切片,看下面这个例子。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> array := [<span class="number">5</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, array, &array, <span class="built_in">len</span>(array), <span class="built_in">cap</span>(array))</span><br><span class="line"> <span class="comment">// Output: Value=[1 2 3 4 5],Pointer=0xc0000a8030, Length=5,Capacity=5</span></span><br><span class="line"> slice1 := array[<span class="number">0</span>:<span class="number">3</span>]</span><br><span class="line"> slice2 := slice1 <span class="comment">// 复制slice1到slice1</span></span><br><span class="line"> slice1[<span class="number">0</span>] = <span class="number">10</span> <span class="comment">// 修改slice1的第一个元素</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000094030, Length=3,Capacity=5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000094048, Length=3,Capacity=5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, array, &array, <span class="built_in">len</span>(array), <span class="built_in">cap</span>(array))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3 4 5],Pointer=0xc0000a8030, Length=5,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过打印结果,可以看到基于数组生成的切片,通过索引修改切片slice1的元素值会影响底层数组值,因此指向同一数组的slice2的值也会更改。</p><h3 id="11-2-内置copy-复制"><a href="#11-2-内置copy-复制" class="headerlink" title="11.2 内置copy()复制"></a>11.2 内置<code>copy()</code>复制</h3><p>在Go中,可以使用内置的<code>copy()</code>函数来复制切片的内容到另一个切片。<code>copy()</code>函数允许将一个切片的元素复制到另一个切片中,它能够确保两个切片之间没有共享底层数组。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice1 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> slice2 := <span class="built_in">make</span>([]<span class="type">int</span>, <span class="built_in">len</span>(slice1)) <span class="comment">// 创建一个与slice1长度相同的空切片</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 将 slice1 中的元素复制到 slice2</span></span><br><span class="line"> <span class="built_in">copy</span>(slice2, slice1) </span><br><span class="line"></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice1, &slice1, <span class="built_in">len</span>(slice1), <span class="built_in">cap</span>(slice1))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000094030, Length=5,Capacity=5</span></span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Pointer=%p, Length=%d,Capacity=%d\n"</span>, slice2, &slice2, <span class="built_in">len</span>(slice2), <span class="built_in">cap</span>(slice2))</span><br><span class="line"> <span class="comment">// Output: Value=[10 2 3],Pointer=0xc000094048, Length=5,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,<code>copy()</code>函数只复制切片中的元素内容,而不会共享底层数组,这意味着对一个切片的修改不会影响到另一个切片,它们是独立的。</p></blockquote><h2 id="12-切片比较"><a href="#12-切片比较" class="headerlink" title="12 切片比较"></a>12 切片比较</h2><p>在Go中,切片不能直接使用==运算符进行比较,因为切片是引用类型,它们指向不同的底层数组即使内容相同也不会被认为相等</p><blockquote><p>注意:当我们使用使用 <strong>==</strong> 符号比较两个切片的时候,编译错误,切片只有和nil比较才能使用 <strong>==</strong> 符号比较判断。</p></blockquote><p>要比较两个切片是否相等,你需要逐个比较它们的元素。可以编写循环来比较切片中的每个元素,或者使用<code>reflect.DeepEqual()</code>函数进行比较。 </p><h3 id="12-1-自定义函数"><a href="#12-1-自定义函数" class="headerlink" title="12.1 自定义函数"></a>12.1 自定义函数</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 切片比较函数</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">slicesEqual</span><span class="params">(slice1, slice2 []<span class="type">int</span>)</span></span> <span class="type">bool</span> {</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(slice1) != <span class="built_in">len</span>(slice2) {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> i := <span class="keyword">range</span> slice1 {</span><br><span class="line"> <span class="keyword">if</span> slice1[i] != slice2[i] {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice1 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> slice2 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"></span><br><span class="line"> slice1 == slice2 <span class="comment">// invalid operation: slice1 == slice2 (slice can only be compared to nil)</span></span><br><span class="line"></span><br><span class="line"> result := slicesEqual(slice1, slice2)</span><br><span class="line"> fmt.Println(<span class="string">"Slices are equal:"</span>, result) <span class="comment">// 输出 true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="12-2-reflect-DeepEqual-函数"><a href="#12-2-reflect-DeepEqual-函数" class="headerlink" title="12.2 reflect.DeepEqual()函数"></a>12.2 <code>reflect.DeepEqual()</code>函数</h3><p><code>reflect.DeepEqual()</code>函数可以比较两个接口类型的值,包括切片。但请注意,这种方法有一些限制,不适用于所有类型的切片,且在性能上可能不如手动比较。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"reflect"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> slice1 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> slice2 := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"></span><br><span class="line"> result := reflect.DeepEqual(slice1, slice2)</span><br><span class="line"> fmt.Println(<span class="string">"Slices are equal:"</span>, result) <span class="comment">// 输出 true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这两种方法都可以用于比较切片,但根据具体情况选择合适的方法。手动比较元素适用于大多数情况,而<code>reflect.DeepEqual()</code>可能更适合于一些特殊情况。</p><h2 id="13-总结"><a href="#13-总结" class="headerlink" title="13 总结"></a>13 总结</h2><p>经过这一章节,相信大家对切片已经比较了解,同时和数组区别有了一个更深的认识,下一章节我们将继续介绍一种常用的数据结构-映射<code>(map)</code>。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 复合类型 </tag>
<tag> 切片 </tag>
</tags>
</entry>
<entry>
<title>Go基础篇-数组</title>
<link href="/article/daa1d97.html"/>
<url>/article/daa1d97.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_index4.png" width="100%" alt="图片名称" align="center"/>在Go语言中,数组是数据管理中至关重要的组件。它们作为核心数据结构,为开发者提供了灵活性、性能和便利性,本文将带您了解Go语言复合数据类型的数组。并深入探讨它的特性、用法和常见操作。<p>在使用数组的时候,我们会使用一些函数来实现,这里我们将介绍Go语言中的内置函数。</p><h2 id="2-内置函数"><a href="#2-内置函数" class="headerlink" title="2 内置函数"></a>2 内置函数</h2><p>Go语言提供了一些内置函数(Built-in Functions),这些函数是在编译器中实现的,并且可以直接使用,无需导入任何包。以下是一些常用的Go内置函数:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1.</span> <span class="built_in">len</span>(): 返回字符串、数组、切片、字典或通道的长度。</span><br><span class="line"><span class="number">2.</span> <span class="built_in">cap</span>(): 返回数组、切片或通道的容量。</span><br><span class="line"><span class="number">3.</span> <span class="built_in">make</span>(): 用于创建切片、映射或通道。</span><br><span class="line"><span class="number">4.</span> <span class="built_in">new</span>(): 用于创建某种类型的指针,并返回其地址。</span><br><span class="line"><span class="number">5.</span> <span class="built_in">append</span>(): 用于向切片追加元素,可以同时追加一个或多个元素。</span><br><span class="line"><span class="number">6.</span> <span class="built_in">copy</span>(): 用于将源slice的元素复制到目标slice,并返回复制的元素个数。</span><br><span class="line"><span class="number">7.</span> <span class="built_in">delete</span>(): 用于从字典中删除指定键的元素。</span><br><span class="line"><span class="number">8.</span> <span class="built_in">close</span>(): 用于关闭通道。</span><br><span class="line"><span class="number">9.</span> <span class="built_in">panic</span>()和<span class="built_in">recover</span>(): 用于处理错误和异常情况。</span><br><span class="line"><span class="number">10.</span> <span class="built_in">print</span>()和<span class="built_in">println</span>(): 用于在控制台打印输出。</span><br></pre></td></tr></table></figure><p>除了以上列出的内置函数,Go语言还提供了其他很多有用的内置函数,涵盖了各种操作和功能,例如类型转换、数学计算、字符串处理等。你可以通过查看官方文档来获取完整的内置函数列表和详细的使用说明。</p><blockquote><p>需要注意的是,虽然这些函数是内置的,但它们也可以被重新定义为普通的标识符。所以,如果你在自己的代码中使用了跟内置函数同名的标识符,那么内置函数将会被覆盖。</p></blockquote><h2 id="3-数组底层原理"><a href="#3-数组底层原理" class="headerlink" title="3 数组底层原理"></a>3 数组底层原理</h2><p>在Go语言中,数组是一种值类型。当你创建一个数组时,Go会在内存中为该数组分配一个连续的内存块,每个元素都占据该内存块中的一部分。数组中的每个元素都可以通过其索引直接访问。因为这种内存布局,数组的访问速度非常快。</p><p>下面是一个简单的图解,展示了一个长度为3,元素类型为int的数组在内存中的布局。</p><table><thead><tr><th align="center">内存地址</th><th align="center">0x00</th><th align="center">0x04</th><th align="center">0x08</th></tr></thead><tbody><tr><td align="center">索引</td><td align="center">0</td><td align="center">1</td><td align="center">2</td></tr><tr><td align="center">值</td><td align="center">arr[0]</td><td align="center">arr[1]</td><td align="center">arr[2]</td></tr></tbody></table><p>当你将一个数组赋值给另一个数组时,Go会创建一个新的内存块,并将原数组的所有元素值复制到新的内存块。这就是为什么说Go中的数组是值类型,而不是引用类型。这也意味着如果你在函数中修改了一个数组,原数组不会被修改,除非你使用指针或者切片。</p><h2 id="4-数组特点"><a href="#4-数组特点" class="headerlink" title="4 数组特点"></a>4 数组特点</h2><p>数组是一种固定长度的数据结构,用于存储相同类型的元素序列。声明数组时,需要指定其长度,这意味着数组的大小在创建后不可更改。数组的索引从0开始,提供了快速访问元素的方式。然而,由于其固定长度的特性,数组在某些场景下的灵活性受到限制。</p><p>数组具有以下特点:</p><ul><li>数组长度不可变,且它不是引用类型。</li><li>数组里数据是相同类型数据。</li><li>数组中元素可以是任意的原始类型,比如int、string等。</li><li>一个数组中元素的个数被称为数组长度。</li><li>数组的长度属于类型一部分,也就是[5]int和[10]int属于不同类型。</li><li>数组占用内存连续性,也就是数组中的元素被分配到连续内存地址中,因而索引数组元素速度非常快。</li></ul><h2 id="5-定义数组"><a href="#5-定义数组" class="headerlink" title="5 定义数组"></a>5 定义数组</h2><p>Go语言中,数组的声明和实现可以通过以下方式进行:</p><h3 id="5-1-var关键字声明"><a href="#5-1-var关键字声明" class="headerlink" title="5.1 var关键字声明"></a>5.1 var关键字声明</h3><p>在Go语言中,可以使用var关键字来声明一个数组。数组的声明语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> arrayName [size]dataType</span><br></pre></td></tr></table></figure><p>其中,arrayName是数组的名称,size是数组的大小, 而且size是必须要指定的,dataType是数组中元素的数据类型。</p><p>例如,声明一个包含5个整数的数组:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> numbers [<span class="number">5</span>]<span class="type">int</span></span><br></pre></td></tr></table></figure><p>在这个例子中,数组的名称是numbers,大小为5个整数元素,即[5]int。</p><p>声明完该数组,我们可以后面再针对该数组进行赋值。一般是直接用{}大括号赋值,或者通过索引设置数组元素。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> numbers [<span class="number">5</span>]<span class="type">int</span></span><br><span class="line"><span class="comment">// 直接通过{}大括号实现</span></span><br><span class="line">numbers = [<span class="number">5</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"><span class="comment">// 或者通过索引依次赋值</span></span><br><span class="line">numbers[<span class="number">0</span>] = <span class="number">1</span></span><br><span class="line">numbers[<span class="number">1</span>] = <span class="number">2</span></span><br><span class="line">numbers[<span class="number">2</span>] = <span class="number">3</span></span><br><span class="line">numbers[<span class="number">3</span>] = <span class="number">4</span></span><br><span class="line">numbers[<span class="number">4</span>] = <span class="number">5</span></span><br></pre></td></tr></table></figure><h3 id="5-2-声明并初始化数组"><a href="#5-2-声明并初始化数组" class="headerlink" title="5.2 :=声明并初始化数组"></a>5.2 :=声明并初始化数组</h3><p>在声明数组的同时,可以使用大括号{}来初始化数组的元素。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 声明并初始化值</span></span><br><span class="line"><span class="keyword">var</span> numbers [<span class="number">5</span>]<span class="type">int</span> = [<span class="number">5</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br></pre></td></tr></table></figure><p>或者可以使用简化的声明和初始化方式:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 声明并初始化值</span></span><br><span class="line">numbers := [<span class="number">5</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br></pre></td></tr></table></figure><p>如果你不确定数组的长度,可以让Go编译器自动计算数组的长度,在初始化的时候,可以使用 <strong>…</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 数组大小可以根据数组内容自动推断</span></span><br><span class="line"><span class="keyword">var</span> numbers1 = [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"><span class="comment">// 数组大小可以根据数组内容自动推断</span></span><br><span class="line">numbers2 := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br></pre></td></tr></table></figure><p>这样,数组numbers就被声明并初始化为包含元素1到5的整数数组。</p><h2 id="6-访问数组"><a href="#6-访问数组" class="headerlink" title="6 访问数组"></a>6 访问数组</h2><p>可以通过索引来访问数组中的元素。索引从0开始,逐个递增。例如,要访问上面声明的数组numbers的第一个元素,可以使用<code>numbers[0]</code>。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> first := numbers[<span class="number">0</span>]</span><br><span class="line"> fmt.Println(<span class="string">"First element: "</span>, first) <span class="comment">// Output: First element: 1</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们还可以通过<code>len()</code>和<code>cap()</code>函数计算数组的长度和容量</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> fmt.Printf(<span class="string">"Value=%d,Length=%d,Capacity=%d\n"</span>, numbers, <span class="built_in">len</span>(numbers), <span class="built_in">cap</span>(numbers)) <span class="comment">// Output: Value=[1 2 3 4 5],Length=5,Capacity=5</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="7-遍历数组"><a href="#7-遍历数组" class="headerlink" title="7 遍历数组"></a>7 遍历数组</h2><p>有两种主要的方法可以遍历Go中的数组。你可以使用传统的<code>for</code>循环,也可以使用<code>range</code>关键字。</p><h3 id="7-1-For关键字遍历"><a href="#7-1-For关键字遍历" class="headerlink" title="7.1 For关键字遍历"></a>7.1 For关键字遍历</h3><p>首先可以通过for关键字遍历,其中需要借助<code>len()</code>函数计算数组长度。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="built_in">len</span>(numbers); i++ {</span><br><span class="line"> fmt.Println(numbers[i])</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="7-2-Range关键字遍历"><a href="#7-2-Range关键字遍历" class="headerlink" title="7.2 Range关键字遍历"></a>7.2 Range关键字遍历</h3><p>也可以通过range关键字遍历数组,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> index, value := <span class="keyword">range</span> numbers {</span><br><span class="line"> fmt.Printf(<span class="string">"Index: %d, Value: %d\n"</span>, index, value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果不需要使用索引,可以通过下划线’_’代替:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> <span class="keyword">for</span> _, value := <span class="keyword">range</span> numbers {</span><br><span class="line"> fmt.Println(value)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通常来说,我们使用<code>for range</code>方式迭代可能会好一点,因为这种迭代可以保证不会出现数组越界的情况,每次迭代对数组的访问可以省略对下标越界判断,当然具体使用,因实际情况不同而不同。</p><h2 id="8-修改数组元素"><a href="#8-修改数组元素" class="headerlink" title="8 修改数组元素"></a>8 修改数组元素</h2><p>可以通过索引来修改数组中的元素。例如,要将上面声明的数组numbers的第一个元素修改为0,可以使用<code>numbers[0] = 0</code>。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> numbers := [...]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>}</span><br><span class="line"> numbers[<span class="number">0</span>] = <span class="number">0</span></span><br><span class="line"> fmt.Println(numbers) <span class="comment">// Output: [0,2,3,4,5]</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当你把一个数组作为参数传给函数,然后在函数中修改数组的其中一个元素,这并不会改变原数组的值,因为参数值实际上是该数组的副本,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> arr1 := [<span class="number">3</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> arr2 := changeElement(arr1) <span class="comment">// 修改数组元素</span></span><br><span class="line"> fmt.Println(arr1) <span class="comment">// Output: [1 2 3]</span></span><br><span class="line"> fmt.Println(arr2) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改数组第一个元素的值为10,并返回修改后的数组</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeElement</span><span class="params">(array [3]<span class="type">int</span>)</span></span> [<span class="number">3</span>]<span class="type">int</span> {</span><br><span class="line"> array[<span class="number">0</span>] = <span class="number">10</span></span><br><span class="line"> <span class="keyword">return</span> array</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>如果要想在函数中修改数组的值,可以通过指针实现。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> arr1 := [<span class="number">3</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> arr2 := changeElement(&arr1) <span class="comment">// 修改数组元素</span></span><br><span class="line"> fmt.Println(arr1) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line"> fmt.Println(arr2) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 修改数组第一个元素的值为10</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">changeElement</span><span class="params">(array *[3]<span class="type">int</span>)</span></span> [<span class="number">3</span>]<span class="type">int</span> {</span><br><span class="line"> <span class="comment">// 获取第一个元素的地址</span></span><br><span class="line"> ptr := &array[<span class="number">0</span>]</span><br><span class="line"> fmt.Println(<span class="string">"第一个元素值为:"</span>, *ptr) <span class="comment">// 通过指针访问元素的值 Output: 第一个元素值为: 1</span></span><br><span class="line"> <span class="comment">// 通过指针修改数组元素的值</span></span><br><span class="line"> *ptr = <span class="number">10</span></span><br><span class="line"> <span class="keyword">return</span> *array</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="9-数组复制"><a href="#9-数组复制" class="headerlink" title="9 数组复制"></a>9 数组复制</h2><p>在Go语言中,数组是<strong>值类型</strong>。当你将一个数组赋值给另一个变量时,会复制整个数组的内容,而不是复制对数组的引用。这意味着对一个数组的修改不会影响到另一个数组,例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> arr1 := [<span class="number">3</span>]<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> arr2 := arr1 <span class="comment">// 复制arr1到arr2</span></span><br><span class="line"> arr1[<span class="number">0</span>] = <span class="number">10</span> <span class="comment">// 修改arr1的第一个元素</span></span><br><span class="line"> fmt.Println(arr1) <span class="comment">// Output: [10 2 3]</span></span><br><span class="line"> fmt.Println(arr2) <span class="comment">// Output: [1 2 3]</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个例子中,虽然修改了arr1的第一个元素,但arr2并不受影响,因为在赋值时是将arr1的内容复制给了arr2,它们是完全独立的数组。</p><h2 id="10-数组比较"><a href="#10-数组比较" class="headerlink" title="10 数组比较"></a>10 数组比较</h2><p>在go语言中,可以使用比较运算符“==”或“!=”来进行数组比较,判断两个数组是否相等。</p><blockquote><p>注意:只有当两个数组的所有元素都是相等的时候数组才是相等的,不能比较两个类型不同的数组,否则程序将无法完成编译。</p></blockquote><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 通过 == 来比较数组</span></span><br><span class="line"> arr1 := [<span class="number">3</span>]<span class="type">string</span>{<span class="string">"Hello"</span>, <span class="string">"Ratel"</span>}</span><br><span class="line"> arr2 := [<span class="number">3</span>]<span class="type">string</span>{<span class="string">"Hello"</span>, <span class="string">"Ratel"</span>}</span><br><span class="line"> fmt.Println(<span class="string">"arr1 == arr2 "</span>, arr1 == arr2) <span class="comment">// Output: arr1 == arr2 true</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="10-1-数组长度不同"><a href="#10-1-数组长度不同" class="headerlink" title="10.1 数组长度不同"></a>10.1 数组长度不同</h3><p>根据数组特点我们知道,即便是元素类型相同,数组长度只要不同,编译器都认为这是两种不同的数据类型,所以数组无法通过==比较,编译器会直接报错。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 数组长度不同,不可以通过 == 来比较数组</span></span><br><span class="line"> arr1 := [<span class="number">3</span>]<span class="type">string</span>{<span class="string">"Hello"</span>, <span class="string">"Ratel"</span>, <span class="string">"Wu"</span>}</span><br><span class="line"> arr2 := [<span class="number">2</span>]<span class="type">string</span>{<span class="string">"Hello"</span>, <span class="string">"Ratel"</span>}</span><br><span class="line"> fmt.Println(<span class="string">"arr1 == arr2 "</span>, arr1 == arr2) </span><br><span class="line"> <span class="comment">// invalid operation: arr1 == arr2 (mismatched types [3]string and [2]string)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="10-2-数组元素类型不同"><a href="#10-2-数组元素类型不同" class="headerlink" title="10.2 数组元素类型不同"></a>10.2 数组元素类型不同</h3><p>如果数组的长度相同,数据类型不同,编译器同样认为这是两种不同的数据类型,所以数组也无法通过==比较,编译器会直接报错。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 数组元素数据类型不同,不可以通过 == 来比较数组</span></span><br><span class="line"> arr1 := [<span class="number">2</span>]<span class="type">int</span>{<span class="number">10</span>, <span class="number">20</span>}</span><br><span class="line"> arr2 := [<span class="number">2</span>]<span class="type">string</span>{<span class="string">"Hello"</span>, <span class="string">"Ratel"</span>}</span><br><span class="line"> fmt.Println(<span class="string">"arr1 == arr2 "</span>, arr1 == arr2) </span><br><span class="line"> <span class="comment">// invalid operation: arr1 == arr2 (mismatched types [2]int and [2]string)</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="11-总结"><a href="#11-总结" class="headerlink" title="11 总结"></a>11 总结</h2><blockquote><p>需要注意的是,在Go中数组的长度是固定的,因此无法直接删除或调整数组的大小。如果需要删除数组中的元素,可以使用切片来实现类似的效果,我们将在下一章节中介绍。</p></blockquote><p>在Go语言中,数组类型是非常重要的类型,数组本身的赋值和函数传参都是通过复制的方式处理的,理解数组的底层原理有助于更好的使用数组,但是Go语言中很少直接使用数组,原因就是不同长度的数组因为类型不同无法直接赋值,我们下章节将介绍切片,看看切片的操作使用,欢迎阅读。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 复合类型 </tag>
<tag> 数组 </tag>
</tags>
</entry>
<entry>
<title>Go基础篇-关键字&变量&常量&基本数据类型</title>
<link href="/article/3a64c617.html"/>
<url>/article/3a64c617.html</url>
<content type="html"><![CDATA[<h2 id="1-序"><a href="#1-序" class="headerlink" title="1 序"></a>1 序</h2><hr><img src="/img/go/go_index1.png" width="100%" alt="首页图片" align="center"/>在计算机编程中,数据类型是一种基础概念,它定义了数据的特性、存储方式以及可以对其执行的操作。数据类型的正确选择和使用对于编写高效、可靠的代码至关重要。Go语言作为一门现代化、简洁而强大的编程语言,提供了丰富的数据类型,使得开发人员能够更好地控制和操作数据。<p>本文将带您踏上一段探索Go语言基本数据类型的奇妙之旅。我们将深入探讨Go语言中的布尔型、整数型、浮点型和字符串型等基本数据类型的特性、用法和常见操作。</p><p>变量就是承载各种数据类型的容器,变量的定义又离不开关键字,所以我们先看Go的关键字。</p><h2 id="2-关键字"><a href="#2-关键字" class="headerlink" title="2 关键字"></a>2 关键字</h2><p>Go语言具有一些关键字(Keywords),这些关键字具有特殊的含义,不能作为标识符来使用,以下是Go语言中的关键字列表:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">break</span>:用于在循环中跳出循环或在<span class="keyword">switch</span>语句中跳出<span class="keyword">switch</span>语句。</span><br><span class="line"><span class="keyword">case</span>:用于在<span class="keyword">switch</span>语句中分支选择。</span><br><span class="line"><span class="keyword">chan</span>:用于定义通道类型。</span><br><span class="line"><span class="keyword">const</span>:用于定义常量。</span><br><span class="line"><span class="keyword">continue</span>:用于跳过循环中剩余的语句并开始下一次循环。</span><br><span class="line"><span class="keyword">default</span>:在<span class="keyword">switch</span>语句中所有<span class="keyword">case</span>都不匹配时执行的语句块。</span><br><span class="line"><span class="keyword">defer</span>:用于函数结束前执行一个语句块,常用于资源释放。</span><br><span class="line"><span class="keyword">else</span>:在<span class="keyword">if</span>语句中,如果条件不成立时执行的语句块。</span><br><span class="line"><span class="keyword">fallthrough</span>:在<span class="keyword">switch</span>语句中,将控制权转移到下一个<span class="keyword">case</span>语句。</span><br><span class="line"><span class="keyword">for</span>:用于循环语句。</span><br><span class="line"><span class="function"><span class="keyword">func</span>:用于定义函数和方法。</span></span><br><span class="line"><span class="keyword">go</span>:用于启动一个新的goroutine。</span><br><span class="line"><span class="keyword">goto</span>:用于无条件跳转到代码中的某个标签。</span><br><span class="line"><span class="keyword">if</span>:用于条件语句。</span><br><span class="line"><span class="keyword">import</span>:用于导入其他包。</span><br><span class="line"><span class="keyword">interface</span>:用于定义接口类型。</span><br><span class="line"><span class="keyword">map</span>:用于定义映射类型。</span><br><span class="line"><span class="keyword">package</span>:用于定义包,每个Go文件必须在<span class="keyword">package</span>定义的包中。</span><br><span class="line"><span class="keyword">range</span>:用于循环迭代数组、切片、字符串、映射和通道。</span><br><span class="line"><span class="keyword">return</span>:用于从函数返回一个值。</span><br><span class="line"><span class="keyword">select</span>:用于同时等待多个通道操作。</span><br><span class="line"><span class="keyword">struct</span>:用于定义结构体类型。</span><br><span class="line"><span class="keyword">switch</span>:用于根据不同的条件执行不同的分支语句。</span><br><span class="line"><span class="keyword">type</span>:用于定义自定义类型。</span><br><span class="line"><span class="keyword">var</span>:用于定义变量。</span><br></pre></td></tr></table></figure><p>这些关键字在Go语言的语法中扮演着重要的角色,用于定义控制流程、定义变量、创建函数等。需要注意的是,除了上面列出的关键字之外,Go语言还有一些保留字(Reserved Words),虽然目前未被使用,但保留用于将来的扩展和功能增强。这些保留字包括nil、true和false等。</p><h2 id="3-变量"><a href="#3-变量" class="headerlink" title="3 变量"></a>3 变量</h2><p>什么是变量,从数学概念上讲,变量表示没有固定值且可以改变的数,在程序中,变量用来存储各数据类型没有固定值且可以改变的值,你可以可以理解为用于存储值的一种容器,从计算机底层实现角度来看,变量是一段或多段用来存储数据的内存,和C\C++\Java一样,Go是静态强类型语言,因此变量<strong>Variable</strong>需要明确指定类型,编译器也会检查变量类型的正确性。</p><h3 id="3-1-变量定义"><a href="#3-1-变量定义" class="headerlink" title="3.1 变量定义"></a>3.1 变量定义</h3><p>变量定义有两种方式,分别是通过var关键字定义和使用:=短变量定义。</p><h4 id="3-1-1-var关键字定义变量"><a href="#3-1-1-var关键字定义变量" class="headerlink" title="3.1.1 var关键字定义变量"></a>3.1.1 var关键字定义变量</h4><p>变量的定义语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> 变量名 [类型] = 值</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> variableName [<span class="keyword">type</span>] = variableValue</span><br></pre></td></tr></table></figure><p>其中,var是关键字用于定义变量,变量名是你给变量起的名称,类型是变量的数据类型,其中类型是可省略的,表明Go的变量是可以自动推断的,值是各数据类型所表示的值。</p><p>以下是一些例子来说明如何定义不同类型的变量:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义整数类型变量</span></span><br><span class="line"><span class="keyword">var</span> num <span class="type">int</span> <span class="comment">// 定义一个int类型的变量num</span></span><br><span class="line"><span class="comment">// 定义字符串类型变量</span></span><br><span class="line"><span class="keyword">var</span> str <span class="type">string</span> <span class="comment">// 定义一个string类型的变量str</span></span><br><span class="line"><span class="comment">// 定义布尔类型变量</span></span><br><span class="line"><span class="keyword">var</span> flag <span class="type">bool</span> <span class="comment">// 定义一个bool类型的变量flag</span></span><br><span class="line"><span class="comment">// 定义浮点数类型变量</span></span><br><span class="line"><span class="keyword">var</span> score <span class="type">float64</span> <span class="comment">// 定义一个float64类型的变量score</span></span><br><span class="line"><span class="comment">// 定义字符类型变量</span></span><br><span class="line"><span class="keyword">var</span> ch <span class="type">rune</span> <span class="comment">// 定义一个rune类型的变量ch (用于表示Unicode字符)</span></span><br></pre></td></tr></table></figure><blockquote><p>注意:以上变量只是定义变量名,没有初始化值,在定义变量时,如果没有显式地指定初始值,那么变量将会被赋予其类型的零值。例如,整数类型变量的零值是0,字符串类型变量的零值是空字符串,布尔类型变量的零值是false。</p></blockquote><p>在定义变量时,可以省略type,由Go语言自动推断类型。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义整数类型变量并赋值</span></span><br><span class="line"><span class="keyword">var</span> num = <span class="number">20</span></span><br><span class="line"><span class="keyword">var</span> num <span class="type">int</span> = <span class="number">20</span></span><br><span class="line"><span class="comment">// 定义字符串类型变量并赋值</span></span><br><span class="line"><span class="keyword">var</span> str = <span class="string">"Ratel"</span></span><br><span class="line"><span class="keyword">var</span> str <span class="type">string</span> = <span class="string">"Ratel"</span></span><br></pre></td></tr></table></figure><p>我们还可以一次性定义多个变量,且多个变量的数据类型可以不同。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一次性定义name和age两个变量,且数据类型不同</span></span><br><span class="line"><span class="keyword">var</span> (</span><br><span class="line">name <span class="type">string</span> = <span class="string">"Ratel"</span></span><br><span class="line">age <span class="type">int</span> = <span class="number">20</span></span><br><span class="line">)</span><br><span class="line">fmt.Printf(<span class="string">"%T %T"</span>, name, age) <span class="comment">// Output: string int</span></span><br></pre></td></tr></table></figure><h4 id="3-1-2-运算符定义变量"><a href="#3-1-2-运算符定义变量" class="headerlink" title="3.1.2 :=运算符定义变量"></a>3.1.2 :=运算符定义变量</h4><p>我们还可以使用短变量定义语法来更简洁地定义并初始化变量。短变量定义使用冒号等于符号 <strong>:=</strong> 进行赋值,赋值后,Go会自动进行类型推断。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义整数类型变量并赋值</span></span><br><span class="line">num := <span class="number">20</span></span><br><span class="line"><span class="comment">// 定义字符串类型变量并赋值</span></span><br><span class="line">str := <span class="string">"Ratel"</span></span><br><span class="line"><span class="comment">// 定义布尔类型变量并赋值</span></span><br><span class="line">flag := <span class="literal">false</span></span><br><span class="line"><span class="comment">// 定义浮点数类型变量并赋值</span></span><br><span class="line">score := <span class="number">0.2315</span></span><br><span class="line"><span class="comment">// 定义字符类型变量并赋值</span></span><br><span class="line">ch := <span class="string">'A'</span></span><br></pre></td></tr></table></figure><p>在短变量定义中,我们同样可以一次定义多个变量,且多个变量的数据类型可以不同,Golang会根据值自动推断变量类型。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一次性定义name和age两个变量,且数据类型不同</span></span><br><span class="line">name, age := <span class="string">"Ratel"</span>, <span class="number">20</span></span><br><span class="line">fmt.Printf(<span class="string">"%T %T"</span>, name, age) <span class="comment">// Output: string int</span></span><br></pre></td></tr></table></figure><p>此时,name会被自动推断为string类型,age则被推断为int类型。</p><p>综上所述,使用var关键字可以定义不同类型的变量。同时,使用短变量定义语法也是一种常用的快捷方式来定义并初始化变量。</p><h3 id="3-2-作用域"><a href="#3-2-作用域" class="headerlink" title="3.2 作用域"></a>3.2 作用域</h3><p>Golang的变量还具有作用域和生命周期。在同一个作用域中,不能使用相同名称的变量。变量的生命周期由变量在内存中的存储时间来决定。变量的生命周期可以是全局、局部或动态分配的。</p><p>例如,以下是一个在函数内定义并初始化的变量:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> name <span class="type">string</span> = <span class="string">"Ratel"</span></span><br><span class="line"> fmt.Println(name)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在以上例子中,变量name的作用域为main函数,在main函数外无法访问变量name,变量name的生命周期取决于变量name的创建和销毁时间。</p><h2 id="4-常量"><a href="#4-常量" class="headerlink" title="4 常量"></a>4 常量</h2><p>常量,顾名思义,与变量相反,常量用来存储各种数据类型有固定值且不可以改变的值。在Go语言中,常量<strong>Constant</strong>是在程序编译阶段就确定的值,它们在定义时必须赋予一个固定的初值,并且不能被修改。常量的定义和变量的定义有一些差异。</p><p>常量的定义语法如下:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> 常量名 [类型] = 值</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> constantName [<span class="keyword">type</span>] = constantValue</span><br></pre></td></tr></table></figure><p>其中,const是关键字用于定义常量,常量名是你给常量起的名称,类型是常量的数据类型, 是可以省略的,表达式是常量的初始值。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义整数常量</span></span><br><span class="line"><span class="keyword">const</span> Pi = <span class="number">3.14</span> <span class="comment">// 定义一个浮点数类型的常量Pi,并初始化为3.14</span></span><br><span class="line"><span class="keyword">const</span> MaxSize <span class="type">int</span> = <span class="number">100</span> <span class="comment">// 定义一个整数类型的常量MaxSize,并初始化为100</span></span><br><span class="line"><span class="comment">// 定义字符串常量</span></span><br><span class="line"><span class="keyword">const</span> Name = <span class="string">"Ratel"</span> <span class="comment">// 定义一个字符串类型的常量Name,并初始化为"Ratel"</span></span><br><span class="line"><span class="comment">// 定义布尔常量</span></span><br><span class="line"><span class="keyword">const</span> IsDebug = <span class="literal">false</span> <span class="comment">// 定义一个布尔类型的常量IsDebug,并初始化为false</span></span><br><span class="line"><span class="comment">// 定义多个常量</span></span><br><span class="line"><span class="keyword">const</span> (</span><br><span class="line"> Monday = <span class="number">1</span></span><br><span class="line"> Tuesday = <span class="number">2</span></span><br><span class="line"> Wednesday = <span class="number">3</span></span><br><span class="line"> Thursday = <span class="number">4</span></span><br><span class="line"> Friday = <span class="number">5</span></span><br><span class="line"> Saturday = <span class="number">6</span></span><br><span class="line"> Sunday = <span class="number">7</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>这里使用了括号和换行符来分组和格式化多个常量的定义。</p><blockquote><p>值得注意的是,在批量定义常量时,第一个常量的值默认被赋值为0,后续常量的值会根据前面的常量值自动递增。</p></blockquote><p>在Go语言中,常量可以用于各种常见的场景,例如定义数学常量、枚举值、配置参数等。它们具有不可变性,并且可以提高代码的可读性和可维护性。</p><p>需要注意的是,Go语言常量的类型是根据初值自动推导得出的,因此在大多数情况下,常量的类型定义是可选的。</p><p>综上所述,使用const关键字可以定义不同类型的常量,并为其赋予一个固定的初值。常量在编译时确定,并且不能被修改。</p><h2 id="5-基本数据类型"><a href="#5-基本数据类型" class="headerlink" title="5 基本数据类型"></a>5 基本数据类型</h2><hr><p>Go语言基本数据类型包括布尔类型、整数类型、浮点类型、复数类型、字符(串)类型五种类型,我们将依次介绍这些数据类型以及它们的操作。</p><blockquote><p>所有的基础类型都是值类型, 这意味着当它们作为参数传递或从函数返回时,它们通过值传递给函数。</p></blockquote><ol><li>布尔类型(Boolean Type):</li></ol><ul><li>bool:表示真(true)或假(false)的布尔类型。</li></ul><ol start="2"><li>整数类型(Integer Types):</li></ol><ul><li>int:根据平台可能是32位或64位的有符号整数。</li><li>uint:根据平台可能是32位或64位的无符号整数。</li><li>int8、int16、int32、int64:固定大小的有符号整数类型。</li><li>uint8、uint16、uint32、uint64:固定大小的无符号整数类型。</li><li>uintptr:用于存储指针的整数类型。</li></ul><ol start="3"><li>浮点数类型(Floating-Point Types):</li></ol><ul><li>float32:IEEE-754 32位浮点数。</li><li>float64:IEEE-754 64位浮点数。</li></ul><ol start="4"><li>复数类型(Complex Types):</li></ol><ul><li>complex64:包含32位实部和32位虚部的复数类型。</li><li>complex128:包含64位实部和64位虚部的复数类型。</li></ul><ol start="5"><li>字符类型(Character Type):</li></ol><ul><li>byte:与uint8类型相同,用于表示ASCII字符。</li><li>rune:与int32类型相同,用于表示Unicode码点。</li></ul><ol start="6"><li>字符串类型(String Type):</li></ol><ul><li>string:表示一系列字符的字符串类型。</li></ul><h3 id="5-1-布尔类型"><a href="#5-1-布尔类型" class="headerlink" title="5.1 布尔类型"></a>5.1 布尔类型</h3><p>在Go语言中,bool表示布尔类型,它只有两个可能的值:true和false。布尔类型用于表示逻辑条件的真假状态。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> b <span class="type">bool</span> <span class="comment">// 声明一个布尔变量</span></span><br><span class="line"> b = <span class="literal">true</span> <span class="comment">// 赋值为true</span></span><br><span class="line"> fmt.Println(b) <span class="comment">// 输出: true</span></span><br><span class="line"> <span class="comment">// 条件判断</span></span><br><span class="line"> <span class="keyword">if</span> b {</span><br><span class="line"> fmt.Println(<span class="string">"b is true"</span>)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fmt.Println(<span class="string">"b is false"</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 逻辑运算</span></span><br><span class="line"> x := <span class="literal">true</span></span><br><span class="line"> y := <span class="literal">false</span></span><br><span class="line"> fmt.Println(x && y) <span class="comment">// 与运算, 输出: false</span></span><br><span class="line"> fmt.Println(x || y) <span class="comment">// 或运算, 输出: true</span></span><br><span class="line"> fmt.Println(!x) <span class="comment">// 非运算, 输出: false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,在条件判断语句中,只能使用布尔类型的表达式作为条件。不能使用任意其他类型的值进行条件判断。</p></blockquote><h3 id="5-2-整数类型"><a href="#5-2-整数类型" class="headerlink" title="5.2 整数类型"></a>5.2 整数类型</h3><p>Go语言同时提供了有符号和无符号的整数类型,其中包括int8、int16、int32和int64四种大小截然不同的有符号整数类型,分别对应8、16、32、64 bit(二进制位)大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。</p><p>此外还有两种整数类型int和uint,它们分别对应特定CPU平台的字长(机器字大小),其中int表示有符号整数,应用最为广泛,uint表示无符号整数。实际开发中由于编译器和计算机硬件的不同,int和uint所能表示的整数大小会在32bit或64bit之间变化。</p><p>用来表示Unicode字符的rune类型和int32类型是等价的,通常用于表示一个Unicode码点。这两个名称可以互换使用。同样,byte和uint8也是等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。</p><p>尽管在某些特定的运行环境下int、uint和uintptr的大小可能相等,但是它们依然是不同的类型,比如int和int32,虽然int类型的大小也可能是32bit,但是在需要把int类型当做int32类型使用的时候必须显示的对类型进行转换,反之亦然。</p><p>Go语言中有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号数的取值范围是从-2(n-1) 到 2(n-1)-1。无符号整数的所有bit位都用于表示非负数,取值范围是0到2n-1。例如,int8类型整数的取值范围是从-128到127,而uint8类型整数的取值范围是从0到255。</p><p>最后,还有一种无符号的整数类型uintptr,它没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> a <span class="type">int</span> = <span class="number">10</span> </span><br><span class="line"> <span class="keyword">var</span> b <span class="type">int</span> = <span class="number">5</span> </span><br><span class="line"> <span class="keyword">var</span> c <span class="type">uint</span> = <span class="number">15</span> </span><br><span class="line"> <span class="keyword">var</span> d <span class="type">uint</span> = <span class="number">7</span> </span><br><span class="line"> </span><br><span class="line"> sum := a + b <span class="comment">// 加法运算 </span></span><br><span class="line"> diff := a - b <span class="comment">// 减法运算 </span></span><br><span class="line"> product := a * b <span class="comment">// 乘法运算 </span></span><br><span class="line"> quotient := a / b <span class="comment">// 除法运算 </span></span><br><span class="line"> remainder := a % b <span class="comment">// 取模运算 </span></span><br><span class="line"> </span><br><span class="line"> fmt.Println(<span class="string">"Sum:"</span>, sum) <span class="comment">// 输出: Sum: 15 </span></span><br><span class="line"> fmt.Println(<span class="string">"Difference:"</span>, diff) <span class="comment">// 输出: Difference: 5 </span></span><br><span class="line"> fmt.Println(<span class="string">"Product:"</span>, product) <span class="comment">// 输出: Product: 50 </span></span><br><span class="line"> fmt.Println(<span class="string">"Quotient:"</span>, quotient) <span class="comment">// 输出: Quotient: 2 </span></span><br><span class="line"> fmt.Println(<span class="string">"Remainder:"</span>, remainder) <span class="comment">// 输出: Remainder: 0</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 无符号整数的运算示例 </span></span><br><span class="line"> unsignedSum := c + d <span class="comment">// 加法运算 </span></span><br><span class="line"> unsignedDiff := c - d <span class="comment">// 减法运算 </span></span><br><span class="line"> unsignedProduct := c * d <span class="comment">// 乘法运算 </span></span><br><span class="line"> unsignedQuotient := c / d <span class="comment">// 除法运算 </span></span><br><span class="line"> unsignedRemainder := c % d <span class="comment">// 取模运算 </span></span><br><span class="line"> </span><br><span class="line"> fmt.Println(<span class="string">"Unsigned Sum:"</span>, unsignedSum) <span class="comment">// 输出: Unsigned Sum: 22 </span></span><br><span class="line"> fmt.Println(<span class="string">"Unsigned Difference:"</span>, unsignedDiff) <span class="comment">// 输出: Unsigned Difference: 8 </span></span><br><span class="line"> fmt.Println(<span class="string">"Unsigned Product:"</span>, unsignedProduct) <span class="comment">// 输出: Unsigned Product: 105 </span></span><br><span class="line"> fmt.Println(<span class="string">"Unsigned Quotient:"</span>, unsignedQuotient) <span class="comment">// 输出: Unsigned Quotient: 2 </span></span><br><span class="line"> fmt.Println(<span class="string">"Unsigned Remainder:"</span>, unsignedRemainder) <span class="comment">// 输出: Unsigned Remainder: 1 </span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="5-3-浮点数类型"><a href="#5-3-浮点数类型" class="headerlink" title="5.3 浮点数类型"></a>5.3 浮点数类型</h3><p>float32是32位的浮点数类型,它可以表示大约6个小数位的精度。这种类型适用于对内存占用有限且精度要求不高的场景。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> num1 <span class="type">float32</span> = <span class="number">3.14</span></span><br></pre></td></tr></table></figure><p>float64是64位的浮点数类型,它可以表示大约15个小数位的精度。这种类型适用于需要更高精度的计算或涉及较大数值范围的场景。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> num2 <span class="type">float64</span> = <span class="number">3.141592653589793</span></span><br></pre></td></tr></table></figure><p>注意,如果没有指定具体的浮点数类型,默认情况下,Go语言会将浮点数值视为float64类型。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> num3 = <span class="number">3.14</span> <span class="comment">//默认为float64类型</span></span><br></pre></td></tr></table></figure><p>简单四则运算。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> a := <span class="type">float32</span>(<span class="number">3.14</span>)</span><br><span class="line"> b := <span class="type">float32</span>(<span class="number">2.5</span>)</span><br><span class="line"> sum := a + b <span class="comment">// 加法运算</span></span><br><span class="line"> diff := a - b <span class="comment">// 减法运算</span></span><br><span class="line"> product := a * b <span class="comment">// 乘法运算</span></span><br><span class="line"> quotient := a / b <span class="comment">// 除法运算</span></span><br><span class="line"> fmt.Println(sum) <span class="comment">// 输出: 5.6400003</span></span><br><span class="line"> fmt.Println(diff) <span class="comment">// 输出: 0.6400001</span></span><br><span class="line"> fmt.Println(product) <span class="comment">// 输出: 7.8500004</span></span><br><span class="line"> fmt.Println(quotient) <span class="comment">// 输出: 1.256</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="5-3-1-注意事项"><a href="#5-3-1-注意事项" class="headerlink" title="5.3.1 注意事项"></a>5.3.1 注意事项</h4><ul><li>浮点数不适合用于精确计算,因为它们是基于二进制表示的近似值。在进行计算时可能会出现舍入误差。</li><li>不要使用等号(==)来比较两个浮点数是否相等,因为舍入误差可能导致结果不准确。通过定义一个误差范围来判断浮点数是否接近。例如:<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">epsilon := <span class="number">1e-9</span> <span class="comment">// 定义一个小的误差范围</span></span><br><span class="line"><span class="keyword">if</span> math.Abs(f1-f2) < epsilon {</span><br><span class="line"> <span class="comment">// 浮点数接近</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li>浮点数运算可能会导致溢出或下溢。务必注意结果是否在所选类型的范围内。</li><li>避免在循环中使用浮点数作为循环控制条件,因为舍入误差可能导致无限循环或提前退出循环。</li><li>在使用浮点数进行计算时,可以通过引入math包来执行更复杂的数学操作,例如开方、幂等等。</li><li>尽量使用float64类型,除非有特别的需求。虽然float32占用的内存更小,但float64提供了更高的精度,并且在大多数情况下,性能影响不大。</li></ul><p>总结起来,虽然Go提供了浮点类型进行小数运算,但需要注意浮点数的近似性、舍入误差和比较等问题。如果需要精确计算,应考虑使用整数类型或专门的十进制数库。</p><h3 id="5-4-复数类型"><a href="#5-4-复数类型" class="headerlink" title="5.4 复数类型"></a>5.4 复数类型</h3><p>在Go语言中,复数类型用于表示复数数值。Go语言中的复数类型是内置的,由complex64和complex128两种类型组成。</p><blockquote><p>complex64类型表示一个复数,其中实部和虚部都是float32类型。complex128类型表示一个复数,其中实部和虚部都是float64类型。</p></blockquote><p>复数有两种定义方式,一种是通过complex(a, b)声明,其中a是实部,b是虚部,例如</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// complex</span></span><br><span class="line"><span class="keyword">var</span> c1 <span class="type">complex64</span> = <span class="built_in">complex</span>(<span class="number">4</span>, <span class="number">6</span>) </span><br></pre></td></tr></table></figure><p>另外一种是通过a + bi方式声明,其中a是实部,b是虚部,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a + bi</span></span><br><span class="line"><span class="keyword">var</span> c2 <span class="type">complex64</span> = <span class="number">2</span> + <span class="number">3i</span></span><br></pre></td></tr></table></figure><p>然后我们看一些关于复数的一些计算操作:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span></span> {</span><br><span class="line"> <span class="keyword">var</span> c1 <span class="type">complex64</span> = <span class="built_in">complex</span>(<span class="number">4</span>, <span class="number">6</span>)</span><br><span class="line"> <span class="keyword">var</span> c2 <span class="type">complex64</span> = <span class="number">2</span> + <span class="number">3i</span></span><br><span class="line"> fmt.Println(<span class="string">"real(c1) ="</span>, <span class="built_in">real</span>(c1)) <span class="comment">// 获取实部,输出: 4</span></span><br><span class="line"> fmt.Println(<span class="string">"imag(c1) ="</span>, <span class="built_in">imag</span>(c1)) <span class="comment">// 获取虚部,输出: 6</span></span><br><span class="line"> fmt.Println(<span class="string">"Mod(c1) ="</span>, Mod(c1)) <span class="comment">// 获取模长,输出:7.211102550927978</span></span><br><span class="line"> sum := c1 + c2</span><br><span class="line"> diff := c1 - c2</span><br><span class="line"> product := c1 * c2</span><br><span class="line"> quotient := c1 / c2</span><br><span class="line"> fmt.Println(sum) <span class="comment">// 输出: 6+9i</span></span><br><span class="line"> fmt.Println(diff) <span class="comment">// 输出: 2+3i</span></span><br><span class="line"> fmt.Println(product) <span class="comment">// 输出: -10+24i</span></span><br><span class="line"> fmt.Println(quotient) <span class="comment">// 输出: 2+0i</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 计算复数的模</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Mod</span><span class="params">(c <span class="type">complex64</span>)</span></span> <span class="type">float64</span> {</span><br><span class="line"> a := <span class="built_in">real</span>(c) * <span class="built_in">real</span>(c)</span><br><span class="line"> b := <span class="built_in">imag</span>(c) * <span class="built_in">imag</span>(c)</span><br><span class="line"> <span class="keyword">return</span> math.Sqrt(<span class="type">float64</span>(a + b))</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>注意精度:在使用复数进行计算时,需要注意浮点数的精度问题。由于浮点数精度的限制,复数计算可能会产生舍入误差。因此,在比较复数是否相等时,不应该直接使用==运算符,而应该使用近似判断方法,如判断实部和虚部的差值是否在允许的误差范围内。</p></blockquote><p>Go语言还提供了math/cmplx包,其中包含一些用于复数计算的函数,如求模、幅角、共轭等。你可以根据需要使用这些函数来进行更复杂的复数计算。</p><p>总之,复数类型在Go语言中提供了处理复数数值的能力,可以进行基本的复数计算操作,用于涉及复数计算、信号处理、物理模拟、图像处理和控制系统等领域。但需要注意浮点数精度和比较的问题,以及根据需求使用适当的复数运算函数和库。</p><h3 id="5-5-字符类型"><a href="#5-5-字符类型" class="headerlink" title="5.5 字符类型"></a>5.5 字符类型</h3><p>字符串中的每一个元素叫做“字符”,在遍历或者单个获取字符串元素时可以获得字符。</p><p>Go语言的字符有以下两种:</p><ul><li>一种是byte类型,或者叫uint8型,代表了ASCII码的一个字符。</li><li>一种是rune类型,代表一个UTF-8字符,当需要处理中文、日文或者其他复合字符时,则需要用到rune类型。rune类型等价于int32类型。它实际上是一个32位的整数类型。rune类型可以用来存储Unicode码点(Unicode code point)。要定义一个字符变量,可以使用单引号将字符括起来。</li></ul><p>byte类型是uint8的别名,对于只占用1个字节的传统ASCII编码的字符来说,完全没有问题,例如 </p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ch <span class="type">byte</span> = <span class="string">'A'</span> <span class="comment">//字符使用单引号括起来</span></span><br></pre></td></tr></table></figure><p>在ASCII码表中,A的值是65,使用16进制表示则为41,所以下面的写法是等效的:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ch <span class="type">byte</span> = <span class="number">65</span> 或 <span class="keyword">var</span> ch <span class="type">byte</span> = <span class="string">'\x41'</span> <span class="comment">//(\x 总是紧跟着长度为2的16进制数)</span></span><br></pre></td></tr></table></figure><p>另外一种可能的写法是\后面紧跟着长度为3的八进制数,例如\377。</p><p>Go语言同样支持Unicode(UTF-8),因此字符同样称为Unicode代码点或者runes,并在内存中使用int来表示。在文档中,一般使用格式U+hhhh来表示,其中h表示一个16进制数。</p><p>在书写Unicode字符时,需要在16进制数之前加上前缀\u或者\U。因为Unicode至少占用2个字节,所以我们使用 int16或者int类型来表示。如果需要使用到4字节,则使用\u前缀,如果需要使用到8个字节,则使用\U前缀。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ch <span class="type">int</span> = <span class="string">'\u0041'</span></span><br><span class="line"><span class="keyword">var</span> ch2 <span class="type">int</span> = <span class="string">'\u03B2'</span></span><br><span class="line"><span class="keyword">var</span> ch3 <span class="type">int</span> = <span class="string">'\U00101234'</span></span><br><span class="line">fmt.Printf(<span class="string">"%d - %d - %d\n"</span>, ch, ch2, ch3) <span class="comment">// integer</span></span><br><span class="line">fmt.Printf(<span class="string">"%c - %c - %c\n"</span>, ch, ch2, ch3) <span class="comment">// character</span></span><br><span class="line">fmt.Printf(<span class="string">"%X - %X - %X\n"</span>, ch, ch2, ch3) <span class="comment">// UTF-8 bytes</span></span><br><span class="line">fmt.Printf(<span class="string">"%U - %U - %U"</span>, ch, ch2, ch3) <span class="comment">// UTF-8 code point</span></span><br></pre></td></tr></table></figure><p>输出:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">65</span> - <span class="number">946</span> - <span class="number">1053236</span></span><br><span class="line">A - β - r</span><br><span class="line"><span class="number">41</span> - <span class="number">3</span>B2 - <span class="number">101234</span></span><br><span class="line">U+<span class="number">0041</span> - U+<span class="number">03</span>B2 - U+<span class="number">101234</span></span><br></pre></td></tr></table></figure><p>格式化说明符%c用于表示字符,当和字符配合使用时,%v或%d会输出用于表示该字符的整数,%U输出格式为U+hhhh的字符串。</p><p>Unicode包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中ch代表字符):<br>判断是否为字母:<code>unicode.IsLetter(ch)</code><br>判断是否为数字:<code>unicode.IsDigit(ch)</code><br>判断是否为空白符号:<code>unicode.IsSpace(ch)</code></p><p>Unicode与ASCII类似,都是一种字符集。</p><p>字符集为每个字符分配一个唯一的ID,我们使用到的所有字符在Unicode字符集中都有一个唯一的 ID,例如上面例子中的 a 在 Unicode 与 ASCII 中的编码都是 97。汉字“你”在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。</p><p>UTF-8是编码规则,将Unicode中字符的ID以某种方式进行编码,UTF-8是一种变长编码规则,从1到4个字节不等。编码规则如下:<br>0xxxxxx表示文字符号0~127,兼容ASCII字符集。<br>从128到0x10ffff表示其他字符。</p><p>根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用3个字节。</p><p>广义的Unicode指的是一个标准,它定义了字符集及编码规则,即Unicode字符集和UTF-8、UTF-16编码等。</p><h3 id="5-6-字符串类型"><a href="#5-6-字符串类型" class="headerlink" title="5.6 字符串类型"></a>5.6 字符串类型</h3><p>Go语言中的字符串类型用string关键字表示。字符串是不可变的序列,可以包含任意Unicode字符。可以使用双引号或反引号将字符串括起来。</p><p>在Go中,字符串是一种基本数据类型,其值是Unicode码点(code point)序列,通常被解释为UTF-8编码的字节序列。在Go语言中,字符串的底层数据结构是一个只读的字节数组,也就是一组连续的字节。</p><p>一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8字符的一个序列(当字符为ASCII码表上的字符时则占用1个字节,其它字符根据需要占用2-4个字节)。</p><p>字符串类型在Go语言中使用双引号(”)或反引号(`)括起来,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">str1 := <span class="string">"Hello, World!"</span> <span class="comment">// 使用双引号括起来的字符串</span></span><br><span class="line">str2 := <span class="string">`This is a multiline </span></span><br><span class="line"><span class="string">string using backticks`</span> <span class="comment">// 使用反引号括起来的多行字符串</span></span><br></pre></td></tr></table></figure><p>字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">\n:换行符</span><br><span class="line">\r:回车符</span><br><span class="line">\t:TAB键</span><br><span class="line">\u或\U:Unicode字符</span><br><span class="line">\\:反斜杠自身</span><br></pre></td></tr></table></figure><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">var</span> str <span class="type">string</span> = <span class="string">"这是我的\nGo语言教程"</span></span><br><span class="line"> fmt.Println(str)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>输出结果为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">这是我的</span><br><span class="line">Go语言教程</span><br></pre></td></tr></table></figure><p>以下是一些常见的字符串操作:</p><p>1.字符串长度:可以使用内置函数<code>len()</code>获取字符串的长度,即字符的个数。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">str := <span class="string">"Hello, World!"</span></span><br><span class="line">length := <span class="built_in">len</span>(str) <span class="comment">// 获取字符串的长度</span></span><br><span class="line">fmt.Println(length) <span class="comment">// 输出:13</span></span><br></pre></td></tr></table></figure><p>2.字符串索引和切片:可以通过索引访问字符串中的单个字符,索引从0开始。还可以使用切片操作提取子字符串,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">str := <span class="string">"Hello, World!"</span></span><br><span class="line">char := str[<span class="number">0</span>] <span class="comment">// 获取第一个字符'H'</span></span><br><span class="line">substr := str[<span class="number">7</span>:<span class="number">12</span>] <span class="comment">// 提取子字符串"World"</span></span><br></pre></td></tr></table></figure><p>3.字符串拼接:可以使用加号(+)运算符将两个字符串连接起来,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">str1 := <span class="string">"Hello"</span></span><br><span class="line">str2 := <span class="string">"World"</span></span><br><span class="line">result := str1 + <span class="string">" "</span> + str2 <span class="comment">// 拼接字符串为"Hello World"</span></span><br></pre></td></tr></table></figure><p>4.字符串比较:可以使用比较运算符(==、!=、<、>、<=、>=)对字符串进行比较,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">str1 := <span class="string">"Hello"</span></span><br><span class="line">str2 := <span class="string">"World"</span></span><br><span class="line"><span class="keyword">if</span> str1 == str2 {</span><br><span class="line"> fmt.Println(<span class="string">"Strings are equal"</span>)</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> fmt.Println(<span class="string">"Strings are not equal"</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>5.字符串遍历:可以使用<code>for range</code>循环遍历字符串中的字符。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">str := <span class="string">"Hello, World!"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> _, char := <span class="keyword">range</span> str {</span><br><span class="line"> fmt.Printf(<span class="string">"%c "</span>, char)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>6.其他类型转化成字符串<br><strong>strconv</strong>包是关于字符串转化的工具集。</p><ul><li>整数转字符串:使用<code>strconv.Itoa()</code>函数将整数类型转换为字符串。</li><li>浮点数转字符串:使用<code>strconv.FormatFloat()</code>函数将浮点数类型转换为字符串。</li><li>布尔值转字符串:可以直接使用<code>strconv.FormatBool()</code>将布尔值转换为字符串。</li><li>其他类型转字符串:可以使用<code>fmt.Sprintf()</code>函数将其他类型转换为字符串。</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"strconv"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 将整数转换为字符串</span></span><br><span class="line"> str1 := strconv.Itoa(<span class="number">42</span>) </span><br><span class="line"> <span class="comment">// 将浮点数转换为字符串, 第一个参数是要转换的浮点数,第二个参数是格式,第三个参数是小数点的精度(-1表示不限制),最后一个参数是指定浮点数的位数(32或64)。</span></span><br><span class="line"> str2 := strconv.FormatFloat(num, <span class="string">'f'</span>, <span class="number">-1</span>, <span class="number">64</span>)</span><br><span class="line"> <span class="comment">// 将布尔值转换为字符串</span></span><br><span class="line"> str3 := strconv.FormatBool(<span class="literal">true</span>) </span><br><span class="line"> <span class="comment">// 将其他类型转换为字符串,其中%d是格式化字符串中的占位符,用于指定要转换的类型。</span></span><br><span class="line"> str4 := fmt.Sprintf(<span class="string">"%d"</span>, num) </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在Go语言中,<code>fmt.Sprintf</code>函数可以使用不同的数据类型的占位符来格式化字符串。以下是一些常见的占位符及其对应的类型:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">%d:表示整数类型(int、int8、int16、int32、int64等)。</span><br><span class="line">%f:表示浮点数类型(float32、float64)。</span><br><span class="line">%s:表示字符串类型(string)。</span><br><span class="line">%t:表示布尔类型(bool)。</span><br><span class="line">%v:表示通用占位符,可以用于任何类型。它会根据值的类型进行适当的格式化。</span><br></pre></td></tr></table></figure><p>除了这些常见的占位符之外,还有一些其他的占位符可以用于特定类型的数据。以下是一些额外的占位符:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">%b:表示二进制数(整数类型以二进制格式输出)。</span><br><span class="line">%x:表示十六进制数(整数类型以十六进制格式输出)。</span><br><span class="line">%X:表示大写十六进制数(整数类型以大写格式的十六进制输出)。</span><br><span class="line">%o:表示八进制数(整数类型以八进制格式输出)。</span><br><span class="line">%u:表示无符号整数类型(如uint、uintptr等)。</span><br><span class="line">%c:表示字符类型(rune类型,Unicode码点)。</span><br></pre></td></tr></table></figure><p>这些占位符可以用于格式化字符串中的特定类型的数据。通过使用适当的占位符,你可以控制数据的输出格式,以满足你的需求。</p><blockquote><p>需要注意的是,以上方法适用于将基本类型转换为字符串。如果需要将自定义类型转换为字符串,可以在自定义类型的方法中实现String()函数来定义其字符串表示形式。</p></blockquote><p>7.字符串转化成其他类型</p><ul><li>字符串转整数:使用<code>strconv.Atoi()</code>函数将字符串转换为整数类型。</li><li>字符串转浮点数:使用<code>strconv.ParseFloat()</code>函数将字符串转换为浮点数类型。</li><li>字符串转布尔值:使用<code>strconv.ParseBool()</code>将字符串转换为布尔类型。</li><li>字符串转复数类型:使用<code>strconv.ParseComplex()</code>函数将字符串转换为复数类型。<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"strconv"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> num1, err := strconv.Atoi(<span class="string">"54"</span>) </span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> { </span><br><span class="line"> fmt.Println(<span class="string">"转换失败:"</span>, err) </span><br><span class="line"> <span class="keyword">return</span> </span><br><span class="line"> } </span><br><span class="line"> fmt.Println(<span class="string">"转换结果:"</span>, num1) </span><br><span class="line"></span><br><span class="line"> num2, err := strconv.ParseFloat(<span class="string">"3.14"</span>, <span class="number">64</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> { </span><br><span class="line"> fmt.Println(<span class="string">"转换失败:"</span>, err) </span><br><span class="line"> <span class="keyword">return</span> </span><br><span class="line"> } </span><br><span class="line"> fmt.Println(<span class="string">"转换结果:"</span>, num2) </span><br><span class="line"></span><br><span class="line"> num3, err := strconv.ParseBool(<span class="string">"true"</span>) </span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> { </span><br><span class="line"> fmt.Println(<span class="string">"转换失败:"</span>, err) </span><br><span class="line"> <span class="keyword">return</span> </span><br><span class="line"> } </span><br><span class="line"> fmt.Println(<span class="string">"转换结果:"</span>, num3) </span><br><span class="line"> </span><br><span class="line"> num4, err := strconv.ParseComplex(<span class="string">"1+2i"</span>, <span class="number">32</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> { </span><br><span class="line"> fmt.Println(<span class="string">"转换失败:"</span>, err) </span><br><span class="line"> <span class="keyword">return</span> </span><br><span class="line"> } </span><br><span class="line"> fmt.Println(<span class="string">"转换结果:"</span>, num4) </span><br><span class="line">} </span><br></pre></td></tr></table></figure><blockquote><p>以上这些函数尝试将字符串解析为指定的类型,并返回解析后的值和可能的错误。在使用这些函数时,记得检查返回的错误以确保转换成功。</p></blockquote></li></ul><p>8.字符串、byte、rune关系和转换</p><p>在Go语言中,字符串底层是一个只读的字节数组,也就是[]byte类型,但是因为字符串是只读的,所以需要使用 rune类型来表示Unicode字符,而不是byte类型。因此,我们可以简单地把string类型看成是一个包含了若干个 rune类型的数组,其中每个rune表示一个Unicode字符。</p><ul><li>[]byte 转 string:使用 string() 方法将字节数组转为字符串类型。</li><li>string 转 []byte:使用 []byte() 方法将字符串类型转为字节数组。</li><li>string 转 []rune:使用 []rune() 方法将字符串类型转为rune数组。</li><li>[]rune 转 string:使用 string() 方法将rune数组转为字符串类型。<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// []byte 转 string</span></span><br><span class="line">bytes := []<span class="type">byte</span>{<span class="number">65</span>, <span class="number">66</span>, <span class="number">67</span>, <span class="number">68</span>}</span><br><span class="line">str := <span class="type">string</span>(bytes) <span class="comment">// "ABCD"</span></span><br><span class="line"><span class="comment">// string 转 []byte</span></span><br><span class="line">str := <span class="string">"ABCD"</span></span><br><span class="line">bytes := []<span class="type">byte</span>(str) <span class="comment">// [65 66 67 68]</span></span><br><span class="line"><span class="comment">// string 转 []rune</span></span><br><span class="line">str := <span class="string">"ABCD"</span></span><br><span class="line">runes := []<span class="type">rune</span>(str) <span class="comment">// [65 66 67 68]</span></span><br><span class="line"><span class="comment">// []rune 转 string</span></span><br><span class="line">runes := []<span class="type">rune</span>{<span class="number">65</span>, <span class="number">66</span>, <span class="number">67</span>, <span class="number">68</span>}</span><br><span class="line">str := <span class="type">string</span>(runes) <span class="comment">// "ABCD"</span></span><br></pre></td></tr></table></figure><blockquote><p>需要注意的是,虽然Go语言的字符串是Unicode编码的,但是它并不是固定长度的。因为一个 Unicode 字符可能由多个字节组成,所以在Go语言中,使用UTF-8编码来表示 Unicode 字符,一个字符可能由1到4个字节组成。因此,在处理字符串时,需要注意字符的长度和字节的长度并不是一一对应的关系。</p></blockquote></li></ul><p>另外,<strong>strings</strong>包中提供了很多关于字符串操作的方法,总而言之,Go语言的字符串类型提供了一系列的操作和函数,可用于处理文本、字符串拼接、搜索和解析等任务。</p><h3 id="5-7-引用类型和非引用类型"><a href="#5-7-引用类型和非引用类型" class="headerlink" title="5.7 引用类型和非引用类型"></a>5.7 引用类型和非引用类型</h3><p>在Go语言中,有两种类型的数据类型:引用类型和非引用类型。</p><ul><li><p>基本数据类型(如int、float、bool、string等)是<strong>非引用类型</strong>。这些类型的变量在内存中分配的是实际值的空间。当传递这些变量时,函数会复制实际值而不是变量本身。在处理基本数据类型时,我们使用值传递。</p></li><li><p>引用类型(如slice、map、channel、interface和函数类型)则是指向底层数据结构的指针的包装器。这些类型的变量在内存中分配的是指向底层数据结构的指针,而不是实际值的空间。在处理引用类型时,我们使用指针传递。</p></li><li><p>指针类型是一种特殊的引用类型,用于指向变量的内存地址。指针变量保存的是变量的地址,而不是实际值。使用指针类型可以通过指针间接访问变量,也可以在函数中传递指针以通过指针访问原始变量。</p></li></ul><h2 id="6-编码"><a href="#6-编码" class="headerlink" title="6 编码"></a>6 编码</h2><p>在计算机中,编码是将一种形式的数据转换为另一种形式的过程。</p><p>在Golang中,编码是指将一组字符或字符串转换为一组字节或二进制数据的过程,通常用于将数据在网络或存储介质中进行传输或存储。</p><p>Golang中支持的编码方式有多种,包括ASCII、UTF-8、UTF-16、UTF-32等。下面简要介绍几种常见的编码方式:</p><ul><li>ASCII 编码:ASCII编码使用一个字节来表示一个字符,即采用8位二进制数表示一个字符,共可以表示2^8 = 256 种不同的字符。</li><li>UTF-8 编码:UTF-8 是一种可变长度的编码方式,使用1~4个字节来表示一个字符。对于ASCII字符,使用一个字节表示;对于中文、韩文等字符,使用3个字节表示;对于某些特殊字符,如表情符号等,需要使用4个字节表示。因此,一个字符串的长度并不等于它所占用的字节数,它由其中的字符数量决定。</li><li>UTF-16 编码:UTF-16使用2个字节来表示一个字符,对于 ASCII 字符,使用一个字节表示;对于大多数中文、日文、韩文等字符,使用2个字节表示;对于某些特殊字符,需要使用4个字节表示。</li><li>UTF-32 编码:UTF-32使用4个字节来表示一个字符,无需考虑可变长度的问题,但对于大部分字符而言,会造成空间浪费。<br>在Golang中,字符串默认采用UTF-8编码,每个字符占用1~4个字节。<figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">对于ASCII字符,使用一个字节表示;</span><br><span class="line">对于中文、韩文等字符,使用3个字节表示;</span><br><span class="line">对于某些特殊字符,如表情符号等,需要使用4个字节表示。</span><br></pre></td></tr></table></figure>Go中还提供了很多用于处理不同编码方式的库和函数,如encoding/json、encoding/base64、unicode/utf8等。这些库和函数可以帮助开发者在不同编码方式之间进行转换和处理。</li></ul><h2 id="7-指针"><a href="#7-指针" class="headerlink" title="7 指针"></a>7 指针</h2><p>什么是指针?有过C\C++开发经验的同学一定非常熟悉指针,同样在Go语言中也一样,指针是一个变量,它存储了另一个变量的内存地址。因此,指针变量指向的是另一个变量的内存地址,后面我们会说明Go语言中指针和C\C++指针有啥区别。</p><p>定义指针变量时,需要在变量名前加上*,表示这是一个指针变量,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> p *<span class="type">int</span></span><br></pre></td></tr></table></figure><p>这表示定义了一个名为p的指向整型变量的指针。使用&运算符可以取得一个变量的地址,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">x := <span class="number">10</span></span><br><span class="line">p := &x</span><br></pre></td></tr></table></figure><p>这表示取得变量x的地址,并将地址赋给指针变量p。<br>使用指针访问变量时,需要使用*运算符,它表示解引用操作符,例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x := <span class="number">10</span></span><br><span class="line">p := &x</span><br><span class="line">fmt.Println(*p)</span><br></pre></td></tr></table></figure><p>这表示打印出指针变量p指向的变量的值,即变量x的值。</p><p>除了定义指针变量和获取变量的地址之外,还可以使用new函数来创建指针变量。例如,创建一个指向整型变量的指针:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">p := <span class="built_in">new</span>(<span class="type">int</span>)</span><br></pre></td></tr></table></figure><p>这将创建一个新的整型变量,并返回它的地址,然后将地址赋给指针变量p。</p><p>指针还可以用于函数参数和返回值,以便在函数调用之间共享数据。</p><p>在使用指针时需要小心,因为如果指针指向一个无效的内存地址,程序可能会崩溃或产生不可预测的行为。因此,使用指针时需要确保指针指向的内存地址是有效的。</p><h3 id="7-1-指针函数传参"><a href="#7-1-指针函数传参" class="headerlink" title="7.1 指针函数传参"></a>7.1 指针函数传参</h3><p>指针可以用于函数参数的传递,当一个函数需要修改实参的值时,可以将实参的地址作为形参传递给函数,通过操作指针来达到修改实参的值的目的。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">modify</span><span class="params">(str *<span class="type">string</span>)</span></span> {</span><br><span class="line"> *str = <span class="string">"hello, world"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> str := <span class="string">"hello"</span></span><br><span class="line"> modify(&str)</span><br><span class="line"> fmt.Println(str) <span class="comment">// 输出:hello, world</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="7-2-指针访问结构体字段"><a href="#7-2-指针访问结构体字段" class="headerlink" title="7.2 指针访问结构体字段"></a>7.2 指针访问结构体字段</h3><p>通过指针可以更方便地访问结构体中的字段,特别是当结构体很大时,传递指针比较传递整个结构体更高效。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Person <span class="keyword">struct</span> {</span><br><span class="line"> Name <span class="type">string</span></span><br><span class="line"> Age <span class="type">int</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> p := &Person{</span><br><span class="line"> Name: <span class="string">"Alice"</span>,</span><br><span class="line"> Age: <span class="number">18</span>,</span><br><span class="line"> }</span><br><span class="line"> p.Name = <span class="string">"Bob"</span></span><br><span class="line"> fmt.Println(p) <span class="comment">// 输出:&{Bob 18}</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="7-3-通过指针动态分配内存"><a href="#7-3-通过指针动态分配内存" class="headerlink" title="7.3 通过指针动态分配内存"></a>7.3 通过指针动态分配内存</h3><p>通过指针可以在运行时动态地分配内存,比如使用<code>new()</code>函数创建一个新的变量并返回它的地址。例如:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> p := <span class="built_in">new</span>(<span class="type">int</span>)</span><br><span class="line"> *p = <span class="number">42</span></span><br><span class="line"> fmt.Println(*p) <span class="comment">// 输出:42</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="7-4-传递可变参数"><a href="#7-4-传递可变参数" class="headerlink" title="7.4 传递可变参数"></a>7.4 传递可变参数</h3><p>在Golang中,可以使用指针传递可变参数。这是因为在使用可变参数时,传递的是一个切片(slice),而切片本身就是指向一个数组的指针。这样的操作,可以避免拷贝大量的数据。</p><p>例如,考虑以下函数,它接受一个可变参数并将其打印出来:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printArgs</span><span class="params">(args ...<span class="type">int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">for</span> _, arg := <span class="keyword">range</span> args {</span><br><span class="line"> fmt.Println(arg)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>现在,如果要将切片作为参数传递给另一个函数,可以使用指针:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printArgsPtr</span><span class="params">(args *[]<span class="type">int</span>)</span></span> {</span><br><span class="line"> <span class="keyword">for</span> _, arg := <span class="keyword">range</span> *args {</span><br><span class="line"> fmt.Println(arg)</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> args := []<span class="type">int</span>{<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>}</span><br><span class="line"> printArgsPtr(&args)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>在这个示例中,我们定义了一个新函数printArgsPtr,它使用指针作为参数来接收切片。函数体内,我们使用*args来解引用指针,从而得到实际的切片,并遍历它以打印每个元素。在主函数中,我们创建了一个切片,并将其地址传递给printArgsPtr函数。</p><blockquote><p>需要注意的是,在使用指针传递变长参数时,如果切片为空,则不能传递nil指针,而应该传递一个空的切片。这是因为在Golang中,使用空的切片和nil指针的含义不同,后面会介绍。</p></blockquote><p>除了上述几个方面,指针在一些特定的场景下也非常有用,比如在处理数据结构时,通过指针可以更高效地操作链表、树等数据结构。但需要注意,过度使用指针可能会导致代码难以维护,需要谨慎使用。</p><h2 id="8-总结"><a href="#8-总结" class="headerlink" title="8 总结"></a>8 总结</h2><p>通过深入学习Go语言基本数据类型,您将能够更好地理解和掌握这些基本数据类型的特性和用法。无论您是初学者还是有经验的开发人员,对基本数据类型的深入理解都是成为一位优秀的Go语言程序员的关键,下一张我们将讲解复合数据类型。</p>]]></content>
<categories>
<category> Golang </category>
</categories>
<tags>
<tag> Golang </tag>
<tag> 基础语法 </tag>
<tag> 基本数据类型 </tag>
</tags>
</entry>
<entry>
<title>MySQL索引使用和优化</title>
<link href="/article/52980.html"/>
<url>/article/52980.html</url>
<content type="html"><![CDATA[<h2 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h2><h3 id="常用规则"><a href="#常用规则" class="headerlink" title="常用规则"></a>常用规则</h3><p>1、表的主键、外键必须有索引;<br>2、数据量超过300的表应该有索引; </p><span id="more"></span><p>3、经常与其他表进行连接的表,在连接字段上应该建立索引;<br>4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;<br>5、索引应该建在选择性高的字段上;<br>6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;<br>7、复合索引的建立需要进行仔细分析;尽量考虑用单字段索引代替: </p><ul><li>正确选择复合索引中的主列字段,一般是选择性较好的字段; </li><li>复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否 极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引; </li><li>如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引; </li><li>如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段; </li><li>如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;<br>8、频繁进行数据操作的表,不要建立太多的索引;<br>9、删除无用的索引,避免对执行计划造成负面影响;</li></ul><p>以上是一些普遍的建立索引时的判断依据。索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。</p><p> 因为太多的索引与不充分、不正确的索引对性能都毫无益处:在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。 另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。 </p><p>总的来说,小型表肯定不建索引,或者数据库记录在亿条数据级以上,还是建议使用非关系型数据库。 还有些特殊字段的数据库,比如BLOB,CLOB字段肯定也不适合建索引。</p><h3 id="对千万级MySQL数据库建立索引的事项及提高性能的手段"><a href="#对千万级MySQL数据库建立索引的事项及提高性能的手段" class="headerlink" title="对千万级MySQL数据库建立索引的事项及提高性能的手段"></a>对千万级MySQL数据库建立索引的事项及提高性能的手段</h3><h4 id="注意事项:"><a href="#注意事项:" class="headerlink" title="注意事项:"></a>注意事项:</h4><p>首先,应当考虑表空间和磁盘空间是否足够。我们知道索引也是一种数据,在建立索引的时候势必也会占用大量表空间。因此在对一大表建立索引的时候首先应当考虑的是空间容量问题。<br>其次,在对建立索引的时候要对表进行加锁,因此应当注意操作在业务空闲的时候进行。</p><h4 id="性能调整方面"><a href="#性能调整方面" class="headerlink" title="性能调整方面"></a>性能调整方面</h4><p>首当其冲的考虑因素便是磁盘I/O。物理上,应当尽量把索引与数据分散到不同的磁盘上(不考虑阵列的情况)。逻辑上,数据表空间与索引表空间分开。这是在建索引时应当遵守的基本准则。<br>其次,我们知道,在建立索引的时候要对表进行全表的扫描工作,因此,应当考虑调大初始化参数db_file_multiblock_read_count的值。一般设置为32或更大。</p><p>再次,建立索引除了要进行全表扫描外同时还要对数据进行大量的排序操作,因此,应当调整排序区的大小。<br> 9i之前,可以在session级别上加大sort_area_size的大小,比如设置为100m或者更大。<br> 9i以后,如果初始化参数workarea_size_policy的值为TRUE,则排序区从pga_aggregate_target里自动分配获得。<br>最后,建立索引的时候,可以加上nologging选项。以减少在建立索引过程中产生的大量redo,从而提高执行的速度。</p><h3 id="MySql在建立索引优化时需要注意的问题"><a href="#MySql在建立索引优化时需要注意的问题" class="headerlink" title="MySql在建立索引优化时需要注意的问题"></a>MySql在建立索引优化时需要注意的问题</h3><p>设计好MySql的索引可以让你的数据库飞起来,大大的提高数据库效率。设计MySql索引的时候有一下几点注意:</p><h4 id="创建索引"><a href="#创建索引" class="headerlink" title="创建索引"></a>创建索引</h4><p>对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。</p><h4 id="复合索引"><a href="#复合索引" class="headerlink" title="复合索引"></a>复合索引</h4><p>比如有一条语句是这样的:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from users where area=’beijing’ and age=22;</span><br></pre></td></tr></table></figure><p>如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。</p><h4 id="索引不会包含有NULL值的列"><a href="#索引不会包含有NULL值的列" class="headerlink" title="索引不会包含有NULL值的列"></a>索引不会包含有NULL值的列</h4><p>只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。</p><h4 id="使用短索引"><a href="#使用短索引" class="headerlink" title="使用短索引"></a>使用短索引</h4><p>对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。</p><h4 id="排序的索引问题"><a href="#排序的索引问题" class="headerlink" title="排序的索引问题"></a>排序的索引问题</h4><p>mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。</p><h4 id="like语句操作"><a href="#like语句操作" class="headerlink" title="like语句操作"></a>like语句操作</h4><p>一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%a%” 不会使用索引而like “aaa%”可以使用索引。</p><h4 id="不要在列上进行运算"><a href="#不要在列上进行运算" class="headerlink" title="不要在列上进行运算"></a>不要在列上进行运算</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from users where YEAR(adddate)</span><br></pre></td></tr></table></figure><h4 id="不使用NOT-IN和操作"><a href="#不使用NOT-IN和操作" class="headerlink" title="不使用NOT IN和操作"></a>不使用NOT IN和操作</h4><p>NOT IN和操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替,id3则可使用id>3 or id</p><h2 id="参考阅读"><a href="#参考阅读" class="headerlink" title="参考阅读"></a>参考阅读</h2><p><a href="http://blog.codinglabs.org/articles/theory-of-mysql-index.html">MySQL索引背后的数据结构及算法原理</a></p>]]></content>
<categories>
<category> MySQL </category>
</categories>
<tags>
<tag> MySQL </tag>
<tag> 索引 </tag>
</tags>
</entry>
<entry>
<title>IntelliJ IDEA的使用</title>
<link href="/article/15457.html"/>
<url>/article/15457.html</url>
<content type="html"><![CDATA[<p>IDEA全称IntelliJ IDEA,是java语言开发的集成环境,IntelliJ在业界被公认为最好的java开发工具之一,尤其在智能代码助手、代码自动提示、重构、J2EE支持、各类版本工具(git、svn、github等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。IDEA是JetBrains公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。它的旗舰版本还支持HTML,CSS,PHP,MySQL,Python等。免费版只支持Java等少数语言,对于这样一个强大的代码编辑器,我们势必要了解他的快捷键操作,方便我们快速的编写实现代码。</p><span id="more"></span><h2 id="快捷键的使用"><a href="#快捷键的使用" class="headerlink" title="快捷键的使用"></a>快捷键的使用</h2><blockquote><p>注意:以下快捷键的使用仅针对于Windows平台的操作,对于MAC和LINUX大家自己对照windows平台idea的使用</p></blockquote><h3 id="跳转"><a href="#跳转" class="headerlink" title="跳转"></a>跳转</h3><ul><li>项目之间的跳转(项目分别位于不同的idea窗口):<br> Ctrl + Alt + [ : 跳转到上一个idea窗口<br> Ctrl + Alt + ] : 跳转到下一个idea窗口</li><li>文件之间的跳转<br> Ctrl + E : 多个文件之间的跳转(最近打开文件)<br> Ctrl + Shift + E : 多个文件之间的跳转(最近编辑修改文件)</li><li>浏览修改位置的跳转<br> Ctrl + Shift + BackSpace : 跳转到上一次编辑的文件 </li><li>浏览位置的跳转<br> Ctrl + Alt + ← : 跳转到上一次浏览文件的位置<br> Ctrl + Alt + → : 跳转到下一次浏览文件的位置</li><li>最近两个文件之间的跳转(windows自带)<br> Ctrl + Tab : 最近两个文件之间随意切换</li><li>根据书签进行跳转<br> 首先要针对你要标记的代码行作为书签,F11或者Ctrl + F11,其中后者可以使用数字进行标记,但我们要跳转的时候就可以<br> 使用Ctrl + 你标记的数字 进行跳转定位</li><li>使用收藏夹<br> 通过Shift + Alt + F : 可以将当前文件(类),当前函数方法 加入到收藏列表<br> 然后Alt + 2 : 可以打开你的收藏的文件和书签</li><li>字符跳转插件emacsIDEAs跳转<br> 1.首先要安装这个插件,自行安装<br> 2.设置快捷键</li><li>编辑区和文件区的来回跳转<br> Alt + 1 : 通过右边的编辑区定位该文件在文件区的位置</li></ul><h3 id="搜索"><a href="#搜索" class="headerlink" title="搜索"></a>搜索</h3><ul><li>通过输入类名称搜索<br> Ctrl + N : 通过输入类名称搜索类文件,如果要搜索jar包里面的类,直接勾选上include non-project classes即可</li><li>通过输入文件名搜索<br> Ctrl + Shift + N : 通过输入文件名称搜索文件,如果要搜索jar包里面的文件,直接勾选上include non-project classes即可</li><li>通过输入字符串搜索<br> Ctrl + Shift + Alt + N : 通过输入字符串搜索(这里的字符串可以是函数,属性,类名,其他文件名称),如果要搜索jar包里面的内容,直接勾选上include non-project symbols即可</li><li>通过输入查找普通字符串<br> Ctrl + Shift + F :通过输入字符串搜索所有的东西</li></ul><h3 id="列操作"><a href="#列操作" class="headerlink" title="列操作"></a>列操作</h3><ul><li>列选<br> Ctrl + Shift + Alt + J : 列选</li></ul>]]></content>
<categories>
<category> 编程工具 </category>
</categories>
<tags>
<tag> IntelliJ IDEA </tag>
<tag> Jetbrains </tag>
</tags>
</entry>
<entry>
<title>多线程之线程顺序执行</title>
<link href="/article/30518.html"/>
<url>/article/30518.html</url>
<content type="html"><![CDATA[<p>  我们知道在多线程中,线程在启动的时候不是马上去执行任务的,而是由cpu调度让哪一个线程执行,通常情况下多线程的执行顺序是随机的,如果我们想要让线程按照一定的顺序让线程执行,怎么做呢?</p><span id="more"></span><h3 id="1-初见"><a href="#1-初见" class="headerlink" title="1.初见"></a>1.初见</h3><p>首先我们先上一段代码,看看执行效果。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread1 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread2 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread3 开始执行"</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line"> thread1.start();</span><br><span class="line"> thread2.start();</span><br><span class="line"> thread3.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>执行结果如图所示:<br><img src="http://120.78.190.213/images/multithread/multithread-orderexecute.png" alt="如图示"><br>从代码运行结果看,线程执行的顺序是随机的,我们无法保证线程按照特定的顺序执行,线程启动之后处于就绪状态,等待CPU调度执行,也就是说如果不做处理话,我们是无法控制的。</p><h3 id="2-相恋"><a href="#2-相恋" class="headerlink" title="2.相恋"></a>2.相恋</h3><h4 id="2-1-设置线程优先级"><a href="#2-1-设置线程优先级" class="headerlink" title="2.1 设置线程优先级"></a>2.1 设置线程优先级</h4><p>线程的执行除了CPU调度外,还有一个因素就是每个线程的优先级,线程的级别有1-10个等级,级别越高表示线程越有可能拿到CPU时间片,但是并不是优先级越高的线程就一定比优先级低的线程更早获得CPU时间片执行任务呢,按但是否定的,我们只能说优先级高的线程更有可能获取的执行权,如果说优先级高的进入了等待,那么优先级低的线程就会执行,如果几个线程都处于就组状态,那毫无疑问优先级高的线程就会先执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread1 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread2 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread3 开始执行"</span>));</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> {</span><br><span class="line">thread3.setPriority(Thread.MAX_PRIORITY);</span><br><span class="line">thread2.setPriority(Thread.NORM_PRIORITY);</span><br><span class="line">thread1.setPriority(Thread.MIN_PRIORITY);</span><br><span class="line">thread1.start();</span><br><span class="line">thread2.start();</span><br><span class="line">thread3.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="http://120.78.190.213/images/multithread/multithread-orderexecute-priority.png" alt="如图示"></p><h4 id="2-2-Thread-join"><a href="#2-2-Thread-join" class="headerlink" title="2.2 Thread.join()"></a>2.2 Thread.join()</h4><p>我们Thread.join()方法就是让主线程等待,新线程执行完了后再继续执行主线程,让我们来看一下Thread.join()方法的源码吧<br>源码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> join(<span class="number">0</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">join</span><span class="params">(<span class="type">long</span> millis)</span></span><br><span class="line"> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> <span class="type">long</span> <span class="variable">base</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> <span class="type">long</span> <span class="variable">now</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (millis < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">"timeout value is negative"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (millis == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">while</span> (isAlive()) {</span><br><span class="line"> wait(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">while</span> (isAlive()) {</span><br><span class="line"> <span class="type">long</span> <span class="variable">delay</span> <span class="operator">=</span> millis - now;</span><br><span class="line"> <span class="keyword">if</span> (delay <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> wait(delay);</span><br><span class="line"> now = System.currentTimeMillis() - base;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>源码很清晰的告诉我们,当我们调用join()方法后,后调用join(long millis),在millis == 0的情况下,如果当前线程是存活的,就让当前线程等待,让新线程继续执行知道死亡,知道原理后就来实现下代码喔。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread1 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread2 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread3 开始执行"</span>));</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> thread1.start();</span><br><span class="line"> thread1.join();</span><br><span class="line"> thread2.start();</span><br><span class="line"> thread2.join();</span><br><span class="line"> thread3.start();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="http://120.78.190.213/images/multithread/multithread-orderexecute-join.png"></p><h4 id="2-3-Executors-newSingleThreadExecutor"><a href="#2-3-Executors-newSingleThreadExecutor" class="headerlink" title="2.3 Executors.newSingleThreadExecutor()"></a>2.3 Executors.newSingleThreadExecutor()</h4><p>Executors.newSingleThreadExecutor()就是创建一个只有一个线程线程池,让我们看了一个源码吧</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title function_">newSingleThreadExecutor</span><span class="params">()</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">FinalizableDelegatedExecutorService</span></span><br><span class="line"> (<span class="keyword">new</span> <span class="title class_">ThreadPoolExecutor</span>(<span class="number">1</span>, <span class="number">1</span>,</span><br><span class="line"> <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">LinkedBlockingQueue</span><Runnable>()));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们可以看到Executors.newSingleThreadExecutor()实际上是创建了一个单线程的线程池,并且维护了一个任务队列,我们知道队列的特点就是FIFO(先进先出),而且线程池每次只能执行一个任务线程,其余的线程实际上放到new LinkedBlockingQueue<Runnable>()这个队列里等待CPU调度,当第一个线程执行完了后,线程池就会从队列取下一个任务线程来执行,以此类推,从而保证了线程的执行顺序,来看看代码怎么实现的吧</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread1 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread2 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Thread</span> <span class="variable">thread3</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Thread</span>(()->System.out.println(<span class="string">"thread3 开始执行"</span>));</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">ExecutorService</span> <span class="variable">service</span> <span class="operator">=</span> Executors.newSingleThreadExecutor();</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException {</span><br><span class="line"> service.execute(thread1);</span><br><span class="line"> service.execute(thread2);</span><br><span class="line"> service.execute(thread3);</span><br><span class="line"> service.shutdown();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="http://120.78.190.213/images/multithread/multithread-orderexecute-executors.png"></p><h3 id="婚恋"><a href="#婚恋" class="headerlink" title="婚恋"></a>婚恋</h3><p>以上就是控制多线程执行顺序方法,有什么不对欢迎指正喔。</p>]]></content>
<categories>
<category> Java </category>
</categories>
<tags>
<tag> 多线程 </tag>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>Redis面试重点</title>
<link href="/article/9351.html"/>
<url>/article/9351.html</url>
<content type="html"><![CDATA[<h3 id="1)Redis为什么使用单进程单线程方式也这么快"><a href="#1)Redis为什么使用单进程单线程方式也这么快" class="headerlink" title="1)Redis为什么使用单进程单线程方式也这么快"></a>1)Redis为什么使用单进程单线程方式也这么快</h3><p>Redis采用的是基于内存的采用的是单进程单线程模型的KV数据库,由C语言编写。官方提供的数据是可以达到100000+的qps。这个数据不比采用单进程多线程的同样基于内存的KV数据库Memcached差。</p><span id="more"></span><p>Redis快的主要原因是:</p><ol><li>完全基于内存</li><li>数据结构简单,对数据操作也简单</li><li>使用多路 I/O 复用模型</li></ol><p>多路I/O 复用模型是利用select、poll、epoll可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。</p><p>和Memcached不同,Redis并没有直接使用Libevent,而是自己完成了一个非常轻量级的对select、epoll、evport、kqueue这些通用的接口的实现。在不同的系统调用选用适合的接口,linux下默认是epoll。因为Libevent比较重更通用代码量也就很庞大,拥有很多Redis用不上的功能,Redis为了追求“轻巧”并且去除依赖,就选择自己去封装了一套。</p><h4 id="单进程单线程好处"><a href="#单进程单线程好处" class="headerlink" title="单进程单线程好处"></a>单进程单线程好处</h4><ul><li>代码更清晰,处理逻辑更简单</li><li>不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗</li><li>不存在多进程或者多线程导致的切换而消耗CPU</li></ul><h4 id="单进程单线程弊端"><a href="#单进程单线程弊端" class="headerlink" title="单进程单线程弊端"></a>单进程单线程弊端</h4><p>无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善;</p><h4 id="其他一些优秀的开源软件采用的模型"><a href="#其他一些优秀的开源软件采用的模型" class="headerlink" title="其他一些优秀的开源软件采用的模型"></a>其他一些优秀的开源软件采用的模型</h4><ul><li>多进程单线程模型:Nginx</li><li>单进程多线程模型:Memcached</li></ul><h3 id="2)五种类型数据类型"><a href="#2)五种类型数据类型" class="headerlink" title="2)五种类型数据类型"></a>2)五种类型数据类型</h3><p>字符串、列表、散列表,集合、有序集合</p><h3 id="3)内存中数据持久化"><a href="#3)内存中数据持久化" class="headerlink" title="3)内存中数据持久化"></a>3)内存中数据持久化</h3><p>使用复制来扩展读性能:复制到多台服务器、提高读性能和可用性</p><p>使用分区来扩展写性能【hash一致性算法】:当数据量大的时候,把数据分散存入多个数据库中,减少单节点的连接压力</p><p>特点</p><ul><li>完全基于内存</li><li>数据结构简单,对数据操作也简单</li><li>使用多路 I/O 复用模型</li></ul><h3 id="4)Redis-适用场景"><a href="#4)Redis-适用场景" class="headerlink" title="4)Redis 适用场景"></a>4)Redis 适用场景</h3><ol><li>缓存 将热点数据放到内存中</li><li>消息队列 List 类型是双向链表,很适合用于消息队列</li><li>计数器 快速、频繁读写操作;string的单线性自增减 ++ –</li><li>共同好友关系 set 交集运算,很容易就可以知道用户的共同好友</li><li>排名 zset有序集合</li></ol><h3 id="5)持久化"><a href="#5)持久化" class="headerlink" title="5)持久化"></a>5)持久化</h3><p>快照持久化</p><p>将某个时间点的所有数据都存放到硬盘上</p><p>可以将快照复制到其它服务器从而创建具有相同数据的服务器副本</p><p>缺点:故障可能丢失最后一次创建快照之后的数据;如果数据量很大,保存快照的时间也会很长。</p><p>AOF 持久化 将写命令添加到 AOF 文件(Append Only File)的末尾</p><ul><li>always: 每个写命令都同步,严重减低服务器的性能;</li><li>everysec :每秒同步一次,比较合适,保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;</li><li>no :让操作系统来决定何时同步,不能给性能带来提升,且会增加奔溃时数据丢失量</li></ul><p> </p><p>随着服务器写请求的增多,AOF 文件会越来越大;Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。</p><p>对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,操作系统决定何时写</p><p>用户可以调用 file.flush() 方法请求尽快将缓冲区存储的数据同步到硬盘</p><p>redis主从复制 分布式数据同步方式</p><p>slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。</p><p>一个从服务器只能有一个主服务器</p><p>从服务器连接主服务器的过程</p><ul><li>主服务器创建快照文件,发送给从服务器。同时记录其间执行的写命令,发送完毕后,开始向从服务器发送写命令;</li><li>从服务器丢弃所有旧数据,载入主服务器的快照文件,然后开始接受主服务器发来的写命令;</li><li>主服务器每执行一次写命令,就向从服务器发送相同的写命令</li></ul><p>主从链 创建一个中间层来分担主服务器的复制工作</p><ul><li>随着负载不断上升,主服务器可能无法很快地更新所有从服务器</li><li>重新连接和重新同步从服务器将导致系统超载</li><li>中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器</li></ul><h3 id="6)redis-主服务器-故障-处理"><a href="#6)redis-主服务器-故障-处理" class="headerlink" title="6)redis 主服务器 故障 处理"></a>6)redis 主服务器 故障 处理</h3><p>当主服务器出现故障时,Redis 常用的做法是新开一台服务器作为主服务器,具体步骤如下:假设 A 为主服务器,B 为从服务器,当 A 出现故障时,让 B 生成一个快照文件,将快照文件发送给 C,并让 C 恢复快照文件的数据。最后,让 B 成为 C 的从服务器。</p><h3 id="7)分片-集群-读并发"><a href="#7)分片-集群-读并发" class="headerlink" title="7)分片 集群 读并发"></a>7)分片 集群 读并发</h3><p>数据划分为多个部分,可以将数据存储到多台机器里,作用:负载均衡、线性级别的性能提升</p><h3 id="8)分片方式:"><a href="#8)分片方式:" class="headerlink" title="8)分片方式:"></a>8)分片方式:</h3><p>客户端代码分片</p><ul><li>Redis Sharding,对Redis数据的key进行hash,相同的key到相同的节点上</li><li>一致性哈希算法</li><li>代理服务器分片 轮询round-bin</li></ul><h3 id="9)redis与数据库的同步-数据一致"><a href="#9)redis与数据库的同步-数据一致" class="headerlink" title="9)redis与数据库的同步 数据一致"></a>9)redis与数据库的同步 数据一致</h3><ul><li><p>一致性要求高场景,实时同步方案,即查询redis,若查询不到再从DB查询,保存到redis;</p></li><li><p>更新redis时,先更新数据库,再将redis内容设置为过期(建议不要去更新缓存内容,直接设置缓存过期),再用ZINCRBY增量修正redis数据</p></li><li><p>并发程度高的,采用异步队列的方式,采用kafka等消息中间件处理消息生产和消费</p></li><li><p>阿里的同步工具canal,实现方式是模拟mysql slave和master的同步机制,监控DB bitlog的日志更新来触发redis的更新,解放程序员双手,减少工作量</p></li><li><p>利用mysql触发器的API进行编程,c/c++语言实现,学习成本高。</p></li></ul><h3 id="10)热数据与Mysql的同步编码实现-数据库上锁"><a href="#10)热数据与Mysql的同步编码实现-数据库上锁" class="headerlink" title="10)热数据与Mysql的同步编码实现 数据库上锁"></a>10)热数据与Mysql的同步编码实现 数据库上锁</h3><p>热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存</p><p>用spring的AOP来构建redis缓存的自动生产和清除,过程如下:</p><ul><li>Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis</li><li>update或者delete 数据库数据<ul><li>高并发的情况下:先对数据库加锁,再删除redis</li><li>查询redis是否存在该数据,若存在则先对数据库加行锁,再删除redis,再update或者delete数据库中数据</li></ul></li><li>update或者delete redis,先更新数据库,再将redis内容设置为过期(建议不要去更新缓存内容,直接设置缓存过期)</li></ul><p> </p><p>出错场景:update先删掉了redis中的该数据,这时另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis</p><h3 id="11)缓存穿透,缓存击穿,缓存雪崩解决方案分析"><a href="#11)缓存穿透,缓存击穿,缓存雪崩解决方案分析" class="headerlink" title="11)缓存穿透,缓存击穿,缓存雪崩解决方案分析"></a>11)缓存穿透,缓存击穿,缓存雪崩解决方案分析</h3><h4 id="缓存穿透"><a href="#缓存穿透" class="headerlink" title="缓存穿透"></a>缓存穿透</h4><p>缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。</p><h4 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h4><p>有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。</p><h4 id="缓存雪崩"><a href="#缓存雪崩" class="headerlink" title="缓存雪崩"></a>缓存雪崩</h4><p>缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。</p><h4 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h4><p>缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。</p><h4 id="缓存击穿"><a href="#缓存击穿" class="headerlink" title="缓存击穿"></a>缓存击穿</h4><p>对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。</p><p>缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。</p><h4 id="解决方案-2"><a href="#解决方案-2" class="headerlink" title="解决方案"></a>解决方案</h4><p>我们的目标是:尽量少的线程构建缓存(甚至是一个) + 数据一致性 + 较少的潜在危险</p><p><a href="https://www.cnblogs.com/raichen/p/7750165.html">https://www.cnblogs.com/raichen/p/7750165.html</a></p>]]></content>
<categories>
<category> 面试 </category>
</categories>
<tags>
<tag> Redis </tag>
<tag> 面试 </tag>
</tags>
</entry>
</search>