花了两天的时间总结了Linux编程时的字符串操作函数和字节序列操作函数,以便后续查阅。
这些函数大都不会去检查传入的参数是否为NULL,因此在使用之前要自己做检查,否则后果你懂的。
一个基本知识点:
字符串处理中,如strcpy,字符串都是以’\0’来判断结束的。
字节序列处理中,如memcpy,操作内存字节,不在乎’\0’或其他字符。
下列函数基本都包含在头文件string.h中,如果不是会特别指出。
#include <string.h>
–
下面开始介绍,给出函数定义并解释作用和用法。
strlen
size_t strlen(const char *s);
计算字符串长度(不包括’\0’)。
strnlen
#define _GNU_SOURCE
size_t strnlen(const char *s, size_t maxlen);
计算字符串长度(不包括’\0’)。但如果字符串长度超过maxlen,返回maxlen。
strcpy
char *strcpy(char *dest, const char *src);
字符串拷贝,将src指向的字符串内容拷贝到dest,拷贝到’\0’为止(包含’\0’)。如果src的字符串长度超过dest提供的空间,则结果不可预期,例如可能发生栈溢出或者覆盖其他变量内容等。
strncpy
char *strncpy(char *dest, const char *src, size_t n);
作用同strcpy,拷贝到’\0’为止,最多拷贝n个字符(不计’\0’)。即,如果strlen(src) < n,则拷贝src后,dest剩余字节都用’\0’填充;如果strlen(src) >= n,则只拷贝n个字符,并且这时需要注意的是,dest就没有地方放’\0’了,因此我们在拷贝n字符时,给dest定义的大小为n+1字节。
另外,strncpy的效率肯定比strcpy低,如果注重效率问题,可以在保证不会越界的前提下使用strcpy。
memcpy
void *memcpy(void *dest, const void *src, size_t n);
将src指向的内存的前n字节拷贝到dest。不能处理src和dest内存有重叠的情况。函数返回指向dest的指针。
bcopy
void bcopy(const void *src, void *dest, size_t n);
该函数类似memcpy,但是src和dest的参数位置不同,能够处理内存区域重叠的情况。已经deprecated,不要用了。
memmove
void *memmove(void *dest, const void *src, size_t n);
将src指向的内存的前n字节拷贝到dest。能够处理src和dest内存有重叠的情况。函数返回指向dest的指针。
memccpy
void *memccpy(void *dest, const void *src, int c, size_t n);
从src拷贝n字节到dest,但是碰到字符c就停止(c会拷贝到dest),函数返回dest中字符c后面的位置的指针,如果在src的n个字节内没有找到c,则返回NULL。
strdup
char *strdup(const char *s);
复制一份字符串s。在函数里面会有malloc操作来为新字符串申请空间,然后拷贝字符串s(包括’\0’)到新字符串。该函数返回新字符串的指针,如果无法申请到内存,则返回NULL(errno=ENOMEM)。
因为有malloc,因此,在用完新字符串指针后,要记得free()掉。
strndup
char *strndup(const char *s, size_t n);
同strdup,最多拷贝n个字符,如果strlen(s)>=n,则新字符串后面会再补一个’\0’。
strdupa,strndupa
char *strdupa(const char *s);
char *strndupa(const char *s, size_t n);
同strdup和strndup,只是申请内存用的是alloca,即在栈上分配内存,这样在用完之后无需free。可用在那种需要来回跳的函数(例如使用longjmp())避免内存泄漏。
strcmp
int strcmp(const char *s1, const char *s2);
比较s1和s2两个字符串的内容。相等则返回0,s1
strncmp
int strncmp(const char *s1, const char *s2, size_t n);
同strcmp,只是最多比较前n个字符。
strcoll
int strcoll(const char *s1, const char *s2);
用来适应系统的本地化需求(见setlocale()),将s1和s2用本地化的格式比较,在”POSIX or C locales”中strcoll和strcmp完全相同。
strxfrm
size_t strxfrm(char *dest, const char *src, size_t n);
该函数本地化有关(见setlocale()),它将src转成本地化格式后,将前n个字符拷贝到dest中。
在比较两个字符串时,先对其进行strxfrm,再strcmp,和直接strcoll比较的效果完全相同。
bcmp
#include <strings.h>
int bcmp(const void *s1, const void *s2, size_t n);
相比strncmp,bcmp比较的是字节序列。如果s1和s2的前n个字节相同,返回0,否则返回非0。
strcasecmp,strncasecmp
#include <strings.h>
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
和strcmp/strncmp类似,只是比较时不区分大小写。
strcat
char *strcat(char *dest, const char *src);
将字符串src的内容拼接到dest中原字符串的后面(从’\0’的位置开始),在最后补一个’\0’。由于新的字符串也需要以’\0’结尾,因此要保证dest的空间大小至少是 dest原字符串长度 + strlen(src) + 1。函数返回指向dest的指针,即拼接后整个字符串的指针。
strncat
char *strncat(char *dest, const char *src, size_t n);
同strcat,只是最多拷贝n个字符到dest中。由于函数的拼接完成后会在新字符串后面补一个’\0’,所以如果strlen(src)>=n,你也不用操心。同样的,要保证dest的空间大小至少是 dest原字符串长度 + n + 1。
index
#include <strings.h>
char *index(const char *s, int c);
在字符串s中查找字符c第一次出现的位置。函数返回指向字符c的指针。如果没找到就返回NULL。查找’\0’也会成功,返回指向’\0’的指针。
rindex
#include <strings.h>
char *rindex(const char *s, int c);
在字符串s中查找字符c最后一次出现的位置,即反向查找。函数返回指向字符c的指针。如果没找到就返回NULL。查找’\0’也会成功,返回指向’\0’的指针。
strchr,strrchr
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
和index/rindex完全相同。只是需要包含的头文件不同。
strchrnul
#define _GNU_SOURCE
char *strchrnul(const char *s, int c);
同strchr。但是在没找到c的时候,会返回指向s末尾空字符即’\0’的指针,而不是NULL。
memchr,memrchr
void *memchr(const void *s, int c, size_t n);
#define _GNU_SOURCE
void *memrchr(const void *s, int c, size_t n);
在s的前n个字节中查找字符c,找到就返回指向字符c的指针,否则返回NULL,memrchr则反向查找。这两个函数和strchr/strrchr的区别是不受’\0’或其他字符的限制,操作的是内存字节。
strstr,strcasestr
char *strstr(const char *haystack, const char *needle);
#define _GNU_SOURCE
char *strcasestr(const char *haystack, const char *needle);
strstr()在字符串haystack中查找子串needle(不匹配’\0’),如果找到,返回needle第一次出现的位置的指针,如果没找到则返回NULL。
strcasestr()做同样的事情,但在比较时忽略大小写。
strfry,memfrob
#define _GNU_SOURCE
char *strfry(char *string);
将string中的字符随机变换位置,生成新的字符串。修改是在原字符串中做的,因此要确保string不是常量,函数返回指向新串即string的指针。
类似的函数还有:
void *memfrob(void *s, size_t n);
它是将内存s的前n个字节做类似加密的动作,改变前n个字节的内容,该函数只用来隐藏一段字节序列,返回指向新串即s的指针。再使用memfrob()可以把加密后的串还原。
strspn
size_t strspn(const char *s, const char *accept);
给定一个字符集合accept,从左至右遍历s中的字符,看是否在accept中,直到遍历到s的某个字符不在accept中停止。函数返回已遍历且位于accept中的字符个数。简单来说就是,计算s开头有多少个连续字符都位于accept数组中。
例如:
strspn(“youryu”, “loveyou”);返回3,因为’y”o”u’在第二个参数中都能找到。
strspn(“hello”, “leho”);返回5,因为整个字符串的字符在第二个参数中都能找到。
strspn(hello, “”);返回0,因为第二个参数是空串,肯定什么都找不到。
strspn(“helloa”, “leho\0a”);返回5,第二个参数实际上是”leho”。
strcspn
size_t strcspn(const char *s, const char *reject);
它是在s中去匹配连续不在参数reject中的字符个数。例如strcspn(“hateyou”, “loveyou”);返回3。
strsep
#define _BSD_SOURCE
char *strsep(char **stringp, const char *delim);
将stringp指向的字符串,用delim中的分隔符切割成多个子串。具体做法是:在stringp的字符串中查找分隔符delim,如果找到,则分隔符的位置改为’\0’,函数返回值指向被分割的前半部分,即stringp的原始地址,而stringp重新指向后半部分,即’\0’后面一个字符的位置。如果没找到分隔符,则函数返回stringp字符串的原内容,stringp赋值为NULL。
例如:
const char * oristr = "In the galaxy far far away.";
char * seps = NULL;
char oriarray[64] = {0};
char * orip = oriarray;
//char * orip = (char *) malloc (64);
strcpy(oriarray, oristr);
do {
seps = strsep(&orip, " md"); //以空格或'm'或'd'为分隔符
printf("orip = %s(%#x), seps = %s(%#x)\n", orip, orip, seps, seps);
} while (orip != NULL);
//free(orip);
通过打印结果可以看出其运行过程:
[root@ubuntu]my_code:$ ./a.out
orip = the galaxy far far away.(0xbfb2efaf), seps = In(0xbfb2efac)
orip = galaxy far far away.(0xbfb2efb3), seps = the(0xbfb2efaf)
orip = far far away.(0xbfb2efba), seps = galaxy(0xbfb2efb3)
orip = far away.(0xbfb2efbe), seps = far(0xbfb2efba)
orip = away.(0xbfb2efc2), seps = far(0xbfb2efbe)
orip = (null)(0), seps = away.(0xbfb2efc2)
需要注意的是,
1.由于要修改stringp(地址改变且分隔符位置被改成’\0’),所以stringp不能是常量字符串,我的例子中可以传入一个malloc的指针,或者一个指向字符数组的指针,但不能直接传&oriarray。
2.如果stringp的原串是malloc得来的,那么一定要保留好起始指针,例如例子中如果使用malloc的代码就会出问题,因为orip指针已经变了。不过一般实际使用时是malloc之后传形参到子函数。
3.另外,参数delim的意思是分隔符的集合,分隔符是一个字符,例子中传入的是” md”,即’ ‘, ‘m’, ‘d’都是分隔符,在stringp中找到任何一个都会停下来做分割操作。那么就会出现这种情况:例如”great”用”ae”分割则会产生”gr”,”“,”t”三个子串,而”great”用”t”分割则会产生”grea”和”“两个子串。可见strsep可能会分割出一个空串。
strtok
char *strtok(char *str, const char *delim);
将字符串str,用delim中的分隔符切割成多个子串,原str中的分隔符位置被改为’\0’。函数每次返回一个子串。在分割一个字符串string时,要求除第一次调用将string传给参数str,后续每次调用要传NULL给参数str。
使用该函数需要注意:
1.strtok的实现里会维护一个静态变量来保存第一次传入的str:后续如果发现传入str是NULL,就知道要继续处理上次的字符串;如果传入的str不为NULL,则知道要处理一个新的字符串。因此,strtok不是线程安全的,如果一个线程在分割字符串,而同一进程里另一个线程要处理另一个字符串,就会混乱。
2.如果str是malloc得来的,那么一定要备份一个指针,以便用完后释放。因为strtok后没有办法拿到原串和剩余串。
3.和strsep的一点不同是,strtok不会产生空串。例如”its a great day!”用”ae”做分隔符,就会产生”its “,” gr”,”t d”, “y!”,原串中间的”ea”是两个连续的分隔符,这时在strtok中相当于做了两次分割才返回的。
例如:
const char * oristr = "In the galaxy far far away.";
char * seps = NULL;
char oriarray[64] = {0};
char * orip = oriarray;
strcpy(orip, oristr);
do {
seps = strtok(orip, " ");
if (seps)
printf("%s\n", seps);
orip = NULL;
} while (seps);
通过打印结果可以看出其运行过程:
[root@ubuntu]my_code:$ ./a.out
In
the
galaxy
far
far
away.
strtok_r
char *strtok_r(char *str, const char *delim, char **saveptr);
该函数为strtok的可重入版本,主要是增加了第三个参数来保存每次剩余的字符串(即分隔符后面的字符串)。同样的,在分割一个字符串string时,要求除第一次调用将string传给参数str,后续每次调用要传NULL给参数str。而参数saveptr在第一次调用时被忽略,后续每次被赋值为指向尚未处理的部分。
这样在多线程中使用strtok_r,使用不同的saveptr指针即可。
例如:
const char * oristr = "In the galaxy far far away.";
char * seps = NULL;
char oriarray[64] = {0};
char * orip = oriarray;
char * savep;
strcpy(orip, oristr);
do {
seps = strtok_r(orip, " ", &savep);
if (seps)
printf("%s, remain=%s\n", seps, savep);
orip = NULL;
} while (seps);
通过打印结果可以看出其运行过程:
[root@ubuntu]my_code:$ ./a.out
In, remain=the galaxy far far away.
the, remain=galaxy far far away.
galaxy, remain=far far away.
far, remain=far away.
far, remain=away.
away., remain=
strpbrk
char *strpbrk(const char *s, const char *accept);
在字符串s中匹配accept中的任一字符,函数返回s中第一个匹配到的字符的指针。例如
char * strp = strpbrk("In the galaxy far far away.", "aeu");
printf("%s\n", strp);
结果为”e galaxy far far away.”。
memset
void *memset(void *s, int c, size_t n);
将s的前n个字节都赋值为c中一字节的值。返回指向s的指针。注意参数c虽然是int类型,但实际只取字节,例如c=0xab1d,则s的每个字节会被赋值为0x1d(是否与大小端有关有待验证)。
bzero
#include <strings.h>
void bzero(void *s, size_t n);
将s的前n个字节都赋值为’\0’,已经deprecated。