我正在将一些代码从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
调用。
如何可靠地执行此类字符串连接?
答案 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的慢点) )