Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > go 值传递和指针传递

go中值传递和指针传递的使用

作者:-代号9527

在Go语言中,使用&和*可以分别取得变量的地址和值,解引用未初始化或为nil的指针会引发空指针异常,正确的做法是先进行nil检查,此外,nil在Go中用于多种类型的空值表示,值传递和指针传递各有适用场景,通常小型数据结构优先考虑值传递以减少解引用开销

1、& 和 *

package main
import(
        "fmt"
)
func main(){

        var age int = 18
        //&符号+变量 就可以获取这个变量内存的地址
        fmt.Println(&age) //0xc0000a2058
        
        //ptr是一个变量,自身也有内存地址
        //&age就是一个地址,是ptr变量的具体的值
        var ptr *int = &age

		//这样直接输出,是ptr这个指针变量的值,即0xc0000a2058
        fmt.Println(ptr)
        
        //ptr这个指针变量自身的地址
        fmt.Println("ptr本身这个存储空间的地址为:",&ptr)
        
        //想获取ptr这个指针或者这个地址指向的那个数据:
        fmt.Printf("ptr指向的数值为:%v",*ptr) //ptr指向的数值为:18
}


在这里插入图片描述

对指针类型的变量再加*,是取真实值,即解引用

x := 10
a := &x // 取变量x的地址,a是一个指向int的指针 (*int 类型)

fmt.Println(*a) // 输出a指向的整数值,即变量x的值,这里将输出 10

2、空指针

* 虽然可以取到指针类型的真实值(解引用),但对nil解引用,会空指针:panic: runtime error: invalid memory address or nil pointer dereference

比如以下情况:

声明了一个指针变量,未初始化就直接解引用

var a *int
fmt.Println(*a)  // 这里将会导致空指针错误

给一个指针变量赋值nil后解引用

var a *int = nil
fmt.Println(*a)  // 这里将会导致空指针错误

调用了一个返回值是指针类型,但返回结果是nil的函数。此时直接解引用会空指针。

func returnNilPointer() *int {
    return nil
}

func main() {
    var a *int = returnNilPointer()
    fmt.Println(*a)  // 这里将会导致空指针错误
}

对指针类型解引用的正确做法是,先判空:

var ptr *int

if ptr != nil {
    fmt.Println(*ptr)  // 安全地解引用ptr
} else {
    fmt.Println("Pointer is nil")
    // 避免解引用nil指针
}

3、nil

源码:

在这里插入图片描述

// 指针
var ptr *int
fmt.Println(ptr)  // 输出: nil,即不指向任何有效的内存地址
// 接口
var iface fmt.Stringer
fmt.Println(iface == nil)  // 输出: true,即接口变量不指向任何具体的实现类对象
// 切片
var s []int
fmt.Println(s == nil)  // 输出: true,即表示一个空切片,即长度和容量都为0的切片
// 映射
var m map[string]int
fmt.Println(m == nil)  // 输出: true,表示一个空映射,即不包含任何键值对的映射
// 通道
var ch chan int
fmt.Println(ch == nil)  // 输出: true,即未初始化的通道默认为 nil
// 定义一个函数类型 HandlerFunc
type HandlerFunc func(int) string

// 声明一个 HandlerFunc 类型的变量 handler,但未赋值,其值为nil
 var handler HandlerFunc

因此,在未初始化的通道中发送或者接口数据、在未初始化的map中进行存储或者取值,就会panic,但切片有一点不同

var ch chan int
ch <- 1  // 尝试向空通道发送数据会导致panic

报错:

var m map[string]int
m["key"] = 1  // 尝试在空映射中存储值会导致panic

报错:

只定义,未初始化的切片,其值为nil,表示一个空切片,即长度和容量都为0的切片,此时,直接s[0] = 1就会发生下标越界panic,但用append方法一切正常,append 函数会根据需要自动初始化切片并分配内存

var s []int
s[0] = 1	// 越界panic

在这里插入图片描述

var s []int
s = append(s, 1)	// 不会panic或者空指针

注意,自定义的结构体的空值不是nil,这一点和Java中的null不一样(但如果加了&,即取地址,那就是自定义结构体的指针类型,其空值为nil)

在这里插入图片描述

此外,go中,所有的变量 (包括结构体变量) 在声明时如果没有显式赋值,会被赋予其类型的零值。比如:

在这里插入图片描述

4、用值传递还是指针传递?

什么时候用值传递,什么时候用指针传递?比如向函数调用栈里的下一个方法传递对象A,二者的区别在于,指针传递,传的是对象A的内存地址,传的是一个小巧的地址。值传递,是复制对象A的数据传下去。

之前有个说法:想在调用的函数内部改变对象A的值,就用指针传递,但这句话也不全对,因为用值传递,照样可以实现同样的效果。比如对象A为:

// 矩形
type Rectangle struct {
	Width, Height int
}

此时,要改变矩形的宽,值传递和引用传递(指针传递)的实现如下:

在这里插入图片描述

注意看二者的返回值,值传递,因为不能直接修改原对象,因此,需要将副本对象整个都返回,引用传递,则是一个void方法,因为其接收一个原对象的内存地址,可以直接修改原对象。这两种实现方式,在此时,没有谁优谁劣。

考虑优先使用值传递,原因如下:

在这里插入图片描述

此外,从底层分析原因:

在这里插入图片描述

最后,如果传递的是一个很大的结构体,那用指针传递更优。

5、补充

关于使用指针类型的场景,还有:insert数据时,数据库中默认值不对的时候:

在这里插入图片描述

到此这篇关于go中值传递和指针传递的使用的文章就介绍到这了,更多相关go 值传递和指针传递内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文