5.6 指针数组以及指向指针的指针
由于指针本身也是变量,所以它们也可以像其它变量一样存储在数组中
下面通过编写 UNIX 程序 sort
的一个简化版本说明这一点
该程序按字母顺序对由文本行组成的集合进行排序
我们在第 3 章中曾描述过一个用于对整型数组中的元素进行排序的 shell
排序函数,并在第 4 章中用快速排序算法对它进行了改进
这些排序算法在此仍然是有效的,但是,现在处理的是长度不一的文本行
并且与整数不同的是,它们不能在单个运算中完成比较或移动操作
我们需要一个能够高效、方便地处理可变长度文本行的数据表示方法
我们引入指针数组处理这种问题
如果待排序的文本行首尾相连地存储在一个长字符数组中,那么每个文本行可通过指向它的第一个字符的指针来访问
这些指针本身可以存储在一个数组中
这样,将指向两个文本行的指针传递给函数 strcmp
就可实现对这两个文本行的比较
当交换次序颠倒的两个文本行时,实际上交换的是指针数组中与这两个文本行相对应的指针,而不是这两个文本行本身
这种实现方法消除了因移动文本行本身所带来的复杂的存储管理和巨大的开销这两个孪生问题
排序过程包括下列 3 个步骤:
- 读取所有输入行
- 对文本行进行排序
- 按次序打印文本行
通常情况下,最好将程序划分成若干个与问题的自然划分相一致的函数,并通过主函数控制其它函数的执行
关于对文本行排序这一步,我们稍后再做说明,现在主要考虑数据结构以及输入和输出函数
输入函数必须收集和保存每个文本行中的字符,并建立一个指向这些文本行的指针的数组
它同时还必须统计输入的行数,因为在排序和打印时要用到这一信息
由于输入函数只能处理有限数目的输入行
所以在输入行数过多而超过限定的最大行数时,该函数返回某个用于表示非法行数的数值,例如 -1
输出函数只需要按照指针数组中的次序依次打印这些文本行即可
#include <stdio.h>
#include <string.h>
#define MAXLINES 5000 /* max #lines to be sorted */
char *lineptr[MAXLINES]; /* pointers to text lines */
int readlines(char *lineptr[], int nlines);
void writelines(char *lineptr[], int nlines);
void qsort(char *lineptr[], int left, int right);
/* sort input lines */
main()
{
int nlines; /* number of input lines read */
if ((nlines = readlines(lineptr, MAXLINES)) >= 0) {
qsort(lineptr, 0, nlines - 1);
writelines(lineptr, nlines);
return 0;
} else {
printf("error: input too big to sort\n");
return 1;
}
}
#define MAXLEN 1000 /* max length of any input line */
int getline(char *, int);
char *alloc(int);
/* readlines: read input lines */
int readlines(char *lineptr[], int maxlines)
{
int len, nlines;
char *p, line[MAXLEN];
nlines = 0;
while ((len = getline(line, MAXLEN)) > 0)
if (nlines >= maxlines || p = alloc(len) == NULL)
return -1;
else {
line[len - 1] = '\0'; /* delete newline */
strcpy(p, line);
lineptr[nlines++] = p;
}
return nlines;
}
/* writelines: write output lines */
void writelines(char *lineptr[], int nlines)
{
int i;
for (i = 0; i < nlines; i++)
printf("%s\n", lineptr[i]);
}
有关函数 getline
的详细信息参见 1.9 节
在该例子中,指针数组 1ineptr
的声明是新出现的重要概念:char *lineptr[MAXLINES];
它表示 1ineptr
是一个具有 MAXLINES
个元素的一维数组,其中数组的每个元素是一个指向字符类型对象的指针
也就是说,lineptr[i]
是一个字符指针,而 *lineptr[i]
是该指针指向的第 i
个文本行的首字符
由于 1ineptr
本身是一个数组名,因此,可按照前面例子中相同的方法将其作为指针使用,这样,writelines
函数可以改写为:
/* writelines: write output lines */
void writelines(char *lineptr[], int nlines)
{
while (nlines-- > 0)
printf("%s\n", *lineptr++);
}
循环开始执行时 *lineptr
指向第一行,每执行一次自增运算都使得 lineptr
指向下 一行,同时对 nlines
进行自减运算
在明确了输入和输出函数的实现方法之后,下面便可以着手考虑文本行的排序问题了
在这里需要对第 4 章的快速排序函数做一些小改动:
首先,需要修改该函数的声明部分
其次,需要调用 strcmp
函数完成文本行的比较运算
但排序算法在这里仍然有效,不需要做任何改动
/* qsort: sort v[left]...v[right] into increasing order */
void qsort(char *v[], int left, int right)
{
int i, last;
void swap(char *v[], int i, int j);
if (left >= right) /* do nothing if array contains */
return; /* fewer than two elements */
swap(v, left, (left + right)/2);
last = left;
for (i = left+1; i <= right; i++)
if (strcmp(v[i], v[left]) < 0)
swap(v, ++last, i);
swap(v, left, last);
qsort(v, left, last-1);
qsort(v, last+1, right);
}
同样,swap
函数也只需要做一些很小的改动:
/* swap: interchange v[i] and v[j] */
void swap(char *v[], int i, int j)
{
char *temp;
temp = v[i];
v[i] = v[j];
v[j] = temp;
}
因为 v
(别名为 lineptr
)的所有元素都是字符指针,并且 temp
也必须是字符指针
因此 temp
与 v
的任意元素之间可以互相复制