linux c string
目录
string.h
其实在C语言的标准库中,也有非常丰富的字符串操作函数。当然了,由于C语言中 字符串 并不是基本数据类型,也没有 类 这个概念,相对来说操作上可能没有Python/Java之类的语言方便。不过了解一下C语言中的字符串操作还是有意义的。
C语言中的字符串操作函数在 string.h 中,不过要了解都有什么函数,阅读string.h并不是什么好的方式。如果是在Unix/Linux系统下,只要执行:
man string
就可以了解都有哪些字符串操作函数了,如下图所示:
其中一些常用的函数及其大致功能如下(具体细节后面再细说):
字符串拷贝
stcpy, strncpy
字符串比较
strcmp, strncmp, strcasecmp, strncasecmp
字符串连接
strcat, strncat
字符查找
strchr, strrchr, strchrnul, strpbrk
建立字符串副本
strdup, strndup, strdupa, strndupa
字符串分割
strsep, strtok, strtok_r
字符串匹配
strstr
下面根据功能的不同来展示各个函数的用法。这里我会用一些实例来进行示范,同时,其结果由org-babel对代码块求值得到。
字符串拷贝(strcpy, strncpy)
strcpy
函数原型:
char *strcpy(char *dest, const char *src);
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char dest[1024] = {0};
char *src = "abcde";
strcpy(dest, src);
printf("%s\n", dest);
return 0;
}
结果:
abcde
strcpy()函数会将源字符串中的结束符('\0')也拷贝到目的字符串中。
注意,strcpy()可能会导致溢出。
strncpy
函数原型:
char *strncpy(char *dest, const char *src, size_t n);
该函数从源字符串中拷贝n个字符到目的字符串;如果源字符串长度不足,则用 NULL 填充,以保证将n个字符写入目的字符串中;如果源字符串中前n个字符不包含字符串结束符,函数不会为目的字符串添加上结束符。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char dest[7] = {0};
char *src = "abcde";
dest[5] = 'A';
strncpy(dest, src, 5);
printf("%s\n", dest);
return 0;
}
结果:
abcdeA
所以如果有需要,应该在拷贝后自己在目的字符串尾部添加结束符。
字符串比较(strcmp, strncmp, strcasecmp, strncasecmp)
strcmp
函数原型:
intstrcmp(const char *s1, const char *s2);
如果s1小于s2,函数返回一个负数;如果s1等于s2,函数返回0;否则返回一个正数。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s1 = "abcde";
char *s2 = "abcef";
char *s3 = "ad";
printf("compare(%s, %s) -> %d\n", s1, s2, strcmp(s1, s2));
printf("compare(%s, %s) -> %d\n", s1, s3, strcmp(s1, s3));
printf("compare(%s, %s) -> %d\n", s2, s3, strcmp(s2, s3));
return 0;
}
结果:
compare(abcde, abcef) -> -1
compare(abcde, ad) -> -2
compare(abcef, ad) -> -2
从这个结果可以发现,strcmp()是根据字典序来对字符串进行比较的。进一步的,还可以发现strcmp()的返回值是比较过程中最后一次比较时两个字符的值的差,如比较"abcde"和"abcef",有:
a - a = 0
b - b = 0
c - c = 0
d - e = -1 #按字典序,大小已分,不再比较
strncmp
函数原型:
intstrncmp(const char *s1, const char *s2, size_t n);
和strcmp()的区别是,strncmp()只对s1和s2的前n个字节进行比较。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s1 = "abcde";
char *s2 = "abcfg";
char *s3 = "abd";
printf("compare(%s, %s, 4) -> %d\n", s1, s2, strncmp(s1, s2, 4));
printf("compare(%s, %s, 2) -> %d\n", s1, s3, strncmp(s1, s3, 2));
printf("compare(%s, %s, 3) -> %d\n", s2, s3, strncmp(s2, s3, 3));
return 0;
}
结果:
compare(abcde, abcfg, 4) -> -2
compare(abcde, abd, 2) -> 0
compare(abcfg, abd, 3) -> -1
strcasecmp
函数原型:
intstrcasecmp(const char *s1, const char *s2);
strcasecmp()也是用来比较字符串的,和strcmp()有两点区别:
- 使用strcasecmp()应该包含 strings.h 而不是 string.h
- strcasecmp()在比较时不区分大小写
示例:
#include <stdio.h>
#include <string.h>
#include <strings.h>
int main(int argc, char *argv[])
{
char *s1 = "AbcdE";
char *s2 = "abcdE";
printf("compare(%s, %s) with case -> %d\n", s1, s2, strcmp(s1, s2));
printf("compare(%s, %s) ignore case -> %d\n", s1, s2, strcasecmp(s1, s2));
return 0;
}
结果:
compare(AbcdE, abcdE) with case -> -32
compare(AbcdE, abcdE) ignore case -> 0
strncasecmp
strncasecmp()之于strcasecmp()就如strncmp()之于strcmp(),不再赘述。
字符串连接(strcat, strncat)
strcat
函数原型:
char *strcat(char *dest, const char *src);
strcat()首先会覆盖掉目的字符串的结束符,然后把源字符串的内容追加到后面,并在最后添加结束符。如果目的字符串缓冲区长度不够,将导致溢出。
strcat()在操作完成后,返回目的字符串的首地址,这样可以方便地进行链式操作。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char dest[1024] = "hello ";
char *src = "world!";
printf("%s\n", strcat(dest, src));
return 0;
}
结果:
hello world!
strncat
函数原型:
char *strncat(char *dest, const char *src, size_t n);
strncat()将最多n个字节的内容追加到目的字符串尾部,并且会在追加后添加终止符号。
同strcat()一样,它返回目的字符串的首地址。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char dest[1024] = "hello ";
char *src = "world!lkjsdljsd";
printf("%s\n", strncat(dest, src, 6));
return 0;
}
结果:
hello world!
字符查找(strchr, strrchr, strchrnul, strpbrk)
strchr
函数原型:
char *strchr(const char *s, int c);
strchr()返回一个字符指针,指向指定字符在指定字符串中第一次出现的位置。如果在指定字符串中没有找到指定字符,则返回 NULL 。该函数的第二个参数按理来说应当是一个字符,不过标准库中确实是int类型。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "hello world!";
char c = 'l';
printf("%s\n", strchr(s, c));
return 0;
}
结果:
llo world!
strrchr
函数原型:
char *strrchr(const char *s, int c);
strrchr()和strchr()类似,但它返回的是指定字符在指定字符串中最后一次出现的位置。如果未找到,同样返回 NULL 。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "hello world!";
char c = 'l';
printf("%s\n", strrchr(s, c));
return 0;
}
结果:
ld!
strchrnul
函数原型:
char *strchrnul(const char *s, int c);
strchrnul()的功能和strchr()只有细微的区别,那就是,当没有找到指定字符时,strchrnul()不返回 NULL ,而是返回字符串结束符的位置。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "abcde";
char c = 'm';
printf("%p, %p\n", s, strchrnul(s, c));
return 0;
}
结果
0x40065c, 0x400661
这里由于strchrnul()的特性,没办法通过打印字符串来了解strchrnul()的操作,不过观察这两个指针的值,会发现:
0x400661 - 0x40065c = 0x5
而字符串s的第六个元素(从0开始,5即第六个),正好是结束符。
strpbrk
函数原型:
char *strpbrk(const char *s, const char *accept);
strpbrk()和strchr()的区别在于,strchr()是从字符串里搜索 一个字符 ,而strpbrk()则是在字符串里搜索 一个字符集中的字符 ,看第二个参数就明白了。strpbrk()遍历字符串,如果发现某个字符在指定的 字符集 中,则立即返回指向该字符的指针。如果最后没有找到任何在指定字符集中的字符,则返回 NULL 。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "Hello World!";
char *accept = "Wo";
printf("%s\n", strpbrk(s, accept));
return 0;
}
结果:
o World!
字符串分割(strtok, strtok_r, strsep)
strtok
函数原型:
char *strtok(char *str, const char *delim);
strtok()根据第二个参数指定的分隔符(可能存在多个不同的分隔符)将指定字符串分割成多个子串。通过多次调用strtok(),可以依次获得字符串的多个子串的首地址。要注意的是,除了第一次调用时将待分割字符串作为第一个参数,后续的调用要将第一个参数置为 NULL 。当字符串已经无法再分割时,strtok()返回 NULL 。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char s[1024] = "abc;lsdk:lskdj,;slsj";
char *delm = ";:,";
char *result = NULL;
int len = strlen(s);
int i = 0;
result = strtok(s, delm);
while (result != NULL) {
printf("Source:%s, Sub:%s\n", s, result);
result = strtok(NULL, delm);
}
return 0;
}
结果:
Source:abc Sub:abc
Source:abc Sub:lsdk
Source:abc Sub:lskdj
Source:abc Sub:slsj
除了上面说过的strtok()的用法外,还要注意的是,作为待分割的字符串,它必须是 可更改的 。否则虽然可以通过编译,但运行会出错。要理解这个现象,首先要了解strtok()的内部机制。
了解其机制,没必要去寻找其实现源代码,只要对它的操作过程进行剖析就知道了。先看下面的代码:
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{
char s[64] = "To be or not to be";
char *delm = " ,."; /* 分隔符:空格 \*/
char *result = NULL;
int i = 0, len = strlen(s);
for (i = 0; i < len; ++i) { /* 逐个打印s中的字符 \*/
printf("%c ", s[i]);
}
printf("\n");
for (i = 0; i < len; ++i) { /* 逐个打印s中字符的数值 \*/
printf("%d ", (int)s[i]);
}
printf("\n");
result = strtok(s, delm);
while (result != NULL) { /* 观察s中字符数值的变化 \*/
for (i = 0; i < len; ++i) {
printf("%d ", (int)s[i]);
}
printf("\n");
result = strtok(NULL, delm);
}
return 0;
}
结果:
col 1 | col 2 | col 3 | col 4 | col 5 | col 6 | col 7 | col 8 | col 9 | col 10 | col 11 | col 12 | col 13 | col 14 | col 15 | col 16 | col 17 | col 18
----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------
T | o | | b | e | | o | r | | n | o | t | | t | o | | b | e
84 | 111 | 32 | 98 | 101 | 32 | 111 | 114 | 32 | 110 | 111 | 116 | 32 | 116 | 111 | 32 | 98 | 101
84 | 111 | 0 | 98 | 101 | 32 | 111 | 114 | 32 | 110 | 111 | 116 | 32 | 116 | 111 | 32 | 98 | 101
84 | 111 | 0 | 98 | 101 | 0 | 111 | 114 | 32 | 110 | 111 | 116 | 32 | 116 | 111 | 32 | 98 | 101
84 | 111 | 0 | 98 | 101 | 0 | 111 | 114 | 0 | 110 | 111 | 116 | 32 | 116 | 111 | 32 | 98 | 101
84 | 111 | 0 | 98 | 101 | 0 | 111 | 114 | 0 | 110 | 111 | 116 | 0 | 116 | 111 | 32 | 98 | 101
84 | 111 | 0 | 98 | 101 | 0 | 111 | 114 | 0 | 110 | 111 | 116 | 0 | 116 | 111 | 0 | 98 | 101
84 | 111 | 0 | 98 | 101 | 0 | 111 | 114 | 0 | 110 | 111 | 116 | 0 | 116 | 111 | 0 | 98 | 101
可以看到,s中的分隔符,逐次地被置为'\0'即字符串结束符。这就是strtok()分割字符串的内部原理了。而strtok()返回的指针,其实就是s中各个子串的起始位置了。如果s指向的内容是无法被修改的,那么strtok()自然也就无法将原先的分隔符置为字符结束符了。
当然了,由于源字符串会被修改,在实际中,如果需要,可以用strdup()来建立一个源字符串的副本。
strtok_r
函数原型:
char *strtok_r(char *str, const char *delim, char **saveptr);
strtok_r()是Linux下的strtok()的可重入版本(线程安全版本),它比strtok()多了一个参数 saveptr ,这个参数用于在分割字符串时保存上下文。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char s[64] = "Hello world";
char *delm = " ";
char *result = NULL, *ptr = NULL;
printf("Source:%p\n", s);
result = strtok_r(s, delm, &ptr);
while (result != NULL) {
printf("Result:%p\t", result);
printf("Saveptr:%p\n", ptr);
printf("---%s\t", result);
printf("---%s\n", ptr);
result = strtok_r(NULL, delm, &ptr);
}
return 0;
}
结果:
Source:0x7fff180f3de0
Result:0x7fff180f3de0 Saveptr:0x7fff180f3de6
---Hello ---world
Result:0x7fff180f3de6 Saveptr:0x7fff180f3deb
---world ---
可以看到,saveptr这个指针在每次调用strtok_r()后就指向了未分割的部分的首地址。相对地,strtok()则是在内部有一个静态缓冲区,通过这个静态缓冲区来记录未处理的起始位置,所以strtok()不是线程安全的。
strsep
函数原型:
char *strsep(char **stringp, const char *delim);
strsep()同样是字符串分割函数,它和strtok()的不同之处在于,它会直接修改待分割的指针的值,让它始终指向未处理部分的起始位置。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char s[64] = "To be or not to be";
char *source = s;
char *delm = " ";
char *result = NULL;
while (source != NULL) {
printf("Source:%s | ", source);
result = strsep(&source, delm);
printf("result:%s\n", result);
}
return 0;
}
结果
Source:To be or not to be | result:To
Source:be or not to be | result:be
Source:or not to be | result: or
Source:not to be | result: not
Source:to be | result: to
Source:be | result: be
因为和strtok()的这个不同之处,strsep不需要区分第一次调用后后续的连续调用,可以用统一的操作来对字符串进行分割。
字符串匹配(strstr)
函数原型:
char *strstr(const char *haystack, const char *needle);
strstr()返回字符串needle在字符串haystack中第一次出现的位置;如果没有匹配,则返回 NULL 。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "To be or not to be.";
char *p = "be";
printf("%s\n", strstr(s, p));
return 0;
}
结果:
be or not to be.
字符串副本创建(strdup, strndup, strdupa, strndupa)
strdup
函数原型:
char *strdup(const char *s);
strdup()调用malloc()分配一块内存并将字符串s的内容拷贝进去,产生s的副本。要注意的是,在最后应该调用free()来释放副本。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "abcde";
char *dup = strdup(s);
printf("%s\n", dup);
free(dup);
return 0;
}
结果:
abcde
strndup
函数原型:
char *strndup(const char *s, size_t n);
strndup()和strdup()类似,但最多只拷贝s的前n个字节。如果s的长度大于n,还会在副本后添加终止符。
示例:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *s = "abcde";
char *dup = strndup(s, 4);
printf("%s\n", dup);
free(dup);
return 0;
}
结果:
abcd
strdupa
函数原型:
char *strdupa(const char *s);
strdupa()和strdup()类似,但在分配内存时,它使用alloca()而不是malloc()。
strndupa
函数原型:
char *strndupa(const char *s, size_t n);
strndupa()之于strdupa()就如strndup()之于strdup(),不再赘述。