strtok()
有人可以解释一下和之间有什么区别strsep()
吗?它们的优点和缺点是什么?为什么我会选择一个而不是另一个。
3 回答
strtok()
和之间的一个主要区别strsep()
是它strtok()
是标准化的(通过 C 标准,因此也通过 POSIX)但strsep()
不是标准化的(通过 C 或 POSIX;它在 GNU C 库中可用,并且起源于 BSD)。因此,可移植代码strtok()
比strsep()
.
另一个区别是strsep()
对不同字符串的函数调用可以交错,而你不能这样做strtok()
(尽管你可以使用strtok_r()
)。因此,strsep()
在库中使用不会意外破坏其他代码,而strtok()
在库函数中使用必须记录在案,因为strtok()
同时使用的其他代码无法调用库函数。
kernel.orgstrsep()
的手册页说:
strsep() 函数是作为 strtok(3) 的替代引入的,因为后者不能处理空字段。
因此,另一个主要区别是George Gaáal在他的回答中强调的。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()
。 - 如果可用,您可以使用 POSIX
strtok_r()
。 - 如果可用,您可以使用 Microsoft 的
strtok_s()
。 - 名义上,您可以使用 ISO/IEC 9899:2011 Annex K.3.7.3.1 函数
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);
请注意,这有 4 个参数,而不是 3 上的其他两个变体中的 3 个参数strtok()
。
来自 GNU C 库手册 - Find Tokens in a String:
strsep
和之间的一个区别strtok_r
是,如果输入字符串连续包含多个分隔符中的字符,则为分隔符中的strsep
每对字符返回一个空字符串。这意味着程序通常应该strsep
在处理之前测试是否返回空字符串。
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>
)。这两个空字符串用于between和。之后找到分隔符时,将分隔符替换为字符并返回。在这之后,又找到了一个分隔符。然后它用字符替换分隔符并返回空字符串。下一个分隔符也是如此。bbb
ccc
"--"
"bbb"
"ccc"
strsep()
' '
"bbb"
'\0'
"bbb"
strsep()
'-'
'\0'
strsep()
当返回指向空字符(即值为 的字符)的指针时,会指示连续的分隔符'\0'
。
strsep()
修改输入字符串以及地址作为第一个参数传递给的指针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()
which 依次调用,并在完成提取所有标记时strtok()
将静态指针设置为strtok()
to 。NULL
控件返回循环function_callng_strtok()
while
,由于指向的静态指针而返回,这使得循环条件和循环退出。strtok()
NULL
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()
因此, and的缺点strsep()
是两者都修改了输入字符串,但与上面所示strsep()
的相比有几个优点。strtok()
从strsep:
strsep() 函数旨在替代 strtok() 函数。虽然出于可移植性原因应该首选 strtok() 函数(它符合 ISO/IEC 9899:1990 (``ISO C90'')),但它无法处理空字段,即检测由两个相邻分隔符分隔的字段,或一次用于多个字符串。strsep() 函数最早出现在 4.4BSD 中。
以供参考: