5.3.2 字符数组与字符串
字符数组中的所有元素都具有字符类型,而字符串也是由字符构成的一个序列。因此,我们可以将一个字符串保存到字符数组中。但在将字符串保存到数组之前,需要对 C 语言中的字符串有更深入的了解。
1.字符串
虽然 C 语言中没有字符串这种数据类型,但字符串在程序中的使用极为普遍,从本书的第一个例子起,我们就一直在与字符串打交道。
之前,我们介绍字符串就是用双引号括起来的一段字符序列。现在要告诉大家的是,字符串还有一个重要的特征:字符串必须以空字符作为结尾。所谓空字符,就是 ASCII 码值为 0 的字符,用转义字符'\0'表示。即使是字符串常量,也会隐含地拥有这个空字符。例如:
"abc"
这是一个字符串常量,虽然字符串看起来只有 a、b、c 3 个字符,但其实在最后一个字符 c 的后面还隐含着一个空字符,即这个字符串是由 4 个字符构成的,即字符 a、字符 b、字符 c 以及空字符,它的大小为 4 字节,如图 5.6 所示。
图 5.6 字符串"abc"所占用的内存
所以,字符串的更准确的定义应该是:以空字符作为结尾的一段字符序列。只不过用双引号括起来的是字符串常量,它会在结尾位置隐含地包含一个空字符,我们看不到而已。
即便是空字符串,它同样也拥有空字符:
"" //空字符串,只有一个空字符
所以,只包含一个空字符的字符串,就是空字符串了,即空字符串的大小是 1 字节。
现在大家应该能明白,printf 函数为什么能够以“%s”的格式来打印字符串了,其实道理很简单,它会一个一个地打印字符串中的字符,直到遇见空字符时停止。
知道了字符串的特点后,现在就可以考虑字符串的存储问题了。由于字符串常量不可修改的原因,因此,在 C 语言中,通常都会将一个字符串存储在字符数组中,以方便对字符串的处理。C 语言中,若要将字符串存储到字符数组中,可以有很多种方式,下面就介绍最常用的几种。
2.字符式存储
所谓字符式存储,就是将字符串“以字符为单位”的形式保存到字符数组中。
例如,我们要将字符串"abc"保存到字符数组中,可以用下面的方法:
char str[4] = {'a', 'b', 'c', '\0'};
定义一个长度为 4 的字符数组 str,并对其进行初始化,将字符串中的所有字符(包括空字符)都初始化给数组元素。
这里要特别注意的是,要确保数组足够容纳得下字符串(3 个普通字符和 1 个空字符),所以字符数组在定义时,它的长度不可小于 4(大于 4 是没问题的)。
鉴于空字符的 ASCII 码值为 0,我们也可以采取数组的部分初始化的方式:
char str[4] = {'a', 'b', 'c'};
初始值列表中只有 3 个初始值,编译器会将数组的前 3 个数组元素初始化为对应的初始值,而将第 4 个数组元素初始化为 0,这样,就隐含地达到将第 4 个数组元素初始化为空字符的目的了。
当然啦,如果不嫌麻烦,我们还可以采用对数组元素赋值的方式,将字符串保存到字符数组中:
3.字符串式存储
此时,大家可能会想,用字符式存储的方法来存储字符串比较麻烦,有没有更简单的方法呢?答案是肯定的。C 语言中,允许用字符串常量替代初始值列表,来进行字符数组的初始化。这种以字符串常量来进行字符数组初始化的方式称为“字符串式存储”,例如:
char str[4] = "abc";
用字符串常量“"abc"”替代了原来的初始值列表,它的初始化效果与使用字符式存储一样,编译器同样会将 4 个数组元素分别初始化值为字母 a、b、c 以及空字符。
在进行字符串式存储时,我们也经常采用空中括号的数组定义方式,即:
char str[] = "abc";
编译器会根据字符串常量的大小来确定数组的长度。这样赋值的好处是不用再担心数组的长度不够,容纳不下所有字符的问题,编译器总会给出一个合适的大小。
可见,使用字符串式存储比字符式存储方便得多。
但是,需要大家注意的是,字符串式存储只能用于字符数组的初始化,而不能用于赋值。毕竟 C 语言中是不允许对数组赋值的,只能对数组元素赋值。例如:
char str[4]; //定义长度为 4 的字符数组 str = "abc"; //错误!不可对数组进行赋值
4.字符串的输入/输出
字符式存储和字符串式存储有一个共同特点:必须在程序编译前就确定好。但有些时候,我们需要在程序的运行过程中来获取用户所输入的字符串,显然,之前的方式都不适用了。对于这种情况,我们还得依靠库函数。
【 例 5-2 】编写程序,能够获取用户输入的字符串,并保存至字符数组中,最后打印出字符数组中的字符串。
对于获取输入问题,首先想到的应该是 scanf 函数,它是我们的老朋友了,在之前的案例中,我们已经多次使用它来获取用户所输入的基本类型数据。其实它不光可以获取基本类型数据,对于字符串,它也可以胜任。
不过,想要获取用户输入的字符串,有一个比较令人头疼的问题:我们需要定义一个长度为多少的字符数组呢?也就是程序运行后,用户所输入的字符串到底有多大,现在是无法确定的。数组定义得小,会容纳不下字符串;数组定义得大,会造成内存的浪费。这个问题我们现在是无法完美解决的,就将它留到动态内存管理那一章吧。
现在怎么办呢?“两害相权取其轻”,数组容纳不下字符串,这太让人尴尬了,那我们就宁肯浪费内存,将数组定义得大一些,确保足够容纳用户输入的字符串:
char buf[1024]; //长度为 1024 的字符数组
这里定义了一个长度为 1024 的字符数组 buf,除去字符串结尾标记的那个空字符,还剩余 1023 个字符的位置,也就是说,只要用户所输入的字符个数不超过 1023,就没问题。
下面可以使用 scanf 函数获取用户输入的字符串:
scanf("%s", buf);
在 scanf 函数的格式化字符串中使用了“%s”,它表示以字符串的格式进行读取。此时,有些细心的读者可能会说:“我发现了一个错误,在参数 buf 前面少了个“&”符号”。是的,在之前所使用的 scanf 函数中,都会在格式化字符串之后的所有参数前面加上“&”符号。但这里却不用,为什么呢?因为 buf 表示的是一个数组,而不是一个变量,如果参数是一个变量则必须在前面加上“&”,而 buf 表示数组,更严格讲,是数组所占用的内存位置,也就是第 6 章会讲到的指针,所以它的前面不需要“&”。
一旦字符数组被存入一个字符串之后,我们就可以像字符串常量一样,使用 printf 函数将它打印到控制台窗口:
printf("%s\n", buf);
同样地,“%s”表示以字符串的格式进行打印,后面的 buf 就是包含字符串的字符数组的名字。该 printf 函数调用语句的功能就是将字符数组 buf 中的内容,按照字符串的格式打印输出。
下面来编写代码并测试,代码如下:
编译并运行程序后,会在窗口上打印一条信息,提示用户输入字符串,然后光标闪烁,等待着用户的输入,如果用户通过键盘输入“Apple”并按回车键,程序会通过 scanf 函数将字符串“Apple”读取并保存到字符数组 buf 中,最后再通过 printf 函数将字符数组 buf 中的字符串打印输出到窗口。整个程序的运行结果如下:
Please enter a string: Apple String content: Apple
需要注意的是,scanf 函数有一个特别之处,就是它在读取数据时,遇到空格字符就会停止。这会导致什么情况呢?
假如我们再次运行该程序,而这次输入的字符串为“Red apple”,运行的结果如下:
Please enter a string: Red apple String content: Red
从结果的最后一行可见,字符数组中存储的字符串并非用户所输入的“Red apple”,而只有“Red”。这是因为 scanf 函数在读取数据时,遇到了“Red”后面的空格而令读取停止,所以最终只将“Red”读取并保存到数组中。也就是说,对于中间含有空白字符的字符串来说,使用 scanf 函数来读取就不行,我们得“另寻他径”。
在“stdio.h”头文件中,还有两个函数是专门用于字符串的输入输出的,它们就是 gets 和 puts。
这两个函数使用起来非常简单,将字符数组作为参数就可以了。gets 可以将用户所输入的一行字符作为字符串读取进来,并保存到字符数组中。所谓一行字符就是以换行字符作为结尾的字符序列,换行字符也是一个转义字符,用'\n'表示,用户按回车键后会产生一个换行字符。需要注意的是,gets 只是遇见换行字符时就结束读取,并不会将换行字符保存到字符数组中。下面将代码中的 scanf 函数换成 gets 函数:
gets(buf);
puts 函数可以将字符数组中的字符串打印输出到控制台窗口中,下面就将代码中的最后一个 printf 函数换成 puts 函数:
puts(buf);
需要注意的是,puts 函数在打印输出字符数组中的字符串后,还会自动加上一个换行字符。
替换之后,代码如下:
现在重新编译运行该程序,结果如下:
Please enter a string: Red apple String content: Red apple
可见,即使输入的字符串包含空格字符,gets 仍能将其读取并保存至字符数组中,最后通过 puts 将完整的字符串打印输出。这里要注意的是,puts 函数不仅可以输出字符数组中的字符串,对字符常量来说也是可以的,上面代码中所有的 printf 函数都可以替换成 puts 函数。不过,对于原 printf 函数中的常量字符串来说,最后的那个'\n'可以省略。
另外,gets 函数现在已经被列为“不建议使用的函数”,这主要是出于安全性方面的考虑,由于 gets 函数缺乏关于字符数组长度的信息,因此,当面对一个比较长的字符串时,在保存至数组的过程中,可能会产生数组越界访问的问题。不过,只要我们谨慎对待、小心处理,还是能避免的。毕竟使用 gets 函数获取用户输入的字符串是最简单便捷的方式。
5.字符串的长度
对于字符串,它除了有大小之外,还有长度的概念。不少人对这两者混淆不清,现在来介绍一下。
字符串的大小指的是字符串所占用内存的字节数,而字符串的长度则是指字符串中有效字符的个数。所谓有效字符,就是除去作为结尾标记的空字符以外的字符。
对于字符串“"abc"”来说,它的大小为 4 字节,而它的长度为 3。如果一个字符串中全是英文字符(有对应 ASCII 码的字符),那么它的大小就是长度加 1。所以,要将一个英文字符串存储到字符数组中,这个数组的长度至少应该等于该字符串的大小,或是字符串的长度加 1。
在 C 标准库中,关于字符串处理的库函数,其中就有获取字符串长度的函数。这个函数的名字是 strlen。要使用它,需要包含“string.h”这个头文件。例如:
printf("%u\n", strlen("abc"));
使用 strlen 函数获取字符串常量"abc"的长度,由于字符串的长度不可能为负数,所以 strlen 函数的返回值是一个无符号的整数类型,因此,在 printf 函数的格式化字符串中使用了“%u”的格式来进行打印输出。
下面来测试一下,程序的代码很简单:
程序运行的结果为:
3
在第 6 章指针中,还将继续介绍字符串,并且介绍更多关于字符串处理的库函数。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论