6.3 使用指针
在Go语言中,变量的地址是由编译系统分配的,有多种使用指针型变量的方式。在本节的内容中,将详细讲解使用Go语言指针的知识。
6.3.1 使用指针变量
使用指针变量可以实现引用传递,即修改指向变量的值。在Go语言中,使用指针变量有以下几个方面需要注意。
(1)定义和初始化指针变量
在定义一个指针变量时,需要在类型或者变量名前加上*表示该变量是一个指针类型。例如:
var p *int // 定义一个整型的指针变量p
要想让指针变量指向某个变量的地址,需要使用取地址符&来获取该变量的地址,并将其赋值给指针变量。例如:
x := 100
p = &x // 指向x变量的地址
(2)访问指针变量指向变量的值
在访问指针变量所指向变量的值时,需要使用解引用操作符*。例如,在前面的一段代码中,通过*p就可以获取到x变量的值,接下来 通过下面的代码即可打印输出p的值。
fmt.Println(*p) // 输出:100
(3)空指针
指针变量的值可能为空,也就是指向没有任何意义的内存地址。这个地址的值被称为空指针,使用nil表示。如果尝试解引用一个空指针,则会导致程序崩溃。因此在使用指针变量之前,需要检查它是否为nil,以避免程序崩溃。例如可以通下面的代码检查:
var p *int
if p == nil {
fmt.Println("p is nil")
}
实例6-1:个税计算器(源码路径:Go-codes\6\bian.go)
实例文件bian.go的具体实现代码如下所示。
package main
import "fmt"
func main() {
var a int= 20 /* 声明实际变量 */
var ip *int /* 声明指针变量 */
ip = &a /* 指针变量的存储地址 */
fmt.Printf("a 变量的地址是: %x\n", &a )
/* 指针变量的存储地址 */
fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
/* 使用指针访问值 */
fmt.Printf("*ip 变量的值: %d\n", *ip )
}
执行后会输出:
a 变量的地址是: c00000e088
ip 变量储存的指针地址: c00000e088
*ip 变量的值: 20
6.3.2 指针数组
在Go语言中,指针数组是指一个数组中存储的元素都是指针类型。这种数组可以用来存储一组指针,每个指针指向一个变量或者对象。使用指针数组,可以方便地实现批量操作,例如对数组中的所有指针进行初始化、赋值或者释放等操作。在接下来的内容中,将详细介绍使用指针数组的方法。
(1)声明指针数组
在声明指针数组时,需要在类型和变量名之间加上*符号来表示该数组存储的是指针类型的元素,例如:
var arr [5]*int //声明一个存储了5个指向int类型变量的指针数组
(2)初始化指针数组
接下来通过数组索引下标即可初始化数组元素的值,例如下面的代码是为上面代码中的数组arr进行初始化赋值。
num1 := 10
num2 := 20
num3 := 30
arr := [3]*int{&num1, &num2, &num3} //初始化一个存储了3个指向int类型变量的指针数组。
(3)访问指针数组
接下来可以和访问普通数组的方法一样访问指针数组,例如下面的代码。
num1 := 10
num2 := 20
num3 := 30
arr := [3]*int{&num1, &num2, &num3}
fmt.Println(*arr[0]) //输出第一个元素的值,也就是num1的值
实例6-2:存储并操作一维数组(源码路径:Go-codes\6\arry01.go)
实例文件arry01.go的具体实现代码如下所示。
package main
import "fmt"
func main() {
nums := [5]int{10, 20, 30, 40, 50}
var ptrs [5]*int
// 将每个元素的地址存储到指针数组中
for i := 0; i < len(nums); i++ {
ptrs[i] = &nums[i]
}
// 通过指针数组修改原始数组中的值
*ptrs[2] = 100
*ptrs[4] = 200
// 输出修改后的原始数组
fmt.Println(nums)
}
在上述代码中,首先定义了一个包含5个整数的数组nums,并定义了一个名为ptrs的指针数组。然后,我们将每个元素的地址存储到ptrs数组中,以便我们可以使用指针数组来操作原始数组。接下来,我们通过指针数组修改了原始数组中的第3个和第5个元素的值。最后,我们输出修改后的原始数组。执行后会输出:
[10 20 100 40 200]
注意:Go语言中的指针数组与C语言中的指针数组类似,都是存储了同一类型变量的指针的数组,并可以通过索引来访问它们指向的变量。因此,在实际应用中,指针数组通常用于存储和管理指向同一类型变量的指针集合,以便于对它们进行批量操作。
6.3.2 将指针做为函数参数
在Go语言中,函数的参数传递有两种方式:值传递和引用传递。其中,值传递是指将实参的值复制一份传递给形参,而引用传递则是将实参的地址传递给形参,使得函数可以直接修改实参的值。使用指针作为函数参数,就可以实现引用传递的效果。在下面的内容中,将详细讲解将指针作为函数参数的使用方法。
(1)在定义函数时,需要在形参前加上*符号来表示该参数是一个指针变量,例如:
func swapInts(a, b *int) {
*a, *b = *b, *a
}
(2)在调用函数时,需要将变量的地址作为实参传递给函数,例如:
x, y := 1, 2
swapInts(&x, &y)
fmt.Println(x, y) // 输出:2 1
(3)在函数内部可以通过解引用操作符*来获取指针所指向变量的值,并对其进行修改。例如,在上述代码中,swapInts函数可以交换两个整数类型的变量的值。
(4)指针变量的指向也可以在函数内部被修改,例如:
func incrPointer(p *int) {
*p++
fmt.Println("incrPointer:", *p)
}
func main() {
x := 100
p := &x
fmt.Println("main:", *p)
incrPointer(p)
fmt.Println("main:", *p)
}
在上述代码中,首先定义了一个函数incrPointer(),它可以将指针所指向的变量增加1。在函数main()中首先定义了一个指针变量p,并将其指向变量x的地址。然后,在调用函数incrPointer()时,将p作为实参传递进去。在该函数内部,我们通过解引用操作符*来获取p所指向变量的值,并对其进行修改。最后,我们再次输出p所指向变量的值,并发现其已经被修改。
下面是一个使用指针作为函数参数值传递的实用例子,实例功能是交换两个整数的值。
实例6-3:交换两个整数的值(源码路径:Go-codes\6\swap.go)
实例文件swap.go的具体实现代码如下所示。
func swap(a *int, b *int) {
temp := *a
*a = *b
*b = temp
}
func main() {
var num1 int = 10
var num2 int = 20
fmt.Println("Before swapping: ", num1, num2)
swap(&num1, &num2)
fmt.Println("After swapping: ", num1, num2)
}
在上述代码中定义了一个名为swap()的函数,该函数接受两个整数指针作为参数,并使用指针交换它们的值。然后,在函数main()中声明并初始化了两个整数变量num1和num2。我们通过传递指向这两个变量的指针调用swap函数来交换它们的值,最后打印出交换后的值。执行后会输出:
Before swapping: 10 20
After swapping: 20 10
在上述实例中,读者需要注意,由于Go语言的函数参数是按值传递的,因此如果我们想要在函数内修改原始变量的值,我们必须将指向这些变量的指针作为参数传递给函数。通过这种方式,函数可以直接访问和修改原始变量的值,而无需返回任何值。
下面是一个使用指针作为函数参数引用传递的实用例子,实例的功能是计算一个整数数组中所有元素的和,并将其存储到指向该和的变量中。
实例6-4:计算数组中所有元素的和(源码路径:Go-codes\6\sum.go)
实例文件sum.go的具体实现代码如下所示。
func sum(nums []int, result *int) {
for _, num := range nums {
*result += num
}
}
func main() {
nums := []int{1, 2, 3, 4, 5}
var total int
sum(nums, &total)
fmt.Println("Total:", total)
}
在上述代码中定义了一个名为sum()的函数,该函数接受一个整数切片和一个整数指针作为参数。它通过遍历切片中的每个元素并将它们累加起来,最终将结果存储到指向该和的变量中。然后,在函数main()中声明并初始化了一个包含5个整数的切片nums,以及一个名为total的整数变量。我们通过传递nums切片和total变量的地址调用sum函数,以便于函数可以直接访问和修改total变量的值。需要注意的是,由于Go语言的函数参数是按值传递的,因此如果我们想要在函数内修改原始变量的值,我们必须将指向这些变量的指针作为参数传递给函数。在这个例子中,我们通过引用传递的方式,使用指针来修改函数外部定义的变量。
执行后会输出:
Total: 15
注意:使用指针作为函数参数可以实现引用传递的效果,灵活地修改实参的值。但是需要注意指针变量的有效性检查、空指针和野指针的问题,以避免出现程序崩溃等错误。同时,也需要注意不要滥用指针,以免引起不必要的麻烦。
6.3.3 指针的指针
在Go语言中,还有一种指针类型叫做指针的指针,它本质上是指向指针的指针变量。指针的指针通常用于函数参数传递及返回多个值的情况。指针的指针的语法很简单,只需要在指针类型前面再加一个星号即可。例如,**int表示指向指针的指针变量。例如在下面的代码(源码路径:Go-codes\6\zhi.go)中,演示了如何声明和使用指针的指针的过程。
func updateVar(p **int) {
x := 10
*p = &x
}
func main() {
var p *int
var pp **int
updateVar(&p)
pp = &p
fmt.Println("Value of p:", *p)
fmt.Println("Value of pp:", **pp)
}
在上述代码中,我们首先在函数参数中声明了一个指向指针的指针变量p,其类型为**int。这意味着p存储了一个指向指针变量的地址。我们可以通过解引用两次来访问并更新指向的变量的值。然后,在函数main()中定义了一个指针变量p和一个指向指针变量的指针变量pp。我们调用函数updateVar(),并将指向指针变量p的指针变量的地址作为参数传递给该函数。这样,函数updateVar()就可以通过解引用两次来访问并更新指针变量p的值。执行后会输出:
Value of p: 10
Value of pp: 10
注意:在对指向指针的指针变量进行解引用时,我们需要多次使用星号来获取存储在内存中的变量的值。例如,*p表示指向整数变量的指针变量的值,而**pp则表示指向指向整数变量的指针变量的指针变量的值。总结来说,指针的指针是Go语言中的一种高级概念,它可以让程序直接操作变量所在的内存地址,从而实现更加灵活和高效的编程。在Go语言中,使用指针的指针需要小心谨慎,因为错误的使用可能会导致不可预见的结果。
请看下面的实例,演示了如何使用指针的指针在函数之间传递多个值的方法。
实例6-5:模拟一个交换机器人的任务(源码路径:Go-codes\6\jiao.go)
本实例模拟了一个交换机器人的任务,其中每个机器人都具有三个属性:name、position和status。我们将使用指向指针的指针来交换两个机器人的位置,并更新它们的状态。实例文件jiao.go的具体实现代码如下所示。
package main
import "fmt"
type robot struct {
name string
position int
status string
}
func swapRobots(r1, r2 **robot) {
temp := *r1
*r1 = *r2
*r2 = temp
(*r1).status = "active"
(*r2).status = "inactive"
}
func main() {
// 创建两个机器人
r1 := &robot{"Robot 1", 1, "active"}
r2 := &robot{"Robot 2", 2, "inactive"}
fmt.Println("Before swap:")
fmt.Println("Robot 1:", *r1)
fmt.Println("Robot 2:", *r2)
// 交换机器人的位置,并更新其状态
swapRobots(&r1, &r2)
fmt.Println("After swap:")
fmt.Println("Robot 1:", *r1)
fmt.Println("Robot 2:", *r2)
}
对上述代码的具体说明如下:
- 首先定义了一个名为robot的结构体,该结构体包含每个机器人的名称、位置和状态。然后,我们定义了函数swapRobots(),该函数接受两个指向指针的指针变量作为参数,这些指针分别指向要交换的机器人。在函数内部,我们使用一个临时变量来保存第一个机器人的指针,然后将第一个机器人的指针更新为第二个机器人的指针,反之亦然。最后,我们通过解引用两次来更新每个机器人的状态。
- 在函数main()中创建了两个机器人对象,并打印它们的属性。然后,我们调用函数swapRobots(),并传递两个指向机器人对象的指针的指针作为参数。这样,函数就可以直接访问和修改每个机器人的属性,实现了机器人位置和状态的交换。
执行后会输出:
Before swap:
Robot 1: {Robot 1 1 active}
Robot 2: {Robot 2 2 inactive}
After swap:
Robot 1: {Robot 2 2 active}
Robot 2: {Robot 1 1 inactive}
6.3.4 指针、数组和函数的综合运用
请看下面的例子,演示了联合使用指针、数组和函数实现一个简单的员工信息管理系统的过程。这个系统可以记录每个员工的姓名、年龄和薪水,并提供计算平均薪水和输出所有员工信息的功能。
实例6-6:员工信息管理系统(源码路径:Go-codes\6\zonghe.go)
实例文件zonghe.go的具体实现代码如下所示。
package main
import (
"fmt"
)
func addEmployee(name *string, age *int, salary *float64) {
fmt.Print("Enter employee name: ")
fmt.Scanln(name)
fmt.Print("Enter employee age: ")
fmt.Scanln(age)
fmt.Print("Enter employee salary: ")
fmt.Scanln(salary)
}
func calcAverageSalary(salaries []float64) float64 {
total := 0.0
for _, salary := range salaries {
total += salary
}
return total / float64(len(salaries))
}
func printEmployees(names []string, ages []int, salaries []float64) {
fmt.Println("Name\tAge\tSalary")
for i := 0; i < len(names); i++ {
fmt.Printf("%s\t%d\t%.2f\n", names[i], ages[i], salaries[i])
}
}
func main() {
var names [3]string
var ages [3]int
var salaries [3]float64
// 添加员工信息
for i := 0; i < len(names); i++ {
addEmployee(&names[i], &ages[i], &salaries[i])
}
// 输出员工信息和平均薪水
printEmployees(names[:], ages[:], salaries[:])
fmt.Println("Average salary:", calcAverageSalary(salaries[:]))
}
对上述代码的具体说明如下:
- 首先定义了三个数组,分别用于存储员工的姓名、年龄和薪水。然后,使用函数addEmployee()为每个员工添加信息,该函数接受指向员工姓名、年龄和薪水的指针作为参数,并通过标准输入从用户那里获取这些信息。
- 然后使用函数calcAverageSalary()计算所有员工的平均薪水,该函数接受一个float64类型的切片作为参数,并返回一个float64类型的平均值。
- 最后,使用函数printEmployees()输出所有员工的姓名、年龄和薪水,并使用函数calcAverageSalary()输出他们的平均薪水。
执行后根据提示输入3名员工的信息,会计算员工的平均工资。例如执行后输出:
Enter employee name: 老管
Enter employee age: 40
Enter employee salary: 50000
Enter employee name: 老王
Enter employee age: 30
Enter employee salary: 5000
Enter employee name: 小王
Enter employee age: 23
Enter employee salary: 3000
Name Age Salary
老管 40 50000.00
老王 30 5000.00
小王 23 3000.00
Average salary: 19333.333333333332
注意:本例子仅仅是一个简单的示例程序,它只能处理固定数量的员工。如果您想要构建更复杂的员工信息管理系统,请考虑使用更高级的技术和库。