分类:
2009-05-27 17:36:45
字符串与数组与打印
在许多程序中,字符串(或字符数组)是很重要的一部分。在GNU C库中提供了广泛字符串工具函数,包括字符串复制、串联、比较、搜索。这些函数中的大多数都能够在内存的任何地方操作;例如,函数memcpy能够用于任何一种数组中拷贝内容。
通常,一些C语言的初学者是直接将这些复制到自己的代码中,但当他们越来越熟悉的时候,在使用这些函数时,通常考虑更多的是可维护性、效率和可移植性。
举例说,你可以在两行C代码中比较两个字符串,但如果你使用内置strcmp函数,你出错的机会会更少。同时,由于这些库函数是经过了优化的,所以使用它们会使你的程序运行得更快。
5.1字符串表示法
这个小节是对一个C程序设计初学者做一个关于字符串概念的概要介绍。这里将说明在C语言中如何表示一个字符串,以及一些通常的缺陷。如果你已经很熟悉它们,可以跳过这一小节。
一 个字符串是字符数组。但字符串变量通常被说明成一个字符型指针char *。这种字符串变量不包括空格符,它将被存话在内存中的某个地方,或者是一个常量,或者是一个动态分配的内存。在内存中的地址将存入这个指针变量,当然,你也可以将一个空指针存入指针变量。空指针没有指向任何东西,所以用空指针表示 字符串将出错。
按习惯,在字符串后面加上一个空字符“。例如,你想测试字符型指针p是不是指向一个以空字符结束字符串,你可以使用!*p或*p=’ '。
一个空字符与一个空指针在概念上是很不同的,尽管它们都表示为整数0。
字符串在C语言源程序中,将其包含在双引号(" ")之内。在ISO C中,"a" "b"与"ab"相同。在GNU C中不允许修改字符串中的内容,因为它是被存放为只读的。
字符数组如果声明为const,则不能修改。而通常更好的方法是将其定义为const char *类型,因为这样,C编译器会检测到想修改这个字符串的企图。
通常字符数组在分配内存时,会加上一个用于存放空字符串(这个空字符串用来标识字符串结束)。在这个文档中,内存分配大小(allocation size)用来表示这个字符串所占用的内存总和,而长度(length)则用来表示不包括空字符在内的总长度。
尝试往字符串变量中存放超过其分配大小的字符是一个声名狼籍的程序错误。当你在编写程序时,请一定要提起注意。有许多库函数可以帮助你完成这个任务。另外,请牢记,你需要为表示字符串终了的空字符留下一个内存空间。
5.2字符串与数组函数的惯例
这一小节将描述所有基于任意数组或任意内存块的函数,讲述这些函数的一些通性的东西。
对 任意内存块操作的函数的名字都是以mem开头的(如memcpy),并且总是带上一个参数,用来指定所操作的内存块大小。这些函数返回 void *类型的数组,数组的成员是bytes型的。你可以传送任何一种类型的指针给这些函数,而且可以使用sizeof操作符来计算它的大小。
相 对应的,专门处理字符串的函数则是以str开头的(如strcpy),这些函数都是以用空字符结束的字符串代替显式的size参数。(虽然有些函数也接受指定的最大长度,但它们同样要检测结束字符串的空字符。这个函数返回char *类型的数组,这些数组的成员是char型的。
5.3字符串长度
我们可以使用strlen函数获得一个字符串的长度。这个函数在头文件string.h中声明。
函数:size_t strlen(const char *s)
这个函数将返回以空字符结束的字符串s的长度。
例如:
strlen ("hello, world") (值为12,加上一个空字符)
当用于字符数组时,strlen函数将返回已存放的字符串内存分配大小,而非整个数组的内存分配大小。我们可以使用sizeof操作符来获取字符数组的内存分配大小:
char string[32] = "hello, world";
sizeof (string) (其值为32)
strlen (string) (其值为12,加上一个空字符)
5.4复制与联接字符串
我们可以使用这一小节中的函数复制字符串和字符数组中的内容,或者将一个字符串的内容附加到另一个字符串中去。这些函数在头文件string.h中声明:
有一个规律可以帮助你记住这一小节中所有函数的参数位置:目标数组都在源数组的左边!所有的函数都返回目标数组的地址。
如果源数组与目标数组或在物理地址上有重迭的话,大多数函数都无法正常地工作。例如,如果目标数组的头与源数组的尾在物理地址上重迭,那么源数组中的一部分内容就会在复制之前被覆盖。甚至可能出现使得标识字符串结束的空字符丢失,而使拷贝无休止地无意义地进行着。
所有会因为这个问题而出错的函数,都会在本手册中显式标识出来。除了这个小节里的函数外,还有sprintf函数(参见格式化输出函数章节)以及scanf函数(参见格式化输入函数章节)。
函数:void * memcpy (void *to, const void *from, size_t size)
这个函数会从数组from的开头处,复制size个字符,并从头写入数组to中。这个函数的参数from和to如果是有重迭的两个数组,那么就会出错。这种情况,请使用memmove函数来代替它。
Memcpy函数返回to的地址;
以下是一个使用memcpy复制一个数组中内容的例子:
struct foo *oldarray, *newarray;
int arraysize;
...
memcpy (new, old, arraysize * sizeof (struct foo));
函数:void * memmove (void *to, const void *from, size_t size)
从from中拷贝size字节到to中,即使这两个内存块有重迭也不影响执行效果。
函数:void * memccpy (void *to, const void *from, int c, size_t size)
这个函数将从form中拷贝不超过size字节到to中去,在拷贝中如果有一个字节与c匹配,就中止拷贝。该函数将返回一个指针。如果在拷贝时发现了c,这个指针指向to,如果没有,就是一个空指针。
函数:void * memset (void *block, int c, size_t size)
该函数将复制size个c(转换成为char型)到block中,返回block的值。
函数:char * strcpy (char *to, const char *from)
复制字符串from到字符串to中去。就象memcpy一样,这个函数也无法处理两个字符串有重迭部分的情况。该返回to的值。
函数:char * strncpy (char *to, const char *from, size_t size)
这个函数与strcpy类似,区别在于strncpy将精确地拷贝size字节到to中去。
如果from的长度大于size,那么strncpy仅拷贝from中的前size个字符。要注意的是,这种情况下,将不会往to中写入表示结束的空字符。
如果from的长度小于size,那么strncpy将拷贝from中所有内容,然后加上一些空字符,使其够size字节。这是十分有用的,但仅在ISO C中可用。
Strncpy也无法正确处理from和to有重迭的情况。
使用strncpy代替strcpy可以避免尝试对to所分配空间之后的操作。但是,如果你要把一个较小的字符串,写到一个大的缓冲中,会使你的程序运行得更慢,因为这时由于size很大,程序花了许多时间做无谓的工作:填写大量的空字符。
函数:char * strdup (const char *s)
拷贝一个以空字符结束的字符串到一个新分配的字符串中。这个新字符串将自动调用malloc函数来分配。如果malloc函数无法为这个新字符串分配空间,strdup将返回一个空指针。如果能够为其分配空间,将返回这个新字符串的地址指针。
函数: char * strndup (const char *s, size_t size)
它与strdup类似,区别在于strndup将复制不多于size字节的字符到新字符串中。
如果s的长度大于size,那么strndup将复制size个字符,再添上一个空字符串。否则,就会完整地被复制。
这个函数与strncpy不同的是,它将保证拷贝出来的字符串一定有表示结束的空字符。
函数:char * stpcpy (char *to, const char *from)
与strcpy类似,区别在于,stpcpy将返回一个指向字符串to结尾的指针(也就是,指向那个表示结束的空字符),而不指向字符串的开始。
例如,下面这个程序使用stpcpy将’foo’和’bar’连接成为’foobar’,然后再打印出来:
#include
#include
int
main (void)
{
char buffer[10];
char *to = buffer;
to = stpcpy (to, "foo");
to = stpcpy (to, "bar");
puts (buffer);
return 0;
}
这个函数不属于ISO和POSIX标准,所以并不经常在UNIX系统上使用,但别自己去创建这个函数,因为可能在MS-DOS中就有。
这个函数也无法正确处理两个字符串有重迭部分的情况。
函数:char * stpncpy (char *to, const char *from, size_t size)
它与stpcpy类似,区别在于,stpncpy将精确地只拷贝size个字符。
如果字符串from的长度比size大,那么stpncpy将只复制前size个字符,然后返回一个指向最后拷贝的那个字符的地址。要注意的是,在这种情况下,最后并没有一个表示字符串结束的空字符。
如果from的长度小于size,那么,stpncpy将拷贝from中的所有字符串,然后在后面加上(size-form长度)个空字符。它将返回一个指向第一个空字符的指针。
这个函数并不属于ISO和POSIX标准,但却在GNU C库的开发经常用到。
它也无法正确地处理源、目标两个字符串有重迭部分的情况。
函数:char * strdupa (const char *s)
它与strdup函数十分类似,区别在于,它不使用malloc来分配一个新字符串,而是使用alloc来分配。
很明显,strdupa仅适合于宏,也就是你无法获得这个函数的地址。虽然有这个限制,但还是有它有用的一面。下面这个程序说明的有时使用malloc将付出更大的代价。
#include
#include
#include
const char path[] = _PATH_STDPATH;
int
main (void)
{
char *wr_path = strdupa (path);
char *cp = strtok (wr_path, ":");
while (cp != NULL)
{
puts (cp);
cp = strtok (NULL, ":");
}
return 0;
}
请注意直接使用目录调用strtok是非法的。
这个函数仅在GNU C中能够使用。
函数:char * strndupa (const char *s, size_t size)
完成功能与strndup类似,不过它像strdupa一样使用alloca来为新字符串分配内存空间。它的优缺点与strdupa一样。
这个函数也只能用于宏,因为无法得到这个函数的地址。
它仅在GNU C中能够使用。
函数:char * strcat (char *to, const char *from)
与strcpy类似,区别在于,它是将字符串from中的内容增加到字符串to的后面。而不是覆盖字符串to。字符串from中的第一个字符将覆盖掉to中原来最后的空字符。
以下是一个与strcat等价的定义:
char *
strcat (char *to, const char *from)
{
strcpy (to + strlen (to), from);
return to;
}
它无法正确处理from与to两个字符串有重迭部分的情况。
函数:char * strncat (char *to, const char *from, size_t size)
它与strcat类似,区别在于附加到字符串to后面的字符将不超过size个。在to的最后将保证有一个空字符,也就是说添加后,总的分配空间将新增size+1。
管理员在2009年8月13日编辑了该文章文章。