注册了自己的网站,自己创建网站赚钱,什么网站可以接室内设计做,如何添加网站代码本章介绍以下内容#xff1a; 函数#xff1a;gets()、gets_s()、fgets()、puts()、fputs()、strcat()、strncat()、strcmp()、strncmp()、strcpy()、strncpy()、sprintf()、strchr() 创建并使用字符串 使用C库中的字符和字符串函数#xff0c;并创建自定义的字符串函数 使用…本章介绍以下内容 函数gets()、gets_s()、fgets()、puts()、fputs()、strcat()、strncat()、strcmp()、strncmp()、strcpy()、strncpy()、sprintf()、strchr() 创建并使用字符串 使用C库中的字符和字符串函数并创建自定义的字符串函数 使用命令行参数 字符串是C语言中最有用、最重要的数据类型之一。虽然我们一直在使用字符串但是要学的东西还很多。C 库提供大量的函数用于读写字符串、拷贝字符串、比较字符串、合并字符串、查找字符串等。通过本章的学习读者将进一步提高自己的编程水平。
11.1 表示字符串和字符串I/O
字符串是以空字符\0结尾的char类型数组。
puts()函数也属于stdio.h系列的输入/输出函数。但是与printf()不同的是puts()函数只显示字符串而且自动在显示的字符串末尾加上换行符。
11.1.1 在程序中定义字符串
1.字符串字面量字符串常量
用双引号括起来的内容称为字符串字面量string literal也叫作字符串常量string constant。
双引号中的字符和编译器自动加入末尾的\0字符都作为字符串储存在内存中
从ANSI C标准起如果字符串字面量之间没有间隔或者用空白字符分隔C会将其视为串联起来的字符串字面量。
如果要在字符串内部使用双引号必须在双引号前面加上一个反斜杠\
字符串常量属于静态存储类别static storage class这说明如果在函数中使用字符串常量该字符串只会被储存一次在整个程序的生命期内存在即使函数被调用多次。用双引号括起来的内容被视为指向该字符串储存位置的指针。这类似于把数组名作为指向该数组位置的指针。
printf()将打印该字符串首字符的地址如果使用ANSI之前的实现可能要用%u或%lu代替%p
*space farers表示该字符串所指向地址上储存的值应该是字符串*space farers的首字符。
2.字符串数组和初始化
一种方法是用足够空间的数组储存字符串。
const char m1[40] Limit yourself to one lines worth.; const表明不会更改这个字符串。 这种形式的初始化比标准的数组初始化形式简单得多
注意最后的空字符。没有这个空字符这就不是一个字符串而是一个字符数组。
在指定数组大小时要确保数组的元素个数至少比字符串长度多1为了容纳空字符。所有未被使用的元素都被自动初始化为0这里的0指的是char形式的空字符不是数字字符0
让编译器计算数组的大小只能用在初始化数组时。如果创建一个稍后再填充的数组就必须在声明时指定大小。声明数组时数组大小必须是可求值的整数。在C99新增变长数组之前数组的大小必须是整型常量包括由整型常量组成的表达式。
char crumbs[n]; // 在C99标准之前无效C99标准之后这种数组是变长数组
字符数组名和其他数组名一样是该数组首元素的地址。
还可以使用指针表示法创建字符串
const char * pt1 Something is pointing at me.; 该声明和下面的声明几乎相同 const char ar1[] Something is pointing at me.;
pt1和ar1都是该字符串的地址。在这两种情况下带双引号的字符串本身决定了预留给字符串的存储空间。尽管如此这两种形式并不完全相同。
3.数组和指针
在数组形式中ar1是地址常量。不能更改ar1如果改变了ar1则意味着改变了数组的存储位置即地址。可以进行类似ar11这样的操作标识数组的下一个元素。但是不允许进行ar1这样的操作。递增运算符只能用于变量名前或概括地说只能用于可修改的左值不能用于常量。
该变量最初指向该字符串的首字符但是它的值可以改变。因此可以使用递增运算符。例如pt1将指向第 2 个字符o。
字符串字面量被视为const数据。由于pt1指向这个const数据所以应该把pt1声明为指向const数据的指针。这意味着不能用pt1改变它所指向的数据但是仍然可以改变pt1的值即pt1指向的位置。如果把一个字符串字面量拷贝给一个数组就可以随意改变数据除非把数组声明为const。
总之初始化数组把静态存储区的字符串拷贝到数组中而初始化指针只把字符串的地址拷贝给指针。
4.数组和指针的区别
char heart[] I love Tillie!; const char *head I love Millie!;
两者主要的区别是数组名heart是常量而指针名head是变量。
首先两者都可以使用数组表示法
其次两者都能进行指针加法操作
但是只有指针表示法可以进行递增操作
假设想让head和heart统一可以这样做 head heart; /* head现在指向数组heart */ 这使得head指针指向heart数组的首元素。 但是不能这样做 heart head; /* 非法构造不能这样写 */ 这类似于x 3;和3 x;的情况。赋值运算符的左侧必须是变量或概括地说是可修改的左值
另外还可以改变heart数组中元素的信息
数组的元素是变量除非数组被声明为const但是数组名不是变量
建议在把指针初始化为字符串字面量时使用const限定符
总之如果不修改字符串不要用指针指向字符串字面量。
5.字符串数组 图11.2 矩形数组和不规则数组
综上所述如果要用数组表示一系列待显示的字符串请使用指针数组因为它比二维字符数组的效率高。但是指针数组也有自身的缺点。mytalents 中的指针指向的字符串字面量不能更改而yourtalentsde 中的内容可以更改。所以如果要改变字符串或为字符串输入预留空间不要使用指向字符串字面量的指针
11.1.2 指针和字符串
显示两个指针的值。所谓指针的值就是它储存的地址。mesg 和 copy 的值都是0x0040a000说明它们都指向的同一个位置。因此程序并未拷贝字符串。语句copy mesg;把mesg的值赋给copy即让copy也指向mesg指向的字符串
11.2 字符串输入
如果想把一个字符串读入程序首先必须预留储存该字符串的空间然后用输入函数获取该字符串。
11.2.1 分配空间
最简单的方法是在声明时显式指明数组的大小 char name[81];
11.2.2 不幸的gets()函数
gets()函数简单易用它读取整行输入直至遇到换行符然后丢弃换行符储存其余字符并在这些字符的末尾添加一个空字符使其成为一个 C 字符串。它经常和 puts()函数配对使用该函数用于显示字符串并在末尾添加换行符。程序清单11.6中演示了这两个函数的用法。
问题出在 gets()唯一的参数是 words它无法检查数组是否装得下输入行
如果输入的字符串过长会导致缓冲区溢出buffer overflow即多余的字符超出了指定的目标空间。
11.2.3 gets()的替代品
1.fgets()函数和fputs()
fgets()函数的第2个参数指明了读入字符的最大数量。如果该参数的值是n那么fgets()将读入n-1个字符或者读到遇到的第一个换行符为止。 如果fgets()读到一个换行符会把它储存在字符串中。这点与gets()不同gets()会丢弃换行符。 fgets()函数的第3 个参数指明要读入的文件。如果读入从键盘输入的数据则以stdin标准输入作为参数该标识符定义在stdio.h中。
因为 fgets()函数把换行符放在字符串的末尾假设输入行不溢出通常要与 fputs()函数和puts()类似配对使用除非该函数不在字符串末尾添加换行符。fputs()函数的第2个参数指明它要写入的文件。如果要显示在计算机显示器上应使用stdout标准输出作为该参数。程序清单11.7演示了fgets()和fputs()函数的用法。
fputs()不在字符串末尾添加换行符
fputs()函数返回指向 char的指针。如果一切进行顺利该函数返回的地址与传入的第 1 个参数相同。但是如果函数读到文件结尾它将返回一个特殊的指针空指针null pointer。该指针保证不会指向有效的数据所以可用于标识这种特殊情况。在代码中可以用数字0来代替不过在C语言中用宏NULL来代替更常见如果在读入数据时出现某些错误该函数也返回NULL。
首先如何处理掉换行符一个方法是在已储存的字符串中查找换行符并将其替换成空字符 while (words[i] ! \n) // 假设\n在words中 i; words[i] \0; 其次如果仍有字符串留在输入行怎么办一个可行的办法是如果目标数组装不下一整行输入就丢弃那些多出的字符 while (getchar() ! \n) // 读取但不储存输入包括\n continue;
空字符或\0是用于标记C字符串末尾的字符其对应字符编码是0。由于其他字符的编码不可能是 0所以不可能是字符串的一部分。
空指针或NULL有一个值该值不会与任何数据的有效地址对应。通常函数使用它返回一个有效地址表示某些特殊情况发生例如遇到文件结尾或未能按预期执行。
空字符是整数类型而空指针是指针类型。两者有时容易混淆的原因是它们都可以用数值0来表示。但是从概念上看两者是不同类型的0。另外空字符是一个字符占1字节而空指针是一个地址通常占4字节。
2.gets_s()函数
gets_s()只从标准输入中读取数据所以不需要第3个参数。
如果gets_s()读到换行符会丢弃它而不是储存它。
如果gets_s()读到最大字符数都没有读到换行符会执行以下几步。首先把目标数组中的首字符设置为空字符读取并丢弃随后的输入直至读到换行符或文件结尾然后返回空指针。接着调用依赖实现的“处理函数”或你选择的其他函数可能会中止或退出程序。
当输入与预期不符时gets_s()完全没有fgets()函数方便、灵活。也许这也是gets_s()只作为C库的可选扩展的原因之一。鉴于此fgets()通常是处理类似情况的最佳选择。
3.s_gets()函数
读取整行输入并用空字符代替换行符或者读取一部分输入并丢弃其余部分。既然没有处理这种情况的标准函数我们就创建一个在后面的程序中会用得上。
丢弃输入行余下的字符保证了读取语句与键盘输入同步。
11.2.4 scanf()函数
scanf()更像是“获取单词”函数而不是“获取字符串”函数
无论哪种方法都从第1个非空白字符作为字符串的开始。如果使用%s转换说明以下一个空白字符空行、空格、制表符或换行符作为字符串的结束字符串不包括空白字符。如果指定了字段宽度如%10s那么scanf()将读取10 个字符或读到第1个空白字符停止先满足的条件即是结束输入的条件 图11.3 字段宽度和scanf()
scanf()函数返回一个整数值该值等于scanf()成功读取的项数或EOF读到文件结尾时返回EOF。
根据输入数据的性质用fgets()读取从键盘输入的数据更合适。例如scanf()无法完整读取书名或歌曲名除非这些名称是一个单词。scanf()的典型用法是读取并转换混合数据类型为某种标准形式。例如如果输入行包含一种工具名、库存量和单价就可以使用scanf()。否则可能要自己拼凑一个函数处理一些输入检查。如果一次只输入一个单词用scanf()也没问题。
在%s转换说明中使用字段宽度可防止溢出
11.3 字符串输出
11.3.1 puts()函数
puts()函数很容易使用只需把字符串的地址作为参数传递给它即可。
puts()在显示字符串时会自动在其末尾添加一个换行符。
用双引号括起来的内容是字符串常量且被视为该字符串的地址。另外储存字符串的数组名也被看作是地址。在第5个puts()调用中表达式str1[5]是str1数组的第6个元素rputs()从该元素开始输出。与此类似第6个puts()调用中str24指向储存pointer中i的存储单元puts()从这里开始输出。
puts()如何知道在何处停止该函数在遇到空字符时就停止输出所以必须确保有空字符。
由于dont缺少一个表示结束的空字符所以它不是一个字符串因此puts()不知道在何处停止。它会一直打印dont后面内存中的内容直到发现一个空字符为止。
11.3.2 fputs()函数
fputs()函数是puts()针对文件定制的版本。
fputs()函数的第 2 个参数指明要写入数据的文件。如果要打印在显示器上可以用定义在stdio.h中的stdout标准输出作为该参数。
与puts()不同fputs()不会在输出的末尾添加换行符。
line数组中的字符串显示在下一行
line数组中的字符串也显示在下一行
如果混合使用 fgets()输入和puts()输出每个待显示的字符串末尾就会有两个换行符。这里关键要注意puts()应与gets()配对使用fputs()应与fgets()配对使用。
11.3.3 printf()函数
printf()也把字符串的地址作为参数
printf()不会自动在每个字符串末尾加上一个换行符。因此必须在参数中指明应该在哪里使用换行符。例如 printf(%s\n, string); 和下面的语句效果相同 puts(string);
printf()的形式更复杂些需要输入更多代码而且计算机执行的时间也更长但是你觉察不到。
使用 printf()打印多个字符串更加简单。
11.4 自定义输入/输出函数
完全可以在getchar()和putchar()的基础上自定义所需的函数。
/* put1.c -- 打印字符串不添加\n */ #include stdio.h void put1(const char * string)/* 不会改变字符串 */ { while (*string ! \0) putchar(*string); }
用数组表示法编写这个函数稍微复杂些 int i 0; while (string[i]! \0) putchar(string[i]); 要为数组索引创建一个额外的变量。
许多C程序员会在while循环中使用下面的测试条件 while (*string)
使用带方括号的写法是为了提醒用户该函数处理的是数组。
用const char * string可以提醒用户实际参数不一定是数组。
11.5 字符串函数
ANSI C把这些函数的原型放在string.h头文件中。其中最常用的函数有 strlen()、strcat()、strcmp()、strncmp()、strcpy()和 strncpy() 还有sprintf()函数其原型在stdio.h头文件中
11.5.1 strlen()函数
fit()函数把第39个元素的逗号替换成\0字符。puts()函数在空字符处停止输出并忽略其余字符。然而这些字符还在缓冲区中下面的函数调用把这些字符打印了出来
所以put()显示该字符并继续输出直至遇到原来字符串中的空字符
11.5.2 strcat()函数
strcat()用于拼接字符串函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾并把拼接后形成的新字符串作为第1个字符串第2个字符串不变。strcat()函数的类型是char *即指向char的指针。strcat()函数返回第1个参数即拼接第2个字符串后的第1个字符串的地址。
11.5.3 strncat()函数
strcat()函数无法检查第1个数组是否能容纳第2个字符串
用strlen()查看第1个数组的长度
用strncat()该函数的第3 个参数指定了最大添加字符数。例如strncat(bugs, addon, 13)将把 addon字符串的内容附加给bugs在加到第13个字符或遇到空字符时停止
11.5.4 strcmp()函数
由于非零值都为“真”所以许多经验丰富的C程序员会把该例main()中的while循环头写成while (strcmp(try, ANSWER))
strcmp()函数只会比较try中第1个空字符前面的部分。所以可以用strcmp()比较储存在不同大小数组中的字符串
ASCII标准规定在字母表中如果第1个字符串在第2个字符串前面strcmp()返回一个负数如果两个字符串相同strcmp()返回0如果第1个字符串在第2个字符串后面strcmp()返回正数
如果两个字符串开始的几个字符都相同会怎样一般而言strcmp()会依次比较每个字符直到发现第 1 对不同的字符为止
char 类型实际上是整数类型所以可以使用关系运算符来比较字符
strcmp()函数比较字符串中的字符直到发现不同的字符为止这一过程可能会持续到字符串的末尾。而strncmp()函数在比较两个字符串时可以比较到字符不同的地方也可以只比较第3个参数指定的字符数
11.5.5 strcpy()和strncpy()函数
如果希望拷贝整个字符串要使用strcpy()函数
strcpy()函数相当于字符串赋值运算符
strcpy()第2个参数temp指向的字符串被拷贝至第1个参数qword[i]指向的数组中。拷贝出来的字符串被称为目标字符串最初的字符串被称为源字符串
target So long; /* 语法错误 */
strcpy()接受两个字符串指针作为参数可以把指向源字符串的第2个指针声明为指针、数组名或字符串常量而指向源字符串副本的第1个指针应指向一个数据对象如数组且该对象有足够的空间储存源字符串的副本。记住声明数组将分配储存数据的空间而声明指针只分配储存一个地址的空间
第一strcpy()的返回类型是 char *该函数返回的是第 1个参数的值即一个字符的地址。第二第 1 个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分
strcpy()把源字符串中的空字符也拷贝在内
strcpy()和 strcat()都有同样的问题它们都不能检查目标空间是否能容纳源字符串的副本。拷贝字符串用 strncpy()更安全该函数的第 3 个参数指明可拷贝的最大字符数
strncpy(target, source, n)把source中的n个字符或空字符之前的字符先满足哪个条件就拷贝到何处拷贝至target中
这样做确保储存的是一个字符串。如果目标空间能容纳源字符串的副本那么从源字符串拷贝的空字符便是该副本的结尾如果目标空间装不下副本则把副本最后一个元素设置为空字符。
11.5.6 sprintf()函数
sprintf()函数声明在stdio.h中而不是在string.h中
sprintf()的第1个参数是目标字符串的地址。其余参数和printf()相同即格式字符串和待写入项的列表
sprintf()的用法和printf()相同只不过sprintf()把组合后的字符串储存在数组formal中而不是显示在屏幕上
11.5.7 其他字符串函数
ANSI C库有20多个用于处理字符串的函数下面总结了一些常用的函数
11.6 字符串示例字符串排序
11.6.1 排序指针而非字符串
11.6.2 选择排序算法
选择排序算法selection sort algorithm 利用for循环依次把每个元素与首元素比较。如果待比较的元素在当前首元素的前面则交换两者。循环结束时首元素包含的指针指向机器排序序列最靠前的字符串。然后外层for循环重复这一过程这次从input的第2个元素开始。当内层循环执行完毕时ptrst中的第2个元素指向排在第2的字符串。这一过程持续到所有元素都已排序完毕
11.7 ctype.h字符函数和字符串
第7章中介绍了ctype.h系列与字符相关的函数。虽然这些函数不能处理整个字符串但是可以处理字符串中的字符
11.8 命令行参数
C编译器允许main()没有参数或者有两个参数一些实现允许main()有更多参数属于对标准的扩展。main()有两个参数时第1个参数是命令行中的字符串数量。过去这个int类型的参数被称为argc 表示参数计数(argument count)。系统用空格表示一个字符串的结束和下一个字符串的开始。因此上面的repeat示例中包括命令名共有4个字符串其中后3个供repeat使用。该程序把命令行字符串储存在内存中并把每个字符串的地址储存在指针数组中。而该数组的地址则被储存在 main()的第 2 个参数中。按照惯例这个指向指针的指针称为argv表示参数值[argument value]。
11.8.1 集成环境中的命令行参数
11.8.2 Macintosh中的命令行参数
11.9 把字符串转换为数字
数字既能以字符串形式储存也能以数值形式储存。把数字储存为字符串就是储存数字字符。例如数字213以2、1、3、\0的形式被储存在字符串数组中。以数值形式储存213储存的是int类型的值
C要求用数值形式进行数值运算如加法和比较。但是在屏幕上显示数字则要求字符串形式因为屏幕显示的是字符
如果需要整数可以使用atoi()函数用于把字母数字转换成整数该函数接受一个字符串作为参数返回相应的整数值
如果字符串仅以整数开头atio()函数也能处理它只把开头的整数转换为字符。例如 atoi(42regular)将返回整数42
在我们所用的C实现中如果命令行参数不是数字atoi()函数返回0。然而C标准规定这种情况下的行为是未定义的。因此使用有错误检测功能的strtol()函数马上介绍会更安全
stdlib.h头文件因为从ANSI C开始该头文件中包含了atoi()函数的原型。除此之外还包含了 atof()和 atol()函数的原型。atof()函数把字符串转换成 double 类型的值 atol()函数把字符串转换成long类型的值。atof()和atol()的工作原理和atoi()类似因此它们分别返回double类型和long类型
strtol()把字符串转换成long类型的值strtoul()把字符串转换成unsigned long类型的值strtod()把字符串转换成double类型的值。这些函数的智能之处在于识别和报告字符串中的首字符是否是数字。而且strtol()和strtoul()还可以指定数字的进制
long strtol(const char * restrict nptr, char ** restrict endptr, int base); 这里nptr是指向待转换字符串的指针endptr是一个指针的地址该指针被设置为标识输入数字结束字符的地址base表示以什么进制写入数字
首先注意当base分别为10和16时字符串10分别被转换成数字10和16。还要注意如果end指向一个字符*end就是一个字符。因此第1次转换在读到空字符时结束此时end指向空字符。打印end会显示一个空字符串以%d转换说明输出*end显示的是空字符的ASCII码
对于第2个输入的字符串当base为10时end的值是a字符的地址。所以打印end显示的是字符串atom打印*end显示的是a字符的ASCII码。然而当base为16时a字符被识别为一个有效的十六进制数strtol()函数把十六进制数10a转换成十进制数266
strtol()函数最多可以转换三十六进制a~z字符都可用作数字。strtoul()函数与该函数类似但是它把字符串转换成无符号值。strtod()函数只以十进制转换因此它值需要两个参数
许多实现使用 itoa()和 ftoa()函数分别把整数和浮点数转换成字符串。但是这两个函数并不是 C标准库的成员可以用sprintf()函数代替它们因为sprintf()的兼容性更好
11.10 关键概念
11.11 本章小结
C字符串是一系列char类型的字符以空字符\0结尾。字符串可以储存在字符数组中。字符串还可以用字符串常量来表示里面都是字符括在双引号中空字符除外。编译器提供空字符。因此joy被储存为4个字符j、o、y和\0。strlen()函数可以统计字符串的长度空字符不计算在内。 字符串常量也叫作字符串——字面量可用于初始化字符数组。为了容纳末尾的空字符数组大小应该至少比容纳的数组长度多1。也可以用字符串常量初始化指向char的指针。 函数使用指向字符串首字符的指针来表示待处理的字符串。通常对应的实际参数是数组名、指针变量或用双引号括起来的字符串。无论是哪种情况传递的都是首字符的地址。一般而言没必要传递字符串的长度因为函数可以通过末尾的空字符确定字符串的结束。 fgets()函数获取一行输入puts()和 fputs()函数显示一行输出。它们都是 stdio.h 头文件中的函数用于代替已被弃用的gets()。 C库中有多个字符串处理函数。在ANSI C中这些函数都声明在string.h文件中。C库中还有许多字符处理函数声明在ctype.h文件中。 给main()函数提供两个合适的形式参数可以让程序访问命令行参数。第1个参数通常是int类型的argc其值是命令行的单词数量。第2个参数通常是一个指向数组的指针argv数组内含指向char的指针。每个指向char的指针都指向一个命令行参数字符串argv[0]指向命令名称argv[1]指向第1个命令行参数以此类推。 atoi()、atol()和atof()函数把字符串形式的数字分别转换成int、long 和double类型的数字。strtol()、strtoul()和strtod()函数把字符串形式的数字分别转换成long、unsigned long和double类型的数字。