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?
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?
Câu trả lời:
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::string
hoặc std::stringstream
nế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::string
hoặ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::string
hoặ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.
scratch
chuỗ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 string
triể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 append
không giúp ích gì vì một khi cơ sở string
lớ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.
str.reserve(1024);
sẽ nhanh hơn thứ này
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
StringBuilder
tồ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 StringBuilder
là 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 ++.
O(n)
nói chung.
Các std::string.append
chứ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();
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 sprintf
nế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.
Vì std::string
trong C ++ có thể thay đổi, bạn có thể sử dụng nó. Nó có một += operator
và 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::stringstream
lớ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.
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ì +
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)
std::stringstream
không hành xử theo cách này.
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.
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>):
cho byte []
cho các chuỗi di chuyển (chuỗi được nối với std::move
)
std::string
đối tượng (chúng ta có quyền sở hữu)cho chuỗi
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::ostringstream
nhưng nó có một vài nhược điểm:
ostringstream
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.
std::ostringstream
.