指针有几种用途,包括:
- 写出快速高效的代码;
- 为解决很多类问题提供方便的途径;
- 支持动态内存分配;
- 使表达式变得紧凑和简洁;
- 提供用指针传递数据结构的能力而不会带来庞大的开销;
- 保护作为参数传递给函数的数据。
用指针可以写出快速高效的代码是因为指针更接近硬件。也就是说,编译器可以更容易地把操作翻译成机器码。指针附带的开销一般不像别的操作符那样大。
很多数据结构用指针更容易实现,比如链表可以用数组实现,也可以用指针实现。然而,指针更容易使用,也能直接映射到下一个或上一个链接。用数组实现需要用到数组下标,不直观,也没有指针灵活。
下图比较形象地展示了用数组和指针实现员工链表时的情形。图中左边用了数组,head
变量表明链表的第一个元素在数组下标10的位置,每一个数组元素都包含表示员工的数据结构。结构的next
字段存放下一个员工在数组中的下标。灰底的元素表示未使用。
右边显示了用指针实现的等价形式。head
变量存放指向第一个员工节点的指针。每个节点存放员工数据和指向链表中下一个节点的指针。
指针形式不仅更清晰,也更灵活。通常创建数组时需要知道数组的长度,这样就会限制链表所能容纳的元素数量。使用指针没有这个限制,因为新节点可以根据需要动态分配。
C的动态内存分配实际上就是通过使用指针实现的。malloc
和free
函数分别用来分配和释放动态内存。动态内存分配可以实现变长数组和数据结构(如链表和队列)。不过,新的C11标准也支持变长数组了。
紧凑的表达式有很强的表达能力,但也比较晦涩,因为很多程序员并不能完全理解指针表示法。紧凑的表达式应该用来满足特定的需要,而不是为了晦涩而晦涩。比如说,下面的代码用了两个不同的printf
函数调用来打印names
的第二个元素的第三个字符。如果对指针的这种用法感到困惑,不用担心,我们会在1.1.6节中详细介绍解引(dereference)的工作原理。尽管两种方式都会显示字母n
,但是数组表示法更简单。
char *names[] = {"Miller","Jones","Anderson"};
printf("%c\n",*(*(names+1)+2));
printf("%c\n",names[1][2]);
指针是创建和加强应用的强大工具,不利之处则是使用指针过程中可能会发生很多问题,比如:
- 访问数组和其他数据结构时越界;
- 自动变量消失后被引用;
- 堆上分配的内存释放后被引用;
- 内存分配之前解引指针。
我们会在后面的文章中深入研究这几类问题。