在面对 Java、C#、VB 这些高级程序设计语言的时候,很少有听到指针的概念。所以长期接触这类语言的开发者,在接触 Go 的时候,基础语法中最不能理解的应该就是指针了。

Java 中的指针

Java 中没有明确的指针的概念。Java 帮我们封装了对象,简化了对象之间引用的关系。

此外,Java 是值传递。在进行值传递的时候,真正传递的是一个 Java 对象的引用。

/**
 * @author Real
 * @since 2023/11/27 22:02
 */
@Data
public class User {

    private String name;

    private int age;

}

首先我们定义一个 User 对象,我们定义一个对 User 方法修改名称的方法。

public static void renameUser(User user, String newName) {
    if (user == null) {
        return;
    }

    user.setName(newName);
}

那么在调用的时候,我们需要传进一个 User 和 newName 变量。

/**
 * @author Real
 * @since 2023/11/27 22:02
 */
@Data
@AllArgsConstructor
public class User {

    private String name;

    private int age;

    public static void renameUser(User user, String newName) {
        if (user == null) {
            return;
        }

        user.setName(newName);
    }

}

调用测试:

public static void main(String[] args) {
    User user = new User("testUser", 20);
    // user: User(name=testUser, age=20); hashcode: -1146741326
    System.out.println("user: " + user + "; hashcode: " + user.hashCode());

    User.renameUser(user, "Real");
    // user: User(name=Real, age=20); hashcode: 2547699
    System.out.println("user: " + user + "; hashcode: " + user.hashCode());
}

可以看到运行的结果如注释所示。但是如果在传入的 User 对象中,对 User 重新赋值,结果会是怎样呢?

① 修改原 rename 方法,将 user 对象重新赋值。

public static void renameUser(User user, String newName) {
    if (user == null) {
        return;
    }

    user = new User("newUser", 18);
    user.setName(newName);
}

② 再次执行测试代码:

可以看到 User 对象并没有重新变化。

Java 中关于值传递和引用传递的讨论可以参考:https://www.zhihu.com/question/31203609/answer/576030121

结合上面的实验以及各种解析,我们可以得出结论:

  • Java 中只有值传递,没有引用传递。将对象作为形参传递,实际传递的是实参的引用地址。
  • 反映在编码中,方法中将形参对象重新指向后修改,这个值不会反映到实参上。

Go 中的指针

Go 中,是有明确的指针类型数据的。

func pointer() {
    str := "Golang"
    // p 是指向 str 的指针
    var p *string = &str
    // 修改了 p, str 的值也发生了改变
    *p = "Go语言"

    fmt.Println(str)
}

Go 语言里面的指针,明确的就是指向数据的地址值。

这里做一个简单的小测验。我们针对简单的数据类型,进行分别进行值传递和引用传递(指针)。查看运行结果,可以发现运用指针的引用传递,最终的结果才是符合预期的。

func pointerTest() {
	// 100,num 没有变化
	num := 100
	add(num)
	fmt.Println(num)

	// 101,指针传递,num 被修改
	realAdd(&num)
	fmt.Println(num)
}

func add(num int) {
	num += 1
}

func realAdd(num *int) {
	*num += 1
}

在上面的 add 方法中,传进来的 num 只是个形参,只是 pointerTest 方法中定义的 num 变量的一个副本。在 add 方法中对这个形参副本进行修改,最终是影响不到实际值的。

而 realAdd 方法,则针对的是指向 num 变量的指针,对这个指针指向的值进行修改,那么最终影响到的也会是这个指针指向的值。

Go 指针的使用

Go 语言中对于指针的操作,开发中使用到的非常有限。

  • 定义指针:var numPointer *int = &num
  • 获取指针指向的值:numberPointValue := *numPointer
// 对普通变量进行 & 操作,返回指针
var pointer *type = &(variable)

// 对指针进行 * 操作,返回指针指向的变量,该变量的值等于指向的变量的值
variableValue := *pointer

运行下列程序。

func numberPointer() {
	num := 100
	var numPointer *int = &num
	numberPointValue := *numPointer

	fmt.Printf("numberPointValue: %d, num == numberPointValue: %t\n", numberPointValue, num == numberPointValue)
	fmt.Printf("num: %d, numberPointer: %p, numberPointer value: %d\n", num, numPointer, *numPointer)
}

运行结果:

numberPointValue: 100, num == numberPointValue: true
num: 100, numberPointer: 0xc0000aa170, numberPointer value: 100

Go 指针的注意事项

一个指针变量可以指向任何一个值的内存地址。它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,与它所指向的值的大小无关。

指针的书写

在书写表达式类似 var p *type 时,切记在 * 号和指针名称间留有一个空格,因为 var p*type 是语法正确的,但是在更复杂的表达式中,它容易被误认为是一个乘法表达式。

符号 * 可以放在一个指针前,如 *intPointer,那么它将得到这个指针指向地址上所存储的值。这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。

func pointerTestAssignment() {
	var i1 = 5
	fmt.Printf("An integer: %d, its location in memory: %p\n", i1, &i1)

	var intP *int
	intP = &i1
	fmt.Printf("The value at memory location %p is %d\n", intP, *intP)
}

这段代码的运行结果如下:

An integer: 5, its location in memory: 0xc0000161c8
The value at memory location 0xc0000161c8 is 5

这里面 i1 的内存地址,可以用下图来表示。

指针的运用

  1. 不能得到一个常量或者基础值字面量的地址:
const i = 5
ptr := &i //error: cannot take the address of i
ptr2 := &10 //error: cannot take the address of 10
  1. 对一个空指针的反向引用是不合法的,并且会使程序崩溃:
package main
func main() {
    var p *int = nil
    *p = 0
}
// in Windows: stops only with: <exit code="-1073741819" msg="process crashed"/>
// runtime error: invalid memory address or nil pointer dereference
  1. Go 语言中不允许对指针进行操作:
    1. Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。因此 c = *p++Go 语言的代码中是不合法的。

Reference

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐