arrays - C:动态分配表
问题描述
我正在开发一个从文件中读取数字并将它们存储在表中的小代码。该文件在开始时告诉用户表格的行数(列数是固定的:3)
例如文件:
3
1 2 20.5
1 4 9.22
2 6 11.7
1 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;
}
}
但是在我用“->”指出的行中,我收到一条错误消息:表达式必须具有指向对象的指针类型。
解决方案
选择正确的类型
首先,如果您需要从文件中读取数字,例如:
1 2 20.5
然后读入一个int
值不会捕获最后一个浮点列。您将需要在 twoint
和 one之间创建一个结构float
,或者简单地使用float
(or double
) 作为类型。
选择正确的聚合对象类型
虽然您可以使用指针到指针(例如float **
)并分配一些指针,然后float
为每行分配一块内存 3- 然后分配给每个分配的指针——如果列数固定为三,然后使用指向数组的指针允许单一分配/单一释放,这比分配一个指针和一个块来float
为每一行保存 3- 容易得多。
而不是使用float **table;
你会使用float (*table)[3]
(a pointer-to-array of float[3]
)
使用指向数组的分配/重新分配
每当您需要读取和存储未知数量的任何对象类型时,您通常会分配一些数字开始,保持可用数字的计数器,然后使用数字,然后何时used == available
,realloc()
为您的对象集合添加更多存储空间和继续。对于任何内存分配,您必须通过 *在代码中使用该内存块之前检查返回来验证分配是否成功。
选择每次添加内存的方式realloc()
。您可以每次都为一些新的固定数量的对象添加存储(例如,每次都添加10 * sizeof object
到您(对于一个小的未知数来说很好)),但是分配/重新分配是相对昂贵的操作。available
realloc
为了平衡内存大小的合理增长与您必须重新分配的次数,一般方案是每次重新分配时将数量加倍available
(例如,您可以最初分配2
对象,然后每次重新分配都会导致4, 8, 16, 32, 64, 128, 256, 512, etc...
重新20
分配会导致存储超过2,000,000
对象,而如果10-more
每次添加,您将拥有200
.
您如何从文件中读取数据
如果您使用格式化输入函数(例如fscanf()
从文件中读取),那么这是一个脆弱的方案。文件中的一个无关字符可能会破坏您从该点开始的阅读。
更好的方法是使用面向行的输入函数,例如fgets()
POSIX getline()
,它将一次从文件中读取一行数据到一个足够大小的字符数组中,然后您可以从中解析所需的值。这样,您可以独立验证您对一行的读取,以及从该行中提取所需的值 - 并且 - 如果您在该行中有额外的字符,最糟糕的情况是您无法从该 1 中获取值-line,但从所有其他人的读取和提取不受影响。
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
,然后尝试使用 解析float
该行中的 3- 值sscanf()
。如果失败,您只需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.2f\n", arr[i][0], arr[i][1], arr[i][2]);
free (arr); /* don't forget to free what you allocate */
}
(注意:上面的第一行只是读取并丢弃。由于您是动态分配内存,您可以简单地读取每个数据行并将其添加到您的,在需要时重新分配,并重复直到您用完行来读取或用完内存,以先到者为准)
问题:为什么可以删除第一行的read和discard,程序还是一样的运行?例如,为什么可以删除这两行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
所有行都已成功读取,并且所有值都存储在您的数组中。
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1)始终保留指向内存块起始地址的指针,(2)它可以在没有时被释放更需要。
您必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出/超出分配块的范围,尝试读取或基于未初始化值的条件跳转,最后确认释放所有分配的内存。
对于 Linuxvalgrind
是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ 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()
分配已分配的内存。其余的只是算术跟踪您分配了多少以及可用的数量。
如果您还有其他问题,请仔细查看并告诉我。
推荐阅读
- yugabyte-db - 如何在 yugabyte-db 中设置 Universe 名称和命名空间?
- typescript - Typescript中“数字”和“任何”之间的类型关系是什么?
- java - 有没有办法让python产生与java相同的结果按位左移?
- css - SCSS 中的 Flexbox 多类选择器
- c# - 处理保存文件迁移的最佳方法
- node.js - 开玩笑测试通过但得到错误:最后连接 ECONNREFUSED 127.0.0.1:80
- android - 使用 Observable.zip(...) 时如何获取 API 响应?
- terraform - 无法从 azure repo(私人 repo)下载 terraform 模块
- laravel - 验证表 api 中的用户条目
- vuejs2 - 如何让插槽在 Quasar 布局中工作