【问题标题】:C: store tokens in growing arrayC:将令牌存储在不断增长的数组中
【发布时间】:2015-11-14 09:54:25
【问题描述】:

我目前正在学习 C 并且想编写一个程序来读取一行数字(从文件中),用空格分隔(例如:43 2 6 120 5 23),然后将这些数字存储在动态数组中. 到目前为止,我的想法是将行作为字符串读取,将其分隔为标记 (strtok),然后将这些标记转换为 int 数字 (atoi) 并存储它们。 我的问题是我必须使用 malloc 分配内存,但不能手动告诉程序需要多少内存,因为它必须能够处理任意长度的行和任意数量的行大小,我不知道该怎么做。我应该使用realloc

我不希望任何人为我做我的工作,只是一个简单的例子和​​/或对做我想做的事情的方法的良好解释会非常有帮助。如果需要更多信息,我会给你。 我知道这里有一些关于这个主题的问题,我已经看过了,我只是很难理解一些东西,而没有解释它们,因为我对 C 编程完全陌生。 当然,如果有人能给我一个可能对我有帮助的链接,那就太好了。

【问题讨论】:

    标签: c arrays memory-management dynamic malloc


    【解决方案1】:

    这是一个使用动态内存的非常标准的应用程序。您将要做的是为初始整数数声明内存,读取直到达到该数,重新分配两倍于原来大小的数组,然后继续读取所有数据。

    虽然将整行数据读入缓冲区,然后根据需要解析缓冲区通常是最佳实践,但在这种情况下,fscanf 是专门为处理单个整数值的读取和转换而设计的(以及它会自动使用分隔值的空格)。

    (下面使用calloc 将所有元素初始化为0malloc 在此示例中很好,但在更复杂的情况下,初始化可防止无意中读取未初始化的元素)

    这是一个简短的例子:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXI 64
    
    int main (int argc, char **argv) {
    
        int *array = NULL;
        size_t idx = 0, max_idx = 0;
        size_t arraysize = MAXI;
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {
            fprintf (stderr, "error: file open failed. '%s'\n", argc > 1 ? argv[1] : "stdin");
            return 1;
        }
    
        /* allocate initial array */
        array = calloc (MAXI, sizeof *array);
    
        /* read values from file */
        while (fscanf (fp, "%d", &array[idx]) == 1) {
            idx++;
    
            /* realloc if necessary */
            if (idx == arraysize) {
                int *tmp = realloc (array, arraysize * sizeof *array * 2);
                if (!tmp) {
                    fprintf (stderr, "error: realloc - virtual memory exhausted.\n");
                    return 1;
                }
                array = tmp;
                memset (array + arraysize, 0, arraysize);  /* zero new memory */
                arraysize *= 2;
            }
        }
    
        /* close file */
        if (fp != stdin) fclose (fp);
    
        max_idx = idx;
    
        /* print array */
        for (idx = 0; idx < max_idx; idx++)
            printf (" array[%3zu] : %d\n", idx, array[idx]);
    
        free (array);   /* free memory */
    
        return 0;
    }
    

    示例数据

    $ cat dat/100intspace.txt
    27086 29317 32736 3356 12059 13921 9388 25672 19828 25390 -1190 25857 ...
    

    输出

    $ ./bin/array_dyn_read_int dat/100intspace.txt
     array[  0] : 27086
     array[  1] : 29317
     array[  2] : 32736
     array[  3] : 3356
     array[  4] : 12059
     array[  5] : 13921
     array[  6] : 9388
     array[  7] : 25672
     array[  8] : 19828
     array[  9] : 25390
     array[ 10] : -1190
     array[ 11] : 25857
     ...
    

    验证您的内存使用情况

    当您动态分配内存时,您有责任 (1) 跟踪您分配的内容; (2) 保留一个指向起始地址的指针(以便稍后释放它); (3) 在不再需要时释放内存。 valgrind 或类似的内存错误检查器使用简单,应该用于验证您的内存使用情况:

    $ valgrind ./bin/array_dyn_read_int dat/100intspace.txt
    ==7348== Memcheck, a memory error detector
    ==7348== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
    ==7348== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
    ==7348== Command: ./bin/array_dyn_read_int dat/100intspace.txt
    ==7348==
     array[  0] : 27086
     array[  1] : 29317
     array[  2] : 32736
     array[  3] : 3356
     array[  4] : 12059
     array[  5] : 13921
     array[  6] : 9388
     array[  7] : 25672
     array[  8] : 19828
     array[  9] : 25390
     array[ 10] : -1190
     array[ 11] : 25857
     ....
    ==7348==
    ==7348== HEAP SUMMARY:
    ==7348==     in use at exit: 0 bytes in 0 blocks
    ==7348==   total heap usage: 3 allocs, 3 frees, 1,336 bytes allocated
    ==7348==
    ==7348== All heap blocks were freed -- no leaks are possible
    ==7348==
    ==7348== For counts of detected and suppressed errors, rerun with: -v
    ==7348== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
    

    如果您有任何问题,请告诉我。

    【讨论】:

      【解决方案2】:

      您可以编写一个单独的函数,该函数将从字符串中提取数字并将它们存储在动态分配的数组中,而无需使用realloc。 要在分配数组之前计算字符串中的数字,您可以使用标准 C 函数 sscanf

      这是一个演示程序

      #include <stdlib.h>
      #include <stdio.h>
      
      size_t get_data( const char *s, int **a )
      {    
          int x;
          size_t n;
          int m;
      
          *a = NULL;
          n = 0;
      
          for ( const char *p = s; sscanf( p, "%d%n", &x, &m ) == 1; p += m ) ++n;
      
          if ( n && ( *a = malloc( n * sizeof( int ) ) ) )
          {
              const char *p = s;
              for ( size_t i = 0; i < n; i++, p += m ) sscanf( p, "%d%n", *a + i, &m );
          }
      
          return n;
      }    
      
      int main( void )
      {
          char s[] = " 43 2 6 120 5 23";
          int *a;
          size_t n = get_data( s, &a );
      
          if ( n )
          {
              for ( size_t i = 0; i < n; i++ ) printf( "%d ", a[i] );
              printf( "\n" );
          }        
      
          free( a );
      }
      

      程序输出是

      43 2 6 120 5 23 
      

      【讨论】:

      • 感谢您的回答,它确实帮助我更好地理解了事情。但是您或任何人会介意更彻底地解释计数部分吗?
      【解决方案3】:

      如果您不介意连续的空白,那么您可以简单地计算它们的数量并加一个:

      #include <stdio.h>
      
      int main(void)
      {
          char line[] = "43 2 6 120 5 23";
      
          size_t numCount = 1;
          char *ptr = line;
          while (*ptr != '\0') {
              if (*ptr == ' ') {
                  numCount++;
              }
              ptr++;
          }
      
          // malloc call, etc.
      
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        读取一行后,代码需要扫描该行两次,一次是计算数字(以便分配正确的内存量),另一次是转换数字并将它们存储在数组中。

        使用strtok的问题是它会修改该行,这会阻止代码使用strtok第二次扫描该行。

        所以解决方案是编写一个简单的for 循环来解析行并计算数字。为此,您需要一个变量来跟踪您是在数字内还是在数字之间。这是伪代码:

        state = outside
        count = 0
        for each character in the line
            if ( state == outside )
            {
                if ( isdigit(character) )
                    count++
                    state = inside
            }
            else
            {
                if ( isspace(character) )
                    state = outside
            }
        

        在循环结束时,可以使用count为数组分配内存,然后可以使用strtokstrtol进行数字转换。请注意,对于字符既不是数字也不是空格的情况,我省略了错误检查。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-06-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-12-25
          相关资源
          最近更新 更多