指针
在Go语言中,指针是一种特殊的数据类型,它存储了另一个变量的内存地址。指针对于理解和使用计算机科学中的引用传递非常重要。通过使用指针,你可以直接访问和修改内存地址上的数据,这可以提高程序的效率并允许函数间直接修改传入变量的值。
基本概念
- 声明指针: 指针变量声明格式为
var ptr *T
其中T
是指针所指向的值的类型。例如,var p *int
表示p
是一个指向整数型数据的指针。 - 获取变量地址: 使用
&
运算符可以获取一个普通变量地址。 - 使用指针访问值: 使用解引用操作符
*
, 可以获取或者设置位于该内存地址上(即该指针所“指向”的位置)的数据。 - 指针的零值: 指针的零值是
nil
,表示指针不指向任何变量。 - 空指针检查: 在解引用指针之前,应该检查指针是否为
nil
,以避免运行时错误。
package main
import"fmt"funcmain(){var a int=100var p *int=&a // 定义一个整型类型的指针p,并将其初始化为a的地址//var p *int = nil // 也可以初始化为空
fmt.Println("a的值:", a)
fmt.Println("a的内存地址:",&a)
fmt.Println("p的值,也就是a的内存地址:", p)
fmt.Println("p的内存地址(指针的指针):",&p)
fmt.Println("p的内存地址上保存的值(解引用p):",*p)*p =200// 更改*p意味着更改了p所执行到那个具体位置上保存着什么(即更改了a)
fmt.Println("a的值:", a)}
指针与函数
在Go语言中,所有参数都是按值传递(包括数组、结构体等)。如果需要在函数外部修改原始数据,则必须使用到Pointer。
package main
import"fmt"funcmodifyValue(p *int, a int){*p =10
a =20}funcmain(){
x :=5
y :=5modifyValue(&x, y)
fmt.Println(x)// 输出10, 因为x 的值已经被modifyValue 函数修改了,modifyValue直接修改了指针p指向的值。
fmt.Println(y)// 输出5, modifyValue函数中,a的值并没有被修改,因为a是函数的局部变量,函数结束后就自动释放了。}
- 变量 x 被成功修改因为它是通过引用(或说地址/指针)传递给函数的。这允许直接访问和更改存储在该地址上的数据。
- 变量 y 没有被更改因为它是通过值传递给函数。即使在函数内部该参数被重新赋予新值(20),这种更改也仅限于函数作用域内,并未影响到原始变量。
指针与引用
Golang中的指针
特性:
- 可以直接操作内存,但不支持指针运算(如C/C++中的加减)。
- 用于传递大型结构体时避免复制,提高效率。
package main
import"fmt"funcmain(){
x :=10
p :=&x // 获取x的地址
fmt.Println(*p)// 输出: 10*p =20// 修改p所指向的数据
fmt.Println(x)// 输出: 20}
Java中的引用
Java没有显式的指针,但对象通过引用传递。
特性:
- 引用是对对象在堆内存中位置的一种间接访问方式。
- 所有非基本类型(如对象、数组)都是通过引用来操作。
publicclassTest{publicstaticvoidmain(String[] args){int[] numbers ={1,2,3};System.out.println(numbers[0]);modifyArray(numbers);System.out.println(numbers[0]);// 输出: 99,因为数组是通过引用传递并修改了原始数据。}publicstaticvoidmodifyArray(int[] arr){
arr[0]=99;}}
联系与对比
- 内存管理:Go允许直接使用指针来管理内存,而Java则依赖垃圾回收机制自动管理对象生命周期,无需手动释放内存。
- 安全性与复杂度:Go提供了更底层的控制能力,但也要求开发者更加小心地处理空指针问题。Java则隐藏了这些细节,提供更高层次抽象以提高安全性。
值传递
值传递(Pass by Value)是一种参数传递机制,其中函数接收的是实参的副本,而不是实参本身。这意味着在函数内部对参数进行的任何修改都不会影响到原始变量。每次调用函数时,都会为参数创建一个新的内存空间来存储这些副本。
特性
- 独立性:由于函数接收到的是实参的副本,因此在函数内部对该参数所做的任何更改都不会影响到外部变量。
- 安全性:这种机制确保了调用者和被调用者之间的数据隔离,避免了不小心修改原始数据的问题。
- 适用场景:值传递通常用于基本数据类型(如整数、浮点数、布尔值等),因为它们占用内存较少且复制开销低。
示例
package main
import"fmt"funcmodifyValue(val int){
val =20// 修改val,但这只是在modifyValue作用域内有效}funcmain(){
x :=10
fmt.Println("Before modifyValue:", x)// 输出: Before modifyValue: 10modifyValue(x)
fmt.Println("After modifyValue:", x)// 输出: After modifyValue: 10}
x
是一个整数,当我们将其传递给modifyValue
时,实际上是将x
的当前值(即10)的副本赋给了形参val
。- 在
modifyValue
中,将形参设置为20并不会改变主程序中变量x
的值,因为它们位于不同的内存空间。 - 因此,在执行完函数后,输出仍然是初始值10,这证明了Go使用的是值传递机制。
特殊
然而,对于切片、映射等引用类型的数据结构,传递的值实际上是一个包含指向底层数据的指针的结构。因此,即使是值传递,也能通过这个指针修改底层数据。
funcmain(){
numbers :=[]int{1,2,3}
fmt.Println(numbers[0])modifyArray(numbers)
fmt.Println(numbers[0])// 输出: 99,因为切片是通过引用传递并修改了原始数据。}funcmodifyArray(arr []int){
arr[0]=99}
- 切片结构:切片本质上是一个描述符,包含三个部分: - 指向底层数组的指针。- 切片长度。- 切片容量。
- 值传递:当你将切片作为参数传入函数时,复制的是这个描述符(即这三个部分),而不是整个底层数组。
练习
- 定义一个结构体
Person
,包含字段Name
(字符串类型)和Age
(整数类型)。关于结构体我们后续详细展开学 - 实现一个函数试图通过值传递来更新结构体的名称。
- 实现另一个函数试图通过指针传递来更新结构体的年龄。
实现
package main
import"fmt"funcmain(){var p Person = Person{"小明",80}
fmt.Println(p)// 通过值传递修改名字,不会修改原来的值updateName(p,"大明")
fmt.Println(p)updateAge(&p,90)
fmt.Println(p)}funcupdateAge(ptr *Person, newAge int){// 当你有一个指向结构体的指针ptr时,Go允许你使用相同的.运算符来访问字段,如ptr.Age。这实际上是对 (*ptr).Age 的简写。// 编译器会自动解引用这个指针以获取实际的值,然后再进行字段访问。
ptr.Age = newAge
}funcupdateName(p Person, newName string){
p.Name = newName
}type Person struct{
Name string
Age int}
- 注意在使用值传递时,对象本身不会被改变,而是对其副本进行操作。
- 使用指针可以直接修改对象,因为它们允许直接访问内存地址。
单元测试
在模块中只允许一个main方法,调试代码极其不方便 ,所以这里引入单元测试,让代码调试方便许多。
单元测试是通过标准库中的
testing
包来实现的。Go提供了一种简单而强大的机制来编写和运行测试。
编写单元测试
- 创建测试文件:- 测试文件通常与被测代码放在同一目录下,并以
_test.go
结尾。例如math_test.go
。 - 编写测试函数:- 测试函数必须以
Test
开头,并且接收一个指向testing.T
类型的参数。- 函数签名示例:func TestFunctionName(t *testing.T)
- 使用断言:- Go没有内置断言库,但可以使用条件语句和
t.Errorf()
或t.Fatalf()
来报告错误。 - 示例代码
// math_test.gopackage math
import"testing"funcTestAdd(t *testing.T){println("单元测试")// 打印错误日志
t.Log("日志!")
t.Errorf("错误!!!!")
t.Fatalf("严重错误!!!!")}
运行单元测试
- 使用命令行工具:在终端中导航到包含
_test.go
文件的目录,然后执行以下命令:
go test
- 如果需要查看详细信息,可以使用
-v
标志:
go test -v
- 或者在idea中直接右键执行run
输出
=== RUN Test1
单元测试
math_test.go:11: 日志!
math_test.go:13: 错误!!!!
math_test.go:15: 严重错误!!!!
--- FAIL: Test1 (0.00s)
FAIL
Process finished with the exit code 1
版权归原作者 程序猿-瑞瑞 所有, 如有侵权,请联系我们删除。