簡體   English   中英

C中strtok和strsep有什么區別

[英]What are the differences between strtok and strsep in C

有人能解釋一下strtok()strsep()之間有什么區別嗎? 它們的優點和缺點是什么? 為什么我會選擇一個而不是另一個。

strtok()strsep()之間的一個主要區別是strtok()是標准化的(通過 C 標准,因此也通過 POSIX)但strsep()沒有標准化(通過 C 或 POSIX;它在 GNU C 中可用庫,起源於 BSD)。 因此,可移植代碼更可能使用strtok()不是strsep()

另一個區別是對不同字符串的strsep()函數的調用可以交錯,而使用strtok()則不能這樣做(盡管使用strtok_r() )。 因此,在庫中使用strsep()不會意外破壞其他代碼,而在庫函數中使用strtok()必須記錄在案,因為同時使用strtok()其他代碼無法調用庫函數。

kernel.org 的strsep()手冊頁說:

strsep() 函數被引入作為 strtok(3) 的替代品,因為后者不能處理空字段。

因此,另一個主要區別是George Gaál在他的回答中強調的一個區別; strtok()允許單個標記之間有多個分隔符,而strsep()期望標記之間有一個分隔符,並將相鄰的分隔符解釋為空標記。

strsep()strtok()修改了它們的輸入字符串,並且都strsep()您識別標記了標記結尾的分隔符(因為它們都在標記結尾之后的分隔符上寫了一個 NUL '\\0' )。

什么時候使用它們?

  • 當您想要空標記而不是允許標記之間有多個分隔符,並且您不介意可移植性時,您將使用strsep()
  • 當您希望在標記之間允許多個分隔符並且您不想要空標記(並且 POSIX 對您來說具有足夠的可移植性strtok_r()時,您可以使用strtok_r() )。
  • 如果你不這樣做,你只會在有人威脅你的生命時使用strtok() 而且你只能使用它足夠長的時間來讓你擺脫危及生命的境地; 然后,您將再次放棄對它的所有使用。 它有毒; 不要使用它。 編寫自己的strtok_r()strsep()比使用strtok()更好。

為什么strtok()有毒?

如果在庫函數中使用strtok()函數是有毒的。 如果您的庫函數使用strtok() ,則必須清楚地記錄下來。

那是因為:

  1. 如果任何調用函數正在使用strtok()並且調用您的函數也使用strtok() ,則會破壞調用函數。
  2. 如果您的函數調用任何調用strtok()的函數,這將破壞您的函數對strtok()的使用。
  3. 如果您的程序是多線程的,那么在任何給定時間,最多有一個線程可以使用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 個參數,而不是strtok()上的其他兩個變體中的 3 個參數。

來自 GNU C 庫手冊 - 在字符串中查找標記

strsepstrtok_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

在輸出中,您可以看到bbbccc之間的兩個空字符串(通過<empty>表示)。 這兩個空字符串用於"--"介於"bbb""ccc" strsep()"bbb"之后發現分隔符' ' ,它將分隔符替換為'\\0'字符並返回"bbb" 在此之后, strsep()找到了另一個分隔符'-' 然后它將分隔符替換為'\\0'字符並返回空字符串。 下一個分隔符也是如此。

strsep()返回指向空字符(即,值為'\\0'的字符strsep()的指針時,將指示連續的分隔符字符

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()這又調用strtok()並將其設置的靜態指針strtok()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()strsep()的缺點是都修改輸入字符串,但strsep()strtok()有幾個優點,如上所示。

strsep

strsep() 函數旨在替代 strtok() 函數。 雖然 strtok() 函數出於可移植性的原因應該是首選(它符合 ISO/IEC 9899:1990 (``ISO C90'')),但它無法處理空字段,即檢測由兩個相鄰分隔符分隔的字段,或一次用於多個字符串。 strsep() 函數首次出現在 4.4BSD 中。


以供參考:

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM