阅读 745

从零学 Go:指针的基本应用

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

前文概览

前面文章介绍了Go 原生数据类型。Golang 中具备丰富的数据类型,基本类型有整型、浮点数、布尔型、字符串型等,除此之外,还有切片、结构体、指针、通道、map、数组等其他类型。本文将会介绍 GO 语言中的指针基本概念与相关用法。

指针

Golang 限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏移,避免了非法修改敏感数据的问题。同时 Golang 中提供的自动垃圾回收机制,也减少了对指针占用内存回收的复杂性。

指针只要包含三个概念:

  • 指针地址
  • 指针类型
  • 指针取值

在程序运行的过程中,每一个变量的值都保存在内存中,变量对应的内存有其特定的地址。假设某一个变量的类型为 T,在 Golang 中,我们可以通过 & 取址符号获取该变量对应内存的地址,生成该变量对应的指针。此时,变量的内存地址即生成的指针的值,指针类型即 *T,称为 T 的指针类型,* 代表指针。

我们可以运行一下 Pointer.go,查看它的执行结果。

package main

import "fmt"

func main()  {
	//声明一个 string 类型
	str := "Golang is Good!"
	//获取 str 的指针
	strPrt := &str

	fmt.Printf("str type is %T, and value is %v\n", str, str)
	fmt.Printf("strPtr type is %T, and value is %v\n", strPrt, strPrt)
}

复制代码

执行结果如下:

str type is string, and value is Golang is Good!
strPtr type is *string, and value is 0xc0000621c0
复制代码

可以看到 str 的类型为 string,它的指针 strPtr 的类型为 *string,指针的值为 0xc0000621c0,即变量 str 内存中的地址。

当然我们可以继续对指针 strPtr 进行取址操作,如下所示:

strPtrPtr := &strPrt
fmt.Printf("strPtrPtr type is %T, and value is %v\n", strPtrPtr, strPtrPtr)
复制代码

它的输出结果为:

strPtrPtr type is **string, and value is 0xc00007a018
复制代码

我们此时获取了 strPtr 对应内存的地址,并保存到 strPtrPtr 指针中。

除了提供对变量进行取址操作获取变量指针的 & 操作,Golang 中也提供了根据指针获取变量值的取值操作 *,通过 * 可以获取指针对应变量的值和对变量进行赋值操作,具体代码如下所示:

package main

import "fmt"

func main()  {
	str := "Golang is Good!"
	strPrt := &str

	fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
	fmt.Printf("strPtr type is %T, and value is %v\n", strPrt, strPrt)

	newStr := *strPrt	//获取指针对应变量的值
	fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)

    *strPrt = "Java is Good too!"	//通过指针对变量进行赋值
	fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
	fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)

}
复制代码

输出的结果为:

str type is string, value is Golang is Good!, address is 0xc0000621c0
strPtr type is *string, and value is 0xc0000621c0
newStr type is string, value is Golang is Good!, and address is 0xc0000621f0
newStr type is string, value is Golang is Good!, and address is 0xc0000621f0
str type is string, value is Java is Good too!, address is 0xc0000621c0
复制代码

在上述代码中,我们通过 strPtr 指针获取 str 的值赋予给 newStr 变量。可以观察到 strnewStr 是两个不同的变量,它们对应的内存地址不一样,赋值过程中发生了值拷贝。通过指针修改 str 变量的值并不会影响到 newStr,因为这两个变量对应的内存地址不一样。

除了使用 & 对变量进行取址操作创建指针,还可以使用 new 函数直接分配内存,并返回指向内存的指针,此时内存中的值会被初始化为类型的默认值。如下例子所示:

str := new(string)
*str = "Golang is Good!"
复制代码

在上述代码中,通过 new 函数创建了一个 *string 指针,并通过指针对其进行赋值。

在 Golang 的 flag 包中,命令行参数一般以指针的返回,下面我们演示一个读取命令行参数的例子,如下代码所示:

package main

import (
	"flag"
	"fmt"
)

func main()  {
	//定义一个类型为 string,名称为 surname 的命令行参数
	//参数依次是命令行参数的名称,默认值,提示
	surname := flag.String("surname", "王", "您的姓")
	//定义一个类型为 string,名称为 personalName 的命令行参数
	//除了返回指针类型结果,还可以直接传入变量地址获取参数值
	var personalName string
	flag.StringVar(&personalName, "personalName", "小二", "您的名")
	//定义一个类型为 int,名称为 id 的命令行参数
	id := flag.Int("id", 0, "您的 ID")
	//解析命令行参数
	flag.Parse()
	fmt.Printf("I am %v %v, and my id is %v\n", *surname, personalName, *id)

}
复制代码

在上述代码中可以看到,除了直接获取指针类型的返回结果,还可以将参数变量的指针传递给 flag.*Val 方法,获取命令行参数的值。输入以下的执行参数:

go run Flag.go -surname="苍" -personalName="小屋" -id=100
复制代码

Golang 中 flag 支持多种样式的命令行参数,包括:

-id=100
--id=100
-id 100
--id 100
复制代码

输出结果如下:

I am 苍 小屋, and my id is 100
复制代码

小结

在 C/C++ 语言中,指针直接操作内存的特性使得 C/C++ 具备极高的性能,它可以使开发人员操作大块内存数据。与此同时,指针偏移、运算和内存释放可能引发的错误也让指针编程饱受诟病。 本文主要介绍了 Go 语言中指针的基本概念与使用。Go 语言限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏移,避免了非法修改敏感数据的问题。

下面文章将会介绍 GO 中的常量使用。相对于变量运行时可变的特点,常量的值在声明之后是不允许变化。通过const关键字可以声明常量,声明常量的样式与声明变量非常相似。

文章分类
后端
文章标签