正确使用sscanf

时间:2012-03-02 16:45:08

标签: c input token

我应该得到一个可以采用以下任何格式的输入行:

  • 字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。

4 个答案:

答案 0 :(得分:23)

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& Pike建议在其(优秀)图书'The Practice of Programming'或亚马逊The Practice of Programming 1999中动态格式化格式字符串。)


  

刚发现问题:给定"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);

将其插入测试工具会产生与以前相同的结果。请注意,所有代码都受益于审核;即使在工作之后,它也经常(基本上总是)得到改善。

答案 1 :(得分: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;
}

答案 2 :(得分:3)

<强>摘要: 答案分为三个部分。第一部分回答了正确使用sscanf&#34;的一般问题,描述了使用sscanf的好处,以及什么时候最好使用sscanf。第二部分是回答问题的具体部分。第三部分对问题的一般部分和具体部分至关重要,并尽可能简单地描述sscanf的内部工作。

第1部分使用sscanf的优势:使用sscanf正在解决一个大问题 (原始输入线)一次到较小的问题(输出令牌)。

如果行规则定义明确(例如,问题中的行规则定义明确:单词1和单词之间必须有空格。单词2和单词3之间必须有逗号。空格不是必须的单词2和单词3 - 但是任何数量的空格都是可能的。)而不是sscanf可以带来问题的是/否答案&#34;当前的读取行是否符合行规则?&#34; (不试图分析和理解输入文件中输入的内容,或者打算在那里键入的内容),它也可以给出行的输出标记;两个都立即。

为此,将输入字符串分离为令牌,使用%c很方便。我们应该记住,默认情况下,sscanf会跳过空格字符(空格,制表符和换行符),但不会跳过%c,其中sscanf读取空格并将其指定为相应字符变量的值。

使用strtok,确实更加通用和灵活,但它不具备一次读取整行,并使用丰富的词法分析(即%d,%f,%c *,^和所有sscanf的词汇。并且如果线规则被很好地定义,并且是/否答案,那么问题&#34;当前读取线是否符合线规则?&#34 ;;足以使用这些优点。

第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可能包含稍后将删除的空格。 (第二个单词与第三个单词之间应该有逗号,逗号前后可选空格)。

  4. 请注意,%100 [^]%c%100 [^,]没有空格,因为第一个%c之前的空格将导致空格后的字符为erad,即%100之前的空格[ ^,]将在第一个单词和第二个单词之前启用多个空格。

    1. 将下一个字符读入ch2(稍后我们可以检查ch2是否具有逗号值)。

    2. 将输入字符串的剩余部分读取到s3(从第一个无空格读取,直到字符串终结符字符)。

    3. 剩下的就是检查s1,s2和s3的有效性(并测试ch1和ch2的值是快​​速和逗号)。

      第3部分sscanf的内部工作: sscanf()函数,一次开始读取一个字符的格式字符串。这个角色有3个可能的值,一个空格,&#39;%&#39;或者其他。

      1. 如果下一个字符不是空格而不是&#39;%&#39;,那么它开始读取输入字符串 1.1如果输入字符串中的下一个字符不是该字符中的字符  格式字符串,sscanf停止它的工作并返回给调用者  到目前为止它读取的参数数量。  例如:

        n = sscanf(&#34; 2 22.456&#34;,&#34; 2%f&#34;,&amp; FloatArg); / * n为0 * /

        1.2如果输入字符串中的下一个字符是格式中的字符  字符串,比sscanf继续从格式中读取下一个字符  串。

        n = sscanf(&#34; 2 22.456&#34;,&#34; 2%f&#34;,&amp; FloatArg); // n是1 FloatArg = 22.456

      2. 如果格式字符串中的下一个字符是%,则sscanf会跳过 空格并等待以%格式读取字符串。例如对于%f, 它等待以下列格式读取和输入: [+/-] [IntDigiT1] ... [IntDigiTn]&LT; ....取代。 例子:31.25,32,3 2.1如果sscanf没有找到该格式,则返回数字    到目前为止它已经读过的论点。 例如:

        n = sscanf(&#34; aaa&#34;,&#34;%f&#34;,&amp; FloatArg); // n = 0

        2.2如果sscanf读取至少一个数字,或一系列数字后跟a    &#39;。&#39;,比遇到非数字时,它会得出结论    到了漂浮的尽头。 sscanf()将非数字放回    输入,并将读取的值赋给浮点变量。 例1:

        n = sscanf(&#34; 2 22.456&#34;,&#34; 2%f&#34;,&amp; FloatArg); // FloatArg是22.456

        例2:

        n = sscanf(&#34; 22.456&#34;,&#34; 2%f&#34;,&amp; FloatArg); // FloatArg是2.456

      3. 如果格式字符串中的下一个字符是空格,则表示跳过 在下一个输入字符之前的任何空格上。

      4. 一个。读取字符(%c):如果下一个输入字符是空格(例如空格),则为指定的变量分配空格。

        B中。读取字符串(%s):除了空格之外的任何字符都可以接受, 所以scanf()跳过空白到第一个非空白字符,然后保存非空白字符,直到再次碰到空白。 sscanf将字符串终止符添加到指定字符串变量的末尾。

        ℃。答案没有输入格式%变体。 [=%[*] [宽度] [改性剂]类型=]。这部分的一个很好的描述是http://docs.roxen.com/(en)/pike/7.0/tutorial/strings/sscanf.xml 请注意,上面链接中的%[字符]用于回答私有问题,并启用字符串灵活操作。

        d。以上是我在互联网上搜索并在Dev-C ++ 5.11中测试时发现的各种字符串,它不承诺是完整的,建设性的评论,将被感谢接受,并将帮助我改进答案。

答案 3 :(得分:0)

这超出了scanf和朋友的范围,完全诚实;除了“编写你自己的简单解析器”的答案之外,你可以投资yacc来解析语法(词法分析器留给读者练习):

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

这对你来说可能有点矫枉过正,但如果你需要解析甚至超过简单复杂的格式,它就是救星。