0


07 go语言(golang) - 数据类型:指针 & 单元测试

指针

在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是函数的局部变量,函数结束后就自动释放了。}
  1. 变量 x 被成功修改因为它是通过引用(或说地址/指针)传递给函数的。这允许直接访问和更改存储在该地址上的数据。
  2. 变量 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)是一种参数传递机制,其中函数接收的是实参的副本,而不是实参本身。这意味着在函数内部对参数进行的任何修改都不会影响到原始变量。每次调用函数时,都会为参数创建一个新的内存空间来存储这些副本。

特性

  1. 独立性:由于函数接收到的是实参的副本,因此在函数内部对该参数所做的任何更改都不会影响到外部变量。
  2. 安全性:这种机制确保了调用者和被调用者之间的数据隔离,避免了不小心修改原始数据的问题。
  3. 适用场景:值传递通常用于基本数据类型(如整数、浮点数、布尔值等),因为它们占用内存较少且复制开销低。

示例

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}
  • 切片结构:切片本质上是一个描述符,包含三个部分: - 指向底层数组的指针。- 切片长度。- 切片容量。
  • 值传递:当你将切片作为参数传入函数时,复制的是这个描述符(即这三个部分),而不是整个底层数组。

练习

  1. 定义一个结构体Person,包含字段Name(字符串类型)和Age(整数类型)。关于结构体我们后续详细展开学
  2. 实现一个函数试图通过值传递来更新结构体的名称。
  3. 实现另一个函数试图通过指针传递来更新结构体的年龄。

实现

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提供了一种简单而强大的机制来编写和运行测试。

编写单元测试

  1. 创建测试文件:- 测试文件通常与被测代码放在同一目录下,并以 _test.go 结尾。例如 math_test.go
  2. 编写测试函数:- 测试函数必须以 Test 开头,并且接收一个指向 testing.T 类型的参数。- 函数签名示例: func TestFunctionName(t *testing.T)
  3. 使用断言:- Go没有内置断言库,但可以使用条件语句和t.Errorf()t.Fatalf()来报告错误。
  4. 示例代码
// 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
标签: golang 开发语言

本文转载自: https://blog.csdn.net/weixin_39743356/article/details/143241453
版权归原作者 程序猿-瑞瑞 所有, 如有侵权,请联系我们删除。

“07 go语言(golang) - 数据类型:指针 & 单元测试”的评论:

还没有评论