5

我正在将一些代码从 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调用。

我怎样才能可靠地进行这种类型的字符串连接?

4

5 回答 5

4

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

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

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

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

于 2013-01-10T23:23:51.863 回答
2

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

但请记住,strcat()它不接受字符串作为参数,而是接受指针

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

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

注意:您可以使用它strncat()来避免覆盖目标数组的末尾(尽管它会默默地截断您的数据)。我不建议strncpy()为此目的使用。见我对这个问题的咆哮

如果您的系统支持它们,则(非标准)strcpy()strlcat()函数可能对这种事情有用。他们都返回他们试图创建的字符串的总长度。但是它们的使用会降低您的代码的可移植性;另一方面,您可以在任何地方使用开源实现。

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

于 2013-01-10T23:12:09.557 回答
2

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

Shlemiel 找到了一份街头画家的工作,在路中间画出虚线。第一天,他把一罐油漆带到路上,完成了 300 码的路。“那还不错!” 他的老板说,“你是一个快速的工人!” 并付给他一个戈比。

第二天,Shlemiel 只完成了 150 码。“嗯,那不如昨天好,但你仍然是一个快速的工人。150码是体面的,”并付给他一个戈比。

第二天,Shlemiel 粉刷了 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 - ''
于 2013-01-10T23:43:05.297 回答
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;
}
于 2013-01-10T23:37:32.427 回答
0

strcat鉴于字符串看起来如此之小,如果性能成为问题,我倾向于使用并重新审视。

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

于 2013-01-10T23:12:08.060 回答