上海幕墙精制钢 發表於 2025-11-2 09:45:55

Golang语言中切片的长度和容量的概念和使用

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">核心概念</a></li><li><a href="#_label1">底层数组(Underlying Array)</a></li><li><a href="#_label2">图示与示例</a></li><li><a href="#_label3">容量是如何被使用的:append 函数</a></li><li><a href="#_label4">使用make创建切片时指定长度和容量</a></li><li><a href="#_label5">总结与要点</a></li></ul></div><p>本文我们来详细讲解 Go 语言中切片(Slice)的<strong>长度(Length)</strong> 和<strong>容量(Capacity)</strong>。这是理解切片工作原理的核心概念。</p>
<p class="maodian"><a name="_label0"></a></p><h2>核心概念</h2>
<ol><li><p><strong>长度(Length)</strong>:</p>
<ul><li>表示切片<strong>当前实际包含的元素个数</strong>。</li><li>通过内置函数 <code>len(slice)</code> 获取。</li><li>访问切片中索引 <code>&gt;= len(slice)</code> 的元素会引发&ldquo;索引越界&rdquo;的运行时恐慌(panic)。</li></ul></li><li><p><strong>容量(Capacity)</strong>:</p>
<ul><li>表示切片底层数组(Underlying Array)<strong>从切片的起始位置到数组末尾的元素个数</strong>。</li><li>它代表了切片在不分配新内存的情况下,可以增长的最大限度。</li><li>通过内置函数 <code>cap(slice)</code> 获取。</li></ul></li></ol>
<p class="maodian"><a name="_label1"></a></p><h2>底层数组(Underlying Array)</h2>
<p>切片是一个轻量级的数据结构,它提供了对底层数组一个连续片段的引用。它包含三个组件:</p>
<ul><li><strong>指针</strong>:指向底层数组中切片起始位置的元素。</li><li><strong>长度</strong>:切片中元素的数量。</li><li><strong>容量</strong>:从切片起始位置到底层数组末尾的元素数量。</li></ul>
<p>这个概念是理解长度和容量区别的关键。</p>
<p class="maodian"><a name="_label2"></a></p><h2>图示与示例</h2>
<p>让我们通过一个例子和图示来理解。</p>
<div class="jb51code"><pre class="brush:go;">// 1. 创建一个底层数组
arr := int{10, 20, 30, 40, 50} // 数组,长度和容量固定为5

// 2. 基于数组创建一个切片
// slice 从 arr 开始,到 arr 结束 (不包括 arr)
slice := arr //
</pre></div>
<p>此时的内存布局可以这样表示:</p>
<div class="jb51code"><pre class="brush:go;">底层数组:
索引:      0   1   2   3   4
            ^         ^
            |         |
         slice起始   slice结束 (不包括索引4)
         slice指针指向这里
</pre></div>
<ul><li>len(slice):切片包含了 arr, arr, arr 这三个元素,所以 长度为 3。</li><li>cap(slice):切片的指针从 arr 开始,底层数组的末尾是 arr,所以从起始位置到末尾有 arr, arr, arr, arr 这 4个位置,因此 容量为 4。</li></ul>
<div class="jb51code"><pre class="brush:go;">fmt.Println(slice)      // 输出:
fmt.Println(len(slice)) // 输出: 3
fmt.Println(cap(slice)) // 输出: 4
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>容量是如何被使用的:append 函数</h2>
<p>当你使用 <code>append</code> 函数向切片追加新元素时,Go 运行时会检查容量是否足够。</p>
<ol><li><p><strong>容量足够时</strong>:<br />新元素会被直接放入底层数组切片末尾之后的空间,长度增加,容量不变。</p>
<div class="jb51code"><pre class="brush:go;">newSlice := append(slice, 60) // 追加元素 60
fmt.Println(newSlice)          // 输出:
fmt.Println(len(newSlice))   // 输出: 4
fmt.Println(cap(newSlice))   // 输出: 4 (容量刚好够用,没有分配新数组)
fmt.Println(arr)               // 输出: (原数组被修改了!)
</pre></div></li><li><p><strong>容量不足时</strong>:<br />Go 运行时会创建一个新的、更大的底层数组(通常是当前容量的 2 倍,但对于较小的切片,增长策略可能不同),将原有元素复制到新数组中,然后追加新元素。<strong>此时的切片引用的是一个全新的数组,与原数组无关</strong>。</p>
<div class="jb51code"><pre class="brush:go;">newSlice2 := append(newSlice, 70, 80) // 追加两个元素,超出当前容量4
fmt.Println(newSlice2)               // 输出:
fmt.Println(len(newSlice2))            // 输出: 6
fmt.Println(cap(newSlice2))            // 输出: 8 (新数组的容量,通常是旧容量的2倍)
fmt.Println(arr)                     // 输出: (原数组未受影响)
</pre></div></li></ol>
<p class="maodian"><a name="_label4"></a></p><h2>使用make创建切片时指定长度和容量</h2>
<p>你可以使用 <code>make</code> 函数在创建切片时直接指定其初始长度和容量。</p>
<p>语法:<code>make([]T, length, capacity)</code></p>
<div class="jb51code"><pre class="brush:go;">// 创建一个切片,长度为3(前3个元素被初始化为零值),容量为5
s := make([]int, 3, 5)
fmt.Println(s)          // 输出:
fmt.Println(len(s))   // 输出: 3
fmt.Println(cap(s))   // 输出: 5

// 你可以安全地访问和修改 s, s, s
// 你可以追加最多2个新元素(5-3=2)而不会触发重新分配
s = append(s, 1)
fmt.Println(s)          // 输出:
fmt.Println(len(s))   // 输出: 4
</pre></div>
<p>如果省略容量参数,则容量默认与长度相等。</p>
<div class="jb51code"><pre class="brush:go;">s2 := make([]int, 3) // len=3, cap=3
fmt.Println(s2, len(s2), cap(s2)) // 输出: 3 3
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>总结与要点</h2>
<table><thead><tr><th>特性</th><th>长度 (len)</th><th>容量 (cap)</th></tr></thead><tbody><tr><td>含义</td><td>当前元素个数</td><td>可增长的最大限度</td></tr><tr><td>函数</td><td>len(s)</td><td>cap(s)</td></tr><tr><td>动态性</td><td>随 append/slice 操作变化</td><td>在触发扩容前不变</td></tr><tr><td>用途</td><td>决定可访问的元素范围</td><td>影响性能(减少内存分配和复制)</td></tr></tbody></table>
<ul><li><strong>切片是引用类型</strong>。多个切片可以共享同一个底层数组。修改一个切片的元素可能会影响其他共享底层数组的切片。</li><li><strong>&ldquo;扩容&rdquo;是一个相对昂贵的操作</strong>,涉及内存分配和数据复制。在能预估所需数据量的情况下,使用 <code>make([]T, 0, capacity)</code> 预先分配一个足够大的容量是提升性能的有效手段。</li><li>对切片进行<strong>重新切片</strong>(reslicing,如 <code>s2 := s1</code>)操作时,新切片会和原切片共享底层数组。新切片的长度和容量会基于新的起始索引重新计算。</li></ul>
<p>理解长度和容量的区别,能帮助你更好地使用切片,写出更高效、更可靠的 Go 代码。</p>
頁: [1]
查看完整版本: Golang语言中切片的长度和容量的概念和使用