好吧,我得到了您的意见,但让我们尝试一个 struct 数组 以使您的数据处理更易于管理。这不是您最后一个问题的简单补充。尝试协调 5 个单独的数组(其中 3 个是 2D 字符数组)会带来比其价值更多的问题。 struct 是将不同类型的信息作为一个单元进行协调的正确方法。此外,除非您有持续使用 bool 的需求,否则只需使用 int 代替。编译器可以同样高效地处理原生类型 int。
让我们从你的结构开始。 struct 只不过是一个方便的包装器,允许您收集不同的类型并将它们作为一个对象处理。在您的情况下,您的字符串name, plate, date、您的int type 和您的double value(您的amount)可以是struct 的成员。您可以将typedef 用于结构以使其使用更方便,因此您可以像使用普通类型一样简单地使用类型化的nameofstruct,而不是在任何地方编写struct nameofstruct,就像int。例如:
#define MAXS 16u /* max number of structs */
#define MAXNM 32u /* max characters in name and other arrays in struct */
#define MAXC 1024u /* max characters in read buffer */
#define SECPY 31536000u /* seconds per-year */
typedef struct mydata {
char name[MAXNM],
plate[MAXNM],
date[MAXNM];
int type;
double value;
} mydata_t;
使用mydata_t 的typedef 创建一个struct mydata,它是struct mydata 的引用(别名)(您实际上可以省略mydata,而只需键入定义匿名结构)。
现在您可以创建一个struct mydata 或简单mydata_t 的数组,并且该数组的每个元素都是一个可以保存其中每个值的结构。
现在让我们开始解决您的问题。如上所述,只需声明一个 mydata_t 数组,而不是 5 个单独的数组,例如
int main (int argc, char **argv) {
...
int n = 0, ndx = 0; /* NOTE when dealing with array, start at ZERO */
...
mydata_t data[MAXS] = {{ .name = "" }};
现在您有一个 16 结构数组可以使用(根据需要调整数组的常量大小),并且您有 2 个计数器 n 来跟踪您当前正在阅读的结构中的哪个成员,以及 @ 987654347@你正在填充的数组中哪个结构的索引。
使用它们,您可以像在上一个问题中一样阅读每一行,并使用 n 来确定如何处理该行(您可以使用 isspace() 检查第一个字符是否为 whitespae(其中包括 '\n' ) 跳过空行。然后您只需将n 传递给switch(n) 以对该行采取适当的操作(或使用一堆if ... else if ... else if ...)例如:
while (fgets (buf, MAXC, fp)) { /* read each line in file */
if (isspace(*buf)) /* skip blank lines (or start with space) */
continue;
buf[strcspn (buf, "\r\n")] = 0; /* trim '\n' from end of buf */
/* if line isn't a date line, just output line as non-date line */
switch (n) {
case 0: /* fill the name in struct */
case 1: /* fill the plate in struct */
case 2: /* set the type in struct */
...
(注意:buf[strcspn (buf, "\r\n")] = 0; 只是一种方便的方法,可以在将 buf 末尾的 '\n'(或 \r\n)复制到 name, plate, date 等之前对其进行修剪。 .)
在每种情况下,您只需在复制之前验证使用fgets 读取的字符串是否适合您的结构的字符串成员,或者将字符串转换为您需要的数值,例如:
case 0:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].name, buf);
else {
fputs ("error: name exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++; /* advance n counter to act on next member */
break;
或
case 2:
if (sscanf (buf, "%d", &data[ndx].type) != 1) {
fputs ("error: type not an integer.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
(在处理结构本身时,您使用'.'(点)运算符来访问结构的每个成员,或者如果您拥有的是一个指向的指针,则使用'->'(箭头)运算符 struct。当你有一个 stuct 数组时,数组索引 ([..]) 充当取消引用。与其他任何数组完全相同)
由于您不存储year, month, day,您只需要一个检查从现在开始的时间的函数,以便您可以测试它是否超过一年。这样,您只需将日期传递给函数并以秒为单位以 double 的形式返回时间,例如
double check_time_from_now (const char *str)
{
int y, m, d;
if (sscanf (str, "%4d%2d%2d", &y, &m, &d) != 3) {
fprintf (stderr, "error non-date string: '%s'.\n", str);
exit (EXIT_FAILURE);
}
time_t now = time(NULL),
then = fill_broken_down_time (y, m, d);
double secs = difftime (now, then); /* get seconds between dates */
return secs;
}
另一个简单的函数允许您打印结构数组,例如
void prn_data_t_array (mydata_t *data, int n)
{
for (int i = 0; i < n; i++)
printf ("%-12s %-8s %d %9.2f %s\n", data[i].name, data[i].plate,
data[i].type, data[i].value, data[i].date);
}
我们可以查看您的 switch(n) 的其余部分,其中显示了如何处理每种情况:
while (fgets (buf, MAXC, fp)) { /* read each line in file */
if (isspace(*buf)) /* skip blank lines (or start with space) */
continue;
buf[strcspn (buf, "\r\n")] = 0; /* trim '\n' from end of buf */
/* if line isn't a date line, just output line as non-date line */
switch (n) {
case 0:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].name, buf);
else {
fputs ("error: name exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 1:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].plate, buf);
else {
fputs ("error: plate exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 2:
if (sscanf (buf, "%d", &data[ndx].type) != 1) {
fputs ("error: type not an integer.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 3:
if (sscanf (buf, "%lf", &data[ndx].value) != 1) {
fputs ("error: value not a double.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 4:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].date, buf);
else {
fputs ("error: date exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
if (check_time_from_now (data[ndx].date) > SECPY) {
if (data[ndx].type)
data[ndx].value *= 1.5;
else
data[ndx].value *= 2.5;
}
n = 0;
ndx++;
if (ndx == MAXS)
goto arrayfull;
break;
default:
fputs ("error: you shouldn't get here!\n", stderr);
break;
}
}
(注意: 仔细查看 case 4: 以及行计数器 n 是如何重置为零的,以及数组索引 ndx 是如何递增的,因此你可以在你的下一个结构中填充数组。另外注意您可以通过检查 if (ndx == MAXS) 来保护您的数组边界,以确保您编写的结构体不会超过数组中的结构体。)
总而言之,您将拥有:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#define MAXS 16u /* max number of structs */
#define MAXNM 32u /* max characters in name and other arrays in struct */
#define MAXC 1024u /* max characters in read buffer */
#define SECPY 31536000u /* seconds per-year */
typedef struct {
char name[MAXNM],
plate[MAXNM],
date[MAXNM];
int type;
double value;
} mydata_t;
time_t fill_broken_down_time (int y, int m, int d)
{ /* initialize struct members */
struct tm bdt = { .tm_sec=0, .tm_min=0, .tm_hour=0, .tm_mday=d,
.tm_mon=m>0?m-1:0, .tm_year=y-1900, .tm_isdst=-1 };
return mktime(&bdt); /* return mktime conversion to time_t */
}
double check_time_from_now (const char *str)
{
int y, m, d;
if (sscanf (str, "%4d%2d%2d", &y, &m, &d) != 3) {
fprintf (stderr, "error non-date string: '%s'.\n", str);
exit (EXIT_FAILURE);
}
time_t now = time(NULL),
then = fill_broken_down_time (y, m, d);
double secs = difftime (now, then); /* get seconds between dates */
return secs;
}
void prn_data_t_array (mydata_t *data, int n)
{
for (int i = 0; i < n; i++)
printf ("%-12s %-8s %d %9.2f %s\n", data[i].name, data[i].plate,
data[i].type, data[i].value, data[i].date);
}
int main (int argc, char **argv) {
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
int n = 0, ndx = 0; /* NOTE when dealing with array, start at ZERO */
char buf[MAXC]; /* buffer to hold each line read from file */
mydata_t data[MAXS] = {{ .name = "" }};
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line in file */
if (isspace(*buf)) /* skip blank lines (or start with space) */
continue;
buf[strcspn (buf, "\r\n")] = 0; /* trim '\n' from end of buf */
/* if line isn't a date line, just output line as non-date line */
switch (n) {
case 0:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].name, buf);
else {
fputs ("error: name exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 1:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].plate, buf);
else {
fputs ("error: plate exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 2:
if (sscanf (buf, "%d", &data[ndx].type) != 1) {
fputs ("error: type not an integer.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 3:
if (sscanf (buf, "%lf", &data[ndx].value) != 1) {
fputs ("error: value not a double.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 4:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].date, buf);
else {
fputs ("error: date exceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
if (check_time_from_now (data[ndx].date) > SECPY) {
if (data[ndx].type)
data[ndx].value *= 1.5;
else
data[ndx].value *= 2.5;
}
n = 0;
ndx++;
if (ndx == MAXS)
goto arrayfull;
break;
default:
fputs ("error: you shouldn't get here!\n", stderr);
break;
}
}
arrayfull:;
if (fp != stdin) fclose (fp); /* close file if not stdin */
puts ("\ncomputed information\n");
prn_data_t_array (data, ndx); /* print the computed values */
return 0;
}
现在使用您的数据文件:
输入文件示例
$ cat dat/namelbltypevaldate.txt
Hanif Hefaz
BA123HB
0
100.50
20180101
Jacki Shroff
UP673MK
1
3000.99
20170512
使用/输出示例
代码会产生:
$ ./bin/time_from_now3 dat/namelbltypevaldate.txt
computed information
Hanif Hefaz BA123HB 0 251.25 20180101
Jacki Shroff UP673MK 1 4501.48 20170512
如果您选中,则相应的乘数已应用于每个。
我真的不想用 5 个单独的数组来做这个。结构是完成这项工作的合适工具。你可以使用 5 个数组,就像我使用上面的 struct 数组一样——你只是有一个更长的变量列表和更多的名称来跟踪你的代码。看看这个,如果你有更多问题,请告诉我。
动态分配结构数组
从功能上讲,程序并不关心数据的存储位置。但是,使用自动存储进行分配确实有一个缺点,即在您继续添加记录时无法动态增长。使用malloc, calloc, realloc 动态分配数据的替代方法。这会增加一些额外的复杂性,因为现在由您来跟踪分配的可用存储空间、已使用的存储空间,并在您使用的存储空间等于可用存储空间时重新分配。
你不想realloc 每一行——那是低效的。相反,您将分配一些合理的起始数量的结构,填充它们直到达到限制,然后重新分配。重新分配多少取决于您,但常见的重新分配方案是将当前分配的大小加倍(或者您可以添加一些其他倍数,例如 3/2 等...取决于您希望分配的速度增长)。该示例使用了良好的旧 double 方法。
首先,更改(仅打印超过 1 年的行)只是将您的输出移动到内部:
if (check_time_from_now (data[ndx].date) > SECPY) {
添加一个函数来输出记录 ndx 是您可以做的事情,以保持代码主体的整洁,例如
void prn_data_t_rec (mydata_t *data, int n)
{
printf ("%-12s %-8s %d %9.2f %s\n", data[n].name, data[n].plate,
data[n].type, data[n].value, data[n].date);
}
从代码主体调用:
if (check_time_from_now (data[ndx].date) > SECPY) {
if (data[ndx].type)
data[ndx].value *= 1.5;
else
data[ndx].value *= 2.5;
prn_data_t_rec (data, ndx); /* output > 1 year */
}
现在到动态分配。而不是声明:
mydata_t data[MAXS] = {{ .name = "" }};
您只需将声明和初始分配更改为:
#define MAXS 16u /* initial number of structs */
...
int maxs = MAXS; /* variable to track allocated number of struct */
mydata_t *data = calloc (maxs, sizeof *data); /* allocate storage */
(注意: calloc 被选中而不是 malloc,因为我想将所有内存初始化为零并避免编写显式初始化程序来将每个 type 或 value 成员设置为否则为零。允许calloc 这样做的开销可以忽略不计)
现在*验证每个分配
if (!data) { /* validate every allocation */
perror ("calloc-data");
return 1;
}
这同样适用于每次重新分配,但需要注意的是。您始终使用临时指针realloc。如果您使用指针本身重新分配,例如 ptr = realloc (ptr, newsize); 和 realloc 无法返回 NULL - 您会使用 NULL 覆盖您的原始地址,从而造成内存泄漏并失去对现有数据的所有访问权限!
在您到达case 4: 中的realloc 之前,代码中的所有其他内容都是相同的switch()。当(ndx == maxs) 你必须realloc 额外的存储空间,例如
case 4:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].date, buf);
else {
fputs ("error: date excceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
if (check_time_from_now (data[ndx].date) > SECPY) {
if (data[ndx].type)
data[ndx].value *= 1.5;
else
data[ndx].value *= 2.5;
prn_data_t_rec (data, ndx);
}
n = 0;
ndx++;
if (ndx == maxs) { /* check if realloc required */
/* realloc w/temp pointer to 2X current size */
void *tmp = realloc (data, 2 * maxs * sizeof *data);
if (!tmp) { /* validate every allocation */
perror ("realloc-data");
goto outofmem; /* original data still good */
}
data = tmp; /* set data to newly reallocated block */
/* zero the new memory allocated (optional) */
memset (data + maxs, 0, maxs * sizeof *data);
maxs *= 2; /* update the currently allocated max */
}
break;
(注意:继续使用goto -- 如果realloc 失败,这里必须保留你原来的data。即使失败,你原来的所有@987654407 @ 仍然很好 - 由于您使用 临时指针 来测试重新分配,因此您并没有失去对它的访问权限。所以只需跳出开关和 while 循环,您就可以继续不再需要时使用它并free它。
把整个例子放在一起,你会得到:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#define MAXS 16u /* initial number of structs */
#define MAXNM 32u /* max characters in name and other arrays in struct */
#define MAXC 1024u /* max characters in read buffer */
#define SECPY 31536000u /* seconds per-year */
typedef struct {
char name[MAXNM],
plate[MAXNM],
date[MAXNM];
int type;
double value;
} mydata_t;
time_t fill_broken_down_time (int y, int m, int d)
{ /* initialize struct members */
struct tm bdt = { .tm_sec=0, .tm_min=0, .tm_hour=0, .tm_mday=d,
.tm_mon=m>0?m-1:0, .tm_year=y-1900, .tm_isdst=-1 };
return mktime(&bdt); /* return mktime conversion to time_t */
}
double check_time_from_now (const char *str)
{
int y, m, d;
if (sscanf (str, "%4d%2d%2d", &y, &m, &d) != 3) {
fprintf (stderr, "error non-date string: '%s'.\n", str);
exit (EXIT_FAILURE);
}
time_t now = time(NULL),
then = fill_broken_down_time (y, m, d);
double secs = difftime (now, then); /* get seconds between dates */
return secs;
}
void prn_data_t_array (mydata_t *data, int n)
{
for (int i = 0; i < n; i++)
printf ("%-12s %-8s %d %9.2f %s\n", data[i].name, data[i].plate,
data[i].type, data[i].value, data[i].date);
}
void prn_data_t_rec (mydata_t *data, int n)
{
printf ("%-12s %-8s %d %9.2f %s\n", data[n].name, data[n].plate,
data[n].type, data[n].value, data[n].date);
}
int main (int argc, char **argv) {
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
int n = 0, ndx = 0, /* NOTE when dealing with array, start at ZERO */
maxs = MAXS; /* variable to track allocated number of struct */
char buf[MAXC]; /* buffer to hold each line read from file */
mydata_t *data = calloc (maxs, sizeof *data); /* allocate storage */
if (!data) { /* validate every allocation */
perror ("calloc-data");
return 1;
}
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
puts ("\ncomputed information for records > 1 year from now\n");
while (fgets (buf, MAXC, fp)) { /* read each line in file */
if (isspace(*buf)) /* skip blank lines (or start with space) */
continue;
buf[strcspn (buf, "\r\n")] = 0; /* trim '\n' from end of buf */
/* if line isn't a date line, just output line as non-date line */
switch (n) {
case 0:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].name, buf);
else {
fputs ("error: name excceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 1:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].plate, buf);
else {
fputs ("error: plate excceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 2:
if (sscanf (buf, "%d", &data[ndx].type) != 1) {
fputs ("error: type not an integer.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 3:
if (sscanf (buf, "%lf", &data[ndx].value) != 1) {
fputs ("error: value not a double.\n", stderr);
exit (EXIT_FAILURE);
}
n++;
break;
case 4:
if (strlen (buf) < MAXNM)
strcpy (data[ndx].date, buf);
else {
fputs ("error: date excceeds storage.\n", stderr);
exit (EXIT_FAILURE);
}
if (check_time_from_now (data[ndx].date) > SECPY) {
if (data[ndx].type)
data[ndx].value *= 1.5;
else
data[ndx].value *= 2.5;
prn_data_t_rec (data, ndx);
}
n = 0;
ndx++;
if (ndx == maxs) { /* check if realloc required */
/* realloc w/temp pointer to 2X current size */
void *tmp = realloc (data, 2 * maxs * sizeof *data);
if (!tmp) { /* validate every allocation */
perror ("realloc-data");
goto outofmem; /* original data still good */
}
data = tmp; /* set data to newly reallocated block */
/* zero the new memory allocated (optional) */
memset (data + maxs, 0, maxs * sizeof *data);
maxs *= 2; /* update the currently allocated max */
}
break;
default:
fputs ("error: you shouldn't get here!\n", stderr);
break;
}
}
outofmem:;
if (fp != stdin) fclose (fp); /* close file if not stdin */
puts ("\nall stored information\n");
prn_data_t_array (data, ndx); /* print the computed values */
free (data); /* don't forget to free what you allocate */
return 0;
}
(注意:它在循环期间打印> 1 year并在最后输出总记录——根据需要调整)
使用/输出示例
使用相同的数据文件:
$ ./bin/time_from_now4 dat/namelbltypevaldate.txt
computed information for records > 1 year from now
Hanif Hefaz BA123HB 0 251.25 20180101
Jacki Shroff UP673MK 1 4501.48 20170512
all stored information
Hanif Hefaz BA123HB 0 251.25 20180101
Jacki Shroff UP673MK 1 4501.48 20170512
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放。
您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。
对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。
$ ./bin/time_from_now4 dat/namelbltypevaldate.txt
==4331== Memcheck, a memory error detector
==4331== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4331== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4331== Command: ./bin/time_from_now4 dat/namelbltypevaldate.txt
==4331==
computed information for records > 1 year from now
Hanif Hefaz BA123HB 0 251.25 20180101
Jacki Shroff UP673MK 1 4501.48 20170512
all stored information
Hanif Hefaz BA123HB 0 251.25 20180101
Jacki Shroff UP673MK 1 4501.48 20170512
==4331==
==4331== HEAP SUMMARY:
==4331== in use at exit: 0 bytes in 0 blocks
==4331== total heap usage: 12 allocs, 12 frees, 5,333 bytes allocated
==4331==
==4331== All heap blocks were freed -- no leaks are possible
==4331==
==4331== For counts of detected and suppressed errors, rerun with: -v
==4331== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
始终确认您已释放已分配的所有内存并且没有内存错误。
如果您还有其他问题,请告诉我(此时可能需要提出新问题)