从c文件中的特定行读取整数 [英] Read integers from a specific line from a file in c

查看:76
本文介绍了从c文件中的特定行读取整数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您好,我一直在为这个问题苦苦挣扎,经过在互联网上的一些研究没有取得任何成果后,我决定寻求帮助.

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

我知道处理字符串的技巧

 while(fgets(pointer_to_string, length, "file_name.txt"))线++;/* 将 line-integer type- 增加 1*/if(line == your_line)/*对该行的字符串做一些事情*/

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

51 78 45 32 2

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

int a[20];整数;/*第一行的数字*/整数* 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;而(!feof(文件)){如果(行== 1){fscanf(file, "%d", &first_num);p = (int*)malloc(first_num*sizeof(int));}别的{for ( j = 0; j 

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

else if ( line == 2 )

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

我的猜测是我需要在while"中使用一个循环来检查我何时到达一行的末尾或类似的东西,但我真的被卡住了.

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

解决方案

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

一旦您读取了一行文本,您有两个选择,您可以使用 sscanf 将缓冲区中的数字转换为整数值(或者事先知道该行中包含的数字和提供足够数量的转换说明符,或者通过单独转换每个并使用 "%n" 说明符 报告提取的字符数该转换并将缓冲区中的开始增加该数量以进行下一次转换)

您的另一个选择,从错误检查和报告的角度来看,迄今为止最灵活和最可靠的方法是使用 strtol 并使用 endptr 参数来实现其预期目的提供一个指向转换后的最后一位数字的指针,允许您在进行时直接在缓冲区中转换值.请参阅:strtol(3) - Linux 手册页 strtol 提供了区分未转换数字上溢或下溢的故障的能力(将 errno 设置为一个适当的值),并允许您通过 endptr 参数测试转换后是否保留了其他字符,以控制您的值转换循环.

与您编写的任何代码一样,验证每个必要的步骤将确保您能够做出适当的响应.

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

示例输入文件

<块引用>

$ cat dat/int_file.txt51 78 45 32 2

当面对第一行的单个值时,大多数情况下您只想使用 fscanf (file, "%d", &ival); ,这很好,但是 - 使用任何 scanf 系列的陷阱是您必须考虑转换后输入缓冲区中剩余的任何字符.虽然 "%d" 转换说明符将提供所需的转换,但字符提取会在最后一个数字处停止,从而使 '\n' 未读.只要您考虑到这一事实,就可以使用 fscanf 获取第一个值.但是,您必须验证整个过程中的每一步.

让我们看一个例子的开头,打开一个文件(如果没有给出文件名,则从 stdin 读取),验证文件是否打开,然后验证 first_num 被读取,例如

#include #include /* 用于 malloc/free &EXIT_FAILURE */#include /* 用于 strtol 验证 */#include /* 对于 INT_MIN/INT_MAX */#define MAXC 1024/* 不要吝啬缓冲区大小 */int main (int argc, char **argv) {int first_num,/* 你的 first_num */*arr = NULL,/* 一个指向用 int 值填充的块的指针 */nval = 0;/* 转换的值的数量 */字符缓冲区[MAXC];/* 缓冲以保存后续读取的行 *//* 作为第一个参数传递的打开文件(默认值:如果没有参数则为标准输入)*/文件 *fp = argc >1 ?fopen (argv[1], "r"): 标准输入;if (!fp) {/* 验证文件打开以供读取 */perror ("fopen-file");退出(EXIT_FAILURE);}if (fscanf (fp, "%d", &first_num) != 1) {/* read/validate int */fputs("错误:文件格式无效,整数不是第一个.\n", stderr);退出(EXIT_FAILURE);}

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

<代码>\n1 78 45 32 2

由于您要开始对文件中剩余的行进行面向行的读取,您可以简单地第一次调用 fgets 以读取和丢弃 '\n',例如

 if (!fgets (buf, MAXC, fp)) {/* read/discard '\n' */fputs ("错误: 非 POSIX 在第一个整数之后结束.\n", stderr);退出(EXIT_FAILURE);}

(注意:验证.如果文件以非 POSIX 行结尾(例如没有 '\n')结束,fgets 将失败,除非您正在检查,否则您很可能会通过尝试稍后从没有要读取的字符的文件流中读取,然后尝试从内容不确定的缓冲区中读取来调用未定义的行为)

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

/* 为 first_num 整数分配/验证存储空间 */if (!(arr = malloc (first_num * sizeof *arr))) {perror ("malloc-arr");退出(EXIT_FAILURE);}

为了读取文件中的剩余值,您只需调用一次 fgets,然后转换包含在已填充缓冲区中的整数值,但只需稍加考虑,您就可以可以设计一种方法,可以根据需要读取尽可能多的行,直到 first_num 整数被转换或遇到 EOF.无论您是在缓冲区中获取输入还是转换值,可靠的方法都是相同的循环不断,直到您获得所需的内容或用完数据,例如

while (fgets (buf, MAXC, fp)) {/* 读取行直到进行转换 */char *p = buf,/* nptr &用于 strtol 转换的 endptr */*endptr;if (*p == '\n')/* 跳过空行 */继续;while (nval < first_num) {/* 循环直到 nval == first_num */错误号 = 0;/* 为每次转换重置 errno */long tmp = strtol (p, &endptr, 0);/* 调用 strtol */if (p == endptr && tmp == 0) {/* 验证数字转换 *//* 没有数字转换 - 向前扫描到下一个 +/- 或 [0-9] */做p++;而 (*p && *p != '+' && *p != '-' &&( *p <'0' || '9' <*p));if (*p)/* 数字序列的有效开始?*/继续;/* 尝试下一次转换 */别的休息;/* 阅读下一行 */}else if (errno) {/* 验证成功转换 */fputs ("错误:转换中上溢/下溢.\n", stderr);退出(EXIT_FAILURE);}else if (tmp < INT_MIN || INT_MAX < tmp) {/* 验证 int */fputs("错误:值超出'int'的范围.\n", stderr);退出(EXIT_FAILURE);}else {/* 有效转换 - 在 int 范围内 */arr[nval++] = tmp;/* 将值添加到数组 */if (*endptr && *endptr != '\n')/* 如果字符仍然存在 */p = endptr;/* 更新 p 到 endptr */else/* 否则 */休息;/* 保释 */}}if (nval == first_num)/* 是否填充了所有值?*/休息;}

现在让我们稍微拆解一下.发生的第一件事是声明与 strtol 一起工作所需的指针,并将用 fgets 填充的 buf 的起始地址分配给 p 然后从文件中读取一行.没有必要在空行上尝试转换,所以我们测试 buf 中的第一个字符,如果它是 '\n' 我们得到下一行:

<代码> ...if (*p == '\n')/* 跳过空行 */继续;...

一旦您有一个非空行,您就开始转换循环并尝试转换,直到您拥有的值数量等于 first_num 或到达该行的末尾.您的循环控制很简单:

 while (nval < first_num) {/* 循环直到 nval == first_num */...}

在循环中,您将通过在每次转换之前重置 errno = 0; 并将转换返回分配给临时 来完全验证您尝试使用 strtol 的转换>long int 值.(例如字符串到长),例如

 错误号 = 0;/* 为每次转换重置 errno */long tmp = strtol (p, &endptr, 0);/* 调用 strtol */

进行转换后,您需要验证三个条件才能进行良好的整数转换,

  1. 如果没有数字被转换,则 p == endptr(并且根据手册页返回设置为零).所以要检查这种情况是否发生,你可以检查:if (p == endptr && tmp == 0);
  2. 如果在数字转换过程中出现错误,无论发生哪个错误,errno 都将设置为非零值,允许您使用 if 检查转换中的错误(错误).您还可以进一步深入了解手册页中指定的发生了什么,但出于验证目的,了解是否发生错误就足够了;最后
  3. 如果数字被转换并且没有错误,你仍然没有完成.strtol 转换为 long 的值,该值可能与 int 兼容,也可能不兼容(例如 long8-bytes on x86_64 而 int4-bytes.所以为了确保转换后的值适合你的整数数组,你需要检查返回的值在 INT_MININT_MAX 之前 将值分配给 arr 的元素.

(注意:与上面的1.,仅仅因为没有数字被转换并不意味着行中没有数字,它只是意味着第一个值不是一个数字.您应该使用指针在行中向前扫描以找到下一个 +/-[0-9] 以确定存在更多数值.即代码块中 while 循环的目的)

一旦你有了一个好的整数值,回想一下 endptr 将被设置为最后一个数字转换后的下一个字符.快速检查 *endptr 是否不是 nul-termination 字符,而不是行尾,将告诉您是否还有可用于转换的章程.如果是这样,只需更新 p = endptr 以便您的指针现在指向转换并重复的最后一位数字之后.(此时您也可以使用上面使用的相同 while 循环向前扫描以确定是否存在另一个数值——这由您决定)

一旦循环完成,您需要做的就是检查 nval == first_num 是否需要继续收集值.

总而言之,你可以做类似的事情:

#include #include /* 用于 malloc/free &EXIT_FAILURE */#include /* 用于 strtol 验证 */#include /* 对于 INT_MIN/INT_MAX */#define MAXC 1024/* 不要吝啬缓冲区大小 */int main (int argc, char **argv) {int first_num,/* 你的 first_num */*arr = NULL,/* 一个指向用 int 值填充的块的指针 */nval = 0;/* 转换的值的数量 */字符缓冲区[MAXC];/* 缓冲以保存后续读取的行 *//* 作为第一个参数传递的打开文件(默认值:如果没有参数则为标准输入)*/文件 *fp = argc >1 ?fopen (argv[1], "r"): 标准输入;if (!fp) {/* 验证文件打开以供读取 */perror ("fopen-file");退出(EXIT_FAILURE);}if (fscanf (fp, "%d", &first_num) != 1) {/* read/validate int */fputs("错误:文件格式无效,整数不是第一个.\n", stderr);退出(EXIT_FAILURE);}if (!fgets (buf, MAXC, fp)) {/* 读取/丢弃 '\n' */fputs ("错误: 非 POSIX 在第一个整数之后结束.\n", stderr);退出(EXIT_FAILURE);}/* 为 first_num 整数分配/验证存储 */if (!(arr = malloc (first_num * sizeof *arr))) {perror ("malloc-arr");退出(EXIT_FAILURE);}while (fgets (buf, MAXC, fp)) {/* 读取行直到进行转换 */char *p = buf,/* nptr &用于 strtol 转换的 endptr */*endptr;if (*p == '\n')/* 跳过空行 */继续;while (nval < first_num) {/* 循环直到 nval == first_num */错误号 = 0;/* 为每次转换重置 errno */long tmp = strtol (p, &endptr, 0);/* 调用 strtol */if (p == endptr && tmp == 0) {/* 验证数字转换 *//* 没有数字转换 - 向前扫描到下一个 +/- 或 [0-9] */做p++;而 (*p && *p != '+' && *p != '-' &&( *p <'0' || '9' <*p));if (*p)/* 数字序列的有效开始?*/继续;/* 尝试下一次转换 */别的休息;/* 去读下一行 */}else if (errno) {/* 验证成功转换 */fputs ("错误:转换中上溢/下溢.\n", stderr);退出(EXIT_FAILURE);}else if (tmp < INT_MIN || INT_MAX < tmp) {/* 验证 int */fputs("错误:值超出'int'的范围.\n", stderr);退出(EXIT_FAILURE);}else {/* 有效转换 - 在 int 范围内 */arr[nval++] = tmp;/* 将值添加到数组 */if (*endptr && *endptr != '\n')/* 如果字符仍然存在 */p = endptr;/* 更新 p 到 endptr */else/* 否则 */休息;/* 保释 */}}if (nval == first_num)/* 是否填充了所有值?*/休息;}if (nval < first_num) {/* 验证找到的所需整数 */fputs("error: EOF before all integers read.\n", stderr);退出(EXIT_FAILURE);}for (int i = 0; i < nval; i++)/* 循环输出每个整数 */printf ("arr[%2d] : %d\n", i, arr[i]);免费 (arr);/* 不要忘记释放你分配的内存 */if (fp != stdin)/* 并关闭您打开的所有文件流 */fclose (fp);返回0;}

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

示例使用/输出

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

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

为什么要找额外的麻烦?

通过彻底理解转换过程并解决几行额外的麻烦,您最终会得到一个例程,该例程可以为您需要的任何数量的整数提供灵活的输入处理,而不管输入文件格式如何.让我们看看输入文件的另一种变体:

更具挑战性的输入文件

$ cat dat/int_file2.txt51 784532 2 144 91 270富

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

更具挑战性的输入文件

如果我们再次加大赌注怎么办?

$ cat dat/int_file3.txt51两扣我鞋,78关门45是九十的一半富吧32 是 16 乘以 2,144 是毛数,91 不是素数和 270 口径巴兹

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

但我想指定开始阅读的行

好的,让我们用另一个输入文件来配合这个例子.说:

从给定行读取的示例输入

$ cat dat/int_file4.txt51,2扣我的鞋,7,8关门45是九十的一半富吧32 是 16 乘以 2,144 是毛数,91 不是素数和 270 口径巴兹1 78 45 32 2 27 41 39 1111一只敏捷的棕色狐狸跳过懒狗

我需要改变什么?唯一需要的更改是跳过前 10 行并从 11 行开始转换循环.为此,您需要添加一个变量来保存行的值以开始读取整数(比如 rdstart)和一个变量来保存行数,以便我们知道何时开始读取(比如linecnt),例如

 int first_num,* arr = NULL,nval = 0,rdstart = argc >2 ?strtol(argv[2], NULL, 0) : 2,linecnt = 1;

(注意:开始读取整数的行被作为程序的第二个参数,如果没有指定,则使用默认的 2 行 -- 是的,您应该对 strtol 的这种使用应用相同的完整验证,但我留给您)

还有什么需要改变的?不多.而不是简单地读取和丢弃 fscanf 留下的 '\n',只需这样做 linecnt-1 次(或只是 linecnt 自您初始化 linecnt = 1; 以来的时间).为此,只需将您对 fgets 的第一次调用包装在一个循环中(并更改错误消息以使其有意义),例如

 while (linecnt < rdstart) {/* 循环直到 linecnt == rdstart */if (!fgets (buf, MAXC, fp)) {/* 读取/丢弃行 */fputs ("错误: 少于请求的行数.\n", stderr);退出(EXIT_FAILURE);}linecnt++;/* 增加 linecnt */}

就是这样.(并注意它会继续处理前 3 个输入文件,只需省略第二个参数...)

示例输出从第 11 行开始

有用吗?

$ ./bin/fgets_int_file_line dat/int_file4.txt 11arr[0]:1arr[1]:78arr[2]:45arr[3]:32arr[4]:2

检查一下,如果您还有其他问题,请告诉我.有很多方法可以做到这一点,但到目前为止,如果您学会了如何使用 strtol(所有 strtoX 函数的工作方式都非常相似),那么您将遥遥领先处理数字转换的游戏.

Hello there I've been struggling with this problem for some time and after some research on the internet that didn't bear any fruit I decided to ask for some help.

I need to read some integers from a file and from a specific line and then do something with them.

I know this trick for handling strings of characters

 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*/

I know that 'fgets()' will read everything until it reaches a newline '\n' so that makes it easy,but my problem is a bit different. I need to read from a file integers, for example:

5
1 78 45 32 2

In my particular case the number on the first line represents the number of integers located on the second line separated by a blank space, so i need to read the first number then create a pointer to an array to which I will allocate memory:

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

Of course the memory allocation will be done after I read the first number from the file.

So I guess I'd be easier to just show you my struggle:

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

Weirdly enough this program actually works for this example ( number of elements on the 1st line and array on the 2nd ) but I have a feeling that it is flawed or at least I can't call it "clean" mostly because I'm not really sure how that loop works I know that 'feof' function is used to reach the end of a file so as long as I'm not there yet it will return a non-zero value and that's why I can memorize the number on the 1st line but I don't know when and how it checks the loop.At first I thought that it does it at the end of every line so but that would imply that if I were to change the 'else' with:

else if ( line == 2 )

it would still need to work properly, which it doesn't.So I would appreciate some explanations for how that loop actually works.

My guess is that I need a loop in the 'while' to check when I reached the end of a line or something like that but I'm really stuck.

My real question is for how to read integers separated by space from a specific line from a file and not necessarily the example I gave you ( that one is for someone who wouldn't mind helping me out )

解决方案

Let's start with some basics. When reading lines from a file (or lines of input from the user), you will generally want to use a line-oriented input function such as fgets or POSIX getline to make sure you read an entire line at a time and not have what is left in your input buffer depend on which scanf conversion specifier was used last. With fgets you will need to provide a buffer of sufficient size to hold the entire line, or dynamically allocate and realloc as needed until an entire line is read (getline handles this for you). You validate an entire line was read by checking the last character is the '\n' character or that the length of the buffer is less than the maximum size (both are left to you below).

Once you have a line of text read, you have two options, you can either use sscanf to convert the digits in your buffer to integer values (either knowing the number contained in the line beforehand and providing an adequate number of conversion specifiers, or by converting each individually and using the "%n" specifier to report the number of characters extracted for that conversion and incrementing the start within your buffer by that amount for the next conversion)

Your other option, and by far the most flexible and robust from an error checking and reporting standpoint is to use strtol and use the endptr parameter for its intended purpose of providing a pointer to one past the last digit converted allowing you to walk down your buffer directly converting values as you go. See: strtol(3) - Linux manual page strtol provides the ability to discriminate between a failure where no digits were converted, where overflow or underflow occurred (setting errno to an appropriate value), and allows you to test whether additional characters remain after the conversion through the endptr parameter for control of your value conversion loop.

As with any code you write, validating each necessary step will ensure you can respond appropriately.

Let's start with your sample input file:

Example Input File

$ cat dat/int_file.txt
5
1 78 45 32 2

When faced with a single value on the first line, a majority of the time you will simply want to convert the original value with fscanf (file, "%d", &ival);, which is fine, but -- the pitfalls of using any of the scanf family is YOU must account for any characters left in the input buffer following conversion. While the "%d" conversion specifier will provide the needed conversion, character extract stops with the last digit, leaving the '\n' unread. As long as you account for that fact, it's fine to use fscanf to grab the first value. However, you must validate each step along the way.

Let's look at the beginning of an example doing just that, opening a file (or reading from stdin if no filename is given), validating the file is open, and then validating first_num is read, e.g.

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

At this point, your input buffer contains:

\n
1 78 45 32 2

Since you are going to embark on a line-oriented read of the remaining lines in the file, you can simply make your first call to fgets for the purpose of reading and discarding the '\n', e.g.

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

(note: the validation. If the file had ended with a non-POSIX line end (e.g. no '\n'), fgets would fail and unless you are checking, you are likely to invoke undefined behavior by attempting to later read from a file stream where no characters remain to be read and thereafter attempting to read from a buffer with indeterminate contents)

You can allocate storage for first_num number of integers at this point and assign the starting address for that new block to arr for filling with integer values, e.g.

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

For reading the remaining values in your file, you could just make a single call to fgets and then turn to converting the integer values contained within the buffer filled, but with just a little forethought, you can craft an approach that will read as many lines as needed until first_num integers have been converted or EOF is encountered. Whether you are taking input or converting values in a buffer, a robust approach is the same Loop Continually Until You Get What You Need Or Run Out Of Data, e.g.

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

Now let's unpack this a bit. The first thing that occurs is you declare the pointers needed to work with strtol and assign the starting address of buf which you fill with fgets to p and then read a line from your file. There is no need to attempt conversion on a blank line, so we test the first character in buf and if it is a '\n' we get the next line with:

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

Once you have a non-empty line, you start your conversion loop and will attempt conversions until the number of values you have equals first_num or you reach the end of the line. Your loop control is simply:

    while (nval < first_num) {  /* loop until nval == first_num */
        ...
    }

Within the loop you will fully validate your attempted conversions with strtol by resetting errno = 0; before each conversion and assigning the return of the conversion to a temporary long int value. (e.g. string-to-long), e.g.

        errno = 0;              /* reset errno for each conversion */
        long tmp = strtol (p, &endptr, 0);  /* call strtol */

Once you make the conversion, you have three conditions to validate before you have a good integer conversion,

  1. if NO digits were converted, then p == endptr (and per the man page the return is set to zero). So to check whether this condition occurred, you can check: if (p == endptr && tmp == 0);
  2. if there was an error during conversion of digits, regardless of which error occurred, errno will be set to a non-zero value allowing you to check for an error in conversion with if (errno). You can also further dive into which occurred as specified in the man page, but for validation purposes here it is enough to know whether an error occurred; and finally
  3. if digits were converted and there was no error, you are still not done. The strtol conversion is to a value of long that may or may not be compatible with int (e.g. long is 8-bytes on x86_64 while int is 4-bytes. So to ensure the converted value will fit in your integer array, you need to check that the value returned is within INT_MIN and INT_MAX before you assign the value to an element of arr.

(note: with 1. above, just because no digits were converted does not mean there were no digits in the line, it just means the first value was not a digit. You should scan forward in the line using your pointer to find the next +/- or [0-9] to determine in further numeric values exist. That is the purpose of the while loop within that code block)

Once you have a good integer value, recall that endptr will be set to the next character after the last digit converted. A quick check whether *endptr is not the nul-terminating character and not the line-ending will tell you whether charters remain that are available for conversion. If so, simply update p = endptr so that your pointer now points one past the last digit converted and repeat. (you can also scan forward at this point with the same while loop used above to determine if another numeric value exists -- this is left to you)

Once the loop completes, all you need do is check if nval == first_num to know if you need to continue collecting values.

Putting it altogether, you could do something similar to:

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

(note: the final check of if (nval < first_num) after exiting the read and conversion loop)

Example Use/Output

With your example file, you would get the following:

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

Why Go To The Extra Trouble?

By thoroughly understanding the conversion process and going to the few additional lines of trouble, you end up with a routine that can provide flexible input handling for whatever number of integers you need regardless of the input file format. Let's look at another variation of your input file:

A More Challenging Input File

$ cat dat/int_file2.txt
5

1 78

      45

32 2 144 91 270

foo

What changes are needed to handle retrieving the same first five integer values from this file? (hint: none - try it)

An Even More Challenging Input File

What if we up the ante again?

$ 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

What changes are needed to read the first 5 integer values from this file? (hint: none)

But I Want To Specify the Line To Start Reading From

OK, let's take another input file to go along with the example. Say:

An Example Input Reading From A Given Line

$ 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

What would I have to change? The only changes needed are changes to skip the first 10 lines and begin your conversion loop at line 11. To do that you would need to add a variable to hold the value of the line to start reading integers on (say rdstart) and a variable to hold the line count so we know when to start reading (say linecnt), e.g.

    int first_num,
        *arr = NULL,
        nval = 0,
        rdstart = argc > 2 ? strtol(argv[2], NULL, 0) : 2,
        linecnt = 1;

(note: the line to start the integer read from is taken as the 2nd argument to the program or a default of line 2 is used if none is specified -- and yes, you should apply the same full validations to this use of strtol, but that I leave to you)

What else needs changing? Not much. Instead of simply reading and discarding the '\n' left by fscanf, just do that linecnt-1 times (or just linecnt time since you initialized linecnt = 1;). To accomplish that, simply wrap your first call to fgets in a loop (and change the error message to make sense), e.g.

    while (linecnt < rdstart) { /* loop until linecnt == rdstart */
        if (!fgets (buf, MAXC, fp)) {   /* read/discard line */
            fputs ("error: less than requested no. of lines.\n", stderr);
            exit (EXIT_FAILURE);
        }
        linecnt++;  /* increment linecnt */
    }

That's it. (and note it will continue to handle the first 3 input files as well just by omitting the second parameter...)

Example Output Start At Line 11

Does it work?

$ ./bin/fgets_int_file_line dat/int_file4.txt 11
arr[ 0] : 1
arr[ 1] : 78
arr[ 2] : 45
arr[ 3] : 32
arr[ 4] : 2

Look things over and let me know if you have further questions. There are many ways to do this, but by far, if you learn how to use strtol (all the strtoX functions work very much the same), you will be well ahead of the game in handling numeric conversion.

这篇关于从c文件中的特定行读取整数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆