String 字符串
String 字符串
这里将讨论 C 风格字符串,你可能已经在数组章节中见过。实际上,C 风格字符串本质上是字符数组,只是带有一点特殊的调料来指示字符串的结束位置。这里将涵盖一些用于处理字符串的工具——比如复制它们、连接它们以及获取它们的长度。
String 是什么
请注意,除了 C 风格字符串(数组),还有字符串字面量,例如"this"。实际上,这两种字符串类型在内存中都是字符的简单集合。它们唯一的区别在于字符串字面量不可修改,而数组可以修改。接受 C 风格字符串的函数在字符串未修改的情况下会接受字符串字面量(如果修改字符串,则程序会崩溃)。有些看似字符串的内容并非字符串;特别是单引号内的字符,如'a',不是字符串。它只是一个字符,可以赋值给字符串中的特定位置,但不能作为字符串处理。(还记得数组在传递给函数时表现得像指针吗?字符则不会,所以如果你将单个字符传递给函数,它将不起作用;函数期待的是 char*,而不是 char。)
总结:字符串是字符的数组。字符串字面量是被双引号包围的单词。
"This is a static string"C 风格字符串总是以空字符终止,字面意思是'\0'字符(值为 0),所以要声明一个 49 个字母的字符串,需要额外考虑一个字符,所以你会想要这样声明:
char string[50];这将声明一个长度为 50 个字符的字符串。不要忘记数组索引从 0 开始,而不是 1。此外,我们用空字符(字面意思是'\0')考虑了额外字符。重要的是要记住字符串末尾会有一个额外字符,就像句子末尾总是有点号一样。由于这个字符串终止符是不可打印的,它不被计为字母,但它仍然占据一个空间。实际上,在一个 50 个字符的数组中,你只能容纳 49 个字母和一个末尾的空字符来终止字符串。
注意,像
char *my_string;这样的内容也可以用作字符串。如果你读过指针章节,你可以做类似这样的操作:
arry = malloc( sizeof(*arry) * 256 );
这允许你像访问数组一样访问 arry。要释放你分配的内存,只需使用 free:
free ( arry );使用字符串
字符串适用于存储各种长输入。如果你要让用户输入他的或她的名字,你必须使用字符串。使用 scanf()输入字符串是可行的,但它会在读取到第一个空格后终止字符串,而且,因为 scanf 不知道数组有多大,当用户输入的字符串长度超过字符串的大小(作为输入“缓冲区”)时,它会导致“缓冲区溢出”。
处理这个问题有几种方法,但最简单和最安全的方法可能是使用 fgets 函数,该函数在 stdio.h 中声明。
fgets 函数的原型是:
char *fgets (char *str, int size, FILE* file);这里有几个新东西。首先,让我们澄清关于那个古怪的 FILE* 指针的问题。存在这个指针的原因是 fgets 应该能够从磁盘上的任何文件中读取,而不仅仅是从用户的键盘(或其他“标准输入”设备)。暂时,每当我们调用 fgets 时,我们只需传递一个名为 stdin 的变量,该变量在 stdio.h 中定义,它指向“标准输入”。这实际上告诉程序从键盘读取。fgets 的其他两个参数,str 和 size,分别是存储从输入读取的数据的位置和 char* str 的大小。最后,当 fgets 成功从输入读取时,它会返回 str。
当 fgets 实际从用户读取输入时,它会读取最多 size - 1 个字符,然后在最后一个读取的字符后放置空终止符。fgets 会读取输入,直到没有更多空间存储数据或用户按下回车键。请注意,fgets 可能会填满为 str 分配的整个空间,但它永远不会返回一个非空终止符的字符串。
让我们看一个使用 fgets 的例子,然后我们会讨论一些需要留意的陷阱。
例如:
#include <stdio.h>
int main()
{
/* 一个很长的字符串 */
char string[256];
printf( "Please enter a long string: " );
/* 注意stdin */
fgets ( string, 256, stdin );
printf( "You entered a very long string, %s", string );
getchar();
}记住,当你传递 string 时,你实际上是在传递数组的地址,因为数组不需要地址运算符(&)来传递它们的地址,所以数组 string 中的值会被修改。
在使用 fgets 时需要注意的一点是,它会读取输入并在字符串中包含换行符('\n'),除非字符串中没有空间存储它。这意味着你可能需要手动移除输入。一种方法是在字符串中搜索换行符,然后将其替换为空终止符。这会是什么样子?在查看下面的内容之前,试着想出一种方法来实现它:
char input[256];
int i;
fgets( input, 256, stdin );
for ( i = 0; i < 256; i++ )
{
if ( input[i] == '\n' )
{
input[i] = '\0';
break;
}
}在这里,我们只是遍历输入,直到找到换行符,然后将其替换为空终止符。注意,如果输入长度小于 256 个字符,用户必须按下了回车键,这会在字符串中包含换行符!(顺便说一句,除了这个例子之外,还有其他方法可以解决这个问题,这些方法使用了 string.h 库中的函数。)
使用 string.h 操作 C 字符串
string.h 是一个包含许多字符串操作函数的头文件。其中之一是字符串比较函数。
int strcmp ( const char *s1, const char *s2 );strcmp 将接受两个字符串。它将返回一个整数。这个整数可能是:
/*
* 负数,如果s1小于s2;
* 零,如果s1等于s2;
* 正数,如果s1大于s2;
*/strcmp 执行区分大小写的比较;如果字符串除了大小写不同外完全相同,则被视为不同。Strcmp 还将字符数组的地址传递给函数,以便访问。
char *strcat ( char *dest, const char *src );strcat 是"字符串连接"的缩写;连接是一个花哨的词,意思是添加到末尾或追加。它将第二个字符串追加到第一个字符串。它返回连接后字符串的指针。注意这个函数;它假设 dest 足够大,可以容纳 src 的全部内容以及它自己的内容。
char *strcpy ( char *dest, const char *src );strcpy 是"字符串复制"的缩写,意思是它将 src 的全部内容复制到 dest。Strcpy 后 dest 的内容将与 src 完全相同,以至于 strcmp(dest, src) 将返回 0。
size_t strlen ( const char *s );strlen 将返回字符串的长度,减去终止字符('\0')。Size_t 无需担心。只需将其视为一个不能为负的整数,实际上它就是如此。(Size_t 类型只是表示该值用于表示某个大小的目的。)
这里是一个使用了许多之前描述的函数的小程序:
#include <stdio.h> /* stdin, printf, and fgets */
#include <string.h> /* 对于所有字符串函数 */
/* 这个函数用于从使用fgets输入的字符串末尾删除换行符。
注意,由于我们把它变成了它自己的函数,我们可以很容易地选择一种更好的技术来删除换行符。
函数不是很棒吗? */
void strip_newline( char *str, int size )
{
int i;
/* 删除空终止符 */
for ( i = 0; i < size; ++i )
{
if ( str[i] == '\n' )
{
str[i] = '\0';
/* 我们完成了,所以只要通过返回退出函数 */
return;
}
}
/* 如果我们一直到这里,肯定没有换行符 */
}
int main()
{
char name[50];
char lastname[50];
char fullname[100]; /* 数组足够大,可以容纳全名 */
printf( "请输入您的姓名: " );
fgets( name, 50, stdin );
/* 参见上面的定义 */
strip_newline( name, 50 );
/* 当两个字符串相等时,strcmp返回0 */
if ( strcmp ( name, "Alex" ) == 0 )
{
printf( "那也是我的名字.\n" );
}
else
{
printf( "那不是我的名字.\n" );
}
// 找出你名字的长度
printf( "你的名字有 %d 个字这么长\n", strlen ( name ) );
printf( "输入您的姓: " );
fgets( lastname, 50, stdin );
strip_newline( lastname, 50 );
fullname[0] = '\0';
/* strcat会寻找\0然后把第二个字符串接在那个位置 */
strcat( fullname, name ); /* 复制 name 到 full name */
strcat( fullname, " " ); /* 用空格分隔名字 */
strcat( fullname, lastname ); /* 复制 lastname 到 fullname 最后面 */
printf( "你的全名是 %s\n",fullname );
getchar();
return 0;
}安全编程
上述字符串函数都依赖于字符串末尾存在空终止符。这并不总是安全的保证。此外,其中一些函数,尤其是 strcat,依赖于目标字符串能够容纳附加到末尾的整个字符串。尽管你可能永远不会犯这种错误,但历史上,在像 strcat 这样的函数中意外写越界数组的问题一直是主要问题。
幸运的是,在 C 的设计者无穷的智慧中,他们包含了旨在帮助你避免这些问题的函数。类似于 fgets 以缓冲区能容纳的最大字符数来处理的方式,存在一些字符串函数,它们接受一个额外的参数来指示目标缓冲区的长度。例如,strcpy 函数有一个类似的 strncpy 函数。
char *strncpy ( char *dest, const char *src, size_t len );它只会从 src 复制 len 个字节到 dest(len 应该小于 dest 的大小,否则写入仍然可能超出数组的边界)。不幸的是,strncpy 可能会导致一个烦人的问题:它不保证 dest 会有一个空终止符附加(如果 src 的字符串比 dest 长,这种情况可能会发生)。你可以通过使用 strlen 来获取 src 的长度,并确保它能够适应 dest 来避免这个问题。当然,如果你打算那样做,那么你一开始可能就不需要 strncpy,对吧?错误。现在它迫使你必须注意这个问题,这是战斗中非常重要的一部分。
