根据官方的解释如下:

Go has pointers. A pointer holds the memory address of a value.

The type *T is a pointer to a T value. Its zero value is nil.

var p *int

The & operator generates a pointer to its operand.

i := 42
p = &i

The * operator denotes the pointer’s underlying value.

fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p

This is known as “dereferencing” or “indirecting”.

Unlike C, Go has no pointer arithmetic.

特别是最后一局话,Unlike C, Go has no pointer arithmetic

意思是,Go 语言不允许直接对指针进行算术运算。

比如像 C 语言中,可以对指针进行加减运算,例如 p++p = p + 4,以此来移动指针指向的内存地址。类似于 C 语言中常用于数组的遍历等。

原因有以下几点:

  1. 安全性:指针算术运算容易导致程序访问非法内存地址,引发错误甚至崩溃。Go 语言通过禁止指针算术运算,提高了程序的安全性 1
  2. 简化垃圾回收:指针算术运算会使垃圾回收器的实现更加复杂。没有指针算术运算,垃圾回收器可以更容易地追踪和管理内存 1
  3. 代码可读性:指针算术运算可能会使代码难以理解和维护。Go 语言提倡使用循环和索引来访问数组元素,这通常比指针算术运算更清晰易懂 1

C 的代码如下:

#include <stdio.h>
 
int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int *p = arr; // 指向数组首元素的指针
    printf("\n"); // 添加换行符,使输出更清晰
 
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", *p); // 访问指针指向的元素
        p++;               // 指针移动到下一个元素
    }
    printf("\n"); // 添加换行符,使输出更清晰
    return 0;
}

实际写代码中遇到的问题

在进行一个计算两数之和的代码,发现赋值为空了。整个 func 的代码如下:

package main
 
import "fmt"
 
type ListNode struct {
	Val  int
	Next *ListNode
}
 
func addTwoNumbersBroken(l1 *ListNode, l2 *ListNode) *ListNode {
	var head *ListNode
	var tail *ListNode
	carry := 0
 
	for l1 != nil || l2 != nil {
		n1, n2 := 0, 0
		if l1 != nil {
			n1 = l1.Val
			l1 = l1.Next
		}
		if l2 != nil {
			n2 = l2.Val
			l2 = l2.Next
		}
 
		sum := n1 + n2 + carry
		sum, carry = sum%10, sum/10
 
		if head == nil {
			head = &ListNode{Val: sum}
			tail = head.Next // 错误:head.Next 此时为 nil
		} else {
			tail = &ListNode{Val: sum} // 错误:没有连接到链表
			tail = tail.Next           // 错误:tail 始终为 nil
		}
	}
 
	if carry != 0 {
		tail = &ListNode{Val: carry} // 错误:没有连接到链表
	}
 
	return head
}
 
func addTwoNumbersCorrect(l1 *ListNode, l2 *ListNode) *ListNode {
	var head *ListNode
	var tail *ListNode
	carry := 0
 
	for l1 != nil || l2 != nil {
		n1, n2 := 0, 0
		if l1 != nil {
			n1 = l1.Val
			l1 = l1.Next
		}
		if l2 != nil {
			n2 = l2.Val
			l2 = l2.Next
		}
 
		sum := n1 + n2 + carry
		sum, carry = sum%10, sum/10
 
		if head == nil {
			head = &ListNode{Val: sum}
			tail = head
		} else {
			tail.Next = &ListNode{Val: sum}
			tail = tail.Next
		}
	}
 
	if carry != 0 {
		tail.Next = &ListNode{Val: carry}
	}
 
	return head
}
 
func printList(head *ListNode) {
	current := head
	for current != nil {
		fmt.Printf("%d ", current.Val)
		current = current.Next
	}
	fmt.Println()
}
 
func main() {
	// 创建两个链表 3->4->5 和 6->7->8
	l1 := &ListNode{Val: 3, Next: &ListNode{Val: 4, Next: &ListNode{Val: 5}}}
	l2 := &ListNode{Val: 6, Next: &ListNode{Val: 7, Next: &ListNode{Val: 8}}}
 
	fmt.Println("Broken version:")
	resultBroken := addTwoNumbersBroken(l1, l2)
	printList(resultBroken) // 输出:9
 
	fmt.Println("Correct version:")
	resultCorrect := addTwoNumbersCorrect(l1, l2)
	printList(resultCorrect) // 输出:9 1 2 1
}

上面代码可运行。出错的地方就是 addTwoNumbersBroken 函数中。

出错代码如下:

if head == nil {
    head = &ListNode{Val: sum}
    tail = head.Next
} else {
    tail = &ListNode{Val: sum}
    tail = tail.Next
}

tail = head.Next 的问题就是,刚刚创建了一个新的 head 节点的 Next 字段没有赋值,默认值是 nil。因此 tail 指针会被赋值为 nil。这意味着 tail 不会指向任何有效的节点。

正确做法如下:

if head == nil {
  head = &ListNode{Val: sum}
  tail = head.Next
} else {
  tail.Next = &ListNode{Val: sum}
  tail = tail.Next
}