请问有人能解释一下 strtok()
和 strsep()
之间的区别吗?它们各自的优缺点是什么?
为什么我会选择其中一个而不是另一个。
strtok()
和strsep()
之间的一个主要区别是,strtok()
是标准化的(由C标准和因此也由POSIX标准化),而strsep()
没有被C或POSIX标准化;它在GNU C库中可用,并起源于BSD。因此,便携式代码更可能使用strtok()
而不是strsep()
。
另一个区别是,对不同字符串调用strsep()
函数可以交错进行,而您不能使用strtok()
来做到这一点(尽管您可以使用strtok_r()
)。因此,在库中使用strsep()
不会意外破坏其他代码,而在库函数中使用strtok()
必须进行文档记录,因为同时使用strtok()
的其他代码无法调用库函数。
strsep()
的手册页面在kernel.org上说:
因此,另一个主要区别是George Gaál在他的答案中强调的;strsep()函数被引入作为strtok(3)的替代,因为后者无法处理空字段。
strtok()
允许一个标记之间有多个分隔符,而strsep()
期望标记之间只有一个分隔符,并将相邻的分隔符解释为空标记。
strsep()
和strtok()
都会修改它们的输入字符串,也都不允许你确定哪个分隔符字符标记了标记的结尾(因为两者都会在标记结束后写入一个NUL '\0'
覆盖分隔符)。
strsep()
,并且当您不关心可移植性时。strtok_r()
(如果POSIX对您足够可移植)。strtok()
。并且您只会使用它足以使您摆脱生命威胁的时间;然后您将再次放弃所有使用它的方法。它是有毒的;不要使用它。编写自己的strtok_r()
或strsep()
比使用strtok()
更好。strtok()
是有毒的?strtok()
函数在库函数中使用时是有毒的。如果您的库函数使用了strtok()
,必须清楚地记录下来。
原因如下:
strtok()
并调用您使用strtok()
的函数,则会中断调用函数。strtok()
的函数,则会破坏您的函数对strtok()
的使用。strtok()
——跨一系列strtok()
调用。这个问题的根源是调用之间保存状态,使得strtok()
可以从上次离开的地方继续。没有明智的方法来解决这个问题,除了“不要使用strtok()
”。
strsep()
。strtok_r()
。strtok_s()
。strtok_s()
,但其接口与strtok_r()
和Microsoft的strtok_s()
都不同。BSD strsep()
:
char *strsep(char **stringp, const char *delim);
POSIX strtok_r()
:
char *strtok_r(char *restrict s, const char *restrict sep, char **restrict state);
微软 strtok_s()
:
char *strtok_s(char *strToken, const char *strDelimit, char **context);
附录 K strtok_s()
:
char *strtok_s(char * restrict s1, rsize_t * restrict s1max,
const char * restrict s2, char ** restrict ptr);
strtok()
变体中的3个参数。strtok()
和strsep()
的第一个区别在于它们处理输入字符串中连续定界符的方式。
strtok()
处理连续定界符字符的方式为:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
const char* delims = " -"; // delimiters - space and hyphen character
char* token;
char* ptr = strdup(teststr);
if (ptr == NULL) {
fprintf(stderr, "strdup failed");
exit(EXIT_FAILURE);
}
printf ("Original String: %s\n", ptr);
token = strtok (ptr, delims);
while (token != NULL) {
printf("%s\n", token);
token = strtok (NULL, delims);
}
printf ("Original String: %s\n", ptr);
free (ptr);
return 0;
}
输出:
# ./example1_strtok
Original String: aaa-bbb --ccc-ddd
aaa
bbb
ccc
ddd
Original String: aaa
"bbb"
和 "ccc"
紧挨着出现。 strtok()
不会指示连续的分隔符字符的出现。此外,strtok()
会修改输入字符串。
strsep()
处理连续分隔符字符:#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
const char* delims = " -"; // delimiters - space and hyphen character
char* token;
char* ptr1;
char* ptr = strdup(teststr);
if (ptr == NULL) {
fprintf(stderr, "strdup failed");
exit(EXIT_FAILURE);
}
ptr1 = ptr;
printf ("Original String: %s\n", ptr);
while ((token = strsep(&ptr1, delims)) != NULL) {
if (*token == '\0') {
token = "<empty>";
}
printf("%s\n", token);
}
if (ptr1 == NULL) // This is just to show that the strsep() modifies the pointer passed to it
printf ("ptr1 is NULL\n");
printf ("Original String: %s\n", ptr);
free (ptr);
return 0;
}
输出:
# ./example1_strsep
Original String: aaa-bbb --ccc-ddd
aaa
bbb
<empty> <==============
<empty> <==============
ccc
ddd
ptr1 is NULL
Original String: aaa
<empty>
表示)在bbb
和ccc
之间。这两个空字符串是为了"--"
而存在的。当strsep()
在"bbb"
后找到定界符字符' '
时,它将分隔符字符替换为'\0'
字符并返回"bbb"
。此后,strsep()
发现另一个定界符字符'-'
。然后它会将分隔符字符替换为'\0'
字符并返回空字符串。下一个定界符字符也是如此。strsep()
返回指向空字符的指针(即值为'\0'
的字符)时,表示连续的定界符字符。
strsep()
修改输入字符串以及作为第一个参数传递的指针的指针。strtok()
依赖于静态变量来跟踪字符串中当前解析位置。此实现需要在开始第二个字符串之前完全解析一个字符串。但是,strsep()
不需要这样做。strtok()
未完成时调用strtok()
:#include <stdio.h>
#include <string.h>
void another_function_callng_strtok(void)
{
char str[] ="ttt -vvvv";
char* delims = " -";
char* token;
printf ("Original String: %s\n", str);
token = strtok (str, delims);
while (token != NULL) {
printf ("%s\n", token);
token = strtok (NULL, delims);
}
printf ("another_function_callng_strtok: I am done.\n");
}
void function_callng_strtok ()
{
char str[] ="aaa --bbb-ccc";
char* delims = " -";
char* token;
printf ("Original String: %s\n", str);
token = strtok (str, delims);
while (token != NULL)
{
printf ("%s\n",token);
another_function_callng_strtok();
token = strtok (NULL, delims);
}
}
int main(void) {
function_callng_strtok();
return 0;
}
输出:
# ./example2_strtok
Original String: aaa --bbb-ccc
aaa
Original String: ttt -vvvv
ttt
vvvv
another_function_callng_strtok: I am done.
function_callng_strtok()
仅打印标记 "aaa"
,并且不打印输入字符串的其余标记,因为它调用了 another_function_callng_strtok()
,后者又调用了 strtok()
并在提取所有标记后将 strtok()
的静态指针设置为 NULL
。控制权回到 function_callng_strtok()
的 while
循环,由于静态指针指向 NULL
,因此 strtok()
返回 NULL
,使循环条件为 false
,从而退出循环。strsep()
未完成时调用 strsep()
:#include <stdio.h>
#include <string.h>
void another_function_callng_strsep(void)
{
char str[] ="ttt -vvvv";
const char* delims = " -";
char* token;
char* ptr = str;
printf ("Original String: %s\n", str);
while ((token = strsep(&ptr, delims)) != NULL) {
if (*token == '\0') {
token = "<empty>";
}
printf("%s\n", token);
}
printf ("another_function_callng_strsep: I am done.\n");
}
void function_callng_strsep ()
{
char str[] ="aaa --bbb-ccc";
const char* delims = " -";
char* token;
char* ptr = str;
printf ("Original String: %s\n", str);
while ((token = strsep(&ptr, delims)) != NULL) {
if (*token == '\0') {
token = "<empty>";
}
printf("%s\n", token);
another_function_callng_strsep();
}
}
int main(void) {
function_callng_strsep();
return 0;
}
输出:
# ./example2_strsep
Original String: aaa --bbb-ccc
aaa
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
<empty>
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
<empty>
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
bbb
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
ccc
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
在这里,您可以看到,在完全解析一个字符串之前调用strsep()
没有任何区别。
因此,strtok()
和strsep()
的缺点是两者都修改输入字符串,但是strsep()
比strtok()
具有上述优点。
来自strsep:
strsep()函数旨在替换strtok()函数。虽然应该出于可移植性原因首选strtok()函数(它符合ISO / IEC 9899:1990(“ISO C90”)),但它无法处理空字段,即检测由两个相邻分隔符字符限定的字段,或仅用于单个字符串。 strsep()函数最初出现在4.4BSD中。
参考:
strtok_s()
声明如下:char *strtok_s(char * restrict s1, rsize_t * restrict s1max, const char * restrict s2, char ** restrict ptr);
这与Microsoft的strtok_s()
或POSIX的strtok_r()
的接口不匹配。即使它被实现了,差异也很烦人——它限制了Annex K函数的实用性。另请参见Do you use the TR 24731 'safe' functions?。 - Jonathan Lefflerstrsep()
函数时可以交替使用不同的字符串,而strtok()
则不能这样做”的意思是,你可以使用strsep()
同时从两个不同的字符串中分离出标记,先从string1
中取一个标记,然后再从string2
中取一个标记;而strtok()
则要求你在处理string1
之前完全分割它,然后再处理string2
或者反过来。这意味着,strtok()
无法进行多线程操作,但它也严重限制了单线程程序的效率。 - Jonathan Lefflerstrtok()
函数用于识别由分隔符分隔的标记。任何不是分隔符的字符都是标记的一部分。调用strtok()
时,它会跳过任何前导分隔符,然后识别第一个非分隔符并记录其位置,然后返回该位置。它跳过一个或多个组成标记的非分隔符。当它遇到分隔符或到达字符串的末尾(空字节'\0'
)时,它确保标记以空字符结尾。_ […继续…]_ - Jonathan Lefflerstrtok()
相关的问题所在),并返回刚刚找到的令牌的起始指针。当下一次使用NULL
指针作为第一个参数调用它时,它从私有变量中检索到达的位置,并恢复其扫描,跳过任何分隔符,直到找到非分隔符为止。即使在处理单个字符串时,不同的strtok()
调用之间的分隔符字符集也可以不同。 - Jonathan Lefflerstrtok_r()
、strtok_s()
和strsep()
函数都避免了私有变量;用户向函数传递存储空间以获取该信息。这意味着它们是线程安全和可重入的,并且可以用于并行分析不同的字符串。 - Jonathan Leffler