字符串处理集的函数定义在<string.h>头文件中,该头文件定义了size_t类型和空指针NULL宏。该头文件提供了一些分析和操作字符串的函数,其中的一些函数以更通用的方式处理内存。本文一一介绍。
1. 函数
1.1 memchr &&strchr
函数原型:
void *memchr(const void *s, int c, size_t n); void *memrchr(const void *s, int c, size_t n); void *rawmemchr(const void *s, int c); char *strchr(const char *s, int c); char *strrchr(const char *s, int c); /* 反向搜索 */
函数功能:
- memchr :该函数在s指向的对象的开始n个字符中搜索c第一次出现的位置;并返回指向第一次出现处的指针,如果没有找到则返回NULL。注意,s中的每个元素和c都被当做unsigned char类型处理。
- memrchr :该函数与memchr函数功能类似,不过他会从s指向的对象开始的n个字符中反向搜索来找c第一次出现的位置。
- rawmemchr :该函数与memchr类似,但它没有n这个参数。该函数假设我们已经可以确定c一定在s中的某个位置。因此他省去了n这个参数来提高效率。但如果s中没有c,则结果是不可预测的。
- strchr && strrchr 与memchr && memrchr功能类似,不过只能用于字符串。
看下面这个例子:
#include <stdio.h> #include <string.h> int main() { char *str = "Hello World!"; char *loc1, *loc2, *loc3; loc1 = loc2 = loc3 = NULL; loc1 = memchr(str, 'o', 8); loc2 = memrchr(str, 'o', 8); loc3 = rawmemchr(str, 'o'); printf("&str[0]:%pn", &str[0]); printf("loc1:%p,value:%cn",loc1, *loc1); printf("loc2:%p,value:%cn",loc2, *loc2); printf("loc3:%p,value:%cn",loc3, *loc3); return 0; }
编译运行结果:
&str[0]:0x400794 loc1:0x400798,value:o loc2:0x40079b,value:o loc3:0x400798,value:o
1.2 memcmp && strcmp && strcoll
函数原型:
int memcmp(const void *s1, const void *s2, size_t n); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); int strcoll(const char *s1, const char *s2);
函数功能:
- memcmp 按照字典顺序比较s1与s2指向的内存处的前n个字符,把每个值都当做unsigned char类型处理。相同返回0,s1大于s2返回大于0的值,否则返回小于0的值。
- strcmp && strncmp 与memcmp功能类似,不过前两者只能比较字符串,而memcmp对比较者的类型没有要求。
- strcmp通过检测字符串结束符来找到停止检测的位置;strncmp比较s1与s2的前n个字节,如果提前遇到了字符串结束符,也停止比较。
- strcoll 与strcmp类似,不过该函数使用由当前场景的LC_COLLATE类别所指定的编码顺序进行字符串比较,LC_COLLATE是由setlocale( )函数设置的。
1.3 memcpy && memmove
函数原型:
void *memcpy(void *dest, const void *src, size_t n); void *memmove(void *dest, const void *src, size_t n);
函数功能:
两个函数都是从将src处的n个字节数据拷贝到dest处。不同的是memcpy函数不允许dest和src有重叠的地址(如果有重叠地址,结果未知),但memmove函数允许:因为memmove函数先将src拷贝到一个临时位置,然后再从这个临时位置拷贝到dest处。
1.4 memset
函数原型:
void *memset(void *s, int c, size_t n);
函数功能:
这个函数我们经常用,很熟悉,就是将s指向的内存处的n个字节初始化为c(把c拷贝到s处)。返回值为s。
1.5 strcat && strncat
函数原型:
char *strcat(char *dest, const char *src); char *strncat(char *dest, const char *src, size_t n);
函数功能:
这两个函数都是将src串接到dest后面,src会覆盖dest的字符串结束符,并且两个函数都会在串接后的字符串后面添加一个字符串结束符。函数返回dest。需要注意以下几个问题:
- strcat 并没有规定将src中多少个字符串接到dest后面,而是通过检测src中的字符串结束符来确定何时停止拷贝,所以使用strcat时必须保证src包含字符串结束符。
- strncat 通过参数n来指定将src中多少个字符串接到dest后面,但是并非一定是n个,停止串接的条件是:遇到字符串结束符或者串接了n个字符,优先满足的决定串接的字符数目。
- 不论是strcat还是strncat,一定要保证dest有足够的空间来容纳串接后的字符串,否则结果是不可预测的。
- 从安全角度来说,尽量使用strncat而不是strcat。
1.6 strcpy && strncpy
函数原型:
char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n);
函数功能:
strcpy将src拷贝到dest处,包括src的字符串结束符。src必须包含字符串结束符,因为拷贝以此作为拷贝的结束标志。strncpy将src的n个字节拷贝到dest处,但是并非一定是n个,停止拷贝的条件是:遇到字符串结束符或者拷贝了n个字符,优先满足的决定串接的字符数目。需要注意以下几点:
- dest必须有足够的空间来容纳要拷贝的内容,否则结果未知。
- 对于strncpy,如果拷贝的src的n个字节中没有字符串结束符,那最终得到的字符串将不包含字符串结束符。如果src的长度小于n,将用NULL填充使总长度达到n。
- 从安全角度考虑,尽量使用strncpy而不是strcpy。
1.7 strxfrm
函数原型:
size_t strxfrm(char *dest, const char *src, size_t n);
函数功能:
函数转换src中的字符串,并把包括字符串结束符在内的前n个字符拷贝到dest指向的数组中;转换的标准是两个转换后的字符串按与strcmp( )相同的顺序放置,就向strcoll( )放置未转换的字符串那样;函数返回转换后的字符串长度(不包括字符串结束符)。
1.8 strspn && strcspn && strpbrk
函数原型:
size_t strspn(const char *s, const char *accept); size_t strcspn(const char *s, const char *reject); char *strpbrk(const char *s, const char *accept);
函数功能:
- strspn返回s中完全由accept中的字符组成的最大起始段的长度。
- strcspn返回不包含reject中任何字符的s的最大起始段的长度。
- strpbrk返回一个指针,它指向s中第一个与accept中任何字符相同的字符位置,如果没有一个相同则返回NULL。
1.9 strstr
函数原型:
char *strstr(const char *haystack, const char *needle);
函数功能:
函数返回一个指针,它指向haystack中第一次出现needle中字符序列(不包括字符串结束符)的位置,如果没有匹配的则返回NULL。
1.10 strerror
函数原型:
char *strerror(int errnum);
函数功能:
返回指向对应于存储在errnum中的错误号的错误信息字符串的指针,这个字符串是依赖于实现的。
1.11 strlen
函数原型:
size_t strlen(const char *s);
函数功能:
strlen用于求字符串的长度,与sizeof的三个区别:
- strlen是函数,sizeof是操作符
- strlen计算的字符串长度不包括字符串结束符(''),而sizeof计算字符串长度时包括字符串结束符
- strlen只能用于计算字符串的长度,sizeof可用于任何类型
1.12 strtok
函数原型:
char *strtok(char *str, const char *delim); char *strtok_r(char *str, const char *delim, char **saveptr);
函数功能:
这两个该函数的使用比较复杂,我们先描述一下,然后结合例子来看。strtok函数把字符串str分为单独的元组(包含字符串结束符的字符串元组),delim包含着被用作元组分隔符的字符。这个函数是顺序调用的,对开始的调用,str应该指向要被分隔为元组的字符串。函数查找非分隔符之后的第一个元组分隔符(delim),并用一个字符串结束符来替换他。函数返回指向保存着第一个元组的字符串的指针。如果没有找到任何元组就返回NULL。要在字符串中找到更多的元组,要再次调用strtok,但是使用NULL作为第一个参数。每个调用都将返回指向下一个元组的指针,如果没有更多的元组就返回NULL。strtok_r是strtok的可重入版本,它使用新增加的参数saveptr来保存下一次将要使用的字符串。除此之外,与strtok用法相同。
我们先看一个使用strtok的例子:
#include <stdio.h> #include <string.h> int main(void) { char data[] = " C ist too#muchnfun!"; const char tokseps[] = " tn#"; char *pt; puts(data); printf("n"); pt = strtok(data, tokseps); while (pt) { puts(pt); pt = strtok(NULL, tokseps); } return 0; }
编译运行结果:
allan@ubuntu:workspace$ ./a.out C is too#much fun! C is too much fun!
我们再看一个man文档的strtok_r的例子:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { char *str1, *str2, *token, *subtoken; char *saveptr1, *saveptr2; int j; if (argc != 4) { fprintf(stderr, "Usage: %s string delim subdelimn", argv[0]); exit(EXIT_FAILURE); } for (j = 1, str1 = argv[1]; ; j++, str1 = NULL) { token = strtok_r(str1, argv[2], &saveptr1); if (token == NULL) break; printf("%d: %sn", j, token); for (str2 = token; ; str2 = NULL) { subtoken = strtok_r(str2, argv[3], &saveptr2); if (subtoken == NULL) break; printf(" --> %sn", subtoken); } } exit(EXIT_SUCCESS); }
编译后执行结果:
allan@ubuntu:workspace$ ./a.out Usage: ./a.out string delim subdelim allan@ubuntu:workspace$ ./a.out 'a/bbb///cc;xxx:yyy:' ':;' '/' 1: a/bbb///cc --> a --> bbb --> cc 2: xxx --> xxx 3: yyy --> yyy
从上面两个例子我们总结一下这两个函数使用时的一些关键点:
- 在使用时,为了获取多个元组,这两个函数一般都是需要多次调用的。第一次调用时str指向要分隔的字符串,后面再调用时,str为NULL。
- delim为一个字符数组,但我们使用的时候一个分隔符是一个数组。也就是说delim中可以包含多个分隔符。比如第一个例子中delim就包含四个分隔符:空格、t、n、#。那么函数是怎么判断的呢?出现任意一个分隔符就分隔字符串(用字符串结束符替换分隔符)。而且多个连续的分隔符将被认为是一个分隔符,比如第一个例子中is后面既有t又有空格,而且连续,但他俩被当成一个处理。
2. 总结
字符串处理是我们在写程序的时候经常会碰到的,而像C++、Java等高级语言都有非常完备的字符串处理函数库(类库/包)。虽然C没有非常丰富的函数库,但是只要掌握这些常用的,那么稍微复杂的字符串处理也可以使用这些函数去实现。总比从头开始写会高效很多。
评论已关闭