2.字符串输入
如果要把一个字符串读入程序,必须先预留存储该字符串的空间,然后用输入函数获取该字符串。
2.1分配空间
预先分配空间则意味着必须为字符串分配足够使用的空间大小,你可能写过char *name; scanf("%s", name);
,虽然可能编译器会通过,但是会给出警告,因为scanf函数将信息拷贝至参数指定的地址上,而此时参数是一个未被初始化的指针,name可能会指向任何地方,则可能会擦除程序中的数据或代码,从而导致程序异常终止。最简单的分配空间方式,就是显示指明数组的大小char name[81];
,还有一种方式则是通过C库函数来分配内存。
2.2gets()函数(C11标准已废除)
在为字符串分配空间之后,便可读入字符串,除了前面介绍的scanf()函数,我们再了解两个读取字符串的函数:fgets()函数和gets()函数。
在读取字符串时,scanf函数和%s转换说明只能读取一个单词,因为scanf函数除了%c转换说明,都以空白符作为输入隔断。但通常我们需要整行读入,在过去gets()函数用来处理这种情况,gets函数简单易用,它读取整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在这些字符结尾加入一个空字符使其成为一个C字符串。它经常和puts()函数配对使用,puts函数用于显示字符串,并在末尾添加换行符。
但是puts()函数有一个十分要命的缺点,gets()函数只知道数组的开始处,并不知道数组到哪结束。如果输入的字符串过长,会导致缓冲区溢出,即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了已分配的尚未使用的内存,并不会立即引起很严重的影响,但是如果它们擦写掉了程序中的数据或代码,会导致程序异常终止,因此,在过去很多人利用该函数插入和运行一些破坏系统安全的代码。虽然目前C标准已经废除了gets函数,但是不少编译器为了兼容以前的代码,大部分选择继续支持gets函数,因为只要gets函数使用得当,它的确是一个很方便的函数。
2.3gets()的替代品
2.3.1 fgets()函数
fgets()函数通过第二个参数限制读入字符数来解决溢出的问题。该函数专门设计用于处理文件输入,fgets()函数与gets()函数的区别:1.fgets函数有第二个参数限制读入字符串的最大长度。如果参数是n,则fgets函数最多读入n-1个字符,或者遇到第一个换行符。2.如果fgets函数遇到一个换行符,会把它存储到字符串当中,而gets函数不会。3.fgets函数的第三个参数指明要读入的文件。如果读入从键盘输入的数据,则以stdin作为参数,该标识符定义在stdio.h中。
因为fgets把换行符读入,通常与fputs()函数一起使用,fputs函数的第二个参数指明它要写入的文件。如果要显示在计算机的显示器上,使用stdout作为该参数。
2.3.2 gets_s()函数
C11新增的gets_s()函数与fgets()函数类似,用一个参数限制读入的字符数。但是gets_s函数只从标准输入(stdin)读取数据,所以不需要第三个参数;gets_s函数读到换行符时,会丢弃它而不是存储它;如果gets_s函数读到最大字符数都没有读到换行符,会执行一下几步:首先把目标数组的首字符设置为空字符,读取并丢弃随后的输入直至读到换行符或者文件结尾,然后返回空指针,接着调用依赖实现的“处理函数”,可能终止或退出函数。
3.字符串输出
3.1puts()函数
puts函数很容易使用,只需把字符串的地址作为参数传递给它即可,如指针、数组名、字符串常量等。puts函数在现实字符串时会自动在其末尾添加一个换行符,puts在遇到空字符时就停止输出。因此千万不要用puts函数输出输出一个普通的char数组。
3.2fputs()函数
fputs函数是puts函数针对文件定制的版本。它们之间的区别就是,fputs的第二个参数指明要写入数据的文件,并且fputs函数不会在输出的末尾添加换行符。
4.字符串函数
C库提供了多个处理字符串的函数,ANSI C把这些函数的原型放在string.h头文件中。
4.1strlen()函数
int strlen(const char* s);
返回字符串s的字符数,末尾的空字符除外。
4.2strcat()函数
char *strcat(char *restrict s1, const char *restrict s2);
把s2指向的字符串拷贝s1指向的字符串,s2指向的字符串第一个字符覆盖s1指向的字符串的空字符,并且该函数返回s1。
4.3strncat()函数
char *strncat(char *restrict s1, const char *restrict s2, size_t n);
与strcat函数类似,只不过strncat是将s2指向的第二个字符串的n个字符拷贝到第一个字符串,或者拷贝到遇到空字符。这是因为strcat函数无法检查第一个数组能否容纳第二个字符串,这就可能造成与gets函数相同的问题,于是通过strncat函数的第三个参数来限定最大拷贝字符数。
4.4strcmp()函数
int strcmp(const char *s1, const char *s2);
比较s1和s2指向的两个字符串。如果完全匹配,则两字符串相同,否则比较首次出现不匹配的字符对。通过字符编码值比较字符,如果两字符串相同,函数返回0;如果第一个字符串小于第二个字符串,函数返回小于0的值;如果第一个字符串大于第二个字符串,函数返回大于0的值。
4.5strncmp()函数
int strcmp(const char *s1, const char *s2, size_t n);
strncmp也与strcmp类似,strncmp函数只比较前n个字符,或者比较到第一个空字符为止。如何返回值与strcmp函数相同。
4.6strcpy()函数strncpy()函数
char *strcpy(char *restrict s1, const char *restrict s2);
把s2指向的字符串拷贝到s1指向的位置,函数返回s1。char *strcpy(char *restrict s1, const char *restrict s2, size_t n);
把s2指向的字符串的n个字符拷贝s1指向的位置,如果在拷贝到n个字符之前遇到空字符,则后面补充若干个空字符,使其长度为n;若拷贝n个字符并且没有遇到空字符,则不添加空字符,函数返回s1。
前面提过,指针表示法只是拷贝指向字符串的地址,而数组表示法则是拷贝的字符串副本。现在我们能够通过strcpy函数来拷贝整个字符串,拷贝出来的字符串被称为目标字符串,最初的字符串被称为源字符串。同时,第一个参数不必指向数组的开始。但是strcpy函数和strcat函数有相同的问题,它们都不能检查目标空间是否能容纳源字符串的副本。所以。拷贝字符串用strncpy函数更安全,根据strncpy函数的介绍,如果目标空间装不下副本,则可能导致字符串中不含空字符,因此需要将副本的最后一个元素设置为空字符,以确保存储的是一个字符串。
前面提到函数参数可以灵活声明为指针、数组和字符串常量,但是声明数组将分配存储空间,而声明指针只需要分配存储一个地址的空间。
4.7sprintf()函数
sprintf()函数声明在stdio.h中,而不是string.h头文件中。该函数与printf函数类似,但是它是把数据写入字符串,而不是打印在显示器上,因此该函数可以把很多个元素组合成一个字符串。sprintf()的第一个参数是目标字符串的地址,其余参数和printf()相同,即格式化字符串和待写入项的列表。