我正在开发一个小代码,从文件中读取数字并将其存储在表中。文件在开头告诉用户表的行数(列数固定为3)
例如文件:
3
1 2 20.5
1 4 9.22
2 6 11.7
11 3 12.1
正如标题所说,我需要根据文件中出现的行数来分配表。有人能帮我一把吗
到目前为止,我做到了:
分配
int** table = malloc(r * sizeof(int)); //r = num of rows
for (i = 0; i < r; i++)
table[i] = malloc(sizeof(int*) * 3); //3 is the number of columns
填充
while (fscanf(fp, "%d %d %lf", &a, &b, &c) == 3) {
for (i = 0; i < r; i++) {
-> table[i][0]=a;
-> table[i][1]=b;
-> table[i][2]=c;
}
}
但在我用"->quot;我得到一个错误:表达式必须有指向对象类型的指针。
选择正确的类型
首先,如果您需要从文件中读取数字,例如:
1 2 20.5
然后读取int
值不会捕获最后一个浮点列。您需要在两个int
和一个float
之间创建一个结构,或者简单地使用float
(或double
)作为类型。
选择正确的聚合对象类型
虽然您可以使用指针到指针(例如float **
)并分配一定数量的指针,然后为每行分配一块内存用于3-float
,然后分配给每个分配的指针——如果列数固定为3,则使用指向数组的指针允许单个分配/单个空闲,这比为每行分配指针和块以保持3-CCD_。
不使用float **table;
,而是使用float (*table)[3]
(指向float[3]
的数组的指针)
使用指向数组的指针进行分配/重新分配
每当你需要读取和存储任何对象类型的未知数字时,你通常会从一开始分配一些数字,保留一个可用数字和使用数字的计数器,然后当used == available
、realloc()
为你的对象集合添加更多的存储空间并继续。对于任何内存分配,您必须在代码中使用该内存块之前,通过*检查返回来验证分配是否成功。
选择每次realloc()
时添加内存的方式。您可以每次为一些新的固定数量的对象添加存储(例如,每次realloc
时将10 * sizeof object
添加到available
(对于较小的未知数量来说很好),但分配/重新分配是相对昂贵的操作。
为了平衡内存大小的合理增长与必须重新分配的次数,一般的方案是每次重新分配时将available
的数量增加一倍(例如,您可以最初分配2
对象,然后每次重新分配都会产生4, 8, 16, 32, 64, 128, 256, 512, etc...
,而使用20
重新分配会产生超过2,000,000
对象的存储空间,而如果每次添加10-more
,则会有200
的存储空间。
如何从文件中读取数据
如果使用格式的输入函数(如fscanf()
)从文件中读取,则这是一个脆弱的方案。文件中的一个无关字符可能会中断从该点开始的读取。
一种更好的方法是使用面向行的输入函数,如fgets()
或POSIXgetline()
,它将一次从文件中读取一行数据到一个足够大的字符数组中,然后您可以从中解析所需的值。这样,您就可以独立验证对一行的读取,以及从该行提取所需的值。如果该行中有多余的字符,最糟糕的情况是您无法从该单行中获取值,但从所有其他行中读取和提取的值不受影响。
有多种方法可以从读取到字符数组、sscanf()
、strtol / strtof, etc..
、strtok / strsep
或任何其他字符串函数的一行数据中提取值,或者只需使用一对指针将所需字符括起来。
要打开文件,将文件名作为程序的第一个参数(如果没有给定参数,则默认情况下从stdin
读取),可以执行以下操作:
#define COLS 3 /* if you need a constant, #define one (or more) */
#define MAXC 256
...
char line[MAXC]; /* buffer to hold each line read */
...
while (fgets (line, MAXC, fp)) { /* read / validate each line */
...
/* parse / validate 3 floats from line into arr */
if (sscanf (line, "%f %f %f", &arr[used][0], &arr[used][1], &arr[used][2]) != 3)
continue;
used++; /* increment no. of used rows */
}
上面,您将把每一行读取到line
中,然后尝试用sscanf()
解析该行中的3-float
值。如果失败,只需continue
并读取下一行。如果成功,则将值提取到数组(表)中,只需增加行数used
并读取下一行即可。
当您分配初始数量的值时,开始为未知数量的数据行分配存储,例如
float (*arr)[COLS]; /* pointer-to-array float[3] */
size_t used = 0, /* allocated rows used */
avail = 2; /* allocated rows available */
...
/* allocate initial 'avail' rows / validate EVERY allocaiton */
if ((arr = malloc (avail * sizeof *arr)) == NULL) {
perror ("malloc-avail");
return 1;
}
然后在循环中,在将一行添加到阵列之前,通过检查if (used == avail)
来检查是否需要重新分配,以确保有可用的存储。如果需要重新分配,您可以重新分配给临时指针,因此当(而不是如果)realloc()
返回NULL
失败时,您不会覆盖指向数据的指针,NULL
会丢失指向该内存块开头的指针,这意味着它无法再释放,从而导致内存泄漏。
只有在验证重新分配成功后,才能将重新分配的内存块分配给指针,例如
while (fgets (line, MAXC, fp)) { /* read / validate each line */
if (used == avail) { /* is arr full, needs reallocation? */
/* always realloc to a temporary pointer */
void *tmp = realloc (arr, 2 * avail * sizeof *arr);
if (!tmp) { /* validate EVERY reallocation */
perror ("realloc-arr");
break; /* break, don't exit, arr still good */
}
arr = tmp; /* assign reallocated block to arr */
avail *= 2; /* update no. of available rows */
}
...
上面只是使用了每次调用realloc()
时将内存块大小加倍的重新分配方案。如果重新分配成功,则通过将可用数字乘以2来更新可用数字。
如果realloc()
失败,由于在对realloc()
的调用中使用了临时指针,因此原始指针仍然指向在对realloc()
的调用之前的数据。因此,无需退出程序,此时收集的数据仍然良好(只是内存不足)。因此,只需break;
您的读取循环并处理您所拥有的数据。
合计
如果你把它放在一起,你会得到类似的东西:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define COLS 3 /* if you need a constant, #define one (or more) */
#define MAXC 256
int main (int argc, char **argv) {
char line[MAXC]; /* buffer to hold each line read */
float (*arr)[COLS]; /* pointer-to-array float[3] */
size_t used = 0, /* allocated rows used */
avail = 2; /* allocated rows available */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* allocate initial 'avail' rows / validate EVERY allocaiton */
if ((arr = malloc (avail * sizeof *arr)) == NULL) {
perror ("malloc-avail");
return 1;
}
if (!fgets (line, MAXC, fp)) /* read / validate / discard 1st line */
return 1;
while (fgets (line, MAXC, fp)) { /* read / validate each line */
if (used == avail) { /* is arr full, needs reallocation? */
/* always realloc to a temporary pointer */
void *tmp = realloc (arr, 2 * avail * sizeof *arr);
if (!tmp) { /* validate EVERY reallocation */
perror ("realloc-arr");
break; /* break, don't exit, arr still good */
}
arr = tmp; /* assign reallocated block to arr */
avail *= 2; /* update no. of available rows */
}
/* parse / validate 3 floats from line into arr */
if (sscanf (line, "%f %f %f", &arr[used][0], &arr[used][1], &arr[used][2]) != 3)
continue;
used++; /* increment no. of used rows */
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (size_t i = 0; i < used; i++) /* output used rows */
printf (" %4g %4g %6.2fn", arr[i][0], arr[i][1], arr[i][2]);
free (arr); /* don't forget to free what you allocate */
}
(注意:第一行以上只是读取和丢弃。由于您是动态分配内存的,您可以简单地读取每条数据行并将其添加到您的数据行中,在需要时重新分配,然后重复,直到您要读取的行数用完或内存用完,以先发生的为准)
问题:为什么你可以删除第一线的读取和丢弃,而程序仍然运行?例如,为什么可以删除if (!fgets (line, MAXC, fp)) return 1;
这两行而不影响程序操作?
示例使用/输出
如果您的数据在文件dat/rowsfloats.txt
中,并且程序被编译为bin/readrowsfloat
,您将收到:
$ ./bin/readrowsfloat dat/rowsfloats.txt
1 2 20.50
1 4 9.22
2 6 11.70
1 3 12.10
已成功读取所有行,并将所有值存储在数组中。
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有2个责任:(1)始终为内存块保留一个指向起始地址的指针,因此,(2)当不再需要时,它可以被释放。
您必须使用内存错误检查程序来确保您不会试图访问内存或在分配的块的边界之外写入,尝试读取或基于未初始化的值进行条件跳转,最后确认您释放了所有分配的内存。
对于Linux,valgrind
是正常的选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行您的程序即可
$ valgrind ./bin/readrowsfloat dat/rowsfloats.txt
==25760== Memcheck, a memory error detector
==25760== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==25760== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==25760== Command: ./bin/readrowsfloat dat/rowsfloats.txt
==25760==
1 2 20.50
1 4 9.22
2 6 11.70
1 3 12.10
==25760==
==25760== HEAP SUMMARY:
==25760== in use at exit: 0 bytes in 0 blocks
==25760== total heap usage: 5 allocs, 5 frees, 5,744 bytes allocated
==25760==
==25760== All heap blocks were freed -- no leaks are possible
==25760==
==25760== For counts of detected and suppressed errors, rerun with: -v
==25760== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
请始终确认您已经释放了分配的所有内存,并且没有内存错误。
慢慢来
这里包含了很多信息,结果比我预期的要长,所以放慢速度,慢慢来了解每一行发生了什么以及为什么。动态内存分配并不困难,但必须循序渐进。
总之,你所做的一切都是分配一块内存来存储东西。如果你需要更大的内存块,你只需要重新分配。重新分配时,如果realloc()
失败,则使用临时指针来避免造成内存泄漏。完成后,只需free()
分配的内存即可。剩下的只是算术记录你分配了多少以及有多少可用。
仔细看看,如果你还有问题,请告诉我。
使用可变长度数组,您可以编写:
int table[r][3];
for (int i = 0; i < r; i++) {
for (int j = 0; j < 3; j++) {
table[i][j] = value_from_fscanf;
}
}
如果你不想使用VLA,由于第二个维度是固定的,你可以写:
int (*table)[3] = malloc(r * 3 * sizeof(int));