4.2 字符串简介 – 第4章 字符串和格式化输入/输出 – C Prime Plus(第6版)

字符串(character string)是一个或多个字符的序列,如下所示:

“Zing went the strings of my heart!”

双引号不是字符串的一部分。双引号仅告知编译器它括起来的是字符串,正如单引号用于标识单个字符一样。

4.2.1 char类型数组和null字符

C语言没有专门用于储存字符串的变量类型,字符串都被储存在char类型的数组中。数组由连续的存储单元组成,字符串中的字符被储存在相邻的存储单元中,每个单元储存一个字符(见图4.1)。

图4.1 数组中的字符串
图4.1 数组中的字符串

注 意 图 4.1 中 数 组 末 尾 位 置 的 字 符 \0 。 这 是 空 字 符 ( null character),C语言用它标记字符串的结束。空字符不是数字0,它是非打印字符,其ASCII码值是(或等价于)0。C中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。

因此,程序清单4.1中有40个存储单元的字符串,只能储存39个字符,剩下一个字节留给空字符。

那么,什么是数组?可以把数组看作是一行连续的多个存储单元。用更正式的说法是,数组是同类型数据元素的有序序列。程序清单4.1通过以下声明创建了一个包含40个存储单元(或元素)的数组,

每个单元储存一个char类型的值:

char name[40];

name后面的方括号表明这是一个数组,方括号中的40表明该数组中的元素数量。char表明每个元素的类型(见图4.2)。

图4.2 声明一个变量和声明一个数组
图4.2 声明一个变量和声明一个数组

字符串看上去比较复杂!必须先创建一个数组,把字符串中的字符逐个放入数组,还要记得在末尾加上一个\0。还好,计算机可以自己处理这些细节。

4.2.2 使用字符串

试着运行程序清单4.2,使用字符串其实很简单。

程序清单4.2 praise1.c程序

/* praise1.c -- 使用不同类型的字符串 */
#include <stdio.h>
#define PRAISE "You are an extraordinary being."
int main(void)
{
    char name[40];
    printf("What's your name? ");
    scanf("%s", name);
    printf("Hello, %s.%s\n", name, PRAISE);
    return 0;
}

%s告诉printf()打印一个字符串。%s出现了两次,因为程序要打印两个字符串:一个储存在name数组中;一个由PRAISE来表示。运行praise1.c,其输出如下所示:

What's your name? Angela Plains
Hello, Angela.You are an extraordinary being.

你不用亲自把空字符放入字符串末尾,scanf()在读取输入时就已完成这项工作。也不用在字符串常量PRAISE末尾添加空字符。稍后我们会解释#define指令,现在先理解PRAISE后面用双引号括起来的文本是一个字符串。编译器会在末尾加上空字符。

注意(这很重要),scanf()只读取了Angela Plains中的Angela,它在遇到第1个空白(空格、制表符或换行符)时就不再读取输入。因此,scanf()在读到Angela和Plains之间的空格时就停止了。一般而言,根据%s转换说明,scanf()只会读取字符串中的一个单词,而不是一整句。C语言还有其他的输入函数(如,fgets()),用于读取一般字符串。后面章节将详细介绍这些函数。

字符串和字符

字符串常量”x”和字符常量’x’不同。区别之一在于’x’是基本类型(char),而”x”是派生类型(char数组);区别之二是”x”实际上由两个字符组成:’x’和空字符\0(见图4.3)。

图4.3 字符'x'和字符串"x"
图4.3 字符’x’和字符串”x”

4.2.3 strlen()函数

上一章提到了 sizeof 运算符,它以字节为单位给出对象的大小。strlen()函数给出字符串中的字符长度。因为 1 字节储存一个字符,读者可能认为把两种方法应用于字符串得到的结果相同,但事实并非如此。请根据程序清单4.3,在程序清单4.2中添加几行代码,看看为什么会这样。

程序清单4.3 praise2.c程序

/* praise2.c */
// 如果编译器不识别%zd,尝试换成%u或%lu。
#include <stdio.h>
#include <string.h> /* 提供strlen()函数的原型 */
#define PRAISE "You are an extraordinary being."
int main(void)
{
    char name[40];
    printf("What's your name? ");
    scanf("%s", name);
    printf("Hello, %s.%s\n", name, PRAISE);
    printf("Your name of %zd letters occupies %zd memory cells.\n",
    strlen(name), sizeof name);
    printf("The phrase of praise has %zd letters ",
    strlen(PRAISE));
    printf("and occupies %zd memory cells.\n", sizeof PRAISE);
    return 0;
}

如果使用ANSI C之前的编译器,必须移除这一行:

#include <string.h>

string.h头文件包含多个与字符串相关的函数原型,包括strlen()。

第11章将详细介绍该头文件(顺带一提,一些ANSI之前的UNIX系统用strings.h代替string.h,其中也包含了一些字符串函数的声明)。

一般而言,C 把函数库中相关的函数归为一类,并为每类函数提供一个头文件。例如,printf()和scanf()都隶属标准输入和输出函数,使用stdio.h头文件。string.h头文件中包含了strlen()函数和其他一些与字符串相关的函数(如拷贝字符串的函数和字符串查找函数)。

注意,程序清单4.3使用了两种方法处理很长的printf()语句。第1种方法是将printf()语句分为两行(可以在参数之间断为两行,但是不要在双引号中的字符串中间断开);第 2 种方法是使用两个printf()语句打印一行内容,只在第2条printf()语句中使用换行符(\n)。运行该程序,其交互输出如下:

What's your name? Serendipity Chance
Hello, Serendipity.You are an extraordinary being.
Your name of 11 letters occupies 40 memory cells.
The phrase of praise has 31 letters and occupies 32 memory cells.

sizeof运算符报告,name数组有40个存储单元。但是,只有前11个单元用来储存Serendipity,所以strlen()得出的结果是11。name数组的第12个单元储存空字符,strlen()并未将其计入。图4.4演示了这个概念。

图4.4 strlen()函数知道在何处停止
图4.4 strlen()函数知道在何处停止

对于 PRAISE,用 strlen()得出的也是字符串中的字符数(包括空格和标点符号)。然而,sizeof运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。该程序并未明确告诉计算机要给字符串预留多少空间,所以它必须计算双引号内的字符数。

第 3 章提到过,C99 和 C11 标准专门为 sizeof 运算符的返回类型添加了%zd 转换说明,这对于strlen()同样适用。对于早期的C,还要知道sizeof和strlen()返回的实际类型(通常是unsigned或unsigned long)。

另外,还要注意一点:上一章的 sizeof 使用了圆括号,但本例没有。圆括号的使用时机否取决于运算对象是类型还是特定量?运算对象是类型时,圆括号必不可少,但是对于特定量,可有可无。也就是说,对于类型,应写成sizeof(char)或sizeof(float);对于特定量,可写成sizeof name或sizeof 6.28。尽管如此,还是建议所有情况下都使用圆括号,如sizeof(6.28)。

程序清单4.3中使用strlen()和sizeof,完全是为了满足读者的好奇心。在实际应用中,strlen()和 sizeof 是非常重要的编程工具。例如,在各种要处理字符串的程序中,strlen()很有用。详见第11章。

下面我们来学习#define指令。


本人于2023年3月18日22:48:40学习完以上内容,特与此记录并分享。

© 版权声明
THE END
喜欢就点赞支持一下吧,如果觉得不错或日后有所需要,可以收藏文章和关注作者哦。
点赞2打赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容