Go语言高并发与微服务实战 - 学习笔记
第3章 Go语言基础
3.3 基本语法
3.3.3 指针
在C/C++语言中,指针直接操作内存的特性使得C/C++具备极高的性能,开发人员通过它直接操作和管理大块内存数据。
但与此同时,指针偏移、运算和内存释放可能引发的错误也让指针编程饱受诟病。
Go语言限制了指针类型的偏移和运算能力,使得指针类型具备了指针高效访问的特性,但又不会发生指针偏移,避免了非法修改敏感数据的问题。
同时Go语言中提供的自动垃圾回收机制,也减少了对指针占用内存回收的复杂性。
在G0语言中,指针包含以下三个概念:
- 指针地址
- 指针类型
- 指针取值
在程序运行的过程中,每一个变量的值都保存在内存中,变量对应的内存有其特定的地址。
假设某一个变量的类型为T,在Go语言中,我们可以通过取址符号&获取该变量对应内存的地址,生成该变量对应的指针。此时,变量的内存地址即生成的指针的值,指针类型即“*T”,称为T的指针类型,“*”代表指针。
举个例子,Pointer.go 程序,看看运行结果
package main
import "fmt"
func main() {
// 声明一个string 类型
str := "Golang is Good"
// 获取str 的指针
strPtr := &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", strPtr, strPtr)
}
运行结果:
可以看到str的类型为string,它的指针strPtr的类型为*string,指针的值为0xc000088220,即为变量str内存中的地址。
当然我们可以继续对指针srPr进行取址操作,如下所示:
strPtrPtr := &strPtr
fmt.Printf("strPtrPtr type is %T, and value is %v\n", strPtrPtr, strPtrPtr)
运行结果:
我们此时获取了strPtr对应内存的地址,并保存到strPtrPtr指针中。
除了提供对变量进行取址操作获取变量指针的&操作,Go语言中也提供了根据指针获取变量值的取值操作(*),通过取值操作(*)可以获取指针对应变量的值和对变量进行赋值操作,具体代码如下所示:
package main
import "fmt"
func main() {
// 声明一个string 类型
str := "Golang is Good"
// 获取str 的指针
strPtr := &str
fmt.Printf("str type is %T, value is %v, address is %p\n", str, str, &str)
fmt.Printf("strPtr type is %T, value is %v\n", strPtr, strPtr)
newStr := *strPtr // 获取指针对应变量的值
fmt.Printf("newStr type is %T, value is %v, and address is %p\n", newStr, newStr, &newStr)
*strPtr = "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, and value is %v,address is %p\n", str, str, &str)
}
运行结果:
在上述代码中,我们通过strPtr指针获取str的值赋予给newStr变量。可以观察到str和newStr是两个不同的变量,它们对应的内存地址不一样,赋值过程中发生了值拷贝。
值拷贝会创建新的内存空间,然后将原有变量的值复制到新的内存空间中,形成两个独立的变量。通过指针修改str变量的值并不会影响到newStr,因为这两个变量对应的内存地址不一样。
除了使用&对变量进行取址操作创建指针,还可以使用new 函数直接分配内存,并返回指向内存的指针,此时内存中的值会被初始化为类型的默认值。
如下例子所示:
str := new(string)
*str = "Golang is Good!"
在上述代码中,通过new函数创建了一个*string指针,并通过指针对其进行赋值。
在Go语言的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)
}
在上述代码中可以看到,除了直接获取指针类型的返回结果,还可以将参数变量的指针传递给f1ag.*Val方法,获取命令行参数的值。
输入以下的执行参数:
go run FlagTest.go -surname="Ding" -personalName="Jiaxiong" -id=100
运行结果
Go语言中flag支持多种样式的命令行参数,包括:
-id=100
--id=100
-id 100
--id 100
效果都是一样的