【问题标题】:How can I append to a string many times?如何多次附加到字符串?
【发布时间】:2015-05-31 12:29:45
【问题描述】:

所以我有一个循环,每次都会生成一个字符串,我想将此字符串附加到现有变量中。

char fullString[] = "start: ";

while(x < 200){

    char someVar[] = "test "

    //append someVar to fullString

    x++;

}

所以我想得到一个这样的字符串:

start: test test test test test ...

我可以在任何其他语言上轻松做到这一点,只是不确定如何在c 中做到这一点,有什么想法吗?

【问题讨论】:

  • 首先,您需要确保fullString 足够大以容纳您附加到它的所有字符串。这可以通过使数组非常大或使用mallocrealloc 使用动态分配来完成。
  • 至于实际的串联,请阅读strcat

标签: c string loops char


【解决方案1】:

strcat()、malloc() 和 realloc() 的开销经常被忽略。

我在这个案子上玩了一会儿,得到了一些数字:

% ./catter "Start: " "test " 1000                                                                                                             
    realloc_catter:        379
   prealloc_catter:        154
  realloc_mycatter:        152
 prealloc_mycatter:          8
% ./catter "Start: " "test " 100000  
    realloc_catter:    1453494
   prealloc_catter:     741639
  realloc_mycatter:     733160
 prealloc_mycatter:        365
% ./catter "Start: " "test " 1000000
    realloc_catter:  265374117
   prealloc_catter:  128139699
  realloc_mycatter:  127484834
 prealloc_mycatter:       3397

在这里,我们清楚地看到了 O(n^2) 成本。适当优化的连接方法只需 O(n)。

测试代码是:

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

void measure(char *name, void (*func)(char**,char *,char*,int), char **target, char *initial, char *repeat, int times)
{
    clock_t start, finish;
    start = clock();
    func(target, initial, repeat, times);
    free(*target);
    finish = clock();
    printf("%20s: %10lld\n", name, (long long)(finish - start));
}

void realloc_catter(char **target, char *initial, char *repeat, int times)
{
    *target = malloc(strlen(initial)+1);
    strcpy(*target, initial);
    for (int i = 0; i < times; i++) {
        *target = realloc(*target, strlen(*target) + strlen(repeat) + 1);
        strcat(*target, repeat);
    }
}

void prealloc_catter(char **target, char *initial, char *repeat, int times)
{
    *target = malloc(strlen(initial) + strlen(repeat) * times + 1);
    strcpy(*target, initial);
    for (int i = 0; i < times; i++) {
        strcat(*target, repeat);
    }
}

char *mystrcat(char *target, char *repeat)
{
    for(;;) {
        *target = *repeat;
        if (!*repeat) break;
        target++;
        repeat++;
    }
    return target;
}

void realloc_mycatter(char **target, char *initial, char *repeat, int times)
{
    char *catptr = *target = malloc(strlen(initial)+1);
    strcpy(*target, initial);
    for (int i = 0; i < times; i++) {
        *target = realloc(*target, strlen(*target) + strlen(repeat) + 1);
        catptr = mystrcat(catptr, repeat);
    }
}

void prealloc_mycatter(char **target, char *initial, char *repeat, int times)
{
    char *catptr = *target = malloc(strlen(initial) + strlen(repeat) * times + 1);
    strcpy(*target, initial);
    for (int i = 0; i < times; i++) {
        catptr = mystrcat(catptr, repeat);
    }
}

int main(int argc, char **argv)
{
    if (argc < 4) exit(1);
    char *initial = argv[1];
    char *repeat = argv[2];
    int times = atoi(argv[3]);

    char *target;

    measure("realloc_catter", realloc_catter, &target, initial, repeat, times);

    measure("prealloc_catter", prealloc_catter, &target, initial, repeat, times);

    measure("realloc_mycatter", realloc_mycatter, &target, initial, repeat, times);

    measure("prealloc_mycatter", prealloc_mycatter, &target, initial, repeat, times);

    return 0;
}

mystrcat() 函数返回一个指向最后一个字符串位置的指针,保存下一次调用必须再次遍历字符串。此测试代码也可通过gist 获得。

【讨论】:

    【解决方案2】:

    我今天早上写了这个答案,但后来停电了。无论如何,这里都是为了引导提问者理解他/她。

    这在 C 中并不容易,因为您必须自己管理字符串空间(其他语言为您做的)。

    本质上,每次你想追加到现有的字符串,你必须计算它的当前长度,要追加的字符串的长度,分配新的空间来容纳两者,将现有的部分复制到新的字符串内存中,追加新字符串,释放旧字符串。

    有多种方法可以加快速度,例如使用 realloc,正如其他人所建议的那样,预先分配更大的缓冲区,跟踪当前长度等;但是,如果您要附加可变长度的字符串,则该方法不会改变。

    【讨论】:

      【解决方案3】:

      使用strcat(),您有进入the Schlemiel's algorithm 的风险。

      保持当前字符串长度和sprintf()(或snprintf())代替:

      char *result;
      size_t len = 0;
      while (1) {
          /* make sure result points to a large enough area! */
          len += sprintf(result + len, "%s", "test ")
          if (stuff()) break;
      }
      

      【讨论】:

        【解决方案4】:

        如果您以迭代方式重复添加字符串,则会导致 O(n^2) 运行时间。相反,应该预先分配全部内存,并逐步构建字符串。

        这里有一些代码可以做到这一点。

        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        
        // append_extra returns a string consisting of init
        // with count copies of extra appended.
        // Or NULL on failure.
        char *append_extra(char *init, char *extra, int count) {
            size_t len_init = strlen(init);
            size_t len_extra = strlen(extra);
            char *result = malloc(len_init + len_extra * count + 1);
            if (!result) {
               return 0;
            }
            char *p = result;
            strcpy(p, init);
            p += len_init;
            for (int i = 0; i < count; i++) {
                strcpy(p, extra);
                p += len_extra;
            }
            return result;
        }
        
        int main(void) {
            char *result = append_extra("hello", " world", 10);
            if (!result) exit(1);
            printf("'%s'\n", result);
            return 0;
        }
        

        【讨论】:

          【解决方案5】:

          你可以这样使用,

          char *full =malloc(10);// allocating the memory
           if ( full == NULL ){ 
                   printf("allocation failed\n");
                   return;
           }
          strcpy(full,"Start: ");
          char someVar[] ="test ";
          char *temp;
          while(x < 200 ) {
               temp=realloc(full,(strlen(full)+1)+(strlen(someVar)+1));//reallocating for store repeatedly
               if ( temp == NULL )
                        printf("allocation failed\n");
                   break;
               }
               full=temp;
               strcat(full,someVar);
               x++;
          }
          

          【讨论】:

          • 这对于大 x 来说会很慢。使用一些分页来提高性能
          • O(n^2) 运行时,sizeof(full) 是错误的,并且您遇到了一个错误。
          • 是 sizeof() 错误,需要根据strlen() 计算字节数,注意容纳空字符strlen(str) + 1 NMDW
          • 你是对的,通过这种方式可以连接他想​​要的任何字符串长度,但我认为这也可以在不使用“temp”的情况下实现 while(x
          【解决方案6】:

          根据 DrKoch 的建议,realloc 的多次调用可能会影响您的执行时间,具体取决于 realloc 的实现。

          如果您已经要附加 someVar 多少次,您可以一次分配所有需要的动态内存:

          char *full;
          char someVar[] ="test ";
          int  nb_append = 200;
          int  x = 0;
          
          if ((full = malloc(sizeof (*full) * 8) == NULL)// allocating the memory
             // Handle malloc error;
          strcpy(full,"Start: ");
          if (realloc(full,sizeof(*full) * 8 + sizeof(someVar) * nb_append) == NULL) //reallocating for store only once
             // Handle realloc error;
          while (x < nb_append ) {
               strcat(full, someVar);
               x++;
          }
          

          【讨论】:

          • sizeof(*full) 是 1。
          • 你是对的,使用 strcat 来做这件事不是一个好主意。正如 Anonymous 所建议的那样,从正确位置开始的简单 strcpy 效率更高。
          【解决方案7】:

          你应该有一个足够大的缓冲区来容纳整个连接的字符串。

          如下图使用字符串函数strcat()

          char fullString[2000] = "start: ";
          
          while(x<200)
          {
              char someVar[] = "test "; // What ever valid string you want to append to the existing string
              strcat(fullString,someVar);
              x++;
          }
          

          【讨论】:

          • 很酷!如果我期望fullString 介于 100,000 到 100 万个字符之间,会发生什么情况。 fullString[1000000] 会有什么问题吗?
          • @matt 是的。在这种情况下,您应该使用动态内存分配。 malloc 和 realloc。
          • 是的@matt 然后你需要根据输入字符串长度进行动态内存分配继续分配内存并在连接之前继续检查realloc()
          猜你喜欢
          • 2020-02-04
          • 1970-01-01
          • 1970-01-01
          • 2012-01-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-13
          相关资源
          最近更新 更多