【问题标题】:How to make a dynamic string?如何制作动态字符串?
【发布时间】:2020-11-02 20:00:16
【问题描述】:

我正在尝试制作一个我认为应该很简单的程序。我想询问一个单词并将其存储在一个大小合适的向量中(逻辑上更多\n)。为此,程序要求用户写一个以. 结尾的单词。然后,程序逐个字母读取并将这些字母存储在一个字符串中,该字符串在读取字母时动态创建。

int main() {
   int i = 0, tam = 0;
   char *cad = (char *)malloc(sizeof(char));
   char c;

   printf("word: ");

   while (c != '.') {
       scanf("%c", c);
       cad[i] = c;
       i++;
       cad = realloc(cad, (i + 1) * sizeof(char));
   }
   cad[i] = '\0';

   for (i = 0; cad[i] == '\0'; i++) {
       tam++;
   }
   printf("tam: %d\n", tam);

   return 0;
}

我做了这个,但我认为它没有任何用处

【问题讨论】:

  • 学究式:在 C 中,我们没有向量。
  • 是的,我知道@RobertSsupportsMonicaCellio
  • 你在scanf中错过了“&”
  • 你也错过了为第一个循环初始化 c
  • @sonlas10 用除 '.' 以外的任何东西初始化它

标签: c string dynamic


【解决方案1】:

使用getchar() 从文件中读取单个字节更简单。还要检查分配和重新分配失败:

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

int main() {
    int len = 0;
    char *cad, *new_cad;
    int c;

    printf("word: ");

    cad = malloc(len + 1);
    if (cad == NULL) {
        printf("memory allocation failure\n");
        return 1;
    }
    while ((c = getchar()) != EOF && c != '.') {
        cad[len] = (char)c;
        len++;
        new_cad = realloc(cad, len + 1);
        if (new_cad == NULL) {
            printf("memory allocation failure\n");
            free(cad);
            return 1;
        }
        cad = new_cad;
    }
    cad[len] = '\0';

    printf("tam: %d, string: %s\n", len, cad);
    free(cad);

    return 0;
}

一次重新分配一个字节的动态字符串效率低下,但实施更好的策略是一种优化。首先关注正确性,仅在经过仔细基准测试证明需要时进行优化。

【讨论】:

    【解决方案2】:

    如何制作动态字符串?

    仔细阅读一本书,例如Modern C,然后参考一本好的C reference website。阅读您的 C 编译器(可能是 GCC)和调试器(可能是 GDB)的文档。当然,请仔细阅读您正在使用的每个函数(例如scanf)的文档。

     while(c != '.'){
       scanf("%c", c);
       cad[i] = c;
       i++;
       cad = realloc(cad, (i + 1)*sizeof(char));
     }
    

    您上面的代码既错误又低效。

    这是错误的,因为你使用了scanf(错误地:应该是scanf("%c", &amp;c),但你真的想使用getchar)和realloc,但不处理他们的失败案例

    这是低效的,因为您可能不想在每个循环中重新分配字符串。除非您的内存非常紧张(实际上不太可能),否则您应该考虑只偶尔使用一次realloc(因为realloc 可能是一项昂贵的操作)。例如,您可以首先 malloc 一个 128 字节的缓冲区,然后 realloc 仅在需要时(当您确实读取 127 字节时)到例如newsize=3*oldsize/2 (所以第二次到 192 字节等...)

    如果您编译时包含所有警告和调试信息(例如 gcc -Wall -Wextra -g),您将收到有用的警告。

    【讨论】:

    • @BasileStarykevitch 我猜你也错过了 scanf 中的 ''&"
    • 更不对,因为c是未初始化的,scanf("%c", c);应该是scanf("%c", &amp;c);
    【解决方案3】:

    进行了所有更改,这应该会为您提供所需的输出。

    #include <stdio.h>
    #include <stdlib.h>
    int main(){
       int i = 0, tam = 0;
       char *cad = (char*)malloc(sizeof(char));
       char c = 'l';
    
       printf("word: ");
    
       while(c != '.'){
           c= getchar();
           cad[i] = c;
           i++;
           cad = realloc(cad, (i + 1)*sizeof(char));
       }
       cad[i] = '\0';
    
    /*
    As pointed out by Bruno, this for loop just calculates the length of the char array, so commented this
    for(i = 0; cad[i] == '\0'; i++){
        tam++;
    }
    printf("tam: %d\n", tam);
    */
    printf("tam: %d\n", i);
    
       return 0;
    }
    

    【讨论】:

    • 请用简单的getchar替换scanf("%c", &amp;c);,为什么要检查EOF?并且还建议删除关于 tam 的循环,因为它没用并且字符串的长度是已知的(警告代码已编辑您结束循环是错误的)
    • 它对你有用吗?因为它不适合我
    • @bruno OP 想要字符数 = '\n' 对吗?那么,为什么要删除最后一个循环
    • 抱歉@bruno 刚刚看到它。将其删除。
    • c 必须具有int 类型才能正确检查EOF
    【解决方案4】:

    两条建议:

    1. realloc 是一个相对昂贵的操作,你真的不想为每个字符都这样做。一种常见的技术是根据需要将缓冲区大小加倍 - 这将最大限度地减少realloc 调用的总数。您最终可能会出现一些内部碎片,但平均而言应该不会太糟糕。

    2. 如果realloc 无法扩展缓冲区,它将返回NULL 并保留原始缓冲区。因此,您确实希望将realloc 的结果分配回原始指针而不先检查它,否则您可能会丢失对先前分配的内存的引用。

    最好使用getcharfgetc 来读取输入,而不是scanf

    所以:

    #define START_SIZE 2
    ...
    size_t size = START_SIZE;  // tracks the size of the buffer
    size_t length = 0;         // tracks the length of the string in the buffer
    char *cad = calloc( size, sizeof *cad ); 
    
    /**
     * **ALWAYS** check the result of a malloc, calloc, or realloc call.
     */
    if ( !cad )
    {
      fprintf( stderr, "Couldn't allocate initial buffer, bailing out here...\n" );
      return EXIT_FAILURE;
    }
    
    for ( int c = getchar(); c != '.' && c != EOF; c = getchar() )
    {
      /**
       * First, make sure we still have room in our buffer (accounting for the
       * zero terminator) - if we don't, double it.
       */
      if ( length + 1 == size )
      {
        char *tmp = realloc( cad, sizeof *cad * ( 2 * size ) );
        if ( !tmp )
        {
          fprintf( stderr, "realloc failed, exiting loop with what we've read so far\n" );
          break;
        }      
        cad = tmp;
        size *= 2;
      }
      cad[length++] = c;
      cad[length] = 0;   // terminate the string as we go
    }
    

    【讨论】:

    • 有趣的替代方案,但有一个错误:如果用户键入. 作为第一个字符,则字符串未初始化... 随时终止字符串需要也不够。
    • @chqrlie:修复了使用calloc 而不是malloc 的初始化。保持终止,以便在 realloc 失败的情况下正确终止字符串。
    • 虽然这解决了问题,但并不完全一致,因为初始块将完全初始化为所有位为零,但重新分配的块最后会有未初始化的字节。
    • buf[length++] = c; 必须是 cad[length++] = c;buf[length] = 0; // terminate the string as we go 相同,最好放在 for 之后而不是里面
    • for 之后的最后一个 realloc 以将大小减小到已使用的大小也可以提供帮助,如果在 2^n 中分配的缓冲区大小很快就会很长
    猜你喜欢
    • 2018-05-20
    • 2023-03-07
    • 1970-01-01
    • 2014-08-02
    • 2021-11-25
    • 1970-01-01
    • 2018-09-14
    • 2023-04-01
    • 1970-01-01
    相关资源
    最近更新 更多