字符串处理集的函数定义在<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没有非常丰富的函数库,但是只要掌握这些常用的,那么稍微复杂的字符串处理也可以使用这些函数去实现。总比从头开始写会高效很多。