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

时间:2017-02-03 20:37:18

标签: c arrays file readfile

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

我是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);
}

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

2 个答案:

答案 0 :(得分:1)

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

您的代码也会调用strlen()作为字符串数组的内容,strlen()用于计算单个字符串的长度,字符串是{{1}的数组具有非零值的项目,以0结束。因此,char肯定会导致未定义的行为。

此外,如果要存储每个字符串,则需要像使用strlen()在注释行中尝试的那样复制数据,因为在每次迭代中会反复覆盖用于读取行的数组。

您的编译器必须抛出各种警告,如果不是那么它就是您的错,您应该让编译器知道您希望它进行一些诊断以帮助您找到常见的问题,例如指定一个指向strcpy()的指针。

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

char

为了证明它在c中有多复杂,请查看

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;
}

现在,这几乎就像你说的你写的python代码一样,但它肯定会更快,绝对毫无疑问。

有经验的python程序员可以编写一个运行速度比非实验性c程序员快的python程序,然后学习c非常好,因为你可以理解事情是如何工作的,然后你可以推断出如何可能已经实现了python功能,因此理解这一点实际上非常有用。

虽然它确实比在python中做同样复杂,但请注意我写了近10分钟。所以,如果你真的知道自己在做什么,而你真的需要快速做到c当然是一种选择,但是你需要学习许多高级语言程序员都不清楚的概念。

答案 1 :(得分:1)

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

loadlist中读取该行后,您需要提供足够的存储空间来保存该行(以及 nul-terminatedating 字符)。否则,您只需计算行数。在您的情况下,由于您声明了指针数组,因此您不能简单地复制您读取的行,因为数组中的每个指针都没有指向任何已分配的内存块。 (你不能用fgets (buffer, size, FILE*)分配你读取该行的缓冲区的地址,因为(1)它是loadlist函数的本地,当函数堆栈帧是在函数返回时销毁;以及(2)显然它会被每次调用fgets覆盖。

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

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

    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'。您不希望换行符字符悬挂在您存储的字符串的末尾,因此您只需使用 nul-terminatedating 字符覆盖'\n'(例如,只需十进制) 0或等效的 nul-character '\0' - 您的选择)。您可以在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选择返回类型,这可以指示成功/失败,您可以执行类似以下操作:

/** 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 */
}

注意:您可以轻松地将返回类型更改为int并返回读取的行数,或者将指针传递给int(或更好){ {1}})作为一个参数,使存储的行数可用于调用函数。

但是,在这种情况下,我们使用了指向size_t指针数组中所有指针的初始化,所以在调用函数中我们只需要遍历指针数组,直到第一个{{1}遇到以便遍历我们的行列表。汇总一个简短的示例程序,从作为程序的第一个参数给出的文件名读取/存储所有行(最多NULL行)(如果没有给出文件名,则从NULL汇总),你可以做类似的事情:

PTRMAX

最后,在你动态分配内存的任何代码中,你有2个职责关于分配的任何内存块:(1)总是保留一个指向起始地址的指针对于内存块,(2)当不再需要时,它可以释放

使用内存错误检查程序确保您没有在已分配的内存块之外/之外写入,尝试读取或基于未初始化的值跳转,最后确认您已释放所有内存分配了。

对于Linux stdin是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

仔细看看,如果您有任何其他问题,请与我们联系。