从c中的文件的特定行读取整数

时间:2018-12-16 01:38:03

标签: c file pointers

您好,我已经为此问题苦苦挣扎了一段时间,并且在互联网上进行了一些研究之后,我决定寻求帮助。

我需要从文件和特定行中读取一些整数,然后对它们进行处理。

我知道这种处理字符串的技巧

 while(fgets(pointer_to_string, length, "file_name.txt"))
 line++;       /*increment line-integer type- by 1*/

 if(line == your_line) /*do something with the strings at that line*/

我知道'fgets()'会读取所有内容,直到到达换行符'\ n'为止,这样很容易,但是我的问题有点不同。 我需要从文件中读取整数,例如:

5
1 78 45 32 2

在我的特殊情况下,第一行上的数字表示第二行上的整数数,中间用空格隔开,因此我需要读取第一个数字,然后创建一个指向要分配内存的数组的指针:

int a[20];
int num; /*number on first line*/
int* p;
p = a;
p = (int*)malloc(num*sizeof(int));

当然,在我从文件中读取第一个数字后,将完成内存分配。

因此,我想告诉您我的奋斗会更容易:

int main()
{

FILE* file = fopen("files.txt", "r");

int a[20], first_num, j = 0;
int* p = a, line = 1;
while(!feof(file))
{

    if ( line == 1 )
    {


        fscanf(file, "%d", &first_num);
        p = (int*)malloc(first_num*sizeof(int));
    }
    else
    {


        for ( j = 0; j <  first_num; j++)
            fscanf(file, "%d", (p + j));
    }


    line++;

}

for ( j = 0; j < first_num; j++)
{
    printf("\t%d\t", *(p + j));
}
printf("\n%d", first_num);
free(p);

fclose(file);


return 0;
}

这个程序实际上很适合这个例子(第一行的元素数量和第二行的数组数量),但是我感觉它有缺陷,或者至少我不能称其为“干净”,主要是因为“我不太确定该循环是如何工作的,我知道使用“ feof”函数可以到达文件的末尾,只要我不在那里,它将返回一个非零值,这就是为什么我可以记住第一行上的数字,但我不知道它何时以及如何检查循环。起初我以为它在每一行的末尾都执行,所以这意味着如果我要用以下方式更改'else':

else if ( line == 2 )

它仍然需要正常工作,但事实并非如此。因此,我希望对环路的实际工作方式进行一些解释。

我的猜测是,我需要在“ while”中进行循环,以检查何时到达行尾或类似内容,但是我真的很卡住。

我真正的问题是如何从文件的特定行中读取由空格分隔的整数,而不一定是我给您的示例(那是给不介意帮助我的人的)

2 个答案:

答案 0 :(得分:2)

让我们从一些基础知识开始。从文件中读取行(或用户的输入行)时,通常需要使用面向{em> line 的输入功能,例如fgets或POSIX getline确保一次读取整行,并且输入缓冲区中没有剩余的内容取决于最后使用哪个scanf 转换说明符。使用fgets,您将需要提供足够大的缓冲区来容纳整行,或者根据需要动态分配和realloc,直到读取整行(getline为您处理) 。您可以通过检查最后一个字符是'\n'字符还是缓冲区的长度小于最大大小(下面都留给您)来验证是否已读取整行。

读完一行文本后,有两个选择,您可以使用sscanf将缓冲区中的数字转换为整数值(可以事先知道行中包含的数字并提供足够的个转换说明符的数量,或者通过分别转换每个转换符并使用"%n" 说明符来报告为该转换提取的字符数,并增加缓冲区中的起始字符按此金额进行下一次转化)

从错误检查和报告的角度来看,您的另一种选择(也是迄今为止最灵活,最可靠的选择)是使用strtol并使用endptr参数,以达到提供指向在转换后的最后一位之后,您可以沿着缓冲区走,直接在转换时转换值。请参阅:strtol(3) - Linux manual page strtol能够区分未转换任何数字的故障,发生了上溢或下溢(设置{{1 }}设置为适当的值),并允许您通过errno参数测试转换后是否还有其他字符用于控制值转换循环。

与您编写的任何代码一样,验证每个必要步骤将确保您可以适当地进行响应。

让我们从示例输入文件开始:

示例输入文件

endptr

在第一行遇到单个值时,大多数情况下,您只需要使用$ cat dat/int_file.txt 5 1 78 45 32 2 转换原始值,这很好,但是-使用任何{ {1}}系列是您必须考虑转换后输入缓冲区中剩余的所有字符。尽管fscanf (file, "%d", &ival);转换说明符将提供所需的转换,但字符提取会从最后一位停止,而scanf仍未读取。只要您考虑到这一事实,就可以使用"%d"获取第一个值。但是,您必须验证整个过程中的每个步骤。

让我们看一个例子的开头,这样做的例子是:打开一个文件(如果没有给出文件名,则从'\n'读取),确认文件已打开,然后确认fscanf被读取,例如

stdin

此时,您的输入缓冲区包含:

first_num

由于您要对文件中的其余行进行面向行的读取,因此您可以简单地对#include <stdio.h> #include <stdlib.h> /* for malloc/free & EXIT_FAILURE */ #include <errno.h> /* for strtol validation */ #include <limits.h> /* for INT_MIN/INT_MAX */ #define MAXC 1024 /* don't skimp on buffer size */ int main (int argc, char **argv) { int first_num, /* your first_num */ *arr = NULL, /* a pointer to block to fill with int values */ nval = 0; /* the number of values converted */ char buf[MAXC]; /* buffer to hold subsequent lines read */ /* open file passed as 1st argument (default: stdin if no argument) */ FILE *fp = argc > 1 ? fopen (argv[1], "r"): stdin; if (!fp) { /* validate file open for reading */ perror ("fopen-file"); exit (EXIT_FAILURE); } if (fscanf (fp, "%d", &first_num) != 1) { /* read/validate int */ fputs ("error: invalid file format, integer not first.\n", stderr); exit (EXIT_FAILURE); } 进行首次调用,以读取和丢弃\n 1 78 45 32 2 ,例如

fgets

注意:。验证。如果文件以非POSIX行结尾(例如,没有'\n')结束, if (!fgets (buf, MAXC, fp)) { /* read/discard '\n' */ fputs ("error: non-POSIX ending after 1st integer.\n", stderr); exit (EXIT_FAILURE); } 将会失败,除非您正在检查,您很可能会通过尝试稍后从没有剩余字符要读取的文件流中进行读取,然后再尝试从内容不确定的缓冲区中进行读取来调用未定义的行为。

此时,您可以为'\n'个整数分配存储空间,并将该新块的起始地址分配给fgets以填充整数值,例如

first_num

要读取文件中的其余值,您可以只调用一次arr,然后转换为填充的缓冲区中包含的整数值,但是只要稍加预见,您就可以制作一个该方法将读取所需的行数,直到转换了 /* allocate/validate storage for first_num integers */ if (!(arr = malloc (first_num * sizeof *arr))) { perror ("malloc-arr"); exit (EXIT_FAILURE); } 个整数或遇到fgets为止。无论您是在缓冲区中输入输入值还是在缓冲区中转换值,一种健壮的方法都是连续地循环运行,直到获得所需的数据或数据用完为止,例如

first_num

现在让我们稍微打开一下包装。发生的第一件事是您声明使用EOF所需的指针,并将用while (fgets (buf, MAXC, fp)) { /* read lines until conversions made */ char *p = buf, /* nptr & endptr for strtol conversion */ *endptr; if (*p == '\n') /* skip blank lines */ continue; while (nval < first_num) { /* loop until nval == first_num */ errno = 0; /* reset errno for each conversion */ long tmp = strtol (p, &endptr, 0); /* call strtol */ if (p == endptr && tmp == 0) { /* validate digits converted */ /* no digits converted - scan forward to next +/- or [0-9] */ do p++; while (*p && *p != '+' && *p != '-' && ( *p < '0' || '9' < *p)); if (*p) /* valid start of numeric sequence? */ continue; /* go attempt next conversion */ else break; /* go read next line */ } else if (errno) { /* validate successful conversion */ fputs ("error: overflow/underflow in conversion.\n", stderr); exit (EXIT_FAILURE); } else if (tmp < INT_MIN || INT_MAX < tmp) { /* validate int */ fputs ("error: value exceeds range of 'int'.\n", stderr); exit (EXIT_FAILURE); } else { /* valid conversion - in range of int */ arr[nval++] = tmp; /* add value to array */ if (*endptr && *endptr != '\n') /* if chars remain */ p = endptr; /* update p to endptr */ else /* otherwise */ break; /* bail */ } } if (nval == first_num) /* are all values filled? */ break; } 填充的strtol的起始地址分配给buf,然后读取一行从您的文件中。无需尝试在空白行上进行转换,因此我们测试fgets中的第一个字符,如果它是p,则获得下一行:

buf

一旦您有非空行,就开始转换循环,并尝试进行转换,直到您拥有的值的数量等于'\n'或到达行尾为止。您的循环控制很简单:

    ...
    if (*p == '\n')     /* skip blank lines */
        continue;
    ...

在循环中,通过在每次转换之前重置first_num并将转换后的返回值分配为临时 while (nval < first_num) { /* loop until nval == first_num */ ... } 值,您将通过strtol完全验证您尝试的转换。 (例如字符串到长整数),例如

errno = 0;

进行转换后,在进行良好的整数转换之前,您需要先验证三个条件,

  1. 如果未转换任何数字,则long int(并且在手册页中,返回值设置为零)。因此,要检查是否发生这种情况,可以检查: errno = 0; /* reset errno for each conversion */ long tmp = strtol (p, &endptr, 0); /* call strtol */ ;
  2. 如果在数字转换期间发生错误,无论发生哪个错误,p == endptr都将被设置为非零值,从而使您可以使用if (p == endptr && tmp == 0)检查转换中的错误。您还可以按照手册页中的说明进一步了解发生的错误,但是为了进行验证,这里足以知道是否发生了错误。最后
  3. 如果数字已转换并且没有错误,则您仍然没有完成。 errno转换为if (errno)的值,该值可能与strtol不兼容(例如,在x86_64上,longint,而{{1} }是long。因此,要确保转换后的值适合您的整数数组,您需要检查返回的值是否在8-bytesint之前您可以将值分配给4-bytes的元素。

注意:与上面的INT_MIN一样,仅仅因为没有数字被转换并不意味着行中没有数字,这仅意味着第一个值不是数字。您应该使用指针在行中向前扫描,以找到下一个INT_MAXarr以确定是否存在其他数值。这就是该代码块中1.循环的目的)< / p>

一旦您有一个好的整数值,请记住+/-将被设置为转换后的最后一位数字之后的下一个字符。快速检查[0-9]是否不是可终止字符而不是行尾,将告诉您是否剩余可用于转换的宪章。如果是这样,只需更新while,以使您的指针现在指向转换后的最后一位数字并重复。 (您也可以使用与上面相同的endptr循环在此点向前扫描,以确定是否存在另一个数值-留给您)

循环完成后,您需要做的只是检查*endptr是否知道您是否需要继续收集值。

将其完全放在一起,您可以执行以下操作:

p = endptr

注意:退出读取和转换循环后对while的最终检查)

使用/输出示例

使用示例文件,您将获得以下内容:

nval == first_num

为什么要去麻烦多了?

通过彻底了解转换过程并解决一些其他问题,您最终获得了一个例程,该例程可以为所需的任意数量的整数提供灵活的输入处理,而与输入文件格式无关。让我们看一下输入文件的另一种形式:

更具挑战性的输入文件

#include <stdio.h>
#include <stdlib.h> /* for malloc/free & EXIT_FAILURE */
#include <errno.h>  /* for strtol validation */
#include <limits.h> /* for INT_MIN/INT_MAX */

#define MAXC 1024   /* don't skimp on buffer size */

int main (int argc, char **argv) {

    int first_num,      /* your first_num */
        *arr = NULL,    /* a pointer to block to fill with int values */
        nval = 0;       /* the number of values converted */
    char buf[MAXC];     /* buffer to hold subsequent lines read */
    /* open file passed as 1st argument (default: stdin if no argument) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r"): stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("fopen-file");
        exit (EXIT_FAILURE);
    }

    if (fscanf (fp, "%d", &first_num) != 1) {   /* read/validate int */
        fputs ("error: invalid file format, integer not first.\n", stderr);
        exit (EXIT_FAILURE);
    }

    if (!fgets (buf, MAXC, fp)) {   /* read/discard '\n' */
        fputs ("error: non-POSIX ending after 1st integer.\n", stderr);
        exit (EXIT_FAILURE);
    }

    /* allocate/validate storage for first_num integers */
    if (!(arr = malloc (first_num * sizeof *arr))) {
        perror ("malloc-arr");
        exit (EXIT_FAILURE);
    }

    while (fgets (buf, MAXC, fp)) { /* read lines until conversions made */
        char *p = buf,  /* nptr & endptr for strtol conversion */
            *endptr;
        if (*p == '\n')     /* skip blank lines */
            continue;
        while (nval < first_num) {  /* loop until nval == first_num */
            errno = 0;              /* reset errno for each conversion */
            long tmp = strtol (p, &endptr, 0);  /* call strtol */
            if (p == endptr && tmp == 0) {  /* validate digits converted */
                /* no digits converted - scan forward to next +/- or [0-9] */
                do
                    p++;
                while (*p && *p != '+' && *p != '-' && 
                        ( *p < '0' || '9' < *p));
                if (*p)     /* valid start of numeric sequence? */
                    continue;   /* go attempt next conversion */
                else
                    break;      /* go read next line */
            }
            else if (errno) {   /* validate successful conversion */
                fputs ("error: overflow/underflow in conversion.\n", stderr);
                exit (EXIT_FAILURE);
            }
            else if (tmp < INT_MIN || INT_MAX < tmp) {  /* validate int */
                fputs ("error: value exceeds range of 'int'.\n", stderr);
                exit (EXIT_FAILURE);
            }
            else {  /* valid conversion - in range of int */
                arr[nval++] = tmp;      /* add value to array */
                if (*endptr && *endptr != '\n') /* if chars remain */
                    p = endptr;         /* update p to endptr */
                else        /* otherwise */
                    break;  /* bail */
            }
        }
        if (nval == first_num)  /* are all values filled? */
            break;
    }
    if (nval < first_num) { /* validate required integers found */
        fputs ("error: EOF before all integers read.\n", stderr);
        exit (EXIT_FAILURE);
    }

    for (int i = 0; i < nval; i++)  /* loop outputting each integer */
        printf ("arr[%2d] : %d\n", i, arr[i]);

    free (arr);         /* don't forget to free the memory you allocate */

    if (fp != stdin)    /* and close any file streams you have opened */
        fclose (fp);

    return 0;
}

从该文件中检索相同的前五个整数值需要进行哪些更改? (提示:无-尝试)

更具挑战性的输入文件

如果我们再次提高赌注怎么办?

if (nval < first_num)

要从此文件中读取前5个整数值,需要进行哪些更改? (提示:无)

但是我想指定开始阅读的行

好的,我们来看另一个输入文件。说:

从给定行读取输入的示例

$ ./bin/fgets_int_file dat/int_file.txt
arr[ 0] : 1
arr[ 1] : 78
arr[ 2] : 45
arr[ 3] : 32
arr[ 4] : 2

我需要更改什么?唯一需要做的更改就是跳过第一行$ cat dat/int_file2.txt 5 1 78 45 32 2 144 91 270 foo 并在$ cat dat/int_file3.txt 5 1 two buckle my shoe, 78 close the gate 45 is half of ninety foo bar 32 is sixteen times 2 and 144 is a gross, 91 is not prime and 270 caliber baz 行开始转换循环的更改。为此,您需要添加一个变量来保存行的值以开始读取整数(例如$ cat dat/int_file4.txt 5 1,2 buckle my shoe, 7,8 close the gate 45 is half of ninety foo bar 32 is sixteen times 2 and 144 is a gross, 91 is not prime and 270 caliber baz 1 78 45 32 2 27 41 39 1111 a quick brown fox jumps over the lazy dog )和一个变量来保存行数,以便我们知道何时开始读取(例如{{1 }}),例如

10

注意:,将从其中读取整数的行作为程序的第二个参数,如果未指定,则使用默认行11-是,您应该对此rdstart的使用进行相同的完整验证,但我留给您)

还有什么需要改变的?不多。与其简单地读取和丢弃linecnt留下的 int first_num, *arr = NULL, nval = 0, rdstart = argc > 2 ? strtol(argv[2], NULL, 0) : 2, linecnt = 1; ,还不如2次(或者自初始化strtol'\n'次)。为此,只需将对fscanf的第一个调用包装在一个循环中(并更改错误消息以使其有意义),例如

linecnt-1

就是这样。 (请注意,只要省略第二个参数,它将继续处理前三个输入文件...)

示例输出从第11行开始

行得通吗?

linecnt

仔细检查一下,如果还有其他问题,请告诉我。有很多方法可以做到这一点,但是到目前为止,如果您学习如何使用linecnt = 1;(所有fgets函数的工作原理都差不多),您将在处理数字方面领先于游戏转换。

答案 1 :(得分:0)

  

我真正的问题是如何从文件的特定行中读取由空格分隔的整数...(?)

第1步:假设您期望最多N个整数。请以足够大而合理的最大长度阅读该行。我用的是预期最大尺寸的2倍。允许有额外的间距,但过长的线条可能会出现错误或敌对状态。

#define INT_PER_LINE_MAX 20

// About 1 digit per 3 bits, 28/93 is just above log10(2)
#define CHARACTERS_PER_INT_MAX ((sizeof(int)*CHAR_BIT - 1)*28/93 + 2)

// Room for N int, separators and \0
#define LINE_SIZE (INT_PER_LINE_MAX * (CHARACTERS_PER_INT_MAX + 1) + 1)

// I like 2x to allow extra spaces, leading zeros, etc.
char buf[LINE_SIZE * 2];

while (fgets(buf, sizeof buf, file) {
  ...

第2步:调用函数以解析 string

中的N整数
while (fgets(buf, sizeof buf, file) {
  int a[INT_PER_LINE_MAX];
  int count = parse_ints(a, INT_PER_LINE_MAX, buf);
  if (count < 0) {
    puts("bad input");
  } else {
    printf("%d int found\n", count);
  }
}

步骤3:制作parse_ints()。根据{{​​3}}的建议,使用strtol()解析字符串,或者将sscanf()"%d %n"一起使用。后一种方法缺乏可靠的溢出保护。

int parse_ints(int *a, int n, const char *buf) {

  int i;
  // enough room? and more to parse?
  for (i=0; i<n && *buf; i++) {
    int value;
    int n;  // save offset where scanning stopped.
    if (sscanf(buf, "%d %n", &value, &n) != 1) {
      return -1;  // No int scanned.
    }
    a[i] = value;
    buf += n;  // advance the buffer
  }
  if (*buf) {
    return -1;  // Unexpected extra text left over
  }
  return i;
}