
第8章字符串处理在第2章中讲述了字符型数据和字符串常量的基本用法,本章讲述如何对字符串进行存储、引用和处理。8.1字符串的存储与引用8.1.1字符串在内存中的存储形式8在C语言程序中,总是用一对双引号将字符串常量括起来,这里的双引号称为字符串的定界符,其作用是表示字符串的起始和终止位置。但是,将一个字符串常量存储到内存中时,并不存储作为字符串定界符的双引号。既然如此,在内存中如何表示字符串的起始位置和终止位置呢?其实,要确定一个字符串的起始位置很容易,这在稍后的论述中可以看到;而要确定一个字符串的终止位置,则必须添加特殊的标志才能表示出来为此,C语言规定:在内存中存储字符串常量时,必须在其末尾添加空字符"O'(ASCII码为0的字符)作为字符串结束标志。当编译系统将一个字符串常量存人内存时,会自动为它添加结束标志。HeI例如,字符串常量“Hello”在内存中的存储形式如图8.1字符串的存储形式图8.1所示。8.1.2用字符数组存储和引用字符串C语言中有字符串常量,但是并没有与之对应的字符串变量。那么,在C语言程序中如何存储字符串常量呢?既然一个学符型变量能够存储一一个字符,那么,一个字符型数组就可以存储一串字符。因此,在C语言中通常利用字符型数组来存储和处理字符串。除此之外,也可以利用字符型指针来处理字符串。如何将一个字符串存人字符数组中呢?最简单的方法,就是在定义字符数组的同时,将字符串存人字符数组中,这就是字符数组的初始化。1.以单个字符的形式初始化字符数组例如:chars[1o]=("G',‘o',o",'d);此时,若字符个数少于数组元素个数,则多余的数组元素会自动初始化为空字符"0'。这种初始化方式颇为烦琐,所以在实际编程中较少使用。2.以字符串的形式初始化字符数组例如:char s[20]="Good bye";char t[3][2o]=("Hello","How are you","Good bye"]:

很显然,这种初始化方式要简洁方便得多。不过,需要注意下列赋值语句是错误的。char s[20];S="Good bye";因为数组名s是一个指针常量,而常量是不能被赋值的,所以不能直接对数组名进行赋值。3.不指定数组长度初始化字符数组例如:chars[]='G','o',o',"d'}当以单个字符形式初始化字符数组,同时缺省数组长度时,系统不会在末尾自动添加空字符0',因此字符数组s中有4个元素。又如:char t[]="Good";当以字符串形式初始化字符数组时,系统会在末尾自动添加空字符"0,因此字符数组t中有5个元素。由此可见,在程序中以字符串常量形式出现,其末尾就隐含了一个空字符"0;而以字符常量形式出现的,其未尾就不隐含空字符。4.用字符数组名引用字符串将一个字符串存人字符数组后,就可以在程序中通过字符数组名来引用这个字符串。当然,也可以通过数组元素来引用这个字符串中的某个字符例如:#include int main(void)[char t[20]="Good bye";t[5]="B';*用数组名引用字符串*printf("gsin",t);/*用数组元素引用单个字符*,printf("ecln",t[5]):return 0;可见,可以通过给字符数组元素赋值的方式,修改字符数组中的字符串。【例8.1】编写程序实现如下功能,从键盘输人一位数字,将其转换为相应的汉字大写数字输出。编程思路:(1)为便于字符转化,可先将10个汉字存入一个字符数组中。第(2)若将10个汉字作为一个字符串,存人一个一维字符数组中,即采用“charup[21]=8"零壹贰参肆伍陆柒捌玖”这种形式,则从其中提取某个汉字时不甚方便。*(3)若将10个汉字作为10个字符串,存入一个二维字符数组中,操作起来就方便得多。算法设计:字(1)首先,利用初始化语句“charup[10][3]-{"零""壹""贰""叁参""肆""伍""陆""柒"符串"捌""玖"将10个汉字存入一个10行3列的二维字符数组中,每行存储一个汉字。此时,处其行号恰好就是对应的数字,如图8.2所示。理113

(2)在转化时,直接以输入的数字作为行号,而对应行的字符串就10up[0]零是要转化的大写形式。壹10up[1]源程序:up[2] 贰10510up[3]#include10肆int main(void)up[4](intn;10up[5] charup[10][3]={"零""壹","贰","叁","肆","伍","陆",up[6]陆10"柴”,"捌","玖"};柴10up[7]/*每个汉字看作两个字符,不能作为字符常量*/捌10up[8]printf("请输人一位数字:In");1oup[9]玖scanf("gd",&n);图8.210个汉字printf("&sn",up[n]);return 0;在二维字符数组中的存储形式想一想,若要完成一个多位数的转化,应该如何编写程序实现呢?8.1.3用字符指针变量引用字符串在C语言中,除了可以利用字符型数组来存储和引用字符串之外,还可以利用字符指针变量来引用字符串,前提是该字符指针变量已经指向待引用的字符串。1.字符指针指向字符串使字符指针变量指向一个字符串,通常有以下两种方式:(1)字符指针变量赋值方式。例如:char *p:p-"How are you!";(2)字符指针变量初始化方式。例如:char *p="How areyou!"需要注意,这里的赋值或初始化,并不表示将整个字符串存人字符指针变量中;其正确的含义,是首先将该字符串常量存人内存中的空闲区域中,然后将该字符串中首字符的地址赋给指针变量p。因为p是字符指针变量,所以只能存储字符的地址值。2.字符指针引用字符串c将一个字符指针变量指向一个字符串后,就可以在程序中通过字符指针变量名来引用语这个字符串。当然,也可以通过字符指针变量来间接引用这个字符串中的某个字符,言例如:程#include库int main(void)设fchar *p;计p="How are you!";新/*用指针变量名引用字符串*printf("%sln",p);思/*该语句有错*/*(p+4)=A";路114

printf("%cn",*(p+4));/*用指针变量间接引用单个字符*return O;可见,不能通过字符指针修改字符串常量的内容,因为常量的值不可以改变。但是存储在字符数组中的字符串可以修改,因为字符数组的元素是变量8.2字符串的输人和输出在程序中,对于字符串最常用的操作是输人与输出。C语言中,通常利用printf函数和puts函数来输出字符串,利用scanf函数和gets函数来输人字符串。8.2.1用printf函数输出字符串其一般形式为printf("gs",字符串引用)其中,字符串引用可以是字符串常量、字符数组名或字符指针变量(甚至是字符指针的表达式)例如:printf("gsln","Hello");当然该语句也可以简化为printf("Helloln");又如:char a[10]="Hello";printf("gs\n",a);/*输出项是字符数组名*再如:char *p-"Hello";printf("gsln",p);/*输出项是字符指针变量名*/需要注意,虽然可以利用包含%c格式符的printf函数结合循环语句来实现字符串的输出,但是不建议采用这种方式。8.2.2用puts函数输出字符串其一般形式为puts(字符串引用)其中,字符串引用可以是字符串常量、字符数组名或字符指针变量(甚至是字符指针的表达式)。例如:第puts("Hello");8又如:章char a[l0]="Hello";puts(a);字再如:符char *p="Hello";串处puts(p);可以发现,当利用puts函数输出字符串时,能够实现自动换行。理115

8.2.3用scanf函数输入字符串其一般形式为scanf("%s",字符数组名)注意,这里的第二个参数只能是字符数组名。例如:#includeint main(void)[char *p;scanf("s",p);/*该语句错误*/printf("%sln",p);return 0;因为定义一个字符指针变量,只是分配了存储一个地址的内存空间(在32位编译系统中是4字节),并没有分配存储一个字符串的内存空间。此外,虽然可以利用包含%c格式符的scanf函数结合循环语句来实现字符串的输入,但是不建议采用这种方式。c语8.2.4用gets函数输入字符串言程其一般形式为序gets(字符数组名)设注意,这里的函数参数只能是字符数组名计例如:新#include 思int main(void)路116

[char a[30];gets(a):puts(a);return 0;该程序运行时,若键人:How are you则输出结果:How are you同样地,一般不能使用字符指针变量作为gets函数的参数来输人字符串。在C语言中,经常需要以逐个字符的方式对字符串进行处理。下面来看一个以逐个字符的方式复制字符串的例子。【例8.2】从键盘输入一个字符串,存人字符数组S中,然后用逐个字符的方式复制到字符数组t中。编程思路:逐个字符复制就是从源字符串的第0个字符开始,将所有的字符逐个复制到目标数组中。从而写出如下的程序段:t[0]=s[0];t[1]=s[1];t[2]=s[2];....显然,可以归纳为如下的循环结构:1=0;while(循环条件)(t[i]-s[i];i++;那么,这里的循环条件应该如何表示呢?每个字符串都有一个结束标志"0',因此可以用10控制循环。在当前字符不等于"0'时,执行循环体;在当前字符等于"0'时,结束循环。从而得到如下循环:1=0;while(s[ijl=-rlo")(t[i]-s[i];i++;完整的源程序:第#include 8int main(void)章(chars[1ool,t[1oo];int i;字gets(s);符i=0;串while( s[i]="lo)处(tri]= s[i];理117

i++;1puts(t);return 0;该程序输出的结果中,在字符串的末尾出现了一些乱字符。原因何在呢?这是因为在该程序的循环部分,并未将数组s中的"O'复制到数组t中,从而导致数组t中的字符序列,没有正常的字符串结束标记。修正的源程序:#include int main(void)(chars[100],t[100];int i;gets(s);(版社1=0;while( s[ijl=llo')(ti]= s[i];i++;1ti]-"lo";puts(t):return 0;8.3字符处理函数与字符串处理函数除了字符串的输入与输出之外,更重要的操作是对字符串的各种处理,如求字符串长度、字符串连接、字符串比较等。为了使用方便,C语言提供了一组专门用于字符与字符串处理的库函数。在程序中调用字符与字符串处理的库函数时,需要在程序开头分别添加预处理命令#include或#include。8.3.1字符处理函数1.isalpha函数其调用格式为cisalpha(ch)语若字符ch是字母,则该函数返回值非0;否则,返回值为0。言程2.islower函数序设其调用格式为计islower(ch)新若字符ch是小写字母,则该函数返回值非0;否则,返回值为0。思路118

3.isupper函数其调用格式为isupper(ch)若字符ch是大写字母,则该函数返回值非0;否则,返回值为04.isdigit函数其调用格式为isdigit(ch)若字符ch是数字,则该函数返回值非0;否则,返回值为0。5.isalnum函数其调用格式为isalnum(ch)若字符ch是字母或数字,则该函数返回值非0:否则,返回值为0。【例8.3】从键盘输入一行字符,将其中的每个字符按如下规则进行变换:若是大写字母,则转换为相应的小写字母:若是小写字母,则转换为相应的大写字母:否则,保持不变。编程思路:(1)首先写出转换一个字符的程序。#include #includeint main(void)(char ch;printf("请输人一个字符:");ch=getchar();if(isupper(ch))ch=ch+32;else if(islower(ch))ch=ch-32;printf("转换后的字符=%cn",ch);return 0;(2)要转换一行字符,只需要在上述程序的基础上添加一个循环即可。#include #include 第int main(void)8(char ch;章printf("请输人一行字符:");while(ch=n')字(ch=getchar();符if(isupper(ch))串ch=ch+32;处else if(islower(ch))理ch=ch-32;119

putchar (ch);return O;该程序的while循环部分存在赋值在后、判断在前的问题,可以将输人字符的语句合并到while条件中解决,从而得到如下完整的源程序。#include#includeint main(void)(char ch;printf("请输人一行字符:");while((ch=getchar())!=In')(if(isupper(ch))ch=ch+32;else if(islower(ch))ch=ch-32;putchar(ch);1return O;需要注意,在表达式(ch=getchar())!=n'中,ch=getchar()必须用括号括起来。若写成ch=getchar()!=n,则由于!=的优先级高于赋值运算符,从而等价于ch=(getchar()!=n')含义将完全不同。想一想,若将ch=getchar()换成scanf("%c"&ch),为什么不适合采用while(scanf("%c",&ch)!="n)这种形式呢?函数scanf("%c"&ch)的返回值就是所输人的字符吗?8.3.2字符串处理函数1.字符串长度函数strlen在程序中处理字符串时,往往需要知道字符串的长度,而库函数strlen就是用于求得一个字符串的长度的。其调用格式为strlen(字符串引用)该函数的功能是返回一个字符串的有效长度(第一个"0之前的字符个数)。例如:#include #includeint main(void)cfchara[lool="Helloworld!"语printf("sdn",sizeof(a));言printf("sdn",strlen(a));程printf("%d\n",strlen("Helloloworld!"));库return O;设计程序运行结果:新100思12路5120

【例8.4】用字符数组重新编写例8.3中的程序。编程思路:该程序仍然可以采用逐个字符的方式处理字符串,只不过不是原样传送,而是经过变换之后,再传送到原来的数组中。(1)输人一个字符串并存入字符数组s中。(2)对字符数组中的字符逐个进行判断,然后进行变换,再存回原来的数组中(3)输出变换之后的字符串。先写出如下处理一个字符的程序段:1=0;if(isupper(s[ij))s[i]=s[i]+32;elaeif(islower(s[ij))s[i]=s[i]-32;然后循环执行处理一个字符的程序段,即可完成整个字符串的变换。完整的源程序:#include #include 出版int main(void)(char s[200];int i,n;printf("请输人一个字符串:In");gets(s):n=strlen(s);for(i=0;i<=n-l;i++)(if(isupper(s[il))s[i]=s[i]+32;else if(islower(s[ij))s[i]=s[i]-32;1printf("变换之后的字符串为:In");puts(s);return 0;对比例83可以发现,将一批学符作为学符事并利用学符数组进行输入及处理的方式更直观、更方便。因此,编写程序时一般不采用将一批字符利用单个字符变量循环输入及处理的方式。第2.字符串复制函数strcpy8章假设有两个字符数组:char s[20]="Hello",t[20];字如何才能将字符数组s中的字符串复制到字符数组t中呢?符很容易想到的是利用如下赋值语句:串处t=si或理121