想不到吧,這個部落格居然還活著,而且還會填坑。

原本以為常用的字串函式都介紹得差不多了,直到最近工作時看到 strtok 才想到,好像還沒介紹過他。

strtok:字串分割

所屬標頭檔:<string.h>
函式宣告:

char* strtok( char* restrict str, const char* restrict delim );

strtok 的功能是將字串分成一個個的 token,也就是字串片段。聽起來好像很簡單,不過 strtok 的運作方式可能會讓人有點意外。

先說明參數的部分:第一個參數 str 是要被分割的字串,而第二個 delim 則表示要以哪些字元來分割字串,在 delim 裡的所有字元皆會被用於分割 str

接著來看看 strtok 的使用方式,strtok 的呼叫分為兩種類型:初次呼叫後續呼叫

  1. 初次呼叫:以NULL 的字串 str 呼叫 strtok(str, delim)strtok 會將 str 中第一個遇到的分隔字元替換為 \0,也就是進行分割,然後回傳此 token 的指標。

  2. 後續呼叫:在初次呼叫之後,我們要改用 NULL 來呼叫 strtok,也就是 strtok(NULL, delim)。這種情況下,strtok 會從上次分割的地方繼續,尋找下一個分隔字元並進行分割,最後將新找到的 token 回傳。

    有趣的是,在每次的後續呼叫中,delim 都可以換成不同的分隔字元。

strtok 找不到下一個 token,例如已經到底(\0)或字串剩下的部分都是分隔字元,則會回傳 NULL

strtok 會直接修改原本的字串,需謹慎使用。

不過為什麼只傳入 NULLstrtok 就會知道要從哪裡繼續呢?其實 strtok 函式使用了靜態(static)變數來儲存上次分割處的下一個位址,以利在後續呼叫時使用。

每次呼叫 strtok 都會修改這個靜態變數,導致這個函式並不是執行緒安全(thread-safe)的。

來看看具體的使用範例:

#include <stdio.h>
#include <string.h>

int main(void) {
    char str[32] = "hello world foo bar span";

    char *token;
    token = strtok(str, " ");
    while (token) {
        printf("%s\n", token);
        token = strtok(NULL, " ");
    }

    return 0;
}

細心的讀者們可能會想到,要是字串開頭就是分隔字元會發生什麼事呢?這時候 strtok 會一路向後尋找第一個不是分隔字元的字元,並當作此次 token 的起始位置,若找不到則回傳 NULL

另外,若 strdelim 不是以 \0 結尾的字串,就會造成 Undefined behavior。

最後再來看同時有多個分隔字元的例子:

#include <ctype.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    char str[512] =  // Note: this paragraph is generated by ChatGPT
        "The deep sea remains one of the most mysterious places on Earth, with vast areas still unexplored by humans. Strange "
        "creatures, like bioluminescent fish and giant squid, thrive in its cold, dark depths. Scientists believe that "
        "studying these extreme environments could help us understand life on other planets. As technology advances, we may "
        "one day uncover the secrets hidden in the ocean's abyss.";

    char *sentence;
    sentence = strtok(str, ",.");  // split by , or .
    while (sentence) {
        while (isspace(sentence[0])) {  // Skip spaces if any
            sentence++;
        }
        printf("%s\n", sentence);
        sentence = strtok(NULL, ",.");
    }

    return 0;
}

參考資料:

  1. cppreference - strtok
  2. glibc 中 strtok 的實作GitHub 上非官方的 mirror
  3. 我休息了好幾年的腦袋

後記:
  好久不見,這幾年經歷的事實在是太多了,去年更是從年頭忙到年尾。雖然發了篇新文,但也不代表之後會固定更新就是ㄌ
  其實是看到 Google 搜尋後台的報告中,點閱數逐漸下滑,才在想是不是該來救一下的,不過大概也不是發幾篇文就能救得起來XD