sscanf拒绝整数读取中的前导零

时间:2015-05-16 12:27:37

标签: c++ c scanf

我想创建一个简单的" date"使用sscanf读取,接受输入为:

"dd/mm/yyyy"

两者" dd"和" mm"字段可以是2位数长(例如0,6或11,但不是123)。 "年"字段可以是0或4位字段。在这三个字段中的任何一个中,值为0表示必须代之以系统的日期,月份或年份。

该格式必须严格,因此,如果输入的格式不符合模式,则必须通知用户。

我的尝试是:

int d, m, y;
char const* input = "23/7/1990";

int n = sscanf(input, "%2u/%2u/%4u", &d, &m, &y);

if (n != 3) throw InvalidDate("Invalid format");

// Fill 0 values with system date.
// Check date correctness with `mktime` and `localtime`.

问题是此sscanf格式接受非允许的输入:

char const* invalid1 = "23/ 12/ 1990";
char const* invalid2 = "23/12/1990/123whatever......."

那么,是否有任何技巧/修饰符在整数之前拒绝前导零,标记字符串的结尾,或者如果解析了更多输入则导致可检测的失败?

对于最后一种情况(invalid2;字符串末尾可检测到的失败),可能的解决方案是:

int d, m, y;
char trick;

char const* input = "23/7/1990";

int n = sscanf(input, "%2u/%2u/%4u%c", &d, &m, &y, &trick);

// If it fills four fields, means the input was too long.
if (fields != 3) throw InvalidDate("Invalid format");

// Fill 0 values with system date.

但我不知道是否有更好的方法来检测end-of-string。更重要的是,这种格式" (使用前导'%c')导致sscanf认为输入错误的有效日期(例如," 23/6/1990"引发最后一个字符不是填充;如果使用scanf代替sscanf,则会设置ferror。我甚至尝试过使用"%2u/%2u/%4u\0",但是编译器用格式"中的#34; embedded \ 0警告我。

那么,如果不使用正则表达式或stringstream

,最佳解决方案是什么?

顺便说一句,是否有其他方法来欺骗" sscanf

5 个答案:

答案 0 :(得分:1)

你可以使用boost regex库,它可以做很多这些东西。检查以下代码:

#include <boost/regex.hpp>
#include <iostream>
#include <string>

int main()
{
    // Expression to match
    boost::regex e("(^\\d{1,2})/(\\d{1,2})/(\\d{4})$");

    // Results are here
    boost::match_results<std::string::const_iterator>  results;

    std::string val_to_match = "1/11/1990";
    if (boost::regex_search(val_to_match, results, e) && results.size() == 4) {
        std::cout << "Matched "  << results[0] << std::endl; 
        int i = 1;
        while (i < 4) {
            std::cout << "Value: " << i <<  "  "<< results[i] << std::endl;
            i++;
        }
    } else {
        std::cout << "Couldn't match \n";
    }

    return 0;
}

答案 1 :(得分:1)

修改了你的代码,并使其正常工作:

 void parseDate(const char *date) {

      char trick;
      int d, m, y, n = sscanf(date, "%2u/%2u/%4u%c", &d, &m, &y, &trick);

      (n != 3 || y < 999)) ? 
           puts("Invalid format!") : printf("%u %u %u\n", d, m, y);
 }

你提到过&#34;年&#34;可以是零或四位数字,所以我修改了你的代码只接受1000到9999。否则,23/7/1a990案例的年份为1年。

测试了这个并将输出放到文件中。

<强>结果:

Sample date: 23/7/1990
Output: 23 7 1990

Sample date: 23/12/1990/123whatever.......
Output: Invalid format!

Sample date: 23/ 12/ 1990
Output: 23 12 1990

Sample date: 23/12/19a90
Output: Invalid format!

Sample date: 2a/1
Output: Invalid format!

Sample date: a23/12/1990
Output: Invalid format!

Sample date: 23/12/199000
Output: Invalid format!

您可以参考此主题:How to parse and validate a date in std::string in C++?。其中一个答案建议使用strptime

答案 2 :(得分:1)

这个怎么样?您可以使用%[^0-9]转换规范来读取两个数字之间的字符。

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

void process_date(const char* input){
  int d, m, y;
  char sep1[3], sep2[3], trick;
  int n;

  n = sscanf(
    input, "%2u%2[^0-9]%2u%2[^0-9]%4u%c",
    &d, sep1, &m, sep2, &y, &trick);

  if(!(n == 5 && strcmp(sep1, "/") == 0 && strcmp(sep2, "/") == 0)){
    fprintf(stderr, "Invalid format (input = %s).\n", input);
    return;
  }

  printf("d = %d, m = %d, y = %d.\n", d, m, y);
}

int main(){
  process_date("23/7/1990");
  process_date("23/12/1990");
  process_date("23/7/0");
  process_date("23/0/1990");
  process_date("0/7/1990");

  process_date("23/ 12/ 1990");
  process_date("23/12/1990/123whatever.......");
  process_date("123/7/1990");
  process_date("23/12/19a90");
  process_date("2a/1");
  process_date("a23/12/1990");
  process_date("23/12/199000");

  return 0;
}

输出:

d = 23, m = 7, y = 1990.
d = 23, m = 12, y = 1990.
d = 23, m = 7, y = 0.
d = 23, m = 0, y = 1990.
d = 0, m = 7, y = 1990.
Invalid format (input = 23/ 12/ 1990).
Invalid format (input = 23/12/1990/123whatever.......).
Invalid format (input = 123/7/1990).
Invalid format (input = 23/12/19a90).
Invalid format (input = 2a/1).
Invalid format (input = a23/12/1990).
Invalid format (input = 23/12/199000).

答案 3 :(得分:0)

这样的事情怎么样?它没有使用sscanf,但正如评论中所说的那样,很难让这个功能按你的意愿工作:

int d, m, y;

int date[3];        //holds day/month/year in its cells
int tokenCount = 0;
char* pc;
int result = 0;
char* pch = strtok(input, "/");

while (pch != NULL)
{
    if (strlen(pch) == 0)
    {
        throw InvalidDate("Invalid format");
    }

    //atoi is stupid, there's no way to tell whether the string didn't contain a valid integer or if it contained a zero
    result = strtol(pch, &pc, 10);
    if (*pc != 0)
    {
        throw InvalidDate("Invalid format");
    }

    if (tokenCount > 2)     //we got too many tokens
    {
        throw InvalidDate("Invalid format");
    }

    date[tokenCount] = result;
    tokenCount++;

    pch = strtok(NULL, "/");
}

if (tokenCount != 3)
{
    //not enough tokens were supplied
    throw InvalidDate("Invalid format");
}


d = date[0];
m = date[1];
y = date[2];

然后您可以进行更多检查,例如月份是否在1-12之间。

要记住的一点是strtok修改了它收到的字符串,因此请务必复制。

答案 4 :(得分:0)

因此。既然似乎每个人都认为没有办法让sscanf更适合这种模式,我认为最好的解决方案是:

char const* input = "23/7/1990";

int d, m, y;

{ // Search blanks due to `sscanf` limitations.
    for (unsigned i = 0; i < 10 and input[i] != '\0'; ++i)
        if (isspace(input[i]))
           throw InvalidDate("Invalid format");

} { // Check format (with extra input detection).
    char trick;
    int n = sscanf(input, "%2u/%2u/%4u%c", &d, &m, &y, &trick);

    if (n != 3 or (y != 0 and y < 1000))
        throw InvalidDate("Invalid format");
}

// Fill 0 values with system date.
// Check date correctness with `mktime` and `localtime`.

编辑:之前,我使用strpbrk来检测空白(sscanf在数字前忽略它)。该解决方案的问题是strpbrk解析完整的输入,直到找到某些内容。如果输入太长,但没有空白,则执行速度会非常慢。由于我知道输入的最大允许大小,因此我使用for的10循环isspace进行更改。

当然,如果&#39; \ 0&#39;发现太快,但确定太快了#34; for内的内容过于冗长。所以,我将此工作留给sscanf,使第一个for更好地定义。

任何其他&#34;投诉&#34;这个解决方案非常受欢迎。