C Primer Plus 第十一章——字符串和字符串函数
字符串(character string)是以空字符(\0)为结尾的char数组。字符串常量,又称字符串文字,是指位于一对双引号中的任何字符。双引号里的字符加上编译器自动提供的结束表示\0字符,作为一个字符串被存储在内存里。字符串常量属于静态存储(static storage)类。静态存储是指如果在一个函数中使用字符串常量,即使是多次调用了这个函数,该字符串在程序的整个运行过程中只存储一份。整个引号中的内容作为指向该字符串存储位置的指针,这与把数组名作为指向数组存储位置的指针类似。即,字符串同时是一个地址。
//quotes.c——把字符串看作指针
#include<stdio.h>
int main(void)
{
printf("%s, %p, %c\n","We","are",*"space farers");
return 0;
}
*"space farmers"应该产生所指向的地址中的值,即第一个字符。输出结果:
We, 0x0040c010,s
字符串数组及初始化:定义一个字符串数组时,您必须让编译器知道它需要多大空间。一个办法就是制定一个足够大的数组来容纳字符串。指定数组大小时,一定要确保数组元素数比字符串长度至少多1(多出来的1个元素用来容纳空字符)。在初始化声明时如果省略数组大小,则该大小由编译器决定。C99中也可以使用变量作为字符串长度创建一个变长数组(VLA)。同时,也可以使用指针符号创建字符串。
const char m1[40]="Limit yourself to one line's worth.";
const char m2[]=""If you can't think of anything, fake it.";
int n=8;
char crumbs[n]; //C99前无效,C99之后是一个变长数组(VLA)
const char *m3="\nEnough about me- what's your name?"; char m3[]= "\nEnough about me- what's your name?"
使用数组形式 char m3[]和指针形式char *m3创建字符串的不同:数组形式在内存中被分配一个由38个元素的数组(其中每个元素对应一个字符,还有一个附加的元素对应结束的空字符\0。每个元素都被初始化为相应的字符。通常,被引用的字符串存储在可执行的数据段部分;当程序被加载到内存时,字符串也被加载到内存中。被引用的字符串被称为位于静态存储区。但是程序开始运行后才为数组分配存储空间。这时,把被引用的字符串复制到数组中。此后编译器把数组m3看作首元素地址&m3[0]的同义词,注意在数组形式中m3是个地址常量(可以使用m3+1,但不能使用增量运算符++)。 指针形式也在静态存储区为字符串预留38个元素的空间。此外,程序开始执行后还要为指针变量m3预留一个存储位置,以在该指针变量中存储字符串的地址。这个变量初始指向字符串的第一个字符,但是它的值可以改变。因此可以使用增量运算符等。例如,++m3指向第二个字符。
总之,数组初始化是从静态存储区把一个字符串复制给数组,而指针初始化只是赋值字符串的地址。
数组和指针的差别:
char heart[]="I love Tillie!";
char *head = "I love Millie!";
初始化一个存放字符串的字符数组和初始化一个指向字符串的指针的主要不同在于数组名heart是个常量,而指针head是个变量。在实际使用中,两者都可以使用数组符号和指针加法,但只有指针可以使用增量运算符:
while(*(head)!='\0')
putchar(*(head++));
建议在初始化一个指向字符串文字的指针时使用const修饰符,以以防止字符串内容被指针操作改变。
字符串数组:
建立字符串数组可以使用下标来访问多个不同字符串(LIM等于5):
const char *mytal[LIM]= {"Adding numbers swiftly","Multiplying accurately", "Stashing data","Folling instructions to the letter","Understanding the C language"};
mytal是一个由5个指向char的指针组成的数组。也就是说,mytal是一个一维数组,而且数组里的每一个元素都是一个char类型值的地址。数组内实际上并不存放字符串,它只是存放字符串的地址(字符串存在程序中用来存放常量的那部分内存中)。
另一个方法是建立一个二维数组(LINLIM是81):
char mytal_2[LIM][LINLIM];
这里mytal_2是一个5个元素的数组,每一个元素本身又是一个81个char的数组。这种情况下字符串本身也被存储在数组里。两者差别之一就是第二种方法建立了一个所有行的长度都相同的矩形(rectangular)数组,也就是每一个字符串都用81个元素存放。而指针数组建立的是一个不规则(ragged)数组,每一行的长度由初始化字符串决定,不浪费任何存储空间。一句话,mytal存放5个地址,而mytal_2存放5个完整的数组。
字符串输入
char *name;
scanf("%s",name);
这可能会通过编译器,但是读入name的时候,name会覆盖程序中的数据和代码,并可能导致异常终止(即未初始化的指针)。应该在声明中明确指出数组大小:char name[81];
读取字符串的函数:scanf()、gets()和fgets():
char *gets(cha * s)
{ ...
return (s);
}
注意gets()返回的指针与传递给它的指针是同一个指针。输入字符串的唯一备份放在函数参数传递过来的地址中。gets()函数实际的构造更复杂一点,它有两个可能的返回值。如果一切顺利,它返回读入字符串的地址。如果出错或者如果gets()遇到文件结尾,它就返回一个空(或0)地址。这个地址被称为空指针,并用stdio.h里定义的常量NULL来表示。因此gets()可以很方便地以如下形式使用:while(gets(name)!=NULL)
这样的指令使您既可以检查是否到达文件结尾,又可以读取一个值。这种方法比使用getchar()函数简洁得多。顺便,不要混淆空指针和空字符。空指针是一个地址,而空字符是一个char类型的数据对象,其值为0。scnaf()和gets()主要的差别在于它们如何决定字符串何时结束。scnaf()更基于获取单词(word)而不是获取字符串(string);而gets函数会读取所有的字符,直到遇到第一个换行符为止。scanf()使用两种方法决定输入结束。无论哪种方法,字符串都是以遇到的第一个非空白字符开始。如果使用%s格式,字符串读到(但不包括)下一个空白字符(比如空格、制表符或换行符)。如果指定了字段宽度,比如%10s,scanf()就会读入10个字符或直到遇到第一个空白字符,由二者中最先满足的那一个终止输入。scanf()函数返回一个整数值,这个值是成功读取的项目数;或者当遇到文件结束是返回一个EOF。
字符串输出
puts()函数:
puts()函数的使用很简单,只需要给出字符串参数的地址(如双引号中的字符串常量、字符串数组名等)。与printf()不同,puts()显示字符串时自动在其后添加一个换行符。puts()函数遇到空字符('\0')时就会停下来,所以应该确保有空字符存在。不要模仿下面的程序!
/* nono.c——不要效仿这个程序!*/
#include<stdio.h>
int main(void)
{
char side_a[]='SIDE A';
dont[]={'W','O','W','!'};
char side_b[]='SIDE B';
puts(dont); //dont不是一个字符串
return 0;
}
dont缺少一个表示结束的空字符,因此它不是一个字符串,这样puts()就不知道从哪里停止。它只是一直输出内存中dont后面的字符,直到发现一个空字符。为了使这个空字符不太遥远,程序吧dont存储在两个真正的字符串之间。下面是运行示例:
WOW! SIDE A
这里用到的特定编译器在内存中把side_a数组存储在dont数组之后,因此puts()函数继续执行直到遇到了side_a中的空字符。
fputs()函数:
fputs()函数是puts()的面向文件版本。两者之间的主要区别是:
1、fputs()需要第二个参数来说明要写的文件。可以使用stdout(standard output)作为参数来进行输出显示,stdout在stdio.h中定义。
2、与puts()不同,fputs()并不为输出自动添加换行符。
注意:gets()丢掉输入里的换行符,但是puts()为输出自动添加换行符。另一方面,fgets()存储输入中的换行符,而fputs()也不为输出添加换行符。所以假定写一个循环,读取一行并把它回显在下一行,有以下写法:
//使用puts()和gets():
char line[81];
while(gets(line))
puts(line);
//使用fputs()和fgets():
char line[81];
while(fgets(line,81,stdin))
fputs(line,stdout);
puts()是为和gets()一起使用而设计的,而fputs()是为和fgets()一起使用而设计的。
printf()函数:
printf()函数使用起来没有puts()那么方便,但是它可以格式化多种数据类型,因而更通用。它们之间的唯一区别就是printf()并不自动在新行上输出每一个字符串。相反,您必须指明需要另起一行的地方。以下两个语句效果相同:
printf("%s\n",string);
puts(string);
也可以在getchar()和putchar()的基础上建立自己的函数来自定义字符串输入/输出函数。字符串函数
C库提供了许多处理字符串的函数;ANSI C用头文件string.h给出这些函数的原型。下面是一些常用函数:
strlen()函数:
用strlen()函数可以得到字符串的长度(直到遇到第一个'\0')。
strcat()函数:
strcat()(代表string concatenation字符串连接)函数接受两个字符串参数。它将第二个字符串的一份拷贝添加到第一个字符串的结尾,从而使第一个字符串成为一个新的组合字符串,第二个字符串并没有改变。strcat()函数是char*(指向char的指针)类型。这个函数返回它的第一个参数的值,即后面添加了第二个字符串的那个字符串中第一个字符的地址。
strncat()函数:
strcat()函数并不检查第一个数组是否能够容纳第二个字符串。如果没有为第一个数组分配足够大的空间,多出来的字符溢出到相邻存储单元时就会出现问题。strncat()函数需要另一个参数来指明最多允许添加的字符的数目。例如,strncat(bugs,addon,13)函数把addon字符串中的内容添加到bugs上,直到加到13个字符或遇到空字符位置,由二者中先符合的那一个来终止添加过程。因此,把空字符算在内,bugs数组应该足够大,以存放原始字符串(不包括空字符)、增加的最多13个字符和结束的空字符。
strcmp()函数:
strcam()函数可以比较字符串内容(content)而不是字符串地址(address)。这个函数对字符串的操作就像关系运算符对数字的操作一样。特别的,如果两个字符串参数相同,它就返回0。strcmp()函数比较的是字符串,而不是数组。字符串不相同时,如果第一个字符串在字母表中的顺序先于第二个字符串,strcmp()返回一个负数;如果两个字符串相同,它返回0;如果第一个字符串在字母表中的顺序落后于第二个字符串,它返回一个正数。确切的数值依赖于不同的C实现。如果两个字符串中初始的字符相同,strcmp()函数会一直往后查找,直到找到第一对不一致的字符。然后它就返回相应的值。strcmp()是按照机器编码顺序(collating sequence)进行比较的。这意味着字符的比较是根据它们的数字表示法,一般是ASCII值。在ASCII中,大写字母先于小写字母。因此,strcmp("Z","a")是负数。
strncmp()函数:
strcmp()函数比较字符串时,一直比较到找到不同的相应字符,搜索可能要进行到字符串结尾处。而strncmp()函数比较字符串时,可以比较到字符串不同处,也可以比较完由第三个参数指定的字符数。
strcpy()和strncpy()函数:
如果pts1和pts2都是指向字符串的指针,则下面的表达式只复制字符串的地址而不是字符串本身:
pts2=pts1;
如果确实希望复制字符串,可以使用strcpy()函数。strcpy()函数在字符串运算中的作用等价于赋值运算符。它接受两个字符串指针参数,将第二个参数指向的字符串复制到第一个参数指向的字符串数组中。
strcpy()是char*类型,返回第一个参数的值,即一个字符的地址。第一个参数不需要指向数组的开始,这样就可以只复制数组的一部分。
strcpy()和gets()函数同样都有一个问题,那就是都不检查目标字符串是否容纳的下源字符串。复制字符串使用strncpy()比较安全,它需要第三个参数来指明最大可复制的字符数。
sprintf()函数:
在stdio.h而不是string.h中声明,它的作用和printf()一样,但是它写到字符串里而不是写到输出显示。因此,它提供了把几个元素组合成一个字符串的一种途径。sprintf()的第一个参数是目标字符串的地址,其余的参数和printf()一样:一个转换说明字符串,接着是要写的项目的列表。
可以将ctype.h中的函数应用于字符串中的字符。
命令行参数
现代图形界面出现之前是命令行界面。DOS和UNIX就是例子。命令行(command line)是在一个命令行环境下,用户输入的用于运行程序的行。命令行参数(command-line argument)是同一行中的附加项。如下例:
% fuss -r Ginger
C程序通过使用mian()函数的参数读取这些附加项为自己所用:
/* repeat.c——带有参数的main()函数*/
#include<stdio.h>
int main(int argc, char *argv[])
{
intcount;
printf("The command line has %d arguments: \n",argc-1);
for(count=1;count<argc;count++)
priintf("%d: %s\n",count argv[count]);
printf("\n");
return 0;
}
把这个程序便以为可执行文件repeat;下面是从命令行运行该程序的结果:
C>repeat Resistance is futile
THe command line has 3 arguments:
1: Resistance.
2: is
3: futile
C编译器允许main()没有参数,或者有两个参数。有两个参数时,第一个参数是命令行中的字符串数。按照惯例,这个int参数被称为argc(代表argument count)。系统使用空格判断一个字符串结束、另一个字符串开始。第二个参数是一个指向字符串的指针数组。命令行中的每个字符串被存储到内存中,并且分配一个指针指向它。按照惯例,这个指针数组被称为argv(代表argument value)。如果可以(有些操作系统不允许),把程序本身的名字赋值给argv[0],接着把随后的第一个字符串赋给argv[1],等等。
也可以用这种方式声明argv:
int main(int argc, char **argv)
atoi(代表alphanumeric to integer)函数以字符串为参数,返回相应的整数值。这个函数在stdlib.h中定义。此外还有atof()和atol()函数,分别将字符串转换为double和long类型值。更复杂版本:strtol()、strtoul()、strtod()。它们还可以识别并报告字符串中非数字部分的第一个字符,前两者允许指定数字的基数。
long strtol(const char *nptr, char **endptr, int base);//函数原型
****************************************************
以后坚决少写废话!
推荐阅读
-
C Primer Plus 第十一章——字符串和字符串函数
-
C Primer Plus 第十章——数组和指针
-
C语言:利用指针和函数调用编写字符串拷贝函数strcpy
-
c语言中的 strcpy和strncpy字符串函数使用介绍
-
9、试编写程序,输入一个字符串,再输入一个字符ch,将字符串中所有的ch字符替换为字符’*’。 要求定义和调用函数mChar(s, c ),该函数将字符串s中出现的所有c字符替换为’*’。
-
字符串比较strcmp(s1,s2)和*s1==*s2区别和c++比较函数区别
-
C Primer Plus 第4章 字符串和格式化输入输出--4.3 常量和预处理器
-
C语言实现strlen函数和字符串遍历
-
C语言:利用指针和函数调用编写字符串拷贝函数strcpy
-
c语言中的 strcpy和strncpy字符串函数使用介绍