2012-03-02 103 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测试过程中发现的各种字符串,它不承诺是完整的,有建设性的意见,将被接受并感谢,并将帮助我改进答案。