连接c中字符串的最有效方法

时间:2018-09-20 00:19:44

标签: c string memory-management

考虑一个简单的程序,该程序将所有指定的参数连接起来并在标准输出中打印它们。我使用了2个for循环来附加字符串,其中一个用于计算字符串的长度,另一个用于连接字符串。有没有办法只做一次循环?为每个要连接的字符串重新分配内存不是更有效,是吗? Java的StringBuilder如何用C实现?它会像我一样循环两次吗?

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

int main(int argc, char** argv)
{
    size_t len = 0;

    // start for loop at i = 1 to skip the program name specified in argv
    for(int i = 1; i < argc; i++)
        len += strlen(argv[i]) + 1; // +1 for the space 

    char* toAppend = (char*)malloc(len * sizeof(char) + 1);
    toAppend[0] = '\0'; // first string is empty and null terminated 

    for(int i = 1; i < argc; i++)
    {
        strcat(toAppend, argv[i]);
        strcat(toAppend, " ");
    }

    printf(toAppend);
    free(toAppend);
}

3 个答案:

答案 0 :(得分:2)

您的分配方法非常有效,可以测量总长度并仅分配一次。但是,串联循环从一开始就反复测量输出缓冲区的长度,直到连接到它为止,从而导致二次运行时间。

要修复该问题,请跟踪您的位置:

size_t pos = 0;
for(int i = 1; i < argc; i++) {
    size_t len = strlen(argv[i]);
    memcpy(toAppend+pos, argv[i], len);
    pos += len;
    toAppend[pos] = ' ';
    pos++;
}
toAppend[pos] = 0;

这是实际在内存中进行连接的最有效方法,但是最有效的方法是不进行连接。相反:

for(int i = 1; i < argc; i++)
    printf("%s ", argv[i]);

stdio被缓冲的全部原因是,您不必构建任意长度的内存中缓冲区即可进行有效输出;相反,它会自动缓冲到固定大小,并在缓冲区已满时刷新。

请注意,如果您的输入在任何地方都包含printf字符,那么您对%的使用是错误和危险的;应该是printf("%s", toAppend);

如果您要写的是POSIX(或POSIX-ish)系统,而不是纯C,则另一个选择是fmemopen,它使您可以像这样编写循环:

for(int i = 1; i < argc; i++)
    fprintf(my_memfile, "%s ", argv[i]);

答案 1 :(得分:2)

  

在c中连接字符串的有效方法

一种有效的方法是计算字符串长度-并记住它们。

size_t sum = 1; // for \0
if (argc > 2) sum += argc - 2.  // spaces
size_t length[argc];  // This is a VLA, available C99 and optionally in C11
for(int i = 1; i < argc; i++)
  length[i] = strlen(argv[i]);
  sum += length[i];
}

然后分配,然后检查错误。

char *dest = malloc(sum);
if (dest == NULL) Handle_OutOfMemory();

依次复制每个字符串

char *p = dest;
for(int i = 1; i < argc; i++)
  // Use either memcpy() or strcpy().
  // memcpy() tends to be faster for long strings than strcpy().
  memcpy(p, argv[i], length[i]);  
  p += length[i]; // advance insertion point
  if (i > 1) {
    *p++ = ' '; // space separators
  }
}
*p = '\0';

现在使用dest[]

printf("<%s>\n", dest);

完成后释放资源。

free(dest);

  

为每个要串联的字符串重新分配内存不是更有效吗?

通常最好避免重复的重新分配,但是对于小的短字符串,确实没有什么区别。专注于big O。我的答案是O(n)。重定位为O(n*n)

如果性能至关重要,请针对目标系统尝试各种方法和配置文件。关键在于在一台机器上最快的速度在另一台机器上可能有所不同。通常,最好先编写一种合理清晰的方法。

答案 2 :(得分:0)

最有效的方法可能是不使用任何str函数并“手动”复制字符:

char* toAppend = malloc(len + 1);

size_t j = 0;
for(size_t i = 1; i < argc; i++)
{
  for(size_t k = 0; argv[i][k]; k++)
    toAppend[j++] = argv[i][k];
  toAppend[j++] = ' ';
}
toAppend[j - 1] = '\0'; // Remove the last space and NULL-terminate the string