指针基础

翻译原文:Pointer Basics

这是翻译的斯坦福大学官网上的 CSLibrary 中的一篇课程文档

这个文档介绍指针的基础知识,因为很多计算机语言中都会用到它,例如:C,C++,Java,以及 Pascal。

这是 Stanford CS Education Library 的106号文档。其他免费资料在 cslibrary.stanford.edu 中可以获取到。

1 – 指针规则1

关于指针,好的地方是控制他们工作的规则非常简单。这些规则可以组合得到复杂的结果,但是单个的规则还是很简单。

1) 指针与指针对象

指针存放了对一些东西的引用 。不幸的是,指针指向的目标还没有一个固定的术语,不同的计算机语言中,指针指向的内容很多。我们用术语 指针对象 来代指指针指向的事物,我们就来研究一下那些在所有语言中都正确的 指针/指针对象 关系的基本性质。术语 “引用”和 “指针”的意思非常相似——“引用”意在更高层的讨论,而“指针”是指传统的编译语言对地址指针的实现。对于这里所讲的基本的 指针/指针对象 规则,这两个术语可以认为是等同的。

上面的图中画出了一个名为 x 的指针,指向了一个保存了数值42 的指针对象。指针通常被画成一个矩形盒子,指针中保存的引用被画成一个箭头,从指针的盒子开始,指向它的指针对象。

分配指针和给指针分配指针对象是分开的两个步骤。你可以认为 指针/指针对象 结构是在两个层级的操作。两个层级都必须设置才能正常工作。最常见的错误集中于编写手动操作指针的代码时,忘记了设置指针对象。有时候不接触指针对象的指针操作被称为“浅”,而操作指针对象的指针操作称为“深”。

2) 解引用

解引用 操作从指针开始,沿着箭头一直访问到它的指针对象。目的可能是查看指针对象的状态或者改变指针对象的状态。

对一个指针执行的解引用操作只在这个指针有一个指针对象——这个指针对象必须已经被分配并且指针必须已经设置为指向它—— 的情况下有效。最常见的错误就是代码中忘记为指针设置指针对象。在 Java 中,运行时会礼貌地标记错误的解引用。在编译语言中,例如 C,C++,和Pascal中,错误的解引用有时会崩溃,有时会以一些微妙而随机的方式破坏内存。因此编译语言中的指针异常很难追踪。

3) 指针赋值

两个指针之间的 指针赋值 会使他们指向同一个指针对象。所以 y=x; 的赋值会让 y 指向与 x 相同的指针对象。指针赋值不接触指针对象。它只是改变一个指针来让它拥有与其他指针相同的引用。指针赋值后,这两个指针被称为 共享 指针对象。

2 – 代码示例

有不同计算机语言的代码示例。所有的版本都有相同的结构,证明相同的基本规则和指针只是;他们只有语法上的不同。不考虑特定语言,示例的基本结构如下…

1. 分配 xy 两个指针。让他们不指向任何指针对象。
2. 分配一个指针对象,然后设置 x 指向它。每种语言都有自己的语法来实现这一步操作。重要的是指针对象的内存是动态分配的,然后让x 指向这个指针对象
3.解引用 x 将42保存到指针对象中。这是解引用操作的基本示例。从 x 开始,沿着箭头方向访问到指针对象。
4. 尝试解引用 y 来保存 13 到指针对象中.这一步会崩溃——因为它还没有被赋值一个指针对象。
5. 赋值 y=x;y 指向 x 的指针对象。现在 xy 指向了相同的指针对象——他们是共享的。
6. 尝试解引用 y 来保存 13到指针对象中。这一次成功了,因为上一步赋值操作为 y 分配了指针对象。

版本

下面是不同语言版本的示例。他们做了相同的事情——只是每种语言的语法不同。

C 版本

指针 xy 分配成本地变量。类型 int* 表示“指针指向 int 类型”。指针不会自动得到指针对象。x 的指针对象是另外用标准库 malloc() 动态分配的。语法 *x 解引用 x 来访问它的指针对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {   
int* x; // Allocate the pointers x and y
int* y; // (but not the pointees)

x = malloc(sizeof(int)); // Allocate an int pointee,
// and set x to point to it

*x = 42; // Dereference x to store 42 in its pointee

*y = 13; // CRASH -- y does not have a pointee yet

y = x; // Pointer assignment sets y to point to x's pointee

*y = 13; // Dereference y to store 13 in its (shared) pointee
}

C语言(或者C++)中另一种使用指针的方式是使用 & 符号(ampersand )运算符来计算栈中指向本地内存的指针。然而,指针对象通常是在堆中动态分配的,所以这就是我们展示的。

Java 版本

Java中最常见的 指针/指针对象结构是一个本地变量作为指针,指向一些类的对象作为指针对象。所以为了按计划创建一个保存了整数类型的指针对象,我们定义一个 IntObj 类来保存一个整型。然后我们可以创建一个 IntObj 指针对象来保存 int。用 IntObj x; 这样的代码来分配指针不会自动分配指针对象。 IntObj 指针对象通过调用 new 来分配。x.value 解引用 x 来访问指针对象中的 .value 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class IntObj {
public int value;
}

public class Binky() {
public static void main(String[] args) {
IntObj x; // Allocate the pointers x and y
IntObj y; // (but not the IntObj pointees)

x = new IntObj(); // Allocate an IntObj pointee
// and set x to point to it

x.value = 42; // Dereference x to store 42 in its pointee

y.value = 13; // CRASH -- y does not have a pointee yet

y = x; // Pointer assignment sets y to point to x's pointee

y.value = 13; // Deference y to store 13 in its (shared) pointee
}
}

C++ 版本

与 上面 C 语言版本唯一的不同是使用了标准操作符 new 来代替 malloc()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {   
int* x; // Allocate the pointers x and y
int* y; // (but not the pointees)

x = new int; // Allocate an int pointee,
// and set x to point to it

*x = 42; // Dereference x to store 42 in its pointee

*y = 13; // CRASH -- y does not have a pointee yet

y = x; // Pointer assignment sets y to point to x's pointee

*y = 13; // Dereference y to store 13 in its (shared) pointee
}

Pascal 版本

这跟 C 语言版本在结构一致,但是使用了 Pascal 语法。^Integer 意思是“指向整型的指针”。分配指针不会自动分配它的指针对象。标准程序 New() 取一个指针参数,分配一个新的指针对象,设置指针指向这个指针对象。表达式 x^ 解引用访问这个指针对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Procedure main
var x:^Integer; /* Allocate the pointers x and y */
var y:^Integer; /* (but not the pointees) */
Begin
New(x); /* Allocate a pointee and set x to point to it */

x^ := 42; /* Deference x to store 42 in its pointee */

y^ := 13; /* CRASH -- y does not have a pointee yet */

y := x; /* Pointer assignment makes y point to x's pointee */

y^ := 13; /* Dereference y to store 13 in its (shared) pointee */
End;

3 – 习题

这些习题考察指针的基本特性。其中两个问题大量使用内存图。内存图是思考指针问题的很好的方法。

Question 1

在上面代码的末尾,y 被指定了一个指针对象,然后解引用它保存数字13到指针对象中,在这发生后,x 的指针对象的值是多少?

答案:13,因为 x 的指针对象也是 y 的指针对象,这就是指针共享的全部作用——多个指针指向一个指针对象。

Question 2

思考下面这张图…

选择中语言,写代码创建上面的指针结构。

答案:基本步骤是…

  1. 分配两个指针
  2. 分配两个指针对象,然后设置两个指针分别指向他们
  3. 保存数字 1 和 2 到这两个指针对象中
  4. 赋值第一个指针指向第二个指针对象。这会丢失对第一个指针对象的引用,这是很少见的,但题目就是这样要求的。

C Code

1
2
3
4
5
6
7
8
9
{
int* x;
int* y;
x = malloc(sizeof(int));
y = malloc(sizeof(int));
*x = 1;
*y = 2;
x = y;
}

Java Code

1
2
3
4
5
6
7
8
9
{
IntObj x;
IntObj y;
x = new IntObj();
y = new IntObj();
x.value = 1;
y.value = 2;
x = y;
}

Question 3

假设你有一个指针对象叫作 “Node”,包含两个东西,一个 int 和一个 指向另一个 Node 的指针(这个Node 类型的的定义在下面)。你可以用这样的指针对象类型在一个结构中构造三个 Node 互相指向的指针对象,像这样…

名叫 x 的指针指向第一个 Node 指针对象。第一个 Node 包含一个指针指向第二个 Node ,第二个包含一个指针指向第三个 Node ,第三个 Node 又包含一个指针指向了第一个 Node。这个结构只能用我们已经见过的指针对象分配,解引用和赋值的规则来创建。使用下面的定义,每一个 Node 都包含了一个 Integer 名为 value ,和一个指针指向另一个名为 next 的 Node。

C Code

1
2
3
4
struct Node {
int value;
struct Node* next;
};

Java Code

1
2
3
4
class Node {
public int value;
public Node next;
};

写代码创建上面图中的结构。为了方便,你可以使用 x 以外的临时指针来访问指针对象中的字段——所以 ->value 访问 x 指针对象中的 名为value 字段。

Answer The basic steps are… 答案 基本步骤…

  1. 分配三个指针:x 指向第一个 Node,然后临时指针 yz 指向其他两个 Node。
  2. 分配三个 Node 指针对象并保存他们的解引用到三个指针中。
  3. 解引用每一个指针对象,保存合适的数值到其指针对象的 value 字段中。
  4. 解引用每个指针来访问其指针对象中的 .next 字段,并使用指针赋值来设置 .next 字段指向合适的 Node。

C Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// Allocate the pointers
struct Node* x;
struct Node* y;
struct Node* z;
// Allocate the pointees
x = malloc(sizeof(Node));
y = malloc(sizeof(Node));
z = malloc(sizeof(Node));
// Put the numbers in the pointees
x->value = 1;
y->value = 2;
z->value = 3;
// Put the pointers in the pointees
x->next = y;
y->next = z;
z->next = x;
}

Java Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{	
// Allocate the pointers
Node x;
Node y;
Node z;
// Allocate the pointees
x = new Node();
y = new Node();
z = new Node();
// Put the numbers in the pointees
x.value = 1;
y.value = 2;
z.value = 3;
// Put the pointers in the pointees
x.next = y;
y.next = z;
z.next = x;
}

这里介绍的 Node 结构实际上是一种真实的数据类型,被用来创建 “链表” 数据结构。链表是指针的实际应用的用途,是提升指针技能的绝佳领域。看Stanford CS Library 中的Linked List BasicsLinked List Problems 了解更多链表知识。


Up to the CS Education Library Home

本文标题:指针基础

文章作者:kinboy

发布时间:2018年11月16日 - 01:01:21

最后更新:2019年07月15日 - 18:05:10

原始链接:http://kinboyw.github.io/2018/11/16/指针基础/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

------ Passage Ending ------