在C中执行大量字符串连接?

时间:2013-01-10 23:07:05

标签: c string concatenation string-concatenation

我正在将一些代码从Java移植到C,到目前为止事情进展顺利。

但是,我在Java中有一个特殊功能,可以自由使用StringBuilder,如下所示:

StringBuilder result = new StringBuilder();
// .. build string out of variable-length data
for (SolObject object : this) {
    result.append(object.toString());
}
// .. some parts are conditional
if (freezeCount < 0) result.append("]");
else result.append(")");

我意识到SO不是代码翻译服务,但我不是要求任何人翻译上述代码。

我想知道如何在C中有效地执行这种类型的质量串连接。它主要是小字符串,但每个字符串由一个条件决定,所以我不能将它们组合成一个简单的sprintf调用。

如何可靠地执行此类字符串连接?

5 个答案:

答案 0 :(得分:4)

将一些“对象”转换为字符串的一种相当“聪明”的方法是:

 char buffer[100];
 char *str = buffer;
 str += sprintf(str, "%06d", 123);
 str += sprintf(str, "%s=%5.2f", "x", 1.234567);

这是相当有效的,因为sprintf返回复制的字符串的长度,所以我们可以通过返回值“移动”str,并继续填写。

当然,如果有真正的Java对象,那么你需要弄清楚如何在C的printf系列中将Java样式的ToString函数变成“%somethign”。

答案 1 :(得分:2)

strcat()的性能问题是它必须扫描目标字符串以找到终止\0'才能开始追加它。

但请记住,strcat()不会将字符串作为参数,而是需要指针

如果你维护一个单独的指针,它始终指向你要追加的字符串的终止'\0',你可以使用该指针作为strcat()的第一个参数,它不会每次都必须重新扫描它。就此而言,您可以使用strcpy() rater而不是strcat()

保持此指针的值并确保有足够的空间作为练习。

注意:您可以使用strncat()来避免覆盖目标数组的末尾(尽管它会以静默方式截断您的数据)。我不建议将strncpy()用于此目的。请参阅my rant on the subject

如果您的系统支持它们,则(非标准)strcpy()strlcat()函数可用于此类事情。它们都返回了他们试图创建的字符串的总长度。但它们的使用使您的代码不那么便携;另一方面,您可以在任何地方使用开源实现。

另一种解决方案是在您要追加的字符串上调用strlen()。这并不理想,因为它会被扫描两次,一次扫描strcat(),一次扫描strlen() - 但至少可以避免重新扫描整个目标字符串。

答案 2 :(得分:2)

连接字符串时性能不佳的原因是内存的重新分配。 Joel Spolsky在他的文章Back to basics中讨论了这一点。他描述了连接字符串的天真方法:

  

Shlemiel得到一份街头画家的工作,在路中间涂上虚线。第一天,他把一罐油漆涂在路上,完成了300码的路程。 “那太好了!”他的老板说,“你是个快工!”并付给他一个科比。

     

第二天Shlemiel只完成150码。 “嗯,这不像昨天那么好,但你仍然是一个快速工作者.150码是值得尊敬的,”并且给他一个科比。

     

第二天Shlemiel画了30码的路。 “只有30个!”喊他的老板。 “这是不可接受的!第一天你做了十倍的工作!发生了什么事?”

     

“我无法帮助它,”Shlemiel说。 “每天我都会越来越远离油漆罐!”

如果可以,您想知道在分配目标缓冲区之前需要多大的目标缓冲区。唯一可行的方法是在要连接的所有字符串上调用strlen。然后分配适当的内存量并使用略微修改的strncpy版本,该版本返回指向目标缓冲区末尾的指针。

// Copies src to dest and returns a pointer to the next available
// character in the dest buffer.
// Ensures that a null terminator is at the end of dest.  If
// src is larger than size then size - 1 bytes are copied
char* StringCopyEnd( char* dest, char* src, size_t size )
{
    size_t pos = 0;
    if ( size == 0 ) return dest;

    while ( pos < size - 1 && *src )
    {
        *dest = *src;
        ++dest;
        ++src;
        ++pos;
    }
    *dest = '\0';
    return dest;
}

请注意,您必须将size参数设置为目标缓冲区结束前剩余的字节数。

这是一个示例测试函数:

void testStringCopyEnd( char* str1, char* str2, size_t size )
{
    // Create an oversized buffer and fill it with A's so that 
    // if a string is not null terminated it will be obvious.
    char* dest = (char*) malloc( size + 10 ); 
    memset( dest, 'A', size + 10 );
    char* end = StringCopyEnd( dest, str1, size );
    end = StringCopyEnd( end, str2, size - ( end - dest ) );
    printf( "length:  %d - '%s'\n", strlen( dest ), dest );
}

int main(int argc, _TCHAR* argv[])
{
    // Test with a large enough buffer size to concatenate 'Hello World'.
    // and then reduce the buffer size from there
    for ( int i = 12; i > 0; --i )
    {
        testStringCopyEnd( "Hello", " World", i );
    }
    return 0;
}

产生:

length:  11 - 'Hello World'
length:  10 - 'Hello Worl'
length:  9 - 'Hello Wor'
length:  8 - 'Hello Wo'
length:  7 - 'Hello W'
length:  6 - 'Hello '
length:  5 - 'Hello'
length:  4 - 'Hell'
length:  3 - 'Hel'
length:  2 - 'He'
length:  1 - 'H'
length:  0 - ''

答案 3 :(得分:1)

如果这些操作非常频繁,您可以在自己的缓冲类中实现它们。示例(为简洁省略了错误处理; - ):

struct buff {
        size_t used;
        size_t size;
        char *data;
        } ;

struct buff * buff_new(size_t size)
{
struct buff *bp;
bp = malloc (sizeof *bp);
bp->data = malloc (size);
bp->size = size;
bp->used = 0;
return bp;
}

void buff_add_str(struct buff *bp, char *add)
{
size_t len;
len = strlen(add);

        /* To be implemented: buff_resize() ... */
if (bp->used + len +1 >= bp->size) buff_resize(bp, bp->used+1+len);

memcpy(buff->data + buff->used, add, len+1);

buff->used += len;
return;
}

答案 4 :(得分:0)

鉴于字符串看起来很小,我倾向于只使用strcat并重新审视性能是否成为问题。

您可以创建自己的方法来记住字符串长度,这样就不需要遍历字符串来查找结尾(如果您对长字符串进行大量附加,则可能是strcat的慢点) )