C - 无法从文本文件读取字符串列表并将其处理到数组中

C - cannot read and process a list of strings from a text file into an array

此代码逐行读取文本文件。但是我需要将这些行放在一个数组中,但我做不到。现在我以某种方式得到了一系列数字。那么如何将文件读入列表。我尝试使用二维列表,但效果不佳。

我是 C 的新手。我主要使用 Python 但现在我想检查 C 是否更快。

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


void loadlist(char *ptext) {

   char filename[] = "Z://list.txt";
   char myline[200];
   FILE * pfile;
   pfile = fopen (filename, "r" );

   char larray[100000];
   int i = 0;
   while (!feof(pfile)) {
       fgets(myline,200,pfile);
       larray[i]= myline;
       //strcpy(larray[i],myline);
       i++;
       //printf(myline);

   }


   fclose(pfile);
   printf("%s \n %d \n  %d \n ","while doneqa",i,strlen(larray));   
   printf("First larray element is: %d \n",larray[0]);

    /* for loop execution */
   //for( i = 10; i < 20; i = i + 1 ){
   //   printf(larray[i]);
   //}


    }


int main ()
{
   time_t stime, etime;
   printf("Starting of the program...\n");
   time(&stime);

   char *ptext = "String";  
   loadlist(ptext);
   time(&etime);

   printf("time to load: %f \n", difftime(etime, stime));

   return(0);
}

此代码逐行读取文本文件。但是我需要将这些行放在一个数组中,但我做不到。现在我以某种方式得到了一组数字。

您看到数字是很自然的,因为您正在使用 "%d" 说明符打印单个字符。实际上,c 中的字符串几乎就是数字数组,这些数字是相应字符的 ascii 值。如果您改为使用 "%c",您将看到代表每个数字的字符。

您的代码还调用了 strlen() 作为字符串数组的内容,strlen() 用于计算单个字符串的长度,字符串是 [= 的数组16=] 项具有 non-zero 值,以 0 结尾。因此,strlen() 肯定会导致未定义的行为。

此外,如果你想存储每个字符串,你需要像你在注释行中尝试的那样复制数据 strcpy() 因为你用于读取行的数组在每个字符串中被一遍又一遍地覆盖迭代。

你的编译器一定会抛出各种警告,如果不是那是你的错,你应该让编译器知道你希望它做一些诊断来帮助你找到常见的问题,比如将指针分配给 char.

您应该修复代码中的多个问题,这是修复大部分问题的代码

void 
loadlist(const char *const filename) {

    char line[100];
    FILE *file;
    // We can only read 100 lines, of 
    // max 99 characters each
    char array[100][100];
    int size;

    size = 0;
    file = fopen (filename, "r" );
    if (file == NULL)
        return;
    while ((fgets(line, sizeof(line), file) != NULL) && (size < 100)) {
        strcpy(array[size++], line);
    }
    fclose(file);

    for (int i = 0 ; i < size ; ++i) {
        printf("array[%d] = %s", i + 1, array[i]);
    }
}

int 
main(void)
{
   time_t stime, etime;
   printf("Starting of the program...\n");
   time(&stime);

   loadlist("Z:\list.txt");
   time(&etime);

   printf("Time to load: %f\n", difftime(etime, stime));

   return 0;
}

只是为了证明它在 c 中有多复杂,看看这个

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

struct string_list {
    char **items;
    size_t size;
    size_t count;
};

void
string_list_print(struct string_list *list)
{
    // Simply iterate through the list and
    // print every item
    for (size_t i = 0 ; i < list->count ; ++i) {
        fprintf(stdout, "item[%zu] = %s\n", i + 1, list->items[i]);
    }
}

struct string_list *
string_list_create(size_t size)
{
    struct string_list *list;
    // Allocate space for the list object
    list = malloc(sizeof *list);
    if (list == NULL) // ALWAYS check this
        return NULL;
    // Allocate space for the items 
    // (starting with `size' items)
    list->items = malloc(size * sizeof *list->items);
    if (list->items != NULL) {
        // Update the list size because the allocation
        // succeeded
        list->size = size;
    } else {
        // Be optimistic, maybe realloc will work next time
        list->size = 0;
    }
    // Initialize the count to 0, because
    // the list is initially empty
    list->count = 0;

    return list;
}

int
string_list_append(struct string_list *list, const char *const string)
{
    // Check if there is room for the new item
    if (list->count + 1 >= list->size) {
        char **items;
        // Resize the array, there is no more room
        items = realloc(list->items, 2 * list->size * sizeof *list->items);
        if (items == NULL)
            return -1;
        // Now update the list
        list->items = items;
        list->size += list->size;
    }
    // Copy the string into the array we simultaneously
    // increase the `count' and copy the string
    list->items[list->count++] = strdup(string);
    return 0;
}

void
string_list_destroy(struct string_list *const list)
{
    // `free()' does work with a `NULL' argument
    // so perhaps as a principle we should too
    if (list == NULL)
        return;
    // If the `list->items' was initialized, attempt
    // to free every `strdup()'ed string
    if (list->items != NULL) {
        for (size_t i = 0 ; i < list->count ; ++i) {
            free(list->items[i]);
        }
        free(list->items);
    }
    free(list);
}

struct string_list *
loadlist(const char *const filename) {
    char line[100]; // A buffer for reading lines from the file
    FILE *file;
    struct string_list *list;
    // Create a new list, initially it has
    // room for 100 strings, but it grows
    // automatically if needed
    list = string_list_create(100);
    if (list == NULL)
        return NULL;
    // Attempt to open the file
    file = fopen (filename, "r");
    // On failure, we now have the responsibility
    // to cleanup the allocated space for the string
    // list
    if (file == NULL) {
        string_list_destroy(list);
        return NULL;
    }
    // Read lines from the file until there are no more
    while (fgets(line, sizeof(line), file) != NULL) {
        char *newline;
        // Remove the trainling '\n'
        newline = strchr(line, '\n');
        if (newline != NULL)
            *newline = '[=11=]';
        // Append the string to the list
        string_list_append(list, line);
    }
    fclose(file);
    return list;
}

int 
main(void)
{
   time_t stime, etime;
   struct string_list *list;
   printf("Starting of the program...\n");
   time(&stime);

   list = loadlist("Z:\list.txt");
   if (list != NULL) {
       string_list_print(list);
       string_list_destroy(list);
   }
   time(&etime);

   printf("Time to load: %f\n", difftime(etime, stime));

   return 0;
}

现在,这几乎可以像您所说的 python 代码一样工作,但肯定会更快,这是毫无疑问的。

有可能经过实验的 python 程序员可以编写比 non-experimented c 程序员运行得更快的 python 程序,但是学习 c 真的很好,因为你可以了解事物的实际运作方式,然后您可以推断出 python 功能可能是如何实现的,因此了解这一点实际上非常有用。

虽然它肯定比在 python 中做同样的事情要复杂得多,但请注意,我用了近 10 分钟写完了这篇文章。所以如果你真的知道你在做什么并且你真的需要它很快 c 当然是一个选择,但是你需要学习许多高级语言程序员不清楚的概念。

有很多方法可以正确地做到这一点。首先,首先弄清楚您实际 need/want 存储的是什么,然后弄清楚该信息的来源,最后决定您将如何为信息提供存储。在您的情况下, loadlist 显然是为了加载行列表(最多 10000 ),以便可以通过静态声明的指针数组访问它们。 (你也可以动态分配指针,但如果你知道你不需要超过 X 个,静态声明它们就可以了(直到你导致 Whosebug...)

阅读 loadlist 中的行后,您需要提供 足够的存储空间 来保存行(加上 nul-terminating字符)。否则,您只是 计算 行数。在你的例子中,因为你声明了一个指针数组,你不能简单地复制你读到的行,因为数组中的每个指针还没有指向任何分配的内存块。 (你不能用 fgets (buffer, size, FILE*) 分配你读入行的缓冲区的地址,因为(1)它是你的 loadlist 函数的本地地址,当函数堆栈帧被销毁时它会消失函数 return; 和 (2) 显然 无论如何每次调用 fgets 它都会被覆盖。

那怎么办?这也很简单,只需使用每行的 strlen 为每行分配存储空间,如@iharob 所说(+1 用于 nul-byte) 然后 malloc 分配一个大小的内存块。然后您可以简单地将读取缓冲区复制到创建的内存块并将指针分配给您的 list (例如代码中的 larray[x] )。现在 gnu 扩展提供了一个 strdup 分配和复制的功能,但请理解这不是 C99 标准的一部分,因此您可以 运行 解决可移植性问题。 (另请注意,如果内存重叠区域是一个问题,您可以使用 memcpy,但我们现在将忽略它,因为您正在从文件中读取行)

分配内存的规则是什么?好吧,您使用 malloccallocrealloc 进行分配,然后在继续之前验证对这些函数的调用是否成功,或者您刚刚进入 未定义行为的领域 通过写入实际上并未分配给您使用的内存区域。那看起来像什么?如果你有指针数组 p 并且你想在索引 idx 处存储长度为 len 的读取缓冲区 buf 中的字符串,你可以简单地执行以下操作:

    if ((p[idx] = malloc (len + 1)))    /* allocate storage */
        strcpy (p[idx], buf);           /* copy buf to storage */
    else
        return NULL;                    /* handle error condition */

现在您可以按如下方式在测试前自由分配,但将分配作为测试的一部分会很方便。长格式为:

    p[idx] = malloc (len + 1);          /* allocate storage */

    if (p[idx] == NULL)  /* validate/handle error condition */
        return NULL;

    strcpy (p[idx], buf);           /* copy buf to storage */

你想怎么做取决于你。

现在您还需要防止超出指针数组末尾的读取。 (你只有一个固定的数字,因为你静态地声明了数组)。您可以很容易地使该检查成为读取循环的一部分。如果你已经为你拥有的指针数量声明了一个常量(例如PTRMAX),你可以这样做:

int idx = 0;            /* index */

while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {
    ...
    idx++;
}

通过根据可用指针的数量检查索引,您可以确保您不会尝试将地址分配给比您拥有的更多的指针。

还有一个未解决的问题是处理将包含在读取缓冲区末尾的 '\n'。回想一下,fgets 阅读 直至并包括 '\n'。您不希望 newline 字符悬挂在您存储的字符串的末尾,因此您只需用 nul-terminating[= 覆盖 '\n' 114=] 字符(例如简单的十进制 0 或等效的 nul-character '[=43=]' -- 您的选择)。您可以在 strlen 调用后进行简单测试,例如

while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {

    size_t len = strlen (buf);  /* get length */

    if (buf[len-1] == '\n') /* check for trailing '\n' */
        buf[--len] = 0;     /* overwrite '\n' with nul-byte */
    /* else { handle read of line longer than 200 chars }
     */
    ...

(注意: 这也带来了读取一行 比您分配给的 200 个字符长 的问题您的读取缓冲区。您通过检查 fgets 是否在末尾包含 '\n' 来检查是否已读取完整行,如果没有,您知道下一次调用 fgets将从同一行再次读取,除非遇到 EOF。在这种情况下,您只需要 realloc 您的存储并将任何其他字符附加到同一行——留待以后讨论)

如果你把所有的部分放在一起,为loadlist选择一个return类型,可以表示success/failure,你可以做一些事情类似于以下内容:

/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
 *  storage is allocated for each line read and pointer
 *  to allocated block is stored at 'p[x]'. (you should
 *  add handling of lines greater than LNMAX chars)
 */
char **loadlist (char **p, FILE *fp)
{
    int idx = 0;            /* index */
    char buf[LNMAX] = "";   /* read buf */

    while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {

        size_t len = strlen (buf);  /* get length */

        if (buf[len-1] == '\n') /* check for trailing '\n' */
            buf[--len] = 0;     /* overwrite '\n' with nul-byte */
        /* else { handle read of line longer than 200 chars }
         */

        if ((p[idx] = malloc (len + 1)))    /* allocate storage */
            strcpy (p[idx], buf);           /* copy buf to storage */
        else
            return NULL;    /* indicate error condition in return */

        idx++;
    }

    return p;   /* return pointer to list */
}

注意: 您可以轻松地将 return 类型更改为 int 和 return 读取或传递的行数指向 int(或更好 size_t)的指针作为参数,使存储的行数在调用函数中可用。

然而,在这种情况下,我们已经将您的指针数组中的所有指针初始化为 NULL,因此返回调用函数我们只需要迭代指针数组直到 f遇到第一个 NULL 是为了遍历我们的行列表。将一个简短的示例程序放在一起,该程序 read/stores 所有行(最多 PTRMAX 行)从作为程序的第一个参数给出的文件名(如果没有给出文件名,则从 stdin 开始),你可以做类似的事情:

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

enum { LNMAX = 200, PTRMAX = 10000 };

char **loadlist (char **p, FILE *fp);

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

    time_t stime, etime;
    char *list[PTRMAX] = { NULL };  /* array of ptrs initialized NULL */
    size_t n = 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;
    }

    printf ("Starting of the program...\n");

    time (&stime);
    if (loadlist (list, fp)) {   /* read lines from fp into list */
        time (&etime);
        printf("time to load: %f\n\n", difftime (etime, stime));
    }
    else {
        fprintf (stderr, "error: loadlist failed.\n");
        return 1;
    }

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    while (list[n]) {   /* output stored lines and free allocated mem */
        printf ("line[%5zu]: %s\n", n, list[n]);
        free (list[n++]);
    }

    return(0);
}


/** read up to PTRMAX lines from 'fp', allocate/save in 'p'.
 *  storage is allocated for each line read and pointer
 *  to allocated block is stored at 'p[x]'. (you should
 *  add handling of lines greater than LNMAX chars)
 */
char **loadlist (char **p, FILE *fp)
{
    int idx = 0;            /* index */
    char buf[LNMAX] = "";   /* read buf */

    while (fgets (buf, LNMAX, fp) && idx < PTRMAX) {

        size_t len = strlen (buf);  /* get length */

        if (buf[len-1] == '\n') /* check for trailing '\n' */
            buf[--len] = 0;     /* overwrite '\n' with nul-byte */
        /* else { handle read of line longer than 200 chars }
         */

        if ((p[idx] = malloc (len + 1)))    /* allocate storage */
            strcpy (p[idx], buf);           /* copy buf to storage */
        else
            return NULL;    /* indicate error condition in return */

        idx++;
    }

    return p;   /* return pointer to list */
}

最后,在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指针到内存块的起始地址,因此,(2) 当不再需要它时可以释放

使用内存错误检查程序来确保您没有写入 beyond/outside 您分配的内存块,试图读取或基于未初始化的值进行跳转,最后确认您已释放所有你分配的内存。

对于Linux valgrind是正常的选择。每个平台都有类似的内存检查器。它们都很简单易用,只需运行你的程序就可以了。

检查一下,如果您有任何其他问题,请告诉我。