(6-2)指 针:使用指针

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)
}

对上述代码的具体说明如下:

  1. 首先定义了一个名为robot的结构体,该结构体包含每个机器人的名称、位置和状态。然后,我们定义了函数swapRobots(),该函数接受两个指向指针的指针变量作为参数,这些指针分别指向要交换的机器人。在函数内部,我们使用一个临时变量来保存第一个机器人的指针,然后将第一个机器人的指针更新为第二个机器人的指针,反之亦然。最后,我们通过解引用两次来更新每个机器人的状态。
  2. 在函数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[:]))
}

对上述代码的具体说明如下:

  1. 首先定义了三个数组,分别用于存储员工的姓名、年龄和薪水。然后,使用函数addEmployee()为每个员工添加信息,该函数接受指向员工姓名、年龄和薪水的指针作为参数,并通过标准输入从用户那里获取这些信息。
  2. 然后使用函数calcAverageSalary()计算所有员工的平均薪水,该函数接受一个float64类型的切片作为参数,并返回一个float64类型的平均值。
  3. 最后,使用函数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

注意:本例子仅仅是一个简单的示例程序,它只能处理固定数量的员工。如果您想要构建更复杂的员工信息管理系统,请考虑使用更高级的技术和库。

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值