一、指针
指针的概念以及定义
一个变量交换的例子
交换两个变量的值在排序过程中是一个高频操作,交换变量值最直接的方式就是通过一个临时变量来实现交换,在 Go 中可以这么来实现:
func main() { a := 10 b := 20 switchVal(a, b) fmt.Println(a, b) } // 定义一个函数:交换两个变量的值 func switchVal(a int, b int) { temp := a a = b b = temp } 复制代码
执行上述代码,输出结果如下:
10 20 复制代码
根据结果来看并没有成功的实现交换两个变量的值,为什么?因为整型是值类型,作为函数参数时会将拷贝非副本作为实际的参数。
那么如何修改呢?
func main() { a := 10 b := 20 switchVal(&a, &b) fmt.Println(a, b) } // 交换两个变量的值 func switchVal(a *int, b *int) { temp := *a *a = *b *b = temp } 复制代码
执行上述代码,输出结果如下:
20 10 复制代码
根据输出结果可以确定,a 和 b 两个变量的变量值交换成功。
指针
上述代码中的 &
在之前的文章有提到过,表示获取变量的内存地址,那么 switchVal 函数中的 *int
又表示什么?
*int
表示指针类型
,指针是一种数据类型,指针中存储的是内存地址,而这个内存地址指向一块具体的内存,这块具体的内存中保存着个中类型的数据。
指针类型包含两个部分,第一个是 *
符号,表示是指针类型,第二个组成部分是基本数据类型标识符,表示指针所执行的内存地址中存储的数据类型。
指针变量与普通变量不同的是,普通变量存储的直接就是变量值,而指针存储的是变量的内存地址。
func main() { var yankee int = 200 // 定义指针 var zulu *int = &yankee fmt.Printf("%T, %v, %v\n", yankee, yankee, &yankee) fmt.Printf("%T, %v, %v\n", zulu, zulu, *zulu) } 复制代码
执行上述代码,输出结果如下:
int, 200, 0xc0000b2008 *int, 0xc0000b2008, 200 复制代码
在普通变量前添加 &
就可以获得该变量的内存地址,在指针变量前添加 *
就可以获取该指针变量中的内存地址所指向的值。如果要修改指针变量内存地址指向的值只需要 *指针变量=newVal
即可。
当然定义指针变量时也可以省略 var
关键字,直接使用 :=
来定义。
func main() { xray := 1.0 whiskey := "Go" victor := []string{"Stark", "Thor", "Steve"} uniform := [...]int{1, 3, 5, 7, 9} tango := map[string]string{ "code": "Tango 6", "number": "6", } tangoCode := tango["code"] fmt.Printf("%T, %v\n", &xray, &xray) fmt.Printf("%T, %v\n", &whiskey, &whiskey) fmt.Printf("%T, %v\n", &victor[0], &victor[0]) fmt.Printf("%T, %v\n", &uniform[0], &uniform[0]) fmt.Printf("%T, %v\n", &tangoCode, &tangoCode) } 复制代码
执行上述代码,输出结果如下:
*float64, 0xc000132008 *string, 0xc000116210 *string, 0xc000118180 *int, 0xc00012a060 *string, 0xc000116220 复制代码
Go 的指针 和 C 语言的指针的区别
C 和 C++ 都提供了指针而且功能强大,可以进行指针的转换、偏移以及运算等,其他静态类型语言如 Java 和动态类型语言如 Python 则是将指针屏蔽,不提供指针的概念以及运算等。
Go 中的指针相比 C 和 C++ 中的指针做了很多的限制,有更高的安全性,不涉及到指针的运算、转换和偏移等功能。
make 函数和 new 函数
先来定义一个指针变量,给指针变量的内存地址赋值,并输出其中保存在内存地址中的值
func main() { var p *int *p = 10 fmt.Println(*p) } 复制代码
执行上述代码,输出结果如下:
这里为什么会报错呢?这里错误信息提示无效的内存地址或者空的指针指向,也就是说没有指定的内存地址来保存 10。
要初始化一个指针变量可以使用 new 函数
func main() { var p *int = new(int) fmt.Println(*p) *p = 10 fmt.Println(*p) } 复制代码
执行上述代码,输出结果如下:
0 10 复制代码
new 函数在这里会现申请一个内存空间,然后将内存空间保存的值设置为 int 的默认值既 0,而且 new 函数返回的是一个内存地址。
func main() { var p2 *int fmt.Println(p2) // 输出结果为:<nil> } 复制代码
其他类型如 Map、Slice 如果只定义不初始化,也会报错。
func main() { var info map[string]string info["name"] = "Stark" fmt.Println(info) var namesSlic []string namesSlic[0] = "Thor" fmt.Println(namesSlic) } 复制代码
也可以使用 make 函数来进行初始化,创建内存保存数据,返回一个具体的实例。
func main() { var info map[string]string = make(map[string]string) fmt.Println(info) info["name"] = "Stark" fmt.Println(info) var namesSlic []string = make([]string, 3) fmt.Println(namesSlic) namesSlic[0] = "Thor" fmt.Println(namesSlic) } 复制代码
在 Go 编程 | 连载 11 - 复杂数据类型 Slice 中使用 new 函数创建一个 Slice 实例,因为 new 函数返回的是内存地址,要获取内存地址中保存的实例则需要使用 *
来获取。
make 函数和 new 函数的区别或者使用场景如下:
- make 和 new 都是用来分配内存的內建函数,且在堆上分配内存,make 即分配内存,也初始化内存;new只是将内存清零,并没有初始化内存。
- make 返回的还是引用类型(实例)本身;而 new 返回的是指向类型的指针(内存地址)。
- make 只能用来分配及初始化类型为 slice,map,channel;new 可以分配任意类型的数据。