预计阅读本页时间:-
字符串(character string)就是一个或多个字符的序列。下面是一个字符串的例子:
“Zing went the strings of my heart!”
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
双引号不是字符串的一部分。它们只是通知编译器其中包含了一个字符串,正如单引号标识着一个字符一样。
4.2.1 char数组类型和空字符
C没有为字符串定义专门的变量类型,而是把它存储在char数组中。字符串中的字符存放在相邻的存储单元中,每个字符占用一个单元;而数组由相邻存储单元组成,所以把字符串存储到数组中是很自然的(请参见图4.1)。
图4.1 字符串在数组中的存储
请注意,图4.1中数组的最后一个位置显示字符\0。这个字符就是空字符(null character), C用它来标记字符串的结束。空字符不是数字0;它是非打印字符,其ASCII码的值为(或者等同于)0。C的字符串存储时通常以这个空字符结束。该字符的存在意味着数组的单元数必须至少比要存储的字符数多1。
那么究竟什么是数组呢?可以把数组看作一行中的多个存储单元。如果您更喜欢正式一点的语言,那么数组就是同一类型的数据元素的有序序列。示例程序通过使用以下声明创建了一个有40个存储单元(或元素)的数组,其中每个单元都可以存储一个char类型的值:
char name [40];
name后面的方括号说明它是一个数组。方括号内的40指出数组中元素的数目。char标识每个元素的类型(请参见图4.2)。
图4.2 声明变量与声明数组
使用字符串开始复杂起来了!必须创建一个数组,把字符串中的字符逐个地放进数组中,还要记着在结尾添加一个\0。幸运的是,计算机可以自己处理大多数这些细节问题。
4.2.2 使用字符串
试着运行程序清单4.2中的程序,您会看到使用字符串很简单。
程序清单4.2 praise1.c程序
%s告诉printf()要打印一个字符串。%s出现两次是因为该程序要打印两个字符串:一个被存储在name数组中,另一个由PRAISE来代表。praisel.c的运行输出应该像下面所示:
What's your name?
Hilary Bubbles
Hello, Hilary. What a super marvelous name!
您无须亲自把空字符插入name数组中。scanf()在读取输入时会替您完成这项任务。也无须在字符串常量PRAISE中包含一个空字符。我们很快就会解释#define语句,暂时只须注意PRAISE后面的一对双引号表示其中的文本是一个字符串。编译器负责插入空字符这件事情。
请注意(这是很重要的),scanf()只读取了Hilary Bubbles的名字Hilary。scanf()开始读取输入以后,会在遇到的第一个空白字符空格(blank)、制表符(tab)或者换行符(newline)处停止读取。因此,它在遇到Hilary和Bubbles之间的空格时,就停止了扫描。一般情况下,使用%s的scanf()只会把一个单词而不是把整个语句作为字符串读入。C使用其他读取输入函数(例如gets())来处理一般的字符串。后面的几章将更全面地研究字符串函数。
字符串和字符
字符串常量“x”与字符常量“x”不同。其中一个区别是‘x’属于基本类型(char),而“x”则属于派生类型(char数组)。第二个区别是“x”实际上由两个字符(‘x’和空字符‘\0’)组成(请参见图4.3)。
图4.3 字符‘x’和字符串“x”
4.2.3 strlen()函数
上一章提到了sizeof运算符,它以字节为单位给出数据的大小。strlen()函数以字符为单位给出字符串的长度。因为一个字符只占用一个字节,所以您可能认为把这两个函数应用到同一个字符串时可以得到相同的结果,然而事实并非如此。请像程序清单4.3中那样向前面的示例程序中添加几行,看看为什么会是这样。
程序清单4.3 praise2.c程序
如果您使用的是ANSI C之前的编译器,那么可能需要删除下面这一行:
#include<string.h>
string.h文件包含许多与字符串相关的函数的原型,包括strlen()。第11章“字符串和字符串函数”更全面地讨论了这个头文件(顺便提一下,一些ANSI之前的UNIX系统使用头文件strings.h而非string.h来包含对字符串函数的声明)。
更一般的情况下,C把C函数库分成多个相关函数的系列,并为每个系列提供一个头文件。例如,printf()和scanf()属于标准输入和输出函数系列,使用stdio.h头文件。strlen()函数和其他一些与字符串相关的函数(例如字符串复制和字符串搜索的函数)同属一个系列,并在string.h头文件中定义。
请注意,程序清单4.3使用了两个方法来处理很长的printf()语句。第一个方法是让一个printf()语句占用两行(您可以在参数之间断开一行,但不要在一个字符串的中间,例如在一对引号之间断开一行)。第二个方法使用两个printf()语句来输出一行,换行符(\n)只出现在第二个语句中。运行该程序会产生如下交互信息:
What's your name?
Morgan Buttercup
Hello, Morgan. What a super marvelous name!
Your name of 6 letters occupies 40 memory cells.
The phrase of praise has 28 letters and occupies 29 memory cells.
看看发生了什么。根据sizeof运算符的报告,数组name有40个内存单元。不过只用了其中前6个单元来存放Morgan,这是strlen()所报告的。数组name的第7个单元中放置空字符,它的存在告诉strlen()在哪里停止计数。图4.4示意了这个概念。
图4.4 strlen()函数知道在哪里停止
对于PRAISE,您会发现strlen()再一次给出了字符串中字符(包括空格和标点符号)的准确数目。Sizeof运算符提供给您的数目比前者大1,这是因为它把用来标志字符串结束的不可见的空字符也计算在内。您并没有告诉计算机为存储该语句分配多大内存,它必须自己计算出双引号之间的字符的数目。
还有一点,前一章在sizeof中使用了圆括号,但是本例却没有。是否使用圆括号取决于您是想获取一个类型的大小还是想获取某个具体量的大小。圆括号对于类型是必需的,而对于具体量则是可选的。也就是说,您应该使用sizeof(char)或sizeof(float),但是可以使用sizeof name或sizeof 6.28。不过,在所有情况下都使用圆括号会更好,例如sizeof(6.28)。
上一个示例程序中使用strlen()和sizeof只是为了满足用户潜意识中的好奇心。然而在实际应用中,strlen()和sizeof都是重要的编程工具。例如,strlen()在各种类型的字符串程序中都很有用,您将在第11章中看到这一点。
下面我们来看#define语句。