正确使用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。

 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建议在他们的(优秀的) “编程实践”一书中动态地格式化格式字符串。)


刚发现一个问题:给出"word1 word2 ,word3" ,它不会读取word3 。 有治疗方法吗?

是的,有一种治疗方法,实际上也是微不足道的。 在非赋值的逗号匹配转换规范之前,在格式字符串中添加一个空格。 从而:

 #include  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: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> Test data: <> n = 3; w1 = <>, w2 = <>, w3 = <> 

一旦'非赋值字符类'只接受逗号,您可以将其缩写为格式字符串中的文字逗号:

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

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

 #include  #include  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; } 

摘要:答案分为三个部分。 第一部分是回答“正确使用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,即%100之前的空格[^,]将在第一个单词和第二个单词之前启用多个空格。

  1. 读取ch2的下一个字符(稍后我们可以检查ch2的值是否为逗号)。

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

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

第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中进行测试时发现的各种字符串,它不承诺是完整的,建设性的评论,将被感谢接受,并将帮助我改进答案。

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

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

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