2012-03-02 40 views
2

我應該得到的輸入行,可以在任一下列格式:正確使用sscanf的

  • 必須有字1和字2
  • 必須有一個逗號之間的空間在字2和字3之間。
  • 空格不是字2和字3之間的必須空格 - 但任意數量的空格都是可能的。

如何區分1,2和3個單詞並將數據放入正確的變量?

word1 
word1 word2 
word1 word2 , word3 
word1 word2,word3 

我想過是這樣的:

sscanf("string", "%s %s,%s", word1, word2, word3); 

,但它似乎並沒有工作。

我使用嚴格的C89。

+0

輸入行是單個字符串嗎?所以你必須從字符串中提取令牌(分隔符是空格和逗號),對不對? – vulkanino 2012-03-02 16:50:33

+0

3個字符串,第一個分隔符是空格第二個分隔符逗號 – Nahum 2012-03-02 16:53:22

+4

使用'sscanf'(以及所有'scanf'-family函數)的最正確方法就是不要使用它們..當然,也有例外,scanf的奇怪'恰好符合你的需求,但通常你最終不得不寫一些黑客來解決它的行爲,在這種情況下,你最好先編寫自己的清理解析器。 – 2012-03-02 16:59:56

回答

17
int n = sscanf("string", "%s %[^, ]%*[, ]%s", word1, word2, word3); 

n中的返回值告訴您成功完成了多少分配。 %[^, ]是一個否定的字符類匹配,可以找到一個不包含逗號或空格的單詞(如果你喜歡,可以添加標籤)。 %*[, ]是找到逗號或空格但禁止分配的匹配項。

我不確定我會在實踐中使用它,但它應該可以工作。但是,它沒有經過測試。


也許更緊密的說明書是:

int n = sscanf("string", "%s %[^, ]%*[,]%s", word1, word2, word3); 

不同的是,該非分配字符類只接受一個逗號。 sscanf()word2後停止在任何空間(或EOS,字符串末尾),並在分配給word3之前跳過空格。前一版允許第二個和第三個單詞之間的空格代替逗號,這個問題並不嚴格允許。

由於pmg建議在評論中,分配轉換規範應給予一個長度,以防止緩衝區溢出。請注意,長度不包含空終止符,因此格式字符串中的值必須小於數組大小(以字節爲單位)。還請注意,printf()允許您使用*,sscanf()等使用*來動態指定大小來抑制分配。這意味着你手頭專門創建的字符串任務:

char word1[20], word2[32], word3[64]; 
int n = sscanf("string", "%19s %31[^, ]%*[,]%63s", word1, word2, word3); 

(Kernighan的&派克建議他們(優秀)的書'The Practice of Programming'動態格式格式字符串)


剛發現一個問題:給出"word1 word2 ,word3",它不會讀取word3。有治癒嗎?

是的,這有一種治療方法,它實際上也是微不足道的。在非賦值逗號匹配轉換規範之前,在格式字符串中添加一個空格。因此:

#include <stdio.h> 

static void tester(const char *data) 
{ 
    char word1[20], word2[32], word3[64]; 
    int n = sscanf(data, "%19s %31[^, ] %*[,]%63s", word1, word2, word3); 
    printf("Test data: <<%s>>\n", data); 
    printf("n = %d; w1 = <<%s>>, w2 = <<%s>>, w3 = <<%s>>\n", n, word1, word2, word3); 
} 

int main(void) 
{ 
    const char *data[] = 
    { 
     "word1 word2 , word3", 
     "word1 word2 ,word3", 
     "word1 word2, word3", 
     "word1 word2,word3", 
     "word1 word2  ,  word3", 
    }; 
    enum { DATA_SIZE = sizeof(data)/sizeof(data[0]) }; 
    size_t i; 
    for (i = 0; i < DATA_SIZE; i++) 
     tester(data[i]); 
    return(0); 
} 

輸出示例:

Test data: <<word1 word2 , word3>> 
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>> 
Test data: <<word1 word2 ,word3>> 
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>> 
Test data: <<word1 word2, word3>> 
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>> 
Test data: <<word1 word2,word3>> 
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>> 
Test data: <<word1 word2  ,  word3>> 
n = 3; w1 = <<word1>>, w2 = <<word2>>, w3 = <<word3>> 

一旦「非分配字符類」只接受一個逗號,可以縮寫,爲在格式字符串文字逗號:

int n = sscanf(data, "%19s %31[^, ] , %63s", word1, word2, word3); 

將其插入測試設備中會產生與以前相同的結果。請注意,所有代碼均可從審閱中受益它可以經常(基本上總是)在其工作之後得到改進。

+0

它是ansi 89兼容嗎?它似乎完美地工作.. – Nahum 2012-03-02 17:01:35

+2

+1:我只是添加一個限制輸入:'... scanf(「%99s」)...'或'... scanf(「%99 [^, ]「)...'用於'char [100]''類型的數組。 – pmg 2012-03-02 17:03:06

+0

+1真的不錯 – LihO 2012-03-02 17:03:18

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

int main() 
{ 
    char str[] ="word1 word2,word3"; 
    char* pch; 
    printf ("Splitting string \"%s\" into tokens:\n",str); 

    pch = strtok(str," ,"); 
    while (pch != NULL) 
    { 
    printf ("%s\n",pch); 
    pch = strtok (NULL, " ,.-"); 
    } 
    return 0; 
} 
+0

這是否允許逗號作爲前兩個單詞之間的分隔符,這是不允許的? – hmjd 2012-03-02 17:01:43

+0

是的,但是通過從第一次調用'strtok()'時刪除逗號來修復這個問題。您可以隨時更改每次通話時設置的分隔符;您不必在每次調用中都使用一組分隔符。 – 2012-03-02 17:14:20

0

這超出了scanf和朋友的範圍,說實話;除了對「寫自己的簡單的解析器」的答案,你可以投資於YACC來解析語法(詞法分析器就留給讀者做練習):

line: oneword | twowords | threewords; 
oneword: word; 
twowords: word word; 
threewords: word word word; 
word: STRING; 

這可能是矯枉過正你在這裏,但如果你需要解析甚至比邊緣複雜的格式,這是一個救星。

+1

有反例證明您的初始陳述是一個過度陳述。例如,將需求超出'sscanf()'可以管理的範圍(例如,用帶逗號的單詞的引號來考慮類似CSV的數據),並不需要太多的複雜性,但這實際上是可行的。 – 2012-03-02 17:13:14

+0

應該說「超出適當使用scanf的範圍」,但whatevz:D – tbert 2012-03-02 17:19:10

3

摘要: 答案分爲三部分。第一部分回答了「正確使用sscanf」的一般問題,描述了使用sscanf的好處,以及何時最好使用sscanf。第二部分回答問題的具體部分。第三部分對問題的一般和特定部分至關重要,並且儘可能完整地描述sscanf的內部工作。

部1中使用的sscanf優點:使用sscanf的是在一次將一個很大的問題 (原始輸入線)到較小的問題(輸出標記)。

如果行規則定義得很好(例如,問題中的行規定義明確:詞1和詞之間必須有空格詞2和詞3之間必須有一個逗號。空格不是必須的在單詞2和單詞3之間 - 但是任意數量的空格都是可能的)。比sscanf可以對「問題的當前讀取行是否符合行規則?」的問題帶來「是/否」的答案。 (沒有試圖分析和理解輸入文件中輸入的內容,或者打算在那裏輸入什麼內容),它也可以給出行的輸出標記;兩者都立即。

爲此,分離的輸入字符串到令牌,它是方便的使用%C。我們應該記住,默認情況下,sscanf跳過空格字符(空格,製表符和換行符),但不在%c的情況下,其中sscanf讀取空格並將其指定爲相應字符變量的值。

使用strtok代替它,確實更加通用和靈活,但它沒有一次讀完整行的優點,並且使用豐富的詞法分析(即%d,%f,%c *,^和所有sscanf的詞彙)。如果線條規則定義良好,以及是/否回答,則問題「當前的讀線是否符合線條規則?」;這些優點可能會被使用。

第2部分回答具體問題:這裏是一個sscanf代碼行,似乎工作,下面是對代碼行的解釋。 (數字100被假定爲比最大輸入線尺寸大。)

呼叫:

n = sscanf(" sssfdf wret  , 123 fdsgs fdgsdfg", 
"%100[^ ]%c%100[^,] %c %100[^\0]", s1, &ch1, s2, &ch2, s3); 

將導致:

s1 = ""sssfdf"; 
ch1=' '; 
s2=""wret  "; 
ch2=','; 
s3=""123 fdsgs fdgsdfg"; 
  1. 閱讀至少100字符或所有字符,直到s1的第一個空格。 (請記住,條件是第一個單詞到第二個單詞之間應該只有一個空格)。

  2. 讀取ch1的下一個字符(稍後我們可以檢查ch1是否具有空間值)。

  3. 讀取最少100個字符或所有字符,直到第一個逗號爲s2,s2可能包含將在稍後移除的空格。 (第二個單詞到第三個單詞之間應該有一個逗號,逗號前後有可選空格)。

注意,%100 [^]%C%100 ^,]自帶沒有空格,因爲第一%C之前的空間會導致字符之後的空間中ERAD到CH1,一個空間在%100 [^,]之前會在第一個單詞和第二個單詞之前啓用多個空格。

  1. 讀取下一個字符ch2(稍後我們可以檢查ch2的值是否爲逗號)。

  2. 將剩餘的輸入字符串讀取到s3(從第一個空白字符開始讀取,直到字符串結束符字符爲止)。

剩下的就是檢查s1,s2和s3的有效性(並且測試ch1和ch2的值是apace和逗號)。

第3部分sscanf的內部工作: sscanf()函數,開始一次讀取其格式字符串的一個字符。這個字符有3個可能的值,一個空格,'%'或其他。

  1. 如果下一個字符不是空格,而不是「%」,比它開始讀取輸入字符串 1.1如果在輸入字符串的下一個字符是不是在 格式字符串,sscanf的字符停止它的工作,並返回給調用者,它的目前讀取的參數數量爲 。 示例:

    n = sscanf(「2 22.456」,「2%f」,& FloatArg);/* n是0 */

    1.2如果輸入字符串中的下一個字符是格式爲 的字符串,則比sscanf繼續讀取格式爲 的字符串中的下一個字符。

    n = sscanf(「2 22.456」,「2%f」,& FloatArg); // n是1 FloatArg = 22。456

  2. 如果格式字符串中的下一個字符是%,則比sscanf跳過 空白並等待以%格式讀取字符串。例如對於%f, 它等待以下列格式讀取和輸入: [+/-] [IntDigiT1] ... [IntDigiTn] < ....>。 示例:31.25,32.,3 2.1如果sscanf沒有找到該格式,則返回它迄今爲止讀取的參數的數目 。 示例:

    n = sscanf(「aaa」,「%f」,& FloatArg); // n = 0的

    2.2如果sscanf的讀取至少一個位,或一系列的數字後跟一個 「」,比當它遇到一個非數字,然後,它得出結論,它具有 達到的末端浮動。 sscanf()將非數字放回到 輸入中,並將讀取的值賦給浮點變量。 示例1:

    n = sscanf(「2 22.456」,「2%f」,& FloatArg); // FloatArg是22.456

    例2:

    N = sscanf的( 「22.456」, 「2%F」,& FloatArg); // FloatArg是2.456

  3. 如果格式字符串中的下一個字符是空格,則表示在下一個輸入字符之前跳過任何空格的 。

A.讀字符(%C):如果下一個輸入字符是一個空白(例如空間),一個空間被分配給所指示的變量。

B.閱讀字符串(%s):除空格之外的任何字符均可接受, 因此scanf()會跳過空格到第一個非空白字符,然後保存非空白字符,直到再次打空白爲止。 sscanf將'\ 0'添加到分配的字符串變量末尾的字符串終止符。

C.答案沒有輸入格式%變化。 [=%[*] [寬度] [改性劑]類型=]。這個部分的一個很好的描述是在http://docs.roxen.com/(en)/pike/7.0/tutorial/strings/sscanf.xml 請注意,上面的鏈接中的%[字符]用於私人問題的答案,並且允許字符串靈活操作。

D.以上是我在互聯網搜索和Dev-C++ 5.11測試過程中發現的各種字符串,它不承諾是完整的,有建設性的意見,將被接受並感謝,並將幫助我改進答案。