Làm thế nào để bạn xây dựng một chuỗi std :: với một null được nhúng?


88

Nếu tôi muốn tạo một chuỗi std :: với một dòng như sau:

std::string my_string("a\0b");

Trong trường hợp tôi muốn có ba ký tự trong chuỗi kết quả (a, null, b), tôi chỉ nhận được một. Cú pháp thích hợp là gì?


4
Bạn sẽ phải cẩn thận với điều này. Nếu bạn thay thế 'b' bằng bất kỳ ký tự số nào, bạn sẽ âm thầm tạo chuỗi sai. Xem: stackoverflow.com/questions/10220401/…
David Stone,

Câu trả lời:


128

Kể từ khi C ++ 14

chúng tôi đã có thể tạo ra std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Trước C ++ 14

Vấn đề là hàm std::stringtạo nhận một const char*giả sử đầu vào là một chuỗi C. Chuỗi C được \0kết thúc và do đó quá trình phân tích cú pháp dừng lại khi nó đến \0ký tự.

Để bù đắp điều này, bạn cần sử dụng hàm tạo xây dựng chuỗi từ một mảng char (không phải chuỗi C). Điều này nhận hai tham số - một con trỏ đến mảng và một độ dài:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Lưu ý: C ++ std::stringKHÔNG \0 -terminated (như đề xuất trong bài viết khác). Tuy nhiên, bạn có thể trích xuất một con trỏ đến bộ đệm bên trong có chứa C-String bằng phương thức này c_str().

Ngoài ra, hãy xem câu trả lời của Doug T dưới đây về việc sử dụng a vector<char>.

Ngoài ra, hãy xem RiaD để biết giải pháp C ++ 14.


6
cập nhật: kể từ c ++ 11 chuỗi được kết thúc bằng null. Điều đó đang được nói, bài đăng của Loki vẫn có giá trị.
matthewaveryusa

14
@mna: Chúng được kết thúc bằng null về mặt lưu trữ, nhưng không phải theo nghĩa là chúng được kết thúc bằng null với phần cuối là null có ý nghĩa (tức là với ngữ nghĩa xác định độ dài chuỗi), đó là ý nghĩa thông thường của thuật ngữ.
Các cuộc đua ánh sáng trong quỹ đạo vào

Giải thích tốt. Cảm ơn bạn.
Joma

22

Nếu bạn đang thực hiện thao tác như bạn làm với chuỗi kiểu c (mảng ký tự), hãy xem xét sử dụng

std::vector<char>

Bạn có nhiều quyền tự do hơn để xử lý nó giống như một mảng giống như cách bạn xử lý một chuỗi c. Bạn có thể sử dụng copy () để sao chép vào một chuỗi:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

và bạn có thể sử dụng nó ở nhiều nơi giống nhau, bạn có thể sử dụng c-string

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Tuy nhiên, đương nhiên, bạn gặp phải những vấn đề tương tự như c-string. Bạn có thể quên thiết bị đầu cuối rỗng của mình hoặc ghi quá không gian được cấp phát.


Nếu bạn nói đang cố gắng mã hóa byte thành chuỗi (byte grpc được lưu trữ dưới dạng chuỗi), hãy sử dụng phương thức vectơ như được chỉ định trong câu trả lời; không theo cách thông thường (xem dưới đây) mà sẽ KHÔNG xây dựng toàn bộ chuỗi byte *bytes = new byte[dataSize]; std::memcpy(bytes, image.data, dataSize * sizeof(byte)); std::string test(reinterpret_cast<char *>(bytes)); std::cout << "Encoded String length " << test.length() << std::endl;
Alex Punnen

13

Tôi không biết tại sao bạn muốn làm điều đó, nhưng hãy thử điều này:

std::string my_string("a\0b", 3);

1
Mối quan tâm của bạn khi làm việc này là gì? Bạn đang đặt câu hỏi về nhu cầu lưu trữ "a \ 0b" bao giờ? hoặc đặt câu hỏi về việc sử dụng chuỗi std :: để lưu trữ như vậy? Nếu sau này, bạn đề xuất điều gì làm giải pháp thay thế?
Anthony Cramp 3/10/08

3
@Constantin thì bạn đang làm sai nếu bạn đang lưu trữ dữ liệu nhị phân dưới dạng một chuỗi. Đó là những gì vector<unsigned char>hoặc unsigned char *được phát minh ra để làm gì.
Mahmoud Al-Qudsi

2
Tôi đã gặp điều này khi cố gắng tìm hiểu thêm về bảo mật của chuỗi. Tôi muốn kiểm tra mã của mình để đảm bảo rằng nó vẫn hoạt động ngay cả khi nó đọc một ký tự rỗng trong khi đọc từ một tệp / mạng mà nó mong đợi là dữ liệu văn bản. Tôi sử dụng std::stringđể chỉ ra rằng dữ liệu nên được coi là văn bản thuần túy, nhưng tôi đang thực hiện một số công việc băm và tôi muốn đảm bảo rằng mọi thứ vẫn hoạt động với các ký tự rỗng có liên quan. Điều đó có vẻ giống như việc sử dụng hợp lệ một chuỗi ký tự có ký tự null được nhúng.
David Stone,

3
@DuckMaestro Không, điều đó không đúng. Một \0byte trong chuỗi UTF-8 chỉ có thể là NUL. Một ký tự được mã hóa nhiều byte sẽ không bao giờ chứa - \0với bất kỳ ký tự ASCII nào khác cho vấn đề đó.
John Kugelman

1
Tôi đã bắt gặp điều này khi cố gắng kích hoạt một thuật toán trong một trường hợp thử nghiệm. Vì vậy, có những lý do xác đáng; mặc dù ít.
namezero

12

Những khả năng mới nào mà các ký tự do người dùng xác định thêm vào C ++? trình bày một câu trả lời thanh lịch: Xác định

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

thì bạn có thể tạo chuỗi của mình theo cách này:

std::string my_string("a\0b"_s);

hoặc thậm chí như vậy:

auto my_string = "a\0b"_s;

Có một cách "kiểu cũ":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

sau đó bạn có thể xác định

std::string my_string(S("a\0b"));

8

Sau đây sẽ hoạt động ...

std::string s;
s.push_back('a');
s.push_back('\0');
s.push_back('b');

Bạn phải sử dụng dấu ngoặc đơn bên trong dấu ngoặc vuông.
jk.

5

Bạn sẽ phải cẩn thận với điều này. Nếu bạn thay thế 'b' bằng bất kỳ ký tự số nào, bạn sẽ âm thầm tạo chuỗi sai bằng cách sử dụng hầu hết các phương pháp. Hãy xem: Quy tắc cho ký tự thoát chuỗi ký tự C ++ .

Ví dụ: tôi đã bỏ đoạn mã trông có vẻ ngây thơ này vào giữa chương trình

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Đây là những gì chương trình này xuất ra cho tôi:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

Đó là câu lệnh in đầu tiên của tôi hai lần, một số ký tự không in được, tiếp theo là một dòng mới, tiếp theo là một thứ gì đó trong bộ nhớ trong mà tôi vừa ghi đè (và sau đó in ra, cho thấy rằng nó đã bị ghi đè). Tệ nhất, ngay cả việc biên dịch điều này với các cảnh báo gcc chi tiết và kỹ lưỡng cũng không cho tôi dấu hiệu nào cho thấy có điều gì đó không ổn và việc chạy chương trình thông qua valgrind cũng không phàn nàn về bất kỳ kiểu truy cập bộ nhớ không phù hợp nào. Nói cách khác, nó hoàn toàn không thể bị phát hiện bởi các công cụ hiện đại.

Bạn có thể gặp phải vấn đề tương tự với cách đơn giản hơn nhiều std::string("0", 100);, nhưng ví dụ trên phức tạp hơn một chút, và do đó khó nhận ra điều gì sai.

May mắn thay, C ++ 11 cung cấp cho chúng ta một giải pháp tốt cho vấn đề bằng cách sử dụng cú pháp danh sách trình khởi tạo. Điều này giúp bạn không phải chỉ định số ký tự (như tôi đã trình bày ở trên, bạn có thể làm sai) và tránh kết hợp các số thoát. std::string str({'a', '\0', 'b'})an toàn cho bất kỳ nội dung chuỗi nào, không giống như các phiên bản có một mảng charvà một kích thước.


2
Là một phần trong quá trình chuẩn bị cho bài đăng này, tôi đã gửi báo cáo lỗi tới gcc với hy vọng rằng họ sẽ thêm cảnh báo để giúp việc này an toàn hơn một chút: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone

4

Trong C ++ 14 bây giờ bạn có thể sử dụng các ký tự

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3

1
và dòng thứ 2 cách khác có thể được viết, độc đáo hơn IMHO, nhưauto s{"a\0b"s};
underscore_d

Câu trả lời tốt đẹp Cảm ơn.
Joma

1

Tốt hơn nên sử dụng std :: vector <char> nếu câu hỏi này không chỉ dành cho mục đích giáo dục.


1

Câu trả lời của người nặc danh là tuyệt vời, nhưng cũng có một giải pháp không phải macro trong C ++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Với hàm này, RawString(/* literal */)sẽ tạo ra cùng một chuỗi như S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

Ngoài ra, có một vấn đề với macro: biểu thức không thực sự là một std::stringnhư được viết, và do đó không thể được sử dụng, ví dụ như để gán-khởi tạo đơn giản:

std::string s = S("a\0b"); // ERROR!

... vì vậy nó có thể thích hợp hơn để sử dụng:

#define std::string(s, sizeof s - 1)

Rõ ràng là bạn chỉ nên sử dụng một hoặc giải pháp khác trong dự án của mình và gọi nó là bất cứ thứ gì bạn cho là phù hợp.


-5

Tôi biết câu hỏi này đã được đặt ra từ rất lâu rồi. Nhưng đối với bất kỳ ai đang gặp vấn đề tương tự có thể quan tâm đến đoạn mã sau.

CComBSTR(20,"mystring1\0mystring2\0")

Câu trả lời này quá cụ thể đối với các nền tảng của Microsoft và không giải quyết được câu hỏi ban đầu (được hỏi về std :: string).
Tháng 6 Rhodes

-8

Hầu như tất cả các triển khai của std :: string đều được kết thúc bằng null, vì vậy bạn có thể không nên làm điều này. Lưu ý rằng "a \ 0b" thực sự dài bốn ký tự do dấu chấm hết null tự động (a, null, b, null). Nếu bạn thực sự muốn làm điều này và phá vỡ hợp đồng của std :: string, bạn có thể làm:

std::string s("aab");
s.at(1) = '\0';

nhưng nếu bạn làm vậy, tất cả bạn bè của bạn sẽ cười nhạo bạn, bạn sẽ không bao giờ tìm thấy hạnh phúc thực sự.


1
chuỗi std :: KHÔNG bắt buộc phải được kết thúc NULL.
Martin York

2
Nó không bắt buộc, nhưng trong hầu hết tất cả các triển khai, có thể là do sự cần thiết của trình truy cập c_str () để cung cấp cho bạn giá trị tương đương được kết thúc bằng null.
Jurney

2
Để có hiệu quả, một ký tự null có thể được giữ ở mặt sau của bộ đệm dữ liệu. Nhưng không có hoạt động nào (tức là các phương thức) trên một chuỗi sử dụng kiến ​​thức này hoặc bị ảnh hưởng bởi một chuỗi chứa ký tự NULL. Ký tự NULL sẽ được thao tác giống hệt như bất kỳ ký tự nào khác.
Martin York

Đây là lý do tại sao thật buồn cười khi chuỗi std :: - hành vi của nó không được xác định trên BẤT KỲ nền tảng nào.

Tôi ước rằng user595447 vẫn ở đây để tôi có thể hỏi họ rằng họ nghĩ họ đang nói về cái quái gì trên Trái đất.
underscore_d
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.