【问题标题】:read consecutive tabs as empty field fscanf将连续选项卡读取为空字段 fscanf
【发布时间】:2018-11-27 06:41:29
【问题描述】:

我有一个文件,其中某些字段由制表符分隔。一行中总是有 12 个选项卡,某些选项卡是连续的,表示一个空字段。我想使用 fscanf 将连续的选项卡读取为空字段并将它们存储在一个结构中。但是好像有问题。这是我的文件:

   usrid    User Id 0   15  string  d   k   y   y           0   0

当我尝试使用 fscanf 进行读取时,一个又一个选项卡不会被识别为空字段,并且数据存储在错误的结构字段中。处理问题的最佳方法是什么?

【问题讨论】:

  • 有一个固定的分隔符,你应该完全避免fscanf。使用fgets 阅读该行并使用strtok 对其进行标记。
  • @DYZ - 您不能使用strtok,因为它将连续分隔符视为单个字段,但strsep 将处理空字段。
  • @DavidC.Rankin 够公平的。 strsep 是。
  • strtok 应在下一个标准版本中删除。如此愚蠢的界面:(
  • @AnttiHaapala:在许多方面它会很好,但他们也应该删除 strtok_s() from Annex K. Either the Microsoft strtok_s()` 或 POSIX strtok_r() 接口就可以了——它们本质上是等价的(但是附件 K strtok_s() 有 4 个参数,而 MS 和 POSIX 只有 3 个)。

标签: c


【解决方案1】:

fscanf 不是首发。读取空字段的唯一方法是使用 "%c" 读取分隔符(这需要您事先知道哪些字段为空 - 不是很有用)否则,取决于 格式说明符使用时,fscanf 将简单地将 tabs 用作前导空格或遇到匹配失败输入失败

从评论继续,为了根据可能分隔空字段的分隔符进行标记,您需要使用strsep,因为strtok 会将连续的分隔符视为一个。

虽然您的字符串有点不清楚tabs 的位置,但使用strsep 进行标记的简短示例如下。请注意,strsep 将指针指向指针作为其第一个参数,例如

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

int main (void) {

    int n = 0;
    const char *delim = "\t\n";
    char *s = strdup ("usrid\tUser Id 0\t15\tstring\td\tk\ty\ty\t\t\t0\t0"),
        *toks = s,   /* tokenize with separate pointer to preserve s */
        *p;

    while ((p = strsep (&toks, delim)))
        printf ("token[%2d]: '%s'\n", n++ + 1, p);

    free (s);
}

(注意:因为strsep会修改字符串指针持有的地址,所以你需要保留一个指向s开头的指针,以便在不再需要时可以释放它—— - 谢谢JL)

使用/输出示例

$ ./bin/strtok_tab
token[ 1]: 'usrid'
token[ 2]: 'User Id 0'
token[ 3]: '15'
token[ 4]: 'string'
token[ 5]: 'd'
token[ 6]: 'k'
token[ 7]: 'y'
token[ 8]: 'y'
token[ 9]: ''
token[10]: ''
token[11]: '0'
token[12]: '0'

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

  • 当您free(s) 时,由于指针不是strdup()strsep() 修改了几次后返回的指针,这不会调用未定义的行为(崩溃)吗?另请注意,strsep() 不是标准 C 或 POSIX 的一部分——但它在 Linux 和 BSD(包括 macOS)上可用。
  • 呃....是的,需要保留对s开头的引用,正在更新...
  • 谢谢你的回复....我想在文件中读取一些数据作为字符串和一些数据作为整数。文件太多,我不想使用 atoi 函数。有没有解决方案如何使用 fscanf 或 sscanf 读取此文件?
  • @Dhan:“fscanf() 是非首发”的哪一部分很难理解。 fscanf() 无法做到这一点。它要求每个转换规范至少有一个字符,但您说的是“某些字段不包含任何字符”。所以,你不能和fscanf() 或者它的朋友一起做。句号。
  • strsep() 不是标准 C 库的一部分。
【解决方案2】:

我想使用 fscanf 将连续的选项卡读取为空字段并将它们存储在一个结构中。

理想情况下,代码应该读取一个,就像fgets()一样,然后解析字符串

但还是留在fscanf(),这可以循环完成。


主要思想是使用"%[^/t/n]" 读取一个令牌。如果下一个字符是'\t',则返回值不会是 1。对此进行测试。宽度限制是明智的。

然后读取分隔符并查找制表符、行尾或是否发生文件结尾/错误。

#define TABS_PER_LINE 12
#define TOKENS_PER_LINE (TABS_PER_LINE + 1)
#define TOKEN_SIZE 100
#define TOKEN_FMT_N "99"

int fread_tab_delimited_line(FILE *istream, int n, char token[n][TOKEN_SIZE]) {
  for (int i = 0; i < n; i++) {
    int token_count = fscanf(istream, "%" TOKEN_FMT_N "[^\t\n]", token[i]);
    if (token_count != 1) {
      token[i][0] = '\0';  // Empty token
    }
    char separator;
    int term_count = fscanf(istream, "%c", &separator);  // fgetc() makes more sense here
    // if end-of-file or end-of-line
    if (term_count != 1 || separator == '\n') {
      if (i == 0 && token_count != 1 && term_count != 1) {
        return 0;
      }
      return i + 1;
    }
    if (separator != '\t') {
      return -1;  // Token too long
    }
  }
  return -1;  // Token too many tokens found
}

示例驾驶代码

void test_tab_delimited_line(FILE *istream) {
  char token[TOKENS_PER_LINE][TOKEN_SIZE];
  long long line_count = 0;
  int token_count;
  while ((token_count = fread_tab_delimited_line(istream, TOKENS_PER_LINE, token)) > 0) {
    printf("Line %lld\n", ++line_count);
    for (int i = 0; i < token_count; i++) {
      printf("%d: <%s>\n", i, token[i]);
    }
  } while (token_count > 0);
  if (token_count < 0) {
    puts("Trouble reading any tokens.");
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-24
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 2018-03-21
    • 2021-11-18
    相关资源
    最近更新 更多