C 语言字符串数组简介

本文内容

目录汇总:C 语言零基础入门教程

现在来看一个在使用字符串时经常遇到的问题:存储字符串数组的最佳方式是什么?最明显的解决方案是创建二维的字符数组,然后按照每行一个字符串的方式把字符串存储到数组中。考虑下面的例子:

1
2
3
char planets[][8] = {"Mercury", "Venus", "Earth",
                     "Mars", "Jupiter", "Saturn",
                     "Uranus", "Neptune", "Pluto"};

(2006 年,国际天文学联合会把冥王星从“行星”降级为“矮行星”,但我出于怀旧仍然把它放在 planets 数组中。)注意,虽然允许省略 planets 数组的行数(因为这个数很容易从初始化器中元素的数量求出),但是 C 语言要求指明列数。

下面给出了 planets 数组的可能形式。并非所有的字符串都足以填满数组的一整行,所以 C 语言用空字符来填补。因为只有 3 个行星的名字需要用满 8 个字符(包括末尾的空字符),所以这样的数组有一点浪费空间。remind.c 程序(如何使用 C 语言的字符串库)就是这种浪费的代表,它把提醒信息按行存储到二维字符数组中,并为每条提醒信息都分配了 60 个字符的空间。在示例中,提醒信息的长度在 18~37 个字符,因此浪费的空间相当可观。

二维数组

因为大部分字符串集是长字符串和短字符串的混合,所以这些例子所暴露的低效性是在处理字符串时经常遇到的问题。我们需要的是参差不齐的数组(ragged array),即每一行有不同长度的二维数组。C 语言本身不提供这种“参差不齐的数组类型”,但它提供了模拟这种数组类型的工具。秘诀就是建立一个特殊的数组,这个数组的元素都是指向字符串的指针

下面是 planets 数组的另外一种写法,这次把它看作元素是指向字符串的指针的数组:

1
2
3
char *planets[] = {"Mercury", "Venus", "Earth",
                   "Mars", "Jupiter", "Saturn",
                   "Uranus", "Neptune", "Pluto"};

看上去改动不是很大,只是去掉了一对方括号,并且在 planets 前加了一个星号。但是,这对 planets 存储方式产生的影响却很大:

指向字符串的指针的数组

planets 的每一个元素都是指向以空字符结尾的字符串的指针。虽然必须为 planets 数组中的指针分配空间,但是字符串中不再有任何浪费的字符。

为了访问其中一个行星的名字,只需要对 planets 数组取下标。由于指针和数组之间的紧密关系,访问行星名字中的字符的方式和访问二维数组元素的方式相同。例如,为了在 planets 数组中搜寻以字母 M 开头的字符串,可以使用下面的循环:

1
2
3
for (i = 0; i < 9; i++)
  if (planets[i][0] == 'M')
    printf("%s begins with M\n", planets[i]);

命令行参数

运行程序时经常需要提供一些信息——文件名或者改变程序行为的开关。考虑 UNIX 的 ls 命令。如果我们按如下方式运行 ls,将显示当前目录中的文件名。

1
ls

但是,如果输入

1
ls –l

那么 ls 会显示一个“很长的”(详细的)文件列表,包括每个文件的大小、文件的所有者、文件最后改动的日期和时间等。为了进一步改变 ls 的行为,可以指定只显示一个文件的详细信息:

1
ls –l remind.c

ls 将显示名为 remind.c 的文件的详细信息。

命令行信息不仅对操作系统命令可用,而且它对所有程序都是可用的。为了能够访问这些命令行参数(C 标准中称为程序参数),必须把 main 函数定义为含有两个参数的函数,这两个参数通常命名为 argcargv

1
2
3
4
int main(int argc, char *argv[])
{
  ...
}

argc(“参数计数”)是命令行参数的数量(包括程序名本身),argv(“参数向量”)是指向命令行参数的指针数组,这些命令行参数以字符串的形式存储。argv[0] 指向程序名,而从 argv[1]argv[argc-1] 则指向余下的命令行参数。

argv 有一个附加元素,即 argv[argc],这个元素始终是一个空指针。空指针是一种不指向任何地方的特殊指针。后面会讨论空指针,目前只需要知道宏 NULL 代表空指针就够了。

如果用户输入命令行

1
ls –l remind.c

那么 argc 将为 3,argv[0] 将指向含有程序名的字符串,argv[1] 将指向字符串 "-l"argv[2] 将指向字符串 "remind.c",而 argv[3] 将为空指针:

命令行参数

这幅图没有详细说明程序名,因为根据操作系统的不同,程序名可能会包括路径或其他信息。如果程序名不可用,那么 argv[0] 会指向空字符串。

因为 argv 是指针数组,所以访问命令行参数非常容易。常见的做法是,期望有命令行参数的程序会设置循环来按顺序检查每一个参数。设定这种循环的方法之一就是使用整型变量作为 argv 数组的下标。例如,下面的循环每行一条地显示命令行参数:

1
2
3
4
int i;

for (i = 1; i < argc; i++)
  printf("%s\n", argv[i]);

另一种方法是构造一个指向 argv[1] 的指针,然后对指针重复进行自增操作来逐个访问数组余下的元素。因为 argv 数组的最后一个元素始终是空指针,所以循环可以在找到数组中一个空指针时停止:

1
2
3
4
char **p;

for (p = &argv[1]; *p != NULL; p++)
  printf("%s\n", *p);

因为 p 是指向字符的指针指针,所以必须小心使用。设置 p 等于 &argv[1] 是有意义的,因为 argv[1] 是一个指向字符的指针。所以 &argv[1] 就是指向指针的指针。因为 *pNULL 都是指针,所以测试 *p!= NULL 是没有问题的。对 p 进行自增操作看起来也是对的——因为 p 指向数组元素,所以对它进行自增操作将使 p 指向下一个元素。显示 *p 的语句也是合理的,因为 *p 指向字符串中的第一个字符。

程序 核对行星的名字

下一个程序 planet.c 举例说明了访问命令行参数的方法。设计此程序的目的是为了检查一系列字符串,从而找出哪些字符串是行星的名字。程序执行时,用户将把待测试的字符串放置在命令行中:

1
planet Jupiter venus Earth fred

程序会指出每个字符串是否是行星的名字。如果是,程序还将显示行星的编号(把最靠近太阳的行星编号为 1):

1
2
3
4
Jupiter is planet 5
venus is not a planet
Earth is planet 3
fred is not a planet

注意,除非字符串的首字母大写并且其余字母小写,否则程序不会认为字符串是行星的名字。

planet.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* Checks planet names */

#include <stdio.h>
#include <string.h>

#define NUM_PLANETS 9

int main(int argc, char *argv[])
{
 char *planets[] = {"Mercury", "Venus", "Earth",
                    "Mars", "Jupiter", "Saturn",
                    "Uranus", "Neptune", "Pluto"};
 int i, j;

 for (i = 1; i < argc; i++) {
   for (j = 0; j < NUM_PLANETS; j++)
     if (strcmp(argv[i], planets[j]) == 0) {
       printf("%s is planet %d\n", argv[i], j + 1);
       break;
     }
   if (j == NUM_PLANETS)
     printf("%s is not a planet\n", argv[i]);
 }

 return 0;
}

程序会依次访问每个命令行参数,把它与 planets 数组中的字符串进行比较,直到找到匹配的名字或者到了数组的末尾才停止。程序中最有趣的部分是对 strcmp 函数的调用,此函数的参数是 argv[i](指向命令行参数的指针)和 planets[j](指向行星名的指针)。

请参阅

(完)

comments powered by Disqus

本文内容