go语言八股文(五)

1.go的局部变量是分配在栈上还是在堆上

在Go语言中,局部变量的内存分配(栈或堆)由编译器基于逃逸分析(escape analysis)来决定。以下是总结和具体示例:

栈上分配

  • 当局部变量的生命周期严格限定在函数作用域内,并且没有被外部函数引用时,通常分配在栈上。
  • 变量在函数结束时自动销毁,内存被回收。
示例:
复制代码
package main

import "fmt"

func main() {
    var x int = 20 // x 在栈上分配
    fmt.Println(x)
}

在这个示例中,变量 xmain 函数内部声明并使用,没有逃逸到函数外部,因此它被分配在栈上。

堆上分配(逃逸分析)

  • 当局部变量的生命周期超出函数作用域,例如通过指针返回给外部使用时,编译器会将其分配在堆上。
  • 这种机制称为"逃逸分析",确保变量在作用域外仍然有效。
示例:
复制代码
package main

import "fmt"

func createVar() *int {
    var x int = 30 // 正常情况下,x 应该在栈上分配
    return &x      // 返回 x 的地址,x 逃逸到堆上
}

func main() {
    p := createVar()
    fmt.Println(*p)
}

在这个示例中,函数 createVar 返回了局部变量 x 的地址。由于 x 的地址被返回并可能在函数外部使用,x 逃逸到了堆上。编译器会将其分配在堆上以确保 pcreateVar 函数外部仍然有效。

逃逸分析的其他情况

  • 切片和映射:创建切片或映射时,即使在函数内部创建,它们也会被分配在堆上,因为它们包含动态分配的内存。
  • 通道:创建通道时,也会被分配在堆上。
示例:
复制代码
package main

import "fmt"

func main() {
    a := make([]int, 10) // a 在堆上分配
    fmt.Println(a)
}

func useSlice(slice []int) {
    // 函数内部对 slice 的操作不会影响其在堆上的分配
    slice[0] = 100
}

func main() {
    a := make([]int, 10)
    useSlice(a)
    fmt.Println(a)
}

在这个示例中,make([]int, 10) 创建了一个切片,它被分配在堆上。即使我们将这个切片传递给另一个函数 useSlice,它仍然在堆上,因为切片本身是一个包含指向底层数组的指针的结构体,它的分配不会因函数调用而改变。

总结

Go语言通过逃逸分析自动决定局部变量的内存分配位置:

  • 如果变量的生命周期仅限于函数内部且不会逃逸,它会被分配在栈上。
  • 如果变量需要在函数外部访问,或者其生命周期超出了函数作用域,它会被分配在堆上。 这种自动内存管理简化了内存管理,但开发者也需要理解逃逸分析的规则,以便更好地预测和控制内存分配。

2.几乎所有的类型T都可以有一个对应的指针类型*T

重点回答: 不是。在Go语言中,几乎所有的类型T都可以有一个对应的指针类型*T,不过接口类型的指针是无效的。

扩展知识

  1. 普通情况: 对于大多数类型(包括基础类型、自定义类型、结构体、切片、映射、通道等),你可以使用*T来表示类型T的指针。以下是一些示例:

    var x int
    px := &x // px 是一个指向 int 类型的指针

结构体:

复制代码
type MyStruct struct {
    Field int
}
var m MyStruct
pm := &m // pm 是一个指向 MyStruct 类型的指针

切片:

复制代码
var s []int
ps := &s // ps 是一个指向 []int 类型的指针

映射:

复制代码
var m map[string]int
pm := &m // pm 是一个指向 map[string]int 类型的指针

通道:

复制代码
var ch chan int
pch := &ch // pch 是一个指向 chan int 类型的指针
  1. 特殊情况
  • 数组 :数组类型T也可以有一个对应的指针类型T。在Go中,数组的指针类型Nint(例如*5int)表示一个固定大小为N的数组的指针。

    var arr [5]int
    par := &arr // par 是一个指向 [5]int 类型的指针

  • 接口:接口类型的指针*interface{}是无效的。接口是引用类型,它们本身就可以直接引用其他对象,没有必要使用指针类型。

    var i interface{} = "Hello"
    // pi := &i // 这是不允许的,会导致编译错误

  • 函数:函数类型也可以有一个指针类型。例如,func() int 类型的函数可以有一个 *func() int 类型的指针,但通常我们不常见函数指针类型的直接使用。

    func add(x, y int) int {
    return x + y
    }
    var pf *func(x, y int) int = &add // pf 是一个指向函数的指针

总结:Go语言中大多数类型都有对应的指针类型,但接口类型本身是引用类型,不需要指针类型。指针的使用需要根据具体需求和类型特性来决定。

自学go语言笔记,希望我们可以一起学习!

相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
克里斯蒂亚诺更新3 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a1117765 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang