<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>数组 on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/%E6%95%B0%E7%BB%84/</link><description>Recent content in 数组 on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 23 May 2026 11:55:00 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/%E6%95%B0%E7%BB%84/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 数组为什么这么硬？因为它要把边界写进类型里</title><link>https://blog.cpdd.fyi/posts/go-array-design/</link><pubDate>Sat, 23 May 2026 11:55:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-array-design/</guid><description>&lt;p&gt;同样是复制，一个 &lt;code&gt;[32]byte&lt;/code&gt; 的 hash 往往很安全，一个 struct 里的 &lt;code&gt;[4096]int&lt;/code&gt; 却可能把性能拖得很难看。&lt;/p&gt;
&lt;p&gt;这不是 Go 数组“好不好用”的问题，而是你有没有把它当成正确的东西。&lt;/p&gt;
&lt;p&gt;很多 Go 开发者平时几乎不直接写数组。业务列表用 slice，字典用 map，缓冲区也经常一上来就是 &lt;code&gt;[]byte&lt;/code&gt;。于是数组很容易被误解成 slice 背后的旧零件：知道有这么个东西，但最好别碰。&lt;/p&gt;
&lt;p&gt;这个判断太粗了。&lt;/p&gt;
&lt;p&gt;数组在 Go 里不是为了替代 slice 做动态集合。它的价值恰恰在于“不灵活”：长度固定、类型固定、赋值复制、元素可比较时整体可比较。它把一块数据的形状写死，让函数边界、类型系统和编译器都少猜一点。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-array-design/cover.png" alt="Go 数组的确定形状"&gt;&lt;/p&gt;
&lt;h2 id="数组是值长度是类型"&gt;数组是值，长度是类型&lt;/h2&gt;
&lt;p&gt;Go 的数组变量代表整个数组，不是指向第一个元素的地址。&lt;/p&gt;
&lt;p&gt;这句话听起来像语法点，实际影响很大。你把一个数组赋给另一个变量，复制的是整个数组；你把数组传给函数，函数收到的也是一份数组值。除非你显式传 &lt;code&gt;*[N]T&lt;/code&gt;，否则函数里改不到调用方那份数组。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ValueSemantics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;copyOfOriginal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;copyOfOriginal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;copyOfOriginal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;返回的第一份还是 &lt;code&gt;[1 2 3]&lt;/code&gt;，第二份才是 &lt;code&gt;[99 2 3]&lt;/code&gt;。&lt;/p&gt;</description></item><item><title>Go 里真正难懂的不是 slice，是它背后的数组</title><link>https://blog.cpdd.fyi/posts/go-array-slice-design/</link><pubDate>Sat, 23 May 2026 09:45:00 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-array-slice-design/</guid><description>&lt;p&gt;一次 &lt;code&gt;append&lt;/code&gt;，把原来的数据改坏了。&lt;/p&gt;
&lt;p&gt;代码看起来很普通：从一个 slice 里切一段出来，往这段里追加一个元素。你以为只是改了新变量，结果回头一看，原 slice 里的某个位置也变了。&lt;/p&gt;
&lt;p&gt;这类问题很烦，因为它不像空指针那样直接炸给你看。它更像一个安静的错：数据还在，长度也对，测试偶尔过，线上某个分支才露出一点不对劲。&lt;/p&gt;
&lt;p&gt;很多人这时会说：slice 是引用类型。&lt;/p&gt;
&lt;p&gt;这句话不算完全错，但太粗糙。粗糙到一定程度，就会害人。&lt;/p&gt;
&lt;p&gt;Go 里真正值得理解的，不是“slice 是不是引用类型”，而是：数组负责装数据，slice 只负责描述一段数组。数组给机器确定性，slice 给程序员弹性。Go 同时保留这两个东西，不是语言设计多此一举，而是它不愿意把这两种需求混成一团。&lt;/p&gt;
&lt;p&gt;理解这一点，后面的 &lt;code&gt;append&lt;/code&gt;、扩容、子切片、内存保留，都会变得顺很多。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/go-array-slice-design/array-vs-slice-layout.png" alt="数组和 slice 的内存布局"&gt;&lt;/p&gt;
&lt;h2 id="数组很硬但这正是它的价值"&gt;数组很硬，但这正是它的价值&lt;/h2&gt;
&lt;p&gt;Go 的数组不是“指向一串元素的地址”。数组变量代表的是整个数组。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;[3]int&lt;/code&gt; 和 &lt;code&gt;[4]int&lt;/code&gt; 是两个不同类型。长度进类型，元素连续排列，赋值和传参都会复制整个数组。这些特性放到业务代码里看，确实不够灵活：函数想接收任意长度的整数列表，不能写一个 &lt;code&gt;[N]int&lt;/code&gt; 让 N 动起来；大数组传来传去，也会带来复制成本。&lt;/p&gt;
&lt;p&gt;但站在机器这边看，数组非常舒服。&lt;/p&gt;
&lt;p&gt;长度确定，布局连续，元素类型一致。编译器知道它有多大，CPU 也喜欢顺着连续内存往前读。很多时候，所谓“底层性能”，并不是某个神秘技巧，而是数据摆得足够朴素，机器不用猜。&lt;/p&gt;
&lt;p&gt;所以数组不是 Go 里被 slice 淘汰掉的旧家具。它更像地基。&lt;/p&gt;
&lt;p&gt;你日常很少把数组当动态集合用，不代表它不重要。恰恰相反，slice 的一切便利，都建立在底层数组这块“确定形状的钢板”上。&lt;/p&gt;
&lt;h2 id="slice-不是数组是一张小纸条"&gt;slice 不是数组，是一张小纸条&lt;/h2&gt;
&lt;p&gt;slice 本身并不装元素。&lt;/p&gt;
&lt;p&gt;在 Go 1.25.4 的 runtime 源码里，slice 的核心结构就是三个字段：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsafe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pointer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;可以把它理解成一张小纸条：&lt;/p&gt;</description></item></channel></rss>