Go 语言中的值类型和指针类型区别对比分析
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">✅ 一、值类型 vs 指针类型 — 本质区别</a></li><li><a href="#_label1">✅ 二、本质理解:变量保存的是什么?</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">✔ 值类型(int、float、struct、array 等)</a></li><li><a href="#_lab2_1_1">✔ 指针类型(*int、*struct、*MyType)</a></li></ul><li><a href="#_label2">✅ 三、内存示意图(最直观)</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_2">✔ 值类型</a></li><li><a href="#_lab2_2_3">✔ 指针类型</a></li></ul><li><a href="#_label3">✅ 四、函数传参时的区别(非常重要)</a></li><ul class="second_class_ul"><li><a href="#_lab2_3_4">值类型:传值,拷贝一份</a></li><li><a href="#_lab2_3_5">指针类型:传地址,修改原变量</a></li><ul class="third_class_ul"><li><a href="#_label3_3_5_0">✔ 1. 需要修改函数外的变量</a></li><li><a href="#_label3_3_5_1">✔ 2. struct 较大,避免复制(性能)</a></li><li><a href="#_label3_3_5_2">✔ 3. 实现方法的“接收者”为指针(避免复制)</a></li><li><a href="#_label3_3_5_3">✔ 4. 需要表示 nil(值不存在)</a></li></ul></ul><li><a href="#_label4">✅ 五、结构体特别重要:值类型默认复制!</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">✅ 六、什么时候应该使用指针?</a></li><ul class="second_class_ul"></ul><li><a href="#_label6">✅ 七、一句话总结</a></li><ul class="second_class_ul"></ul></ul></div><p>在 Go 语言中,<strong>变量的类型可以是值类型(Value Type)或指针类型(Pointer Type)</strong>。它们的核心区别在于:<strong>存储的内容不同、内存地址的变化不同、函数传参的行为不同</strong>。</p><p>下面我从本质 + 内存模型 + 例子 + 什么时候用指针 来完整解释。</p>
<p class="maodian"><a name="_label0"></a></p><h2>✅ 一、值类型 vs 指针类型 — 本质区别</h2>
<table><thead><tr><th>特性</th><th>值类型</th><th>指针类型</th></tr></thead><tbody><tr><td>存储内容</td><td><strong>实际数据</strong></td><td><strong>数据的内存地址(指向某个值)</strong></td></tr><tr><td>赋值时</td><td><strong>拷贝整份数据</strong></td><td><strong>拷贝地址(两个指针指向同一块内存)</strong></td></tr><tr><td>改动是否影响原变量</td><td>❌ 不影响</td><td>✔ 会影响</td></tr><tr><td>空值</td><td>无法为 nil</td><td>可以为 nil</td></tr><tr><td>内存分配</td><td>栈为主</td><td>指向堆或栈都可能</td></tr></tbody></table>
<p class="maodian"><a name="_label1"></a></p><h2>✅ 二、本质理解:变量保存的是什么?</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>✔ 值类型(int、float、struct、array 等)</h3>
<p>变量里保存的是“真实的数据”。</p>
<div class="jb51code"><pre class="brush:go;">a := 10
b := a // b 拷贝一份 10
b = 20
fmt.Println(a) // 10 (不会受到影响)</pre></div>
<p><strong>值类型总是复制一份独立的数据。</strong></p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>✔ 指针类型(*int、*struct、*MyType)</h3>
<p>变量里保存的是“另一个变量的地址”。</p>
<div class="jb51code"><pre class="brush:go;">a := 10
b := &a // b 保存 a 的地址
*b = 20
fmt.Println(a) // 20 (a 被修改)
</pre></div>
<p>指针类型意味着多个变量指向同一块内存,因此修改一个会影响另一个。</p>
<p class="maodian"><a name="_label2"></a></p><h2>✅ 三、内存示意图(最直观)</h2>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>✔ 值类型</h3>
<div class="jb51code"><pre class="brush:go;">a (10)
b (10)← 拷贝出的新数据
</pre></div>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>✔ 指针类型</h3>
<div class="jb51code"><pre class="brush:go;">a (10)
b (&a) → 指向 a 的内存地址
</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>✅ 四、函数传参时的区别(非常重要)</h2>
<p class="maodian"><a name="_lab2_3_4"></a></p><h3>值类型:传值,拷贝一份</h3>
<div class="jb51code"><pre class="brush:go;">func add(x int) {
x++
}
a := 10
add(a)
fmt.Println(a) // 10(没有改变)</pre></div>
<p class="maodian"><a name="_lab2_3_5"></a></p><h3>指针类型:传地址,修改原变量</h3>
<div class="jb51code"><pre class="brush:go;">func add(x *int) {
(*x)++
}
a := 10
add(&a)
fmt.Println(a) // 11(被改变了)</pre></div>
<p class="maodian"><a name="_label4"></a></p><h2>✅ 五、结构体特别重要:值类型默认复制!</h2>
<div class="jb51code"><pre class="brush:go;">type User struct {
Name string
}
func change(u User) {
u.Name = "Tom"
}
func main() {
user := User{Name: "Sam"}
change(user)
fmt.Println(user.Name) // Sam(没变)
}</pre></div>
<p>改用指针就会改变原数据:</p>
<div class="jb51code"><pre class="brush:go;">func change(u *User) {
u.Name = "Tom"
}
change(&user)
fmt.Println(user.Name)// Tom</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>✅ 六、什么时候应该使用指针?</h2>
<p class="maodian"><a name="_label3_3_5_0"></a></p><h4>✔ 1. 需要修改函数外的变量</h4>
<p>修改状态、计数器、累加器等。</p>
<p class="maodian"><a name="_label3_3_5_1"></a></p><h4>✔ 2. struct 较大,避免复制(性能)</h4>
<p>Go 的结构体可能很大,复制成本高:</p>
<div class="jb51code"><pre class="brush:go;">func process(u *LargeStruct) { ... }</pre></div>
<p class="maodian"><a name="_label3_3_5_2"></a></p><h4>✔ 3. 实现方法的“接收者”为指针(避免复制)</h4>
<p>很多类型的方法必须用指针接收器:</p>
<div class="jb51code"><pre class="brush:go;">func (u *User) SetName(name string) {
u.Name = name
}</pre></div>
<p class="maodian"><a name="_label3_3_5_3"></a></p><h4>✔ 4. 需要表示 nil(值不存在)</h4>
<div class="jb51code"><pre class="brush:go;">var p *User = nil</pre></div>
<p>值类型永远不能为 nil。</p>
<p class="maodian"><a name="_label6"></a></p><h2>✅ 七、一句话总结</h2>
<ul><li><strong>值类型保存真实值,赋值会复制,不会影响彼此。</strong></li><li><strong>指针类型保存地址,赋值只复制地址,多个变量共享底层数据。</strong></li><li><strong>指针多用于需要改变原数据、避免拷贝大结构体、或表示 nil 的场景。</strong></li></ul>
頁:
[1]