教程集 www.jiaochengji.com
教程集 >  Golang编程  >  golang教程  >  正文 Golang 需要避免踩的 50 个坑(一)

Golang 需要避免踩的 50 个坑(一)

发布时间:2022-02-06   编辑:jiaochengji.com
教程集为您提供Golang 需要避免踩的 50 个坑(一)等资源,欢迎您收藏本站,我们将为您提供最新的Golang 需要避免踩的 50 个坑(一)资源
<blockquote>

最近准备写一些关于golang的技术博文,本文是之前在GitHub上看到的golang技术译文,感觉很有帮助,先给各位读者分享一下。

</blockquote>

<h2>前言</h2>

Go 是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是 Go 本身的设计缺陷。如果你刚从其他语言转到 Go,那这篇文章里的坑多半会踩到。

如果花时间学习官方 doc、wiki、讨论邮件列表、 Rob Pike 的大量文章以及 Go 的源码,会发现这篇文章中的坑是很常见的,新手跳过这些坑,能减少大量调试代码的时间。

<h2>初级篇:1-35</h2>

<h3>1. 左大括号 <code>{</code> 一般不能单独放一行</h3>

在其他大多数语言中,<code>{</code> 的位置你自行决定。Go 比较特别,遵守分号注入规则(automatic semicolon injection):编译器会在每行代码尾部特定分隔符后加 <code>;</code> 来分隔多条语句,比如会在 <code>)</code> 后加分号:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"hello world"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 等效于</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 无函数体</span> <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"hello world"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre> <blockquote>

./main.go: missing function body
./main.go: syntax error: unexpected semicolon or newline before {

</blockquote> <pre><code class="lang-go"><span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"hello world"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

注意代码块等特殊情况:

<pre><code class="lang-go"><span class="token comment">// { 并不遵守分号注入规则,不会在其后边自动加分,此时可换行</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"hello world"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

参考:Golang中自动加分号的特殊分隔符

<h3>2. 未使用的变量</h3>

如果在函数体代码中有未使用的变量,则无法通过编译,不过全局变量声明但不使用是可以的。

即使变量声明后为变量赋值,依旧无法通过编译,需在某处使用它:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">var</span> gvar <span class="token builtin">int</span> <span class="token comment">// 全局变量,声明不使用也可以</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> one <span class="token builtin">int</span> <span class="token comment">// error: one declared and not used</span> two <span class="token operator">:=</span> <span class="token number">2</span> <span class="token comment">// error: two declared and not used</span> <span class="token keyword">var</span> three <span class="token builtin">int</span> <span class="token comment">// error: three declared and not used</span> three <span class="token operator">=</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token comment">// 可以直接注释或移除未使用的变量</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> one <span class="token builtin">int</span> <span class="token boolean">_</span> <span class="token operator">=</span> one two <span class="token operator">:=</span> <span class="token number">2</span> <span class="token function">println</span><span class="token punctuation">(</span>two<span class="token punctuation">)</span> <span class="token keyword">var</span> three <span class="token builtin">int</span> one <span class="token operator">=</span> three <span class="token keyword">var</span> four <span class="token builtin">int</span> four <span class="token operator">=</span> four <span class="token punctuation">}</span> </code></pre>

<h3>3. 未使用的 import</h3>

如果你 import 一个包,但包中的变量、函数、接口和结构体一个都没有用到的话,将编译失败。

可以使用 <code>_</code> 下划线符号作为别名来忽略导入的包,从而避免编译错误,这只会执行 package 的 <code>init()</code>

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token string">"fmt"</span> <span class="token comment">// imported and not used: "fmt"</span> <span class="token string">"log"</span> <span class="token comment">// imported and not used: "log"</span> <span class="token string">"time"</span> <span class="token comment">// imported and not used: "time"</span> <span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token comment">// 可以使用 goimports 工具来注释或移除未使用到的包</span> <span class="token keyword">import</span> <span class="token punctuation">(</span> <span class="token boolean">_</span> <span class="token string">"fmt"</span> <span class="token string">"log"</span> <span class="token string">"time"</span> <span class="token punctuation">)</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token boolean">_</span> <span class="token operator">=</span> log<span class="token punctuation">.</span>Println <span class="token boolean">_</span> <span class="token operator">=</span> time<span class="token punctuation">.</span>Now <span class="token punctuation">}</span> </code></pre>

<h3>4. 简短声明的变量只能在函数内部使用</h3>

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> myvar <span class="token operator">:=</span> <span class="token number">1</span> <span class="token comment">// syntax error: non-declaration statement outside function body</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">var</span> myvar <span class="token operator">=</span> <span class="token number">1</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> </code></pre>

<h3>5. 使用简短声明来重复声明变量</h3>

不能用简短声明方式来单独为一个变量重复声明, <code>:=</code> 左侧至少有一个新变量,才允许多变量的重复声明:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> one <span class="token operator">:=</span> <span class="token number">0</span> one <span class="token operator">:=</span> <span class="token number">1</span> <span class="token comment">// error: no new variables on left side of :=</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> one <span class="token operator">:=</span> <span class="token number">0</span> one<span class="token punctuation">,</span> two <span class="token operator">:=</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span> <span class="token comment">// two 是新变量,允许 one 的重复声明。比如 error 处理经常用同名变量 err</span> one<span class="token punctuation">,</span> two <span class="token operator">=</span> two<span class="token punctuation">,</span> one <span class="token comment">// 交换两个变量值的简写</span> <span class="token punctuation">}</span> </code></pre>

<h3>6. 不能使用简短声明来设置字段的值</h3>

struct 的变量字段不能使用 <code>:=</code> 来赋值以使用预定义的变量来避免解决:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">type</span> info <span class="token keyword">struct</span> <span class="token punctuation">{</span> result <span class="token builtin">int</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">work</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">(</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token builtin">error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token boolean">nil</span> <span class="token punctuation">}</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> data info data<span class="token punctuation">.</span>result<span class="token punctuation">,</span> err <span class="token operator">:=</span> <span class="token function">work</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// error: non-name data.result on left side of :=</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"info: % v\n"</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> data info <span class="token keyword">var</span> err <span class="token builtin">error</span> <span class="token comment">// err 需要预声明</span> data<span class="token punctuation">.</span>result<span class="token punctuation">,</span> err <span class="token operator">=</span> <span class="token function">work</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">if</span> err <span class="token operator">!=</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Printf</span><span class="token punctuation">(</span><span class="token string">"info: % v\n"</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

<h3>7. 不小心覆盖了变量</h3>

对从动态语言转过来的开发者来说,简短声明很好用,这可能会让人误会 <code>:=</code> 是一个赋值操作符。

如果你在新的代码块中像下边这样误用了 <code>:=</code>,编译不会报错,但是变量不会按你的预期工作:

<pre><code class="lang-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token number">1</span> <span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// 1</span> <span class="token punctuation">{</span> <span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// 1</span> x <span class="token operator">:=</span> <span class="token number">2</span> <span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// 2 // 新的 x 变量的作用域只在代码块内部</span> <span class="token punctuation">}</span> <span class="token function">println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// 1</span> <span class="token punctuation">}</span> </code></pre>

这是 Go 开发者常犯的错,而且不易被发现。

可使用 vet 工具来诊断这种变量覆盖,Go 默认不做覆盖检查,添加 <code>-shadow</code> 选项来启用:

<pre><code class="lang-shell"><span class="token operator">></span> go tool vet -shadow main.go main.go:9: declaration of <span class="token string">"x"</span> shadows declaration at main.go:5 </code></pre>

注意 vet 不会报告全部被覆盖的变量,可以使用 go-nyet 来做进一步的检测:

<pre><code class="lang-shell"><span class="token operator">></span> <span class="token variable">$GOPATH</span>/bin/go-nyet main.go main.go:10:3:Shadowing variable <span class="token variable"><span class="token variable">`</span>x<span class="token variable">`</span></span> </code></pre>

<h3>8. 显式类型的变量无法使用 nil 来初始化</h3>

<code>nil</code> 是 interface、function、pointer、map、slice 和 channel 类型变量的默认初始值。但声明时不指定类型,编译器也无法推断出变量的具体类型。

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> x <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token comment">// error: use of untyped nil</span> <span class="token boolean">_</span> <span class="token operator">=</span> x <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> x <span class="token keyword">interface</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token boolean">_</span> <span class="token operator">=</span> x <span class="token punctuation">}</span> </code></pre>

<h3>9. 直接使用值为 nil 的 slice、map</h3>

允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素则会造成运行时 panic

<pre><code class="lang-go"><span class="token comment">// map 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> m <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span> m<span class="token punctuation">[</span><span class="token string">"one"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">1</span> <span class="token comment">// error: panic: assignment to entry in nil map</span> <span class="token comment">// m := make(map[string]int)// map 的正确声明,分配了实际的内存</span> <span class="token punctuation">}</span> <span class="token comment">// slice 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> s <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span> s <span class="token operator">=</span> <span class="token function">append</span><span class="token punctuation">(</span>s<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> </code></pre>

<h3>10. map 容量</h3>

在创建 map 类型的变量时可以指定容量,但不能像 slice 一样使用 <code>cap()</code> 来检测分配空间的大小:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> m <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> <span class="token number">99</span><span class="token punctuation">)</span> <span class="token function">println</span><span class="token punctuation">(</span><span class="token function">cap</span><span class="token punctuation">(</span>m<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// error: invalid argument m1 (type map[string]int) for cap</span> <span class="token punctuation">}</span> </code></pre>

<h3>11. string 类型的变量值不能为 nil</h3>

对那些喜欢用 <code>nil</code> 初始化字符串的人来说,这就是坑:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> s <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token boolean">nil</span> <span class="token comment">// cannot use nil as type string in assignment</span> <span class="token keyword">if</span> s <span class="token operator">==</span> <span class="token boolean">nil</span> <span class="token punctuation">{</span> <span class="token comment">// invalid operation: s == nil (mismatched types string and nil)</span> s <span class="token operator">=</span> <span class="token string">"default"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> s <span class="token builtin">string</span> <span class="token comment">// 字符串类型的零值是空串 ""</span> <span class="token keyword">if</span> s <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span> s <span class="token operator">=</span> <span class="token string">"default"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

<h3>12. Array 类型的值作为函数参数</h3>

在 C/C 中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数内部会改变该数组的值。

在 Go 中,数组是值。作为参数传进函数时,传递的是数组的原始值拷贝,此时在函数内部是无法更新该数组的:

<pre><code class="lang-go"><span class="token comment">// 数组使用值拷贝传参</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">}</span> <span class="token keyword">func</span><span class="token punctuation">(</span>arr <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> arr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">7</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span> <span class="token comment">// [7 2 3]</span> <span class="token punctuation">}</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// [1 2 3] // 并不是你以为的 [7 2 3]</span> <span class="token punctuation">}</span> </code></pre>

如果想修改参数数组:

<ul><li>直接传递指向这个数组的指针类型:</li></ul><pre><code class="lang-go"><span class="token comment">// 传址会修改原数据</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">}</span> <span class="token keyword">func</span><span class="token punctuation">(</span>arr <span class="token operator">*</span><span class="token punctuation">[</span><span class="token number">3</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">(</span><span class="token operator">*</span>arr<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">7</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>arr<span class="token punctuation">)</span> <span class="token comment">// &[7 2 3]</span> <span class="token punctuation">}</span><span class="token punctuation">(</span><span class="token operator">&</span>x<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// [7 2 3]</span> <span class="token punctuation">}</span> </code></pre> <ul><li>直接使用 slice:即使函数内部得到的是 slice 的值拷贝,但依旧会更新 slice 的原始数据(底层 array)</li></ul><pre><code class="lang-go"><span class="token comment">// 会修改 slice 的底层 array,从而修改 slice</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">{</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">}</span> <span class="token keyword">func</span><span class="token punctuation">(</span>arr <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> arr<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token number">7</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// [7 2 3]</span> <span class="token punctuation">}</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// [7 2 3]</span> <span class="token punctuation">}</span> </code></pre>

<h3>13. range 遍历 slice 和 array 时混淆了返回值</h3>

与其他编程语言中的 <code>for-in</code> 、<code>foreach</code> 遍历语句不同,Go 中的 <code>range</code> 在遍历时会生成 2 个值,第一个是元素索引,第二个是元素的值:

<pre><code class="lang-go"><span class="token comment">// 错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"a"</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">,</span> <span class="token string">"c"</span><span class="token punctuation">}</span> <span class="token keyword">for</span> v <span class="token operator">:=</span> <span class="token keyword">range</span> x <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span> <span class="token comment">// 1 2 3</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"a"</span><span class="token punctuation">,</span> <span class="token string">"b"</span><span class="token punctuation">,</span> <span class="token string">"c"</span><span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token boolean">_</span><span class="token punctuation">,</span> v <span class="token operator">:=</span> <span class="token keyword">range</span> x <span class="token punctuation">{</span> <span class="token comment">// 使用 _ 丢弃索引</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>v<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

<h3>14. slice 和 array 其实是一维数据</h3>

看起来 Go 支持多维的 array 和 slice,可以创建数组的数组、切片的切片,但其实并不是。

对依赖动态计算多维数组值的应用来说,就性能和复杂度而言,用 Go 实现的效果并不理想。

可以使用原始的一维数组、“独立“ 的切片、“共享底层数组”的切片来创建动态的多维数组。

<ol><li>

使用原始的一维数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配。

</li><li>

使用“独立”的切片分两步:

</li></ol><ul><li>

创建外部 slice

<ul><li>

对每个内部 slice 进行内存分配

注意内部的 slice 相互独立,使得任一内部 slice 增缩都不会影响到其他的 slice

</li></ul></li></ul><pre><code class="lang-go"><span class="token comment">// 使用各自独立的 6 个 slice 来创建 [2][3] 的动态多维数组</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token number">2</span> y <span class="token operator">:=</span> <span class="token number">4</span> table <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> table <span class="token punctuation">{</span> table<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> y<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <ol><li>使用“共享底层数组”的切片</li></ol><ul><li>

创建一个存放原始数据的容器 slice

</li><li>

创建其他的 slice

</li><li>

切割原始 slice 来初始化其他的 slice

</li></ul><pre><code class="lang-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> h<span class="token punctuation">,</span> w <span class="token operator">:=</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">4</span> raw <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> h<span class="token operator">*</span>w<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> raw <span class="token punctuation">{</span> raw<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> i <span class="token punctuation">}</span> <span class="token comment">// 初始化原始 slice</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>raw<span class="token punctuation">,</span> <span class="token operator">&</span>raw<span class="token punctuation">[</span><span class="token number">4</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// [0 1 2 3 4 5 6 7] 0xc420012120</span> table <span class="token operator">:=</span> <span class="token function">make</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token builtin">int</span><span class="token punctuation">,</span> h<span class="token punctuation">)</span> <span class="token keyword">for</span> i <span class="token operator">:=</span> <span class="token keyword">range</span> table <span class="token punctuation">{</span> <span class="token comment">// 等间距切割原始 slice,创建动态多维数组 table</span> <span class="token comment">// 0: raw[0*4: 0*4 4]</span> <span class="token comment">// 1: raw[1*4: 1*4 4]</span> table<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator">=</span> raw<span class="token punctuation">[</span>i<span class="token operator">*</span>w <span class="token punctuation">:</span> i<span class="token operator">*</span>w <span class="token operator"> </span> w<span class="token punctuation">]</span> <span class="token punctuation">}</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>table<span class="token punctuation">,</span> <span class="token operator">&</span>table<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token comment">// [[0 1 2 3] [4 5 6 7]] 0xc420012120</span> <span class="token punctuation">}</span> </code></pre>

更多关于多维数组的参考

go-how-is-two-dimensional-arrays-memory-representation

what-is-a-concise-way-to-create-a-2d-slice-in-go

<h3>15. 访问 map 中不存在的 key</h3>

和其他编程语言类似,如果访问了 map 中不存在的 key 则希望能返回 nil,比如在 PHP 中:

<pre><code class="lang-shell"><span class="token operator">></span> php -r <span class="token string">'<span class="token variable">$v</span> = ["x"=>1, "y"=>2]; @var_dump(<span class="token variable">$v</span>["z"]);'</span> NULL </code></pre>

Go 则会返回元素对应数据类型的零值,比如 <code>nil</code>、<code>''</code> 、<code>false</code> 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。

检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:

<pre><code class="lang-go"><span class="token comment">// 错误的 key 检测方式</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"one"</span><span class="token punctuation">:</span> <span class="token string">"2"</span><span class="token punctuation">,</span> <span class="token string">"two"</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token string">"three"</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token punctuation">}</span> <span class="token keyword">if</span> v <span class="token operator">:=</span> x<span class="token punctuation">[</span><span class="token string">"two"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> v <span class="token operator">==</span> <span class="token string">""</span> <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"key two is no entry"</span><span class="token punctuation">)</span> <span class="token comment">// 键 two 存不存在都会返回的空字符串</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// 正确示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token keyword">map</span><span class="token punctuation">[</span><span class="token builtin">string</span><span class="token punctuation">]</span><span class="token builtin">string</span><span class="token punctuation">{</span><span class="token string">"one"</span><span class="token punctuation">:</span> <span class="token string">"2"</span><span class="token punctuation">,</span> <span class="token string">"two"</span><span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token string">"three"</span><span class="token punctuation">:</span> <span class="token string">"3"</span><span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token boolean">_</span><span class="token punctuation">,</span> ok <span class="token operator">:=</span> x<span class="token punctuation">[</span><span class="token string">"two"</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token operator">!</span>ok <span class="token punctuation">{</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span><span class="token string">"key two is no entry"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre>

<h3>16. string 类型的值是常量,不可更改</h3>

尝试使用索引遍历字符串,来更新字符串中的个别字符,是不允许的。

string 类型的值是只读的二进制 byte slice,如果真要修改字符串中的字符,将 string 转为 []byte 修改后,再转为 string 即可:

<pre><code class="lang-go"><span class="token comment">// 修改字符串的错误示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token string">"text"</span> x<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"T"</span> <span class="token comment">// error: cannot assign to x[0]</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 修改示例</span> <span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token string">"text"</span> xBytes <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> xBytes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'T'</span> <span class="token comment">// 注意此时的 T 是 rune 类型</span> x <span class="token operator">=</span> <span class="token function">string</span><span class="token punctuation">(</span>xBytes<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// Text</span> <span class="token punctuation">}</span> </code></pre>

注意: 上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的。

更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符

<pre><code class="lang-go"><span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> x <span class="token operator">:=</span> <span class="token string">"text"</span> xRunes <span class="token operator">:=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">rune</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> xRunes<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">'我'</span> x <span class="token operator">=</span> <span class="token function">string</span><span class="token punctuation">(</span>xRunes<span class="token punctuation">)</span> fmt<span class="token punctuation">.</span><span class="token function">Println</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span> <span class="token comment">// 我ext</span> <span class="token punctuation">}</span> </code></pre>

<h3>17. string 与 byte slice 之间的转换</h3>

当进行 string 和 byte slice 相互转换时,参与转换的是拷贝的原始值。这种转换的过程,与其他编程语的强制类型转换操作不同,也和新 slice 与旧 slice 共享底层数组不同。

Go 在 string 与 byte slice 相互转换上优化了两点,避免了额外的内存分配:

<ul><li>在 <code>map[string]</code> 中查找 key 时,使用了对应的 <code>[]byte</code>,避免做 <code>m[string(key)]</code> 的内存分配</li><li>使用 <code>for range</code> 迭代 string 转换为 []byte 的迭代:<code>for i,v := range []byte(str) {...}</code></li></ul>

参考原文

<h4>订阅最新文章,欢迎关注我的公众号</h4>

<h4>系列文章</h4>

Golang 需要避免踩的 50 个坑

本文转载自https://github.com/wuYin/blog/blob/master/50-shades-of-golang-traps-gotchas-mistakes.md

到此这篇关于“Golang 需要避免踩的 50 个坑(一)”的文章就介绍到这了,更多文章或继续浏览下面的相关文章,希望大家以后多多支持JQ教程网!

您可能感兴趣的文章:
golang 利用github自定义module 踩坑系列 类似于「springboot starter 一毛一样」
golang服务发送http请求 400错误 排错备忘
golang map并发读写问题踩坑记录 `concurrent map read and map write`
Golang垃圾回收机制
Jupiter Notebook基础入门(一):如何使用R语言
golang中的mmap使用
避坑!用 Docker 搞定 PHP 开发环境搭建
Golang中defer关键字实现原理
Golang defer 使用时的坑
php架构师都要会什么

[关闭]
~ ~