C ++ tương đương với StringBuffer / StringBuilder?


184

Có lớp Thư viện mẫu tiêu chuẩn C ++ nào cung cấp chức năng nối chuỗi hiệu quả, tương tự như StringBuilder của C # hoặc StringBuffer của Java không?


3
Câu trả lời ngắn gọn là: Có, STL có một lớp cho điều đó và nó là std::ostringstream.
CoffeDeveloper

Này @andrew. Bạn có thể vui lòng thay đổi câu trả lời được chấp nhận? Có một câu trả lời chiến thắng rõ ràng và đó không phải là câu trả lời được chấp nhận hiện tại.
null

Câu trả lời:


53

LƯU Ý câu trả lời này đã nhận được một số sự chú ý gần đây. Tôi không ủng hộ điều này như một giải pháp (nó là một giải pháp tôi đã thấy trong quá khứ, trước STL). Đó là một cách tiếp cận thú vị và chỉ nên được áp dụng std::stringhoặc std::stringstreamnếu sau khi cấu hình mã của bạn, bạn phát hiện ra điều này sẽ cải thiện.

Tôi thường sử dụng một trong hai std::stringhoặc std::stringstream. Tôi chưa bao giờ có bất kỳ vấn đề với những điều này. Tôi thường sẽ đặt trước một số phòng nếu tôi biết trước kích thước thô của chuỗi.

Tôi đã thấy những người khác tạo ra trình xây dựng chuỗi được tối ưu hóa của riêng họ trong quá khứ xa xôi.

class StringBuilder {
private:
    std::string main;
    std::string scratch;

    const std::string::size_type ScratchSize = 1024;  // or some other arbitrary number

public:
    StringBuilder & append(const std::string & str) {
        scratch.append(str);
        if (scratch.size() > ScratchSize) {
            main.append(scratch);
            scratch.resize(0);
        }
        return *this;
    }

    const std::string & str() {
        if (scratch.size() > 0) {
            main.append(scratch);
            scratch.resize(0);
        }
        return main;
    }
};

Nó sử dụng hai chuỗi một cho phần lớn của chuỗi và chuỗi còn lại làm vùng trầy xước để nối các chuỗi ngắn. Nó tối ưu hóa các phần bổ sung bằng cách gộp các hoạt động chắp thêm ngắn trong một chuỗi nhỏ sau đó nối phần này vào chuỗi chính, do đó giảm số lần phân bổ lại cần thiết trên chuỗi chính khi nó lớn hơn.

Tôi không yêu cầu thủ thuật này với std::stringhoặc std::stringstream. Tôi nghĩ rằng nó đã được sử dụng với thư viện chuỗi bên thứ ba trước std :: string, nó đã có từ lâu. Nếu bạn áp dụng một chiến lược như hồ sơ này, ứng dụng của bạn đầu tiên.


13
Phát minh lại bánh xe. std :: chuỗi dòng là câu trả lời thích hợp. Xem câu trả lời tốt dưới đây.
Kobor42

12
@ Kobor42 Tôi đồng ý với bạn khi tôi chỉ ra dòng đầu tiên và cuối cùng của câu trả lời của tôi.
iain

1
Tôi không nghĩ rằng scratchchuỗi thực sự hoàn thành bất cứ điều gì ở đây. Số lượng phân bổ lại của chuỗi chính phần lớn sẽ là một hàm của kích thước cuối cùng của nó, chứ không phải số lượng các hoạt động nối thêm, trừ khi việc stringtriển khai thực sự kém (nghĩa là không sử dụng tăng trưởng theo cấp số nhân). Vì vậy, "bó" lên appendkhông giúp ích gì vì một khi cơ sở stringlớn, nó sẽ chỉ thỉnh thoảng phát triển. Trên hết, nó bổ sung một loạt các hoạt động sao chép dự phòng và có thể phân bổ lại nhiều hơn (do đó gọi tới new/ delete) vì bạn đang nối vào một chuỗi ngắn.
BeeOnRope

@BeeOnRope Tôi đồng ý với bạn.
Iain

Tôi khá chắc chắn str.reserve(1024);sẽ nhanh hơn thứ này
hanshenrik

160

Cách C ++ sẽ là sử dụng std :: stringstream hoặc chỉ là nối chuỗi đơn giản. Các chuỗi C ++ có thể thay đổi được nên việc xem xét hiệu năng của việc ghép nối ít được quan tâm hơn.

liên quan đến định dạng, bạn có thể thực hiện tất cả các định dạng tương tự trên một luồng, nhưng theo một cách khác, tương tự nhưcout . hoặc bạn có thể sử dụng một functor được gõ mạnh, đóng gói cái này và cung cấp giao diện String.Format, ví dụ: boost :: format


59
Chuỗi C ++ có thể thay đổi : chính xác. Toàn bộ lý do StringBuildertồn tại là để bao quát sự không hiệu quả của loại Chuỗi cơ bản bất biến của Java . Nói cách khác StringBuilderlà chắp vá, vì vậy chúng ta nên vui mừng vì chúng ta không cần một lớp như vậy trong C ++.
bobobobo

55
@bobobobo chuỗi bất biến có những lợi ích khác, ngựa của nó cho các khóa học
jk.

8
Không ghép nối chuỗi đơn giản tạo một đối tượng mới, vậy vấn đề tương tự như với tính bất biến trong Java? Xét tất cả các biến là các chuỗi trong ví dụ sau: a = b + c + d + e + f; Không phải nó sẽ gọi toán tử + trên b và c, rồi toán tử + trên kết quả và d, v.v.?
Serge Rogatch

9
Đợi một người, lớp chuỗi tiêu chuẩn biết cách tự biến đổi nhưng điều đó không có nghĩa là không hiệu quả. Theo như tôi biết thì std :: string không thể đơn giản mở rộng kích thước của char bên trong *. Điều đó có nghĩa là biến đổi nó theo cách đòi hỏi nhiều ký tự hơn đòi hỏi phải phân bổ lại và sao chép. Nó không khác gì một vectơ ký tự và chắc chắn tốt hơn là dành chỗ cho không gian bạn cần trong trường hợp đó.
Trygve Skogsholm

7
@TrygveSkogsholm - nó không khác gì một vectơ ký tự, nhưng tất nhiên "dung lượng" của chuỗi có thể lớn hơn kích thước của nó, vì vậy không phải tất cả các phụ lục đều cần một sự phân bổ lại. Nhìn chung, các chuỗi sẽ sử dụng chiến lược tăng trưởng theo cấp số nhân, vì vậy việc bổ sung vẫn được khấu hao vào hoạt động chi phí tuyến tính. Điều đó khác với các Chuỗi bất biến của Java, trong đó mọi thao tác nối thêm cần sao chép tất cả các ký tự trong cả hai Chuỗi sang một chuỗi mới, do đó, một loạt các phụ lục kết thúc như O(n)nói chung.
BeeOnRope

93

Các std::string.appendchức năng không phải là một lựa chọn tốt vì nó không chấp nhận nhiều hình thức dữ liệu. Một thay thế hữu ích hơn là sử dụng std::stringstream; như vậy

#include <sstream>
// ...

std::stringstream ss;

//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";

//convert the stream buffer into a string
std::string str = ss.str();

43

std::string tương đương với C ++: Nó có thể thay đổi.


13

Bạn có thể sử dụng .append () cho các chuỗi đơn giản.

std::string s = "string1";
s.append("string2");

Tôi nghĩ bạn thậm chí có thể làm được:

std::string s = "string1";
s += "string2";

Đối với các hoạt động định dạng của C # StringBuilder, tôi tin rằng snprintf(hoặc sprintfnếu bạn muốn mạo hiểm viết mã lỗi ;-)) vào một mảng ký tự và chuyển đổi lại thành một chuỗi là về tùy chọn duy nhất.


Tuy nhiên, không giống như String.Format của printf hay .NET?
Andy Shellam 17/03/2016

1
Mặc dù hơi khó chịu khi nói rằng họ là cách duy nhất
jk.

2
@jk - chúng là cách duy nhất khi so sánh khả năng định dạng của StringBuilder của .NET, đó là những gì câu hỏi ban đầu được hỏi cụ thể. Tôi đã nói "Tôi tin" vì vậy tôi có thể sai, nhưng bạn có thể chỉ cho tôi cách để có được chức năng của StringBuilder trong C ++ mà không cần sử dụng printf không?
Andy Shellam 17/03/2016

đã cập nhật câu trả lời của tôi để bao gồm một số tùy chọn định dạng thay thế
jk.

6

std::stringtrong C ++ có thể thay đổi, bạn có thể sử dụng nó. Nó có một += operatorvà mộtappend chức năng.

Nếu bạn cần nối thêm dữ liệu số, hãy sử dụng std::to_string hàm.

Nếu bạn muốn linh hoạt hơn nữa ở dạng có thể tuần tự hóa bất kỳ đối tượng nào thành một chuỗi thì hãy sử dụng std::stringstreamlớp. Nhưng bạn sẽ cần triển khai các hàm toán tử phát trực tuyến của riêng mình để nó hoạt động với các lớp tùy chỉnh của riêng bạn.


4

std :: string's + = không hoạt động với const char * (những thứ như "chuỗi cần thêm" dường như), vì vậy chắc chắn sử dụng chuỗi dòng là gần nhất với những gì được yêu cầu - bạn chỉ cần sử dụng << thay vì +


3

Trình tạo chuỗi tiện lợi cho c ++

Giống như nhiều người đã trả lời trước đó, std :: stringstream là phương pháp được lựa chọn. Nó hoạt động tốt và có nhiều tùy chọn chuyển đổi và định dạng. IMO nó có một lỗ hổng khá bất tiện: Bạn không thể sử dụng nó như một lớp lót hoặc như một biểu thức. Bạn luôn phải viết:

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

điều này khá khó chịu, đặc biệt là khi bạn muốn khởi tạo chuỗi trong hàm tạo.

Lý do là, vì a) std :: stringstream không có toán tử chuyển đổi thành std :: string và b) toán tử << () của chuỗi chuỗi không trả về tham chiếu chuỗi, nhưng thay vào đó là tham chiếu std :: Ostream - không thể tính toán thêm dưới dạng luồng chuỗi.

Giải pháp là ghi đè std :: stringstream và cung cấp cho nó các toán tử khớp tốt hơn:

namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
    basic_stringstream() {}

    operator const std::basic_string<T> () const                                { return std::basic_stringstream<T>::str();                     }
    basic_stringstream<T>& operator<<   (bool _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (char _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (signed char _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned char _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (short _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned short _val)                   { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (int _val)                              { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned int _val)                     { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long long _val)                        { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long long _val)               { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (float _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (double _val)                           { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long double _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (void* _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::streambuf* _val)                  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ostream& (*_val)(std::ostream&))  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios& (*_val)(std::ios&))          { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (const T* _val)                         { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
    basic_stringstream<T>& operator<<   (const std::basic_string<T>& _val)      { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};

typedef basic_stringstream<char>        stringstream;
typedef basic_stringstream<wchar_t>     wstringstream;
}

Với điều này, bạn có thể viết những thứ như

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

thậm chí trong các nhà xây dựng.

Tôi phải thú nhận rằng tôi đã không đo hiệu suất, vì tôi chưa sử dụng nó trong môi trường sử dụng nhiều công việc xây dựng chuỗi, nhưng tôi cho rằng nó sẽ không tệ hơn std :: stringstream, vì mọi thứ đã xong thông qua các tham chiếu (ngoại trừ việc chuyển đổi thành chuỗi, nhưng đó cũng là một hoạt động sao chép trong std :: stringstream)


Đây là gọn gàng. Tôi không thấy lý do tại sao std::stringstreamkhông hành xử theo cách này.
einpoklum

1

Bộ chứa Rope có thể có giá trị nếu phải chèn / xóa chuỗi vào vị trí ngẫu nhiên của chuỗi đích hoặc cho một chuỗi char dài. Đây là một ví dụ từ việc triển khai của SGI:

crope r(1000000, 'x');          // crope is rope<char>. wrope is rope<wchar_t>
                                // Builds a rope containing a million 'x's.
                                // Takes much less than a MB, since the
                                // different pieces are shared.
crope r2 = r + "abc" + r;       // concatenation; takes on the order of 100s
                                // of machine instructions; fast
crope r3 = r2.substr(1000000, 3);       // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
                                // correct, but slow; may take a
                                // minute or more.

0

Tôi muốn thêm một cái gì đó mới vì những điều sau đây:

Ở lần đầu tiên tôi thất bại

std::ostringstream 'S operator<<

hiệu quả, nhưng với nhiều suy nghĩ hơn, tôi đã có thể tạo StringBuilder nhanh hơn trong một số trường hợp.

Mỗi lần tôi nối một chuỗi tôi chỉ lưu trữ một tham chiếu đến nó ở đâu đó và tăng bộ đếm của tổng kích thước.

Cách thực sự cuối cùng tôi đã thực hiện nó (Kinh dị!) Là sử dụng bộ đệm mờ (std :: vector <char>):

  • Tiêu đề 1 byte (2 bit để cho biết liệu dữ liệu sau là: chuỗi di chuyển, chuỗi hoặc byte [])
  • 6 bit để nói chiều dài của byte []

cho byte []

  • Tôi lưu trữ trực tiếp byte chuỗi ngắn (để truy cập bộ nhớ tuần tự)

cho các chuỗi di chuyển (chuỗi được nối với std::move)

  • Con trỏ tới một std::stringđối tượng (chúng ta có quyền sở hữu)
  • đặt cờ trong lớp nếu có các byte dành riêng không được sử dụng ở đó

cho chuỗi

  • Con trỏ tới một std::stringđối tượng (không có quyền sở hữu)

Ngoài ra còn có một tối ưu hóa nhỏ, nếu chuỗi được chèn cuối cùng được chuyển vào, nó sẽ kiểm tra các byte được bảo lưu miễn phí nhưng không sử dụng và lưu trữ thêm byte trong đó thay vì sử dụng bộ đệm mờ (điều này là để tiết kiệm bộ nhớ, nó thực sự làm cho nó chậm hơn một chút , có lẽ cũng phụ thuộc vào CPU và hiếm khi thấy các chuỗi có thêm không gian dành riêng)

Điều này cuối cùng đã nhanh hơn một chút std::ostringstreamnhưng nó có một vài nhược điểm:

  • Tôi đã giả sử các loại char chiều dài cố định (vì vậy 1,2 hoặc 4 byte, không tốt cho UTF8), tôi không nói rằng nó sẽ không hoạt động cho UTF8, chỉ là tôi không kiểm tra xem nó có lười không.
  • Tôi đã sử dụng thực hành mã hóa xấu (bộ đệm mờ, dễ làm cho nó không di động, tôi tin rằng tôi là di động bằng cách này)
  • Thiếu tất cả các tính năng của ostringstream
  • Nếu một số chuỗi tham chiếu bị xóa trước khi hợp nhất tất cả các chuỗi: hành vi không xác định.

phần kết luận? sử dụng std::ostringstream

Nó đã sửa chữa nút cổ chai lớn nhất trong khi tốc độ tăng vài% điểm với việc triển khai của tôi không đáng là nhược điểm.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.