*8.7 指针数组和多重指针

 

8.7 指针数组和多重指针

8.7.1 什么是指针数组

一个数组,如果其元素均为指针类型数据,就称为指针数组。也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。例如,定义一个指针数组如下:

int *p[4];

由于[]*优先级高,因此p先与[4]结合,形成p[4]形式,这显然是数组形式,表示p数组有4个元素。然后再与p前面的*结合,*表示此数组是指针类型的,每个数组元素(相当于一个指针变量)都可指向一个整型变量。

注意不要写成:

int (*p)[4];

这是指向一维数组的指针变量。

定义一维指针数组的一般形式为:

类型名* 数组名[数组长度];

类型名中应包括符号*,如int*表示是指向整型数据的指针类型。

使用场景

指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。例如,图书馆有若干本书,想把书名放在一个数组中(见图8.36(a))。然后要对这些书目进行排序和查询。

按一般方法,字符串本身就是一个字符数组。因此要设计一个二维的字符数组才能存放多个字符串。但在定义二维数组时,需要指定列数,也就是说二维数组中每一行中包含的元素个数(即列数)相等。而实际上各字符串(书名)长度一般是不相等的。如按最长的字符串来定义列数,则会浪费许多内存单元,见图8.36(b)。

可以分别定义一些字符串,然后用指针数组中的元素分别指向各字符串,如图8.36(c)中所示:在name[0]中存放字符串"Follow me"的首字符的地址,name[1]中存放字符串"BASIC"的首字符的地址……如果想对字符串排序,不必改动字符串的位置,只须改动指针数组中各元素的指向(即改变各元素的值,这些值是各字符串的首地址)。这样,各字符串的长度可以不同,而且移动指针变量的值(地址)要比移动字符串所花的时间少得多。

例8.27:将若干字符串按字母顺序(由小到大)输出

解题思路

定义一个指针数组name,用各字符串对它进行初始化,即把各字符串中第一个字符的地址赋给指针数组的各元素。然后用选择法排序,但不是移动字符串,而是改变指针数组的各元素的指向。

程序实现
#include<stdio.h>
#include<string.h>

void sort(char *name[], int n);  // 函数声明
void print(char *name[], int n); // 函数声明

int main() {
    char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"}; // 定义指针数组,它的元素分别指向5个字符串
    int n = 5;
    
    sort(name, n);  // 调用sort函数,对字符串排序
    print(name, n); // 调用print函数,输出字符串
    
    return 0;
}

void sort(char *name[], int n) {  // 定义sort函数
    char *temp;
    int i, j, k;
    
    for(i = 0; i < n - 1; i++) {  // 用选择法排序
        k = i;
        for(j = i + 1; j < n; j++)
            if(strcmp(name[k], name[j]) > 0)
                k = j;
        if(k != i) {
            temp = name[i];
            name[i] = name[k];
            name[k] = temp;
        }
    }
}

void print(char *name[], int n) {  // 定义print函数
    int i;
    for(i = 0; i < n; i++)
        printf("%s\n", name[i]);  // 按指针数组元素的顺序输出它们所指向的字符串
}

运行结果

BASIC
Computer design
FORTRAN
Follow me
Great Wall
程序分析

main函数中定义指针数组name,它有5个元素,其初值分别是"Follow me""BASIC""Great Wall""FORTRAN""Computer design"这5个字符串的首字符的地址,见图8.36(c)。这些字符串是不等长的。

sot函数的作用是对字符串排序。sort函数的形参name也是指针数组名,接受实参传过来的name数组首元素(即name[0])的地址,因此形参name数组和实参name数组指的是同一数组。用选择法对字符串排序。strcmp是系统提供的字符串比较函数,name[k]name[j]是第k个和第j个字符串首字符的地址。strcmp(name[k], name[j])的值为:如果name[k]所指的字符串大于name[j]所指的字符串,则此函数值为正值;若相等,则函数值为0;若小于,则函数值为负值。if语句的作用是将两个串中“小”的那个串的序号(kj之一)保留在变量k中。当执行完内循环for语句后,从第i串到第n串这些字符串中,第k串最“小”。若k ≠ i就表示最小的串不是第i串。故将name[i]name[k]对换,也就是将指向第i个字符串的数组元素(是指针型元素)的值与指向第k个字符串的数组元素的值对换,也就是把它们的指向互换。执行完sort函数后指针数组的情况如图8.37所示。

程序改进

print函数也可改写为以下形式:

void print(char *name[], int n) {
    int i = 0;
    char *p;
    while(i < n) {
        p = *(name + i++);
        printf("%s\n", p);
    }
}

其中,“*(name + i++)”表示先求*(name + i)的值,即name[i](它是一个地址),然后使i加1。在输出时,按字符串形式输出从p地址开始的字符串。

 

 

 

8.7.2 指向指针数据的指针变量

在了解了指针数组的基础上,我们需要进一步了解指向指针数据的指针变量,简称为指向指针的指针。从图8.38可以看到,name是一个指针数组,其每一个元素是一个指针型的变量,其值为地址。既然name是一个数组,它的每一元素都有相应的地址。数组名name代表该指针数组首元素的地址。name + iname[i]的地址,也就是指向指针型数据的指针。还可以设置一个指针变量p,它指向指针数组的元素(见图8.38)。p就是指向指针型数据的指针变量。

定义指向指针数据的指针变量

我们可以通过以下方式定义一个指向指针数据的指针变量:

char **p;

p的前面有两个*号。从附录C可以知道,*运算符的结合性是从右到左,因此**p相当于*(*p)。显然*p是指针变量的定义形式。如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,即char **p。可以把它分为两部分来看,即:char*(*p)。后面的(*p)表示p是指针变量,前面的char *表示p指向的是char*型的数据。也就是说,p指向一个字符指针变量(这个字符指针变量指向一个字符型数据)。如果引用*p,就得到p所指向的字符指针变量的值。

printf("%s\n", p);    // 输出 name[2] 的值 (地址)
printf("%s\n", *p);   // 输出 "Great Wall"

例8.28:使用指向指针数据的指针变量

解题思路

定义一个指针数组name,并对它初始化,使name数组中每一个元素分别指向5个字符串。定义一个指向指针型数据的指针变量p,使p先后指向name数组中各元素,输出这些元素所指向的字符串。

程序实现
#include<stdio.h>

int main() {
    char *name[] = {"Follow me", "BASIC", "Great Wall", "FORTRAN", "Computer design"};
    char **p;
    int i;
    
    for(i = 0; i < 5; i++) {
        p = name + i;
        printf("%s\n", *p);
    }
    
    return 0;
}

运行结果

Follow me
BASIC
Great Wall
FORTRAN
Computer design
程序分析

p是指向char*型数据的指针变量,即指向指针的指针。在第一次执行for循环体时,赋值语句p = name + i;使p指向name数组的0号元素name[0]*pname[0]的值,即第一个字符串首字符的地址。用printf函数输出第一个字符串(格式符为%s)。执行5次循环体,依次输出5个字符串。

说明

指针数组的元素也可以不指向字符串,而是指向整型数据或实型数据等,例如:

int a[5] = {1, 3, 5, 7, 9};
int *num[5];
int **p;
for (i = 0; i < 5; i++) {
    num[i] = &a[i];
}

为了得到a[2]中的数据5,可以先使p = num + 2,然后输出**p。注意*pnum[2]的值,而num[2]的值是a[2]的地址,因此**pa[2]的值5,见图8.39。

例8.29:使用指向指针数据的指针变量输出整型数组的值

这是一个简单例子,目的是为了说明其用法。

程序实现
#include<stdio.h>

int main() {
    int a[5] = {1, 3, 5, 7, 9};
    int *num[5] = {&a[0], &a[1], &a[2], &a[3], &a[4]};
    int **p;
    int i;
    
    p = num;  // 使 p 指向 num[0]
    
    for(i = 0; i < 5; i++) {
        printf("%d ", **p);
        p++;
    }
    
    printf("\n");
    return 0;
}

运行结果

1 3 5 7 9
程序分析

程序中定义p是指向指针型数据的指针变量,开始时指向指针数组num的首元素num[0],而num[0]是一个指针型的元素,它指向整型数组a的首元素a[0]。开始时p的值是&num[0]*pnum[0]的值,即&a[0]**pa[0]的值。因此第一个输出的是a[0]的值1。然后执行p++p就指向num[1],再输出**p,就是a[1]的值3了。

指针数组的元素只能存放地址,不能存放整数。通过这个例子,我们可以更好地理解和使用指向指针数据的指针变量.

 

8.7.3 指针数组作 main 函数的形参

指针数组的一个重要应用是作为 main 函数的形参。在以往的程序中,main 函数的定义一般写成以下形式:

int main()

int main(void)

括号中是空的或有 void,表示 main 函数没有参数,调用 main 函数时不必给出实参。这是一般程序常采用的形式。实际上,在某些情况下,main 函数可以有参数,即:

int main(int argc, char *argv[])

其中,argcargv 就是 main 函数的形参,它们是程序的“命令行参数”。argc(argument count 的缩写,意思是参数个数),argv(argument vector 缩写,意思是参数向量),它是一个 char* 指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串的首字符。

命令行参数

如果使用带参数的 main 函数,其第一个形参必须是 int 型,用来接收参数个数,第二个形参必须是字符指针数组,用来接收从操作系统命令行传来的字符串的首字符地址。通常 main 函数和其他函数组成一个文件模块,有一个文件名。对这个文件进行编译和连接,得到可执行文件(后缀为 .exe)。用户执行这个可执行文件,操作系统就调用 main 函数,然后由 main 函数调用其他函数,从而完成程序的功能。

实例解析

假设有一个名为 file1 的文件,它包含以下的 main 函数:

#include <stdio.h>

int main(int argc, char *argv[]) {
    while (argc > 1) {
        printf("%s\n", *++argv);
        --argc;
    }
    return 0;
}

在 Visual C++ 环境下对程序编译和连接后,选择“工程”→“设置”→“调试”→“程序变量”命令,输入 "China Beijing",再运行程序,将会输出以下信息:

China
Beijing

在以上 main 函数中,argc 是指命令行中参数的个数(注意,文件名也作为一个参数)。例如,本例中 argc 的值等于 3(有 3 个命令行参数:file1ChinaBeijing)。argv 是一个指向字符串的指针数组,argv[0] 指向字符串 file1 的首字符,argv[1] 指向字符串 China 的首字符,argv[2] 指向字符串 Beijing 的首字符。

改写程序

上述 main 函数可以改写为:

#include <stdio.h>

int main(int argc, char *argv[]) {
    while (--argc > 0) {
        printf("%s\n", *++argv);
    }
    return 0;
}

其中,*++argv 是先进行 ++argv 的运算,使 argv 指向下一个元素。然后进行 * 的运算,找到 argv 当前指向的字符串,输出该字符串。在开始时,argv 指向字符串 file1++argv 使之指向 China,所以第一次输出的是 China,第二次输出 Beijing

例8.29:实现参数回送的 C 程序

许多操作系统提供了 echo 命令,它的作用是实现“参数回送”,即将 echo 后面的各参数(各字符串)在同一行上输出。实现“参数回送”的 C 程序(文件名为 echo.c)如下:

#include <stdio.h>

int main(int argc, char *argv[]) {
    while (--argc > 0)
        printf("%s%c", *++argv, (argc > 1) ? ' ' : '\n');
    return 0;
}

如果用 UNIX 系统的命令行输入:

./echo Computer and C Language

会在显示屏上输出:

Computer and C Language

这个程序与前面的差别在于:当 argc > 1 时,在输出的两个字符串间输出一个空格。当 argc = 1 时输出一个换行。程序不输出命令名 echo

程序改进

echo 程序也可写成以下形式:

#include <stdio.h>

int main(int argc, char *argv[]) {
    int i;
    for (i = 1; i < argc; i++)
        printf("%s%c", argv[i], (i < argc - 1) ? ' ' : '\n');
    return 0;
}

实际上,main 函数中的形参不一定命名为 argcargv,可以是任意的名字,只是人们习惯用 argcargv 而已。利用指针数组作 main 函数的形参,可以向程序传送命令行参数(这些参数是字符串)。这些字符串的长度事先并不知道,而且各参数字符串的长度一般并不相同,命令行参数的数目也是可以任意的。用指针数组能够较好地满足上述要求。

关于指向指针的指针是 C 语言中比较深入的概念,在此只作简单的介绍,以便为读者提供今后进一步学习的基础。

 

 

 

 

 

 

 

 

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏驰和徐策

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值