根据官方的解释如下:
Go has pointers. A pointer holds the memory address of a value.
The type
*T
is a pointer to aT
value. Its zero value isnil
.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 语言中常用于数组的遍历等。
原因有以下几点:
- 安全性:指针算术运算容易导致程序访问非法内存地址,引发错误甚至崩溃。Go 语言通过禁止指针算术运算,提高了程序的安全性 1。
- 简化垃圾回收:指针算术运算会使垃圾回收器的实现更加复杂。没有指针算术运算,垃圾回收器可以更容易地追踪和管理内存 1。
- 代码可读性:指针算术运算可能会使代码难以理解和维护。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
}