59

我遇到了这两种连接字符串的方法:

共同部分:

char* first= "First";
char* second = "Second";
char* both = malloc(strlen(first) + strlen(second) + 2);

方法一:

strcpy(both, first);
strcat(both, " ");       // or space could have been part of one of the strings
strcat(both, second);

方法二:

sprintf(both, "%s %s", first, second);

在这两种情况下,内容both都是"First Second".

我想知道哪个更有效(我必须执行几个串联操作),或者您是否知道更好的方法。

4

10 回答 10

73

为了可读性,我会选择

char * s = malloc(snprintf(NULL, 0, "%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

如果您的平台支持 GNU 扩展,您还可以使用asprintf()

char * s = NULL;
asprintf(&s, "%s %s", first, second);

如果你被 MS C 运行时卡住了,你必须用它_scprintf()来确定结果字符串的长度:

char * s = malloc(_scprintf("%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

以下很可能是最快的解决方案:

size_t len1 = strlen(first);
size_t len2 = strlen(second);

char * s = malloc(len1 + len2 + 2);
memcpy(s, first, len1);
s[len1] = ' ';
memcpy(s + len1 + 1, second, len2 + 1); // includes terminating null
于 2009-09-05T16:12:27.343 回答
23

不用担心效率:让您的代码具有可读性和可维护性。我怀疑这些方法之间的区别是否会影响您的程序。

于 2009-09-05T15:56:26.440 回答
19

这对你来说有些疯狂,我实际上去测量了它。该死的,想象一下。我想我得到了一些有意义的结果。

我使用双核 P4,运行 Windows,使用 mingw gcc 4.4,使用“gcc foo.c -o foo.exe -std=c99 -Wall -O2”构建。

我从原始帖子中测试了方法1和方法2。最初将 malloc 保持在基准循环之外。方法 1 比方法 2 快 48 倍。奇怪的是,从构建命令中删除 -O2 会使生成的 exe 快 30%(尚未调查原因)。

然后我在循环中添加了一个 malloc 和 free 。这使方法 1 的速度降低了 4.4 倍。方法 2 减慢了 1.1 倍。

因此, malloc + strlen + free 不要在配置文件中占主导地位,以至于避免 sprintf 值得。

这是我使用的代码(除了循环是用 < 而不是 != 实现的,但这破坏了这篇文章的 HTML 呈现):

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 48; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 1; i++)
        sprintf(both, "%s %s", first, second);
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));

    // Takes 3.7 sec with optimisations, 2.7 sec WITHOUT optimisations!
    a(first, second, both);

    // Takes 3.7 sec with or without optimisations
    //b(first, second, both);

    return 0;
}
于 2009-09-05T18:28:17.373 回答
6
size_t lf = strlen(first);
size_t ls = strlen(second);

char *both = (char*) malloc((lf + ls + 2) * sizeof(char));

strcpy(both, first);

both[lf] = ' ';
strcpy(&both[lf+1], second);
于 2009-09-05T16:14:46.667 回答
2

它们应该几乎相同。差异并不重要。我会去,sprintf因为它需要更少的代码。

于 2009-09-05T15:58:38.923 回答
2

差异不太可能重要:

  • 如果您的字符串很小,malloc将淹没字符串连接。
  • 如果您的字符串很大,复制数据所花费的时间将淹没strcat / sprintf之间的差异。

正如其他海报所提到的,这是一个过早的优化。专注于算法设计,只有在分析表明这是一个性能问题时才回来。

也就是说......我怀疑方法1会更快。解析sprintf格式字符串会产生一些(诚然很小)的开销。而且strcat更有可能“内联”。

于 2009-09-05T16:31:55.567 回答
1

sprintf() 旨在处理的不仅仅是字符串, strcat() 是专家。但我怀疑你正在为这些小事出汗。C 字符串从根本上说是低效的,这使得这两种提议的方法之间的差异变得微不足道。阅读Joel Spolsky 的“回归基础”了解血腥细节。

这是 C++ 通常比 C 执行得更好的一个实例。对于重量级字符串处理,使用 std::string 可能更有效,当然也更安全。

[编辑]

[第 2 次编辑]更正的代码(C 字符串实现中的迭代次数过多)、时间和结论相应地改变

我对 Andrew Bainbridge 的评论感到惊讶,即 std::string 速度较慢,但​​他没有发布此测试用例的完整代码。我修改了他的(自动计时)并添加了一个 std::string 测试。测试是在具有默认“发布”选项(即优化)、Athlon 双核、2.6GHz 的 VC++ 2008(本机代码)上进行的。结果:

C string handling = 0.023000 seconds
sprintf           = 0.313000 seconds
std::string       = 0.500000 seconds

因此,尽管 C 字符串约定固有的效率低下,但 strcat() 到目前为止速度更快(您的里程可能会因编译器和选项而异),并且支持我最初的建议,即 sprintf() 携带大量不需要为此目的的包袱. 然而,它仍然是迄今为止可读性和安全性最低的,因此当性能不重要时,IMO 几乎没有什么优点。

我还测试了一个 std::stringstream 实现,它又慢了很多,但对于复杂的字符串格式仍然有优点。

更正后的代码如下:

#include <ctime>
#include <cstdio>
#include <cstring>
#include <string>

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
        sprintf(both, "%s %s", first, second);
}

void c(char *first, char *second, char *both)
{
    std::string first_s(first) ;
    std::string second_s(second) ;
    std::string both_s(second) ;

    for (int i = 0; i != 1000000; i++)
        both_s = first_s + " " + second_s ;
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));
    clock_t start ;

    start = clock() ;
    a(first, second, both);
    printf( "C string handling = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    b(first, second, both);
    printf( "sprintf           = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    c(first, second, both);
    printf( "std::string       = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    return 0;
}
于 2009-09-05T17:28:28.593 回答
0

我不知道如果两个有任何真正的串联完成。将它们背靠背打印并不构成串联。

告诉我,这会更快:

1) a) 将字符串 A 复制到新缓冲区 b) 将字符串 B 复制到缓冲区 c) 将缓冲区复制到输出缓冲区

或者

1) 将字符串 A 复制到输出缓冲区 b) 将字符串 b 复制到输出缓冲区

于 2009-09-05T16:23:12.633 回答
0
  • 与 sprintf 相比,strcpy 和 strcat 是更简单的操作,后者需要解析格式字符串
  • strcpy 和 strcat 很小,因此它们通常会被编译器内联,从而节省更多额外的函数调用开销。例如,在 llvm 中 strcat 将使用 strlen 内联以查找复制起始位置,然后是简单的存储指令
于 2017-06-23T14:45:30.393 回答
-1

两者都不是非常有效,因为这两种方法都必须计算字符串长度或每次都扫描它。相反,由于您无论如何都要计算各个字符串的 strlen(),因此请将它们放入变量中,然后只需 strncpy() 两次。

于 2009-09-05T15:56:00.217 回答