使用来自txt文件的指针在内存中存储和访问数据

时间:2021-10-13 20:24:53

So I'm currently working on a project that uses data from a txt file. The user is prompted for the filename, and the first two lines of the txt file are integers that essentially contain the row and column values of the txt file. There are two things that are confusing me when writing this program the way my instructor is asking. For the criteria, she says:

所以我目前正在开发一个使用txt文件数据的项目。提示用户输入文件名,txt文件的前两行是基本上包含txt文件的行和列值的整数。在我的导师问的方式编写这个程序时,有两件事令我困惑。对于标准,她说:

  1. read in the data and place into an array of data and
  2. 读入数据并放入数据数组中
  3. your code should access memory locations via pointers and pointer arithmetic, no []'s in the code you submit.
  4. 你的代码应该通过指针和指针算法访问内存位置,你提交的代码中没有[]。

The left-most column is an identifier while the rest of the row should be considered as that rows data (floating point values). An example of what the file might contain is:

最左边的列是标识符,而行的其余部分应该被视为行数据(浮点值)。该文件可能包含的示例如下:

3
4
abc123  8.55    5   0   10
cdef123  83.50 10.5 10  55
hig123   7.30   6   0   1.9

My code:

我的代码:

//Creates array for 100 characters for filename
char fileName[100];

printf("Enter the file name to be read from: ");
scanf("%s", fileName);

FILE *myFile;
myFile = fopen(fileName, "r");

//Checks if file opened correctly
if (myFile == NULL)
{
    printf("Error opening file\n"); //full file name must be entered
}
else {
    printf("File opened successfully\n");
}

//gets value of records and value per records from file
//This will be the first 2 lines from line
fscanf(myFile, "%d %d", &records, &valuesPerRecords);

//printf("%d %d\n", records, valuesPerRecords); //Check int values from file
int counter = 0;
char *ptr_data;

ptr_data = (char*)malloc(records*(valuesPerRecords));

int totalElements = records*(valuesPerRecords);

/*If malloc can't allocate enough space, print error*/
if (ptr_data == NULL) {
    printf("Error\n");
    exit(-1);
}

int counter;

for (counter = 0; counter < totalElements; counter++){
    fscanf(myFile, "%s", &ptr_data);
}

so I'm wondering if so far, I'm on the right track. I can't seem to think of a way to have the first column read in as a string, while the rest is read in as integers. I'll also have to use the stored values later and sort them but that's a different problem for a later date.

所以我想知道到目前为止,我是在正确的轨道上。我似乎无法想到让第一列作为字符串读入的方法,而其余的作为整数读入。我还必须稍后使用存储的值并对它们进行排序,但这对于以后的日期来说是一个不同的问题。

1 个解决方案

#1


2  

First off, your prof apparently wants you to become familiar with walking a pointer through a collection of both strings (the labels) and numbers (the floating-point values) using pointer arithmetic without using array indexing. A solid pointer familiarity assignment.

首先,你的教授显然希望你熟悉在不使用数组索引的情况下使用指针算法遍历指针遍历字符串(标签)和数字(浮点值)的集合。一个坚实的指针熟悉任务。

To handle the labels you can use a pointer to pointer to type char (a double pointer) as each pointer will point to an array of chars. You can declare and allocate pointers for labels as follows. (this assumes you have already read the rows and cols values from the input file)

要处理标签,您可以使用指向指针的指针来键入char(双指针),因为每个指针都指向一个字符数组。您可以按如下方式声明和分配标签指针。 (这假设您已经从输入文件中读取了行和列值)

    char buf[MAXC] = "",    /* temporary line buffer    */
         **labels = NULL,   /* collection of labels     */
         **lp = NULL;       /* pointers to walk labels  */
...
    /* allocate & validate cols char* pointers */
    if (!(labels = calloc (rows, sizeof *labels))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

You can do the same thing for your pointer values, except you only need a pointer to type double as you will simply need to allocate for a collection of doubles.

您可以为指针值执行相同的操作,除了您只需要一个指向double类型的指针,因为您只需要为一组双精度分配。

    double  *mtrx = NULL,   /* collection of numbers    */
            *p;             /* pointers to walk numbers */
...
    nptrs = rows * cols;    /* set number of poiners required */

    /* allocate & validate nptrs doubles */
    if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

The use of the pointers lp and p are crucial because you cannot increment either labels or mtrx (without saving the original address) because doing so will lose the pointer to the start of the memory allocated to each, immediately causing a memory leak (you have no way to free the block) and preventing you from ever being able to access the beginning again. Each time you need to walk over labels or mtrx just assign the start address to the pointer, e.g.

使用指针lp和p是至关重要的,因为你不能递增标签或mtrx(不保存原始地址),因为这样做会丢失指向分配给每个指针的内存开始的指针,立即导致内存泄漏(你有无法释放阻止)并阻止您再次访问开头。每次需要遍历标签或mtrx时,只需将起始地址指定给指针,例如

    p  = mtrx;   /* set pointer p to mtrx */
    lp = labels; /* set poiners lp to labels */

Now you are free to read and parse the lines in any manner you choose, but I would strongly recommend using line-oriented-input functions to read each line into a temporary line buffer, and then parse the values you need using sscanf. This has many advantages to reading with fscanf alone. After you read each line, you can parse/validate each value before allocating space for the strings and assigning the values.

现在您可以以任何方式*地读取和解析行,但我强烈建议使用面向行的输入函数将每行读入临时行缓冲区,然后使用sscanf解析所需的值。这与单独使用fscanf读取有许多优点。在读取每一行之后,您可以在为字符串分配空间并分配值之前解析/验证每个值。

(note: I cheat below with a single sscanf call, where you should actually assign a char* pointer to buf, read the label, then loop cols number of times (perhaps using strtok/strtod) checking each value and assigning to mtrx, -- that is left to you)

(注意:我在下面用一个sscanf调用作弊,你实际上应该为buf分配一个char *指针,读取标签,然后循环cols次数(可能使用strtok / strtod)检查每个值并分配给mtrx, - - 留给你)

    /* read each remaining line, allocate/fill pointers */
    while (ndx < rows && fgets (buf, MAXC, fp)) {
        if (*buf == '\n') continue;      /* skip empty lines */
        char label[MAXC] = "";    /* temp storage for labels */
        double val[cols];        /* temp storage for numbers */
        if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
                label, &val[0], &val[1], &val[2], &val[3]) == 
                (int)(cols + 1)) {
            *lp++ = strdup (label);      /* alloc/copy label */
            for (i = 0; i < cols; i++) /* alloc/copy numbers */
                *p++ = val[i];
            ndx++;                        /* increment index */
        }
    }
    if (fp != stdin) fclose (fp);  /* close file if not stdin */

Then it is simply a matter of looping over the values again, using or outputting as needed, and then freeing the memory you allocated. You could do that with something similar to:

然后,只需再次循环遍历值,根据需要使用或输出,然后释放您分配的内存。你可以用类似的东西做到这一点:

    p  = mtrx;                     /* reset pointer p to mtrx */
    lp = labels;                /* reset poiners lp to labels */

    for (i = 0; i < rows; i++) {
        printf (" %-10s", *lp);
        free (*lp++);
        for (j = 0; j < cols; j++)
            printf (" %7.2lf", *p++);
        putchar ('\n');
    }
    free (mtrx);   /* free pointers */
    free (labels);

That's basically one of many approaches. Putting it all together, you could do:

这基本上是众多方法中的一种。把它们放在一起,你可以做到:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum { MAXC = 512 }; /* constants for max chars per-line */

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

    char buf[MAXC] = "",    /* temporary line buffer    */
         **labels = NULL,   /* collection of labels     */
         **lp = NULL;       /* pointers to walk labels  */
    double  *mtrx = NULL,   /* collection of numbers    */
            *p;             /* pointers to walk numbers */
    size_t i, j, ndx = 0, rows = 0, cols = 0, nptrs = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (fgets (buf, MAXC, fp))  /* get rows, ignore blank lines */
        if (sscanf (buf, "%zu", &rows) == 1)
            break;

    while (fgets (buf, MAXC, fp))  /* get cols, ignore blank lines */
        if (sscanf (buf, "%zu", &cols) == 1)
            break;

    if (!rows || !cols) {   /* validate rows & cols > 0 */
        fprintf (stderr, "error: rows and cols values not found.\n");
        return 1;
    }
    nptrs = rows * cols;    /* set number of poiners required */

    /* allocate & validate nptrs doubles */
    if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    /* allocate & validate rows char* pointers */
    if (!(labels = calloc (rows, sizeof *labels))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    p  = mtrx;   /* set pointer p to mtrx */
    lp = labels; /* set poiners lp to labels */

    /* read each remaining line, allocate/fill pointers */
    while (ndx < rows && fgets (buf, MAXC, fp)) {
        if (*buf == '\n') continue;      /* skip empty lines */
        char label[MAXC] = "";    /* temp storage for labels */
        double val[cols];        /* temp storage for numbers */
        if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
                label, &val[0], &val[1], &val[2], &val[3]) == 
                (int)(cols + 1)) {
            *lp++ = strdup (label);      /* alloc/copy label */
            for (i = 0; i < cols; i++) /* alloc/copy numbers */
                *p++ = val[i];
            ndx++;                        /* increment index */
        }
    }
    if (fp != stdin) fclose (fp);  /* close file if not stdin */

    p  = mtrx;                     /* reset pointer p to mtrx */
    lp = labels;                /* reset poiners lp to labels */

    for (i = 0; i < rows; i++) {
        printf (" %-10s", *lp);
        free (*lp++);
        for (j = 0; j < cols; j++)
            printf (" %7.2lf", *p++);
        putchar ('\n');
    }
    free (mtrx);   /* free pointers */
    free (labels);

    return 0;
}

Example Input File Used

使用示例输入文件

$ cat dat/arrinpt.txt
3

4

abc123 8.55 5 0 10

cdef123 83.50 10.5 10 55

hig123 7.30 6 0 1.9

Example Use/Output

示例使用/输出

$ ./bin/arrayptrs <dat/arrinpt.txt
 abc123        8.55    5.00    0.00   10.00
 cdef123      83.50   10.50   10.00   55.00
 hig123        7.30    6.00    0.00    1.90

Memory Use/Error Check

内存使用/错误检查

In any code your write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

在你动态分配内存的任何代码中,你对分配的任何内存块都有2个职责:(1)总是保留一个指向内存块起始地址的指针,这样,(2)它可以在没有时被释放需要更久。

It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an uninitialized value and finally to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice.

您必须使用内存错误检查程序,以确保您没有在已分配的内存块之外/之外写入,尝试读取或基于未初始化的值跳转,并最终确认您已释放所有内存分配了。对于Linux,valgrind是正常的选择。

$ valgrind ./bin/arrayptrs <dat/arrinpt.txt
==22210== Memcheck, a memory error detector
==22210== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22210== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22210== Command: ./bin/arrayptrs
==22210==
 abc123        8.55    5.00    0.00   10.00
 cdef123      83.50   10.50   10.00   55.00
 hig123        7.30    6.00    0.00    1.90
==22210==
==22210== HEAP SUMMARY:
==22210==     in use at exit: 0 bytes in 0 blocks
==22210==   total heap usage: 5 allocs, 5 frees, 142 bytes allocated
==22210==
==22210== All heap blocks were freed -- no leaks are possible
==22210==
==22210== For counts of detected and suppressed errors, rerun with: -v
==22210== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

Always confirm All heap blocks were freed -- no leaks are possible and equally important ERROR SUMMARY: 0 errors from 0 contexts. Note: some OS's do not provide adequate leak and error suppression files (the file that excludes system and OS memory from being reported as in use) which will cause valgrind to report that some memory has not yet been freed (despite the fact you have done your job and freed all blocks you allocated and under your control).

始终确认释放所有堆块 - 不可能发生泄漏,同样重要的错误摘要:0个上下文中的0个错误。注意:某些操作系统不提供足够的泄漏和错误抑制文件(将系统和操作系统内存排除在报告为正在使用的文件),这将导致valgrind报告某些内存尚未被释放(尽管您已经完成了事实)你的工作,并释放你分配和控制的所有块。

Look things over and let me know if you have any questions.

仔细看看,如果您有任何疑问,请告诉我。

#1


2  

First off, your prof apparently wants you to become familiar with walking a pointer through a collection of both strings (the labels) and numbers (the floating-point values) using pointer arithmetic without using array indexing. A solid pointer familiarity assignment.

首先,你的教授显然希望你熟悉在不使用数组索引的情况下使用指针算法遍历指针遍历字符串(标签)和数字(浮点值)的集合。一个坚实的指针熟悉任务。

To handle the labels you can use a pointer to pointer to type char (a double pointer) as each pointer will point to an array of chars. You can declare and allocate pointers for labels as follows. (this assumes you have already read the rows and cols values from the input file)

要处理标签,您可以使用指向指针的指针来键入char(双指针),因为每个指针都指向一个字符数组。您可以按如下方式声明和分配标签指针。 (这假设您已经从输入文件中读取了行和列值)

    char buf[MAXC] = "",    /* temporary line buffer    */
         **labels = NULL,   /* collection of labels     */
         **lp = NULL;       /* pointers to walk labels  */
...
    /* allocate & validate cols char* pointers */
    if (!(labels = calloc (rows, sizeof *labels))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

You can do the same thing for your pointer values, except you only need a pointer to type double as you will simply need to allocate for a collection of doubles.

您可以为指针值执行相同的操作,除了您只需要一个指向double类型的指针,因为您只需要为一组双精度分配。

    double  *mtrx = NULL,   /* collection of numbers    */
            *p;             /* pointers to walk numbers */
...
    nptrs = rows * cols;    /* set number of poiners required */

    /* allocate & validate nptrs doubles */
    if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

The use of the pointers lp and p are crucial because you cannot increment either labels or mtrx (without saving the original address) because doing so will lose the pointer to the start of the memory allocated to each, immediately causing a memory leak (you have no way to free the block) and preventing you from ever being able to access the beginning again. Each time you need to walk over labels or mtrx just assign the start address to the pointer, e.g.

使用指针lp和p是至关重要的,因为你不能递增标签或mtrx(不保存原始地址),因为这样做会丢失指向分配给每个指针的内存开始的指针,立即导致内存泄漏(你有无法释放阻止)并阻止您再次访问开头。每次需要遍历标签或mtrx时,只需将起始地址指定给指针,例如

    p  = mtrx;   /* set pointer p to mtrx */
    lp = labels; /* set poiners lp to labels */

Now you are free to read and parse the lines in any manner you choose, but I would strongly recommend using line-oriented-input functions to read each line into a temporary line buffer, and then parse the values you need using sscanf. This has many advantages to reading with fscanf alone. After you read each line, you can parse/validate each value before allocating space for the strings and assigning the values.

现在您可以以任何方式*地读取和解析行,但我强烈建议使用面向行的输入函数将每行读入临时行缓冲区,然后使用sscanf解析所需的值。这与单独使用fscanf读取有许多优点。在读取每一行之后,您可以在为字符串分配空间并分配值之前解析/验证每个值。

(note: I cheat below with a single sscanf call, where you should actually assign a char* pointer to buf, read the label, then loop cols number of times (perhaps using strtok/strtod) checking each value and assigning to mtrx, -- that is left to you)

(注意:我在下面用一个sscanf调用作弊,你实际上应该为buf分配一个char *指针,读取标签,然后循环cols次数(可能使用strtok / strtod)检查每个值并分配给mtrx, - - 留给你)

    /* read each remaining line, allocate/fill pointers */
    while (ndx < rows && fgets (buf, MAXC, fp)) {
        if (*buf == '\n') continue;      /* skip empty lines */
        char label[MAXC] = "";    /* temp storage for labels */
        double val[cols];        /* temp storage for numbers */
        if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
                label, &val[0], &val[1], &val[2], &val[3]) == 
                (int)(cols + 1)) {
            *lp++ = strdup (label);      /* alloc/copy label */
            for (i = 0; i < cols; i++) /* alloc/copy numbers */
                *p++ = val[i];
            ndx++;                        /* increment index */
        }
    }
    if (fp != stdin) fclose (fp);  /* close file if not stdin */

Then it is simply a matter of looping over the values again, using or outputting as needed, and then freeing the memory you allocated. You could do that with something similar to:

然后,只需再次循环遍历值,根据需要使用或输出,然后释放您分配的内存。你可以用类似的东西做到这一点:

    p  = mtrx;                     /* reset pointer p to mtrx */
    lp = labels;                /* reset poiners lp to labels */

    for (i = 0; i < rows; i++) {
        printf (" %-10s", *lp);
        free (*lp++);
        for (j = 0; j < cols; j++)
            printf (" %7.2lf", *p++);
        putchar ('\n');
    }
    free (mtrx);   /* free pointers */
    free (labels);

That's basically one of many approaches. Putting it all together, you could do:

这基本上是众多方法中的一种。把它们放在一起,你可以做到:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

enum { MAXC = 512 }; /* constants for max chars per-line */

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

    char buf[MAXC] = "",    /* temporary line buffer    */
         **labels = NULL,   /* collection of labels     */
         **lp = NULL;       /* pointers to walk labels  */
    double  *mtrx = NULL,   /* collection of numbers    */
            *p;             /* pointers to walk numbers */
    size_t i, j, ndx = 0, rows = 0, cols = 0, nptrs = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (fgets (buf, MAXC, fp))  /* get rows, ignore blank lines */
        if (sscanf (buf, "%zu", &rows) == 1)
            break;

    while (fgets (buf, MAXC, fp))  /* get cols, ignore blank lines */
        if (sscanf (buf, "%zu", &cols) == 1)
            break;

    if (!rows || !cols) {   /* validate rows & cols > 0 */
        fprintf (stderr, "error: rows and cols values not found.\n");
        return 1;
    }
    nptrs = rows * cols;    /* set number of poiners required */

    /* allocate & validate nptrs doubles */
    if (!(mtrx = calloc (nptrs, sizeof *mtrx))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    /* allocate & validate rows char* pointers */
    if (!(labels = calloc (rows, sizeof *labels))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    p  = mtrx;   /* set pointer p to mtrx */
    lp = labels; /* set poiners lp to labels */

    /* read each remaining line, allocate/fill pointers */
    while (ndx < rows && fgets (buf, MAXC, fp)) {
        if (*buf == '\n') continue;      /* skip empty lines */
        char label[MAXC] = "";    /* temp storage for labels */
        double val[cols];        /* temp storage for numbers */
        if (sscanf (buf, "%s %lf %lf %lf %lf", /* parse line */
                label, &val[0], &val[1], &val[2], &val[3]) == 
                (int)(cols + 1)) {
            *lp++ = strdup (label);      /* alloc/copy label */
            for (i = 0; i < cols; i++) /* alloc/copy numbers */
                *p++ = val[i];
            ndx++;                        /* increment index */
        }
    }
    if (fp != stdin) fclose (fp);  /* close file if not stdin */

    p  = mtrx;                     /* reset pointer p to mtrx */
    lp = labels;                /* reset poiners lp to labels */

    for (i = 0; i < rows; i++) {
        printf (" %-10s", *lp);
        free (*lp++);
        for (j = 0; j < cols; j++)
            printf (" %7.2lf", *p++);
        putchar ('\n');
    }
    free (mtrx);   /* free pointers */
    free (labels);

    return 0;
}

Example Input File Used

使用示例输入文件

$ cat dat/arrinpt.txt
3

4

abc123 8.55 5 0 10

cdef123 83.50 10.5 10 55

hig123 7.30 6 0 1.9

Example Use/Output

示例使用/输出

$ ./bin/arrayptrs <dat/arrinpt.txt
 abc123        8.55    5.00    0.00   10.00
 cdef123      83.50   10.50   10.00   55.00
 hig123        7.30    6.00    0.00    1.90

Memory Use/Error Check

内存使用/错误检查

In any code your write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

在你动态分配内存的任何代码中,你对分配的任何内存块都有2个职责:(1)总是保留一个指向内存块起始地址的指针,这样,(2)它可以在没有时被释放需要更久。

It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an uninitialized value and finally to confirm that you have freed all the memory you have allocated. For Linux valgrind is the normal choice.

您必须使用内存错误检查程序,以确保您没有在已分配的内存块之外/之外写入,尝试读取或基于未初始化的值跳转,并最终确认您已释放所有内存分配了。对于Linux,valgrind是正常的选择。

$ valgrind ./bin/arrayptrs <dat/arrinpt.txt
==22210== Memcheck, a memory error detector
==22210== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==22210== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==22210== Command: ./bin/arrayptrs
==22210==
 abc123        8.55    5.00    0.00   10.00
 cdef123      83.50   10.50   10.00   55.00
 hig123        7.30    6.00    0.00    1.90
==22210==
==22210== HEAP SUMMARY:
==22210==     in use at exit: 0 bytes in 0 blocks
==22210==   total heap usage: 5 allocs, 5 frees, 142 bytes allocated
==22210==
==22210== All heap blocks were freed -- no leaks are possible
==22210==
==22210== For counts of detected and suppressed errors, rerun with: -v
==22210== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

Always confirm All heap blocks were freed -- no leaks are possible and equally important ERROR SUMMARY: 0 errors from 0 contexts. Note: some OS's do not provide adequate leak and error suppression files (the file that excludes system and OS memory from being reported as in use) which will cause valgrind to report that some memory has not yet been freed (despite the fact you have done your job and freed all blocks you allocated and under your control).

始终确认释放所有堆块 - 不可能发生泄漏,同样重要的错误摘要:0个上下文中的0个错误。注意:某些操作系统不提供足够的泄漏和错误抑制文件(将系统和操作系统内存排除在报告为正在使用的文件),这将导致valgrind报告某些内存尚未被释放(尽管您已经完成了事实)你的工作,并释放你分配和控制的所有块。

Look things over and let me know if you have any questions.

仔细看看,如果您有任何疑问,请告诉我。