2013-01-10 30 views
5

我將一些代碼從Java移植到C,到目前爲止事情進展順利。在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(")"); 

我意識到等都不是代碼的翻譯服務,但我不要求任何人來翻譯上面的代碼。

我想知道如何在C中有效地執行這種類型的大量字符串連接。它大多是小字符串,但每個都由條件決定,所以我不能將它們組合成簡單的sprintf調用。

我怎樣才能可靠地做到這種類型的字符串連接?

+0

你嚴格地用C?或者你也可以使用C++嗎? – Nico

+2

那麼你可以將它們合併成一堆sprintf()調用。只需使用返回值... – wildplasser

+0

@Nico我使用純C,並且我想避免C++。 –

回答

4

一個相當「聰明」的方式來CONVER一些「物」的字符串是:

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」家庭。

+0

只要確保無論你'sprintf'-ing不超過99個字符(留下一個'char'作爲終止空字符),否則你會得到一個緩衝區溢出和可能是分段錯誤。 –

+0

我最終做了這個檢查緩衝區溢出(如果有'reallocf'的話)。我想我會在稍後優化它,如果它最終會成爲一個問題,但現在看起來非常優雅 –

0

鑑於字符串看起來很小,我傾向於使用strcat,並在性能成爲問題時重新考慮。

你可以使你自己的方法記住字符串的長度,所以它不需要遍歷字符串來查找結尾(如果你正在做很多附加到長字符串的話,這可能是strcat的慢位)

2

strcat()的性能問題是它必須先掃描目標字符串才能找到終止的\0',然後才能開始附加到它。

但請記住,strcat()不採取作爲參數,它需要指針

如果您維護一個單獨的指針始終指向要追加到字符串的結束'\0',您可以使用該指針作爲第一個參數strcat(),它不會有重新掃描它的每一個時間。對於這個問題,你可以使用strcpy()評估者而不是strcat()

保持此指針的值並確保有足夠的空間作爲練習。

注意:您可以使用strncat()來避免覆蓋目標數組的末尾(儘管它會以靜默方式截斷數據)。爲此,我不建議使用strncpy()。見my rant on the subject

如果您的系統支持它們,那麼(非標準)strcpy()strlcat()函數對於這類事情可能很有用。他們都返回他們嘗試創建的字符串的總長度。但是它們的使用使得你的代碼更加便攜。另一方面,您可以在任何地方使用開源實現。

另一種解決方法是對要追加的字符串調用strlen()。這是不理想的,因爲它然後被掃描兩次,一次被strcat()掃描一遍,一次被strlen()掃描 - 但是至少它可以避免重新掃描整個目標字符串。

+1

「保持這個指針的值,並確保有足夠的空間作爲練習。「但似乎用標準字符串函數做這件事並不是一個好方法,不幸的是,標準字符串函數不會返回指向終止'\ 0'的指針,而是返回字符串的開頭。解決這個限制,我想。 –

+0

你總是可以自己在傳入的字符串上運行strlen,並使用mem *函數來避免另一個strlen調用(以跟蹤內存和字符串尾部) – Eugene

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; 
} 
2

連接字符串時性能較差的原因是內存重新分配。 Joel Spolsky在他的文章Back to basics中對此進行了討論。他描述了連接字符串的簡單方法:

Shlemiel找到了一個街頭畫家的工作,在路中間畫了虛線。第一天,他將一罐油漆塗在路上,完成了300碼的道路。 「這很好!」他的老闆說,「你是一個快速的工人!」並支付給他一個科比。

第二天什萊米爾只能完成150碼。 「那麼,這還不如昨天,但你仍然是一個快速的工人,150碼是可敬的,並支付他科比。

第二天什萊米爾油漆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 - '' 
+0

注意:這是一個terribele答案可比到可怕的strncpy())::它會**總是**保持目標字符串未終止。 – wildplasser

+0

@wildplasser我添加了一個評論,它永遠不會終止目標字符串,並確保調用代碼確保會有一個空終止符。 – Steve

+1

它仍然是可怕的,IMnsvHO。你只能通過名稱放棄(BTW Joel Spolsky具有C++口音,因此你身處優秀公司)在API設計方面,將NUL終止作爲調用者的任務是一個非常糟糕的習慣。字符串是字符串。處理它)注意:我不會downvote。我從來沒有做。相信這一點的人,應該失去,恕我直言。 – wildplasser