2018年2月11日 星期日

C / C++ 針對特定格式空格或跳行 切割字串 strtok() 範例

C / C++ 針對特定格式空格或跳行 切割字串 strtok() 範例

初學語言時做題目很容易遇到的題目就是處理字串,字串的處理常常對初學者來說很常搞不清楚,尤其容易遇到的問題字串的切割。
字串的常見問題可以參考這篇站內文,看完之後應該可以有比較深入的理解
https://charlottehong.blogspot.tw/2017/04/c-char.html
比如說有一個字串是以空白或跳行隔開的,怎麼把他們取出來。比如說
A B C
AB
把它變成可以這樣存取
str[0] => A
str[1] => B
str[2] => C
str[3] => AB
這裡我們引用一個內建已有的函式來做說明 strtok();
    char buff[]="1 2 5 44 66";

    // 設置切割字符
    const char* delim = " ";

    // 切割字串
    char* pch=strtok(buff,delim);
    while(pch) {
        printf("%s, ", pch);
        pch=strtok(NULL, delim);
    } printf("\n");
這是可以在官方上看到的說明,大致上的用法就是第一次使用的時候需要輸入字串的指針,到了第二次以後就輸入 NULL 即可,詳細的運作原理是在 function 裡面使用 static 變數,會把第一次進來的指針存著。
上面這個式子用了開頭設置變數與每次結尾都執行一行代碼,正好符合 for 迴圈的使用方式,可以簡化到 for 迴圈裡面。
實際上這麼做在C++也比較好可以把 pch 包進去內層區域變數不會汙染到外面的變數名稱,C++鼓勵把變數名稱盡可能延後到需要使用才宣告。C語言或舊的style則鼓勵把所有變數名稱都在開頭宣告好。
我是覺得現代編譯器會對這種情況做優化的(沒有詳細去考證,但是其他有很多會被優化到沒影響),所以如果優化開下去就沒效能問題了,當然是盡可能減少汙染搂;然後切開變數的副作用對後面的人也比較友善,看程式碼才不會猜說,這個變數有沒有在什麼地方被偷偷修改了找半天。
    for(char* pch=strtok(buff,delim); pch; pch=strtok(NULL, delim))
        printf("%s, ", pch);
    printf("\n");
有一點需要注意的是 strtok() 對輸入的指針有副作用(英),翻譯過來意思就是會改變其內容,如果輸入的字串是不允許被修改的,需要建立一個站存字串,然後把它複製進去,再把這個站存字串丟進去做 strtok()。

安全問題

這裡的安全有兩個意思,一個是會不會被駭客竊取資料(從記憶體裡面挖如非法存取偷挖),一個是可能引發未定義行為。語意上直覺得看起是A但是實際上卻是跑B,或是後面接手的人不知道潛規則,比如說你讓兩個指針同時指向同一個位址後面的人不知道就 free 這兩個就出問題了,安全的寫法是非必要避免同時指向,不要弄什麼潛規則了。
其實 string.h 很多都不安全,廣一點來說C的輸入指針的函式很多都有危險,這部分可以參考文首的連結貼文有大致說明了一些輕微的小問題。
這個函式的問題是比較明顯的比較好思考,你使用過後他的指針就一直存在那個 static 裡面,如果你沒有特別去處理這個問題,極端狀況駭客可能可以從這個洞獲取你的資料之類的。
這個函式有了安全的版本,把那個站存的指針丟出來給外面的使用者控制了,可以讓你自己用完之後自己歸零。
nullptr 是 C++ 的空指針,在C裡面用可以用NULL替代。(但是反過來C++裡面寫NULL表示空指針是不安全的盡量避免)
    char str[] = "1 2 3 4 5";
    char delims[] = " ";

    char *next = nullptr;
    char *pch  = strtok_s(str, delims, &next);
    for(;pch; pch = strtok_s(nullptr, delims, &next)){
        printf("%s, ", pch);
    } next = pch = nullptr;
    printf("\n");

範例程式

/*****************************************************************************
Name : 
Date : 2018/02/11
By   : CharlotteHonG
Final: 2018/02/11
*****************************************************************************/
#include <stdio.h>
#include <string.h>

int main() {
    char buff[]="1 2 5 44 66";

    // 設置切割字符
    const char* delim = " ";
    // 切割字串
    for(char* pch=strtok(buff,delim); pch; pch=strtok(NULL, delim))
        printf("%s, ", pch);
    printf("\n");

    return 0;
}

沒有留言:

張貼留言