คุณสร้างสตริง std :: ด้วย null ฝังได้อย่างไร


89

หากฉันต้องการสร้าง std :: string ด้วยบรรทัดเช่น:

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

โดยที่ฉันต้องการให้มีอักขระสามตัวในสตริงผลลัพธ์ (a, null, b) ฉันจะได้เพียงตัวเดียว ไวยากรณ์ที่เหมาะสมคืออะไร?


4
คุณจะต้องระวังเรื่องนี้ หากคุณแทนที่ 'b' ด้วยอักขระตัวเลขใด ๆ คุณจะสร้างสตริงที่ไม่ถูกต้อง ดู: stackoverflow.com/questions/10220401/…
David Stone

คำตอบ:


129

ตั้งแต่ C ++ 14

เราสามารถสร้างตัวอักษรได้ 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";
}

ก่อน C ++ 14

ปัญหาคือตัวstd::stringสร้างที่const char*สมมติว่าอินพุตเป็นสตริง C สตริง C ถูก\0ยกเลิกและการแยกวิเคราะห์จะหยุดเมื่อถึง\0อักขระ

เพื่อชดเชยสิ่งนี้คุณต้องใช้ตัวสร้างที่สร้างสตริงจากอาร์เรย์ถ่าน (ไม่ใช่ C-String) สิ่งนี้ใช้สองพารามิเตอร์ - ตัวชี้ไปยังอาร์เรย์และความยาว:

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.

หมายเหตุ: C ++ std::stringจะไม่ \0 -terminated (ตามข้อเสนอแนะในการโพสต์อื่น ๆ ) อย่างไรก็ตามคุณสามารถดึงตัวชี้ไปยังบัฟเฟอร์ภายในที่มี C-String c_str()กับวิธีการ

ตรวจสอบคำตอบของ Doug Tด้านล่างเกี่ยวกับการใช้ไฟล์vector<char>.

ตรวจสอบRiaDสำหรับโซลูชัน C ++ 14


8
อัปเดต: เมื่อสตริง c ++ 11 สิ้นสุดด้วย null ดังที่กล่าวไว้ท่าของโลกิยังคงใช้ได้
matthewaveryusa

14
@mna: พวกมันถูกยกเลิกด้วย null ในแง่ของการจัดเก็บ แต่ไม่ใช่ในแง่ที่ว่าพวกมันถูกยกเลิกด้วยค่าว่างด้วยการสิ้นสุดค่าว่างที่มีความหมาย (เช่นด้วยความหมายที่กำหนดความยาวสตริง) ซึ่งเป็นความหมายปกติของคำศัพท์
Lightness Races ใน Orbit

อธิบายได้ดี ขอขอบคุณ.
Joma

22

หากคุณกำลังทำการปรับแต่งเช่นเดียวกับที่คุณทำกับสตริงสไตล์ c (อาร์เรย์ของอักขระ) ให้พิจารณาใช้

std::vector<char>

คุณมีอิสระมากขึ้นในการปฏิบัติเหมือนอาร์เรย์ในลักษณะเดียวกับที่คุณปฏิบัติต่อ c-string คุณสามารถใช้ copy () เพื่อคัดลอกลงในสตริง:

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

และคุณสามารถใช้มันได้ในหลาย ๆ ที่เดียวกันคุณสามารถใช้ c-strings ได้

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

อย่างไรก็ตามคุณประสบปัญหาเช่นเดียวกับ c-strings คุณอาจลืมเทอร์มินัลว่างของคุณหรือเขียนผ่านพื้นที่ที่จัดสรรไว้


ถ้าคุณบอกว่าพยายามเข้ารหัสไบต์เป็นสตริง (grpc bytes ถูกเก็บเป็นสตริง) ให้ใช้วิธีการเวกเตอร์ตามที่ระบุในคำตอบ ไม่ใช่วิธีปกติ (ดูด้านล่าง) ซึ่งจะไม่สร้างสตริงทั้งหมด 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

ฉันไม่รู้ว่าทำไมคุณถึงต้องการทำสิ่งนั้น แต่ลองทำสิ่งนี้:

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

1
คุณกังวลอะไรในการทำเช่นนี้ คุณกำลังตั้งคำถามว่าต้องจัดเก็บ "a \ 0b" หรือไม่? หรือตั้งคำถามเกี่ยวกับการใช้ std :: string สำหรับการจัดเก็บดังกล่าว? ถ้าเป็นอย่างหลังมีอะไรแนะนำเป็นทางเลือก
Anthony Cramp

3
@Constantin คุณกำลังทำอะไรผิดพลาดหากคุณจัดเก็บข้อมูลไบนารีเป็นสตริง นั่นคือสิ่งที่vector<unsigned char>หรือunsigned char *ถูกคิดค้นขึ้นสำหรับ
Mahmoud Al-Qudsi

2
ฉันเจอสิ่งนี้ในขณะที่พยายามเรียนรู้เพิ่มเติมเกี่ยวกับความปลอดภัยของสตริง ฉันต้องการทดสอบโค้ดของฉันเพื่อให้แน่ใจว่ายังใช้งานได้แม้ว่าจะอ่านอักขระว่างในขณะที่อ่านจากไฟล์ / เครือข่ายสิ่งที่คาดว่าจะเป็นข้อมูลที่เป็นข้อความ ฉันใช้std::stringเพื่อระบุว่าข้อมูลควรได้รับการพิจารณาว่าเป็นข้อความธรรมดา แต่ฉันกำลังดำเนินการแฮชอยู่และฉันต้องการให้แน่ใจว่าทุกอย่างยังคงใช้งานได้โดยมีอักขระว่างที่เกี่ยวข้อง ดูเหมือนว่าการใช้สตริงลิเทอรัลที่ถูกต้องกับอักขระ null ที่ฝังไว้
David Stone

3
@DuckMaestro ไม่นั่นไม่จริง \0ไบต์ในสตริง UTF-8 เท่านั้นที่สามารถจะ NUL อักขระที่เข้ารหัสแบบหลายไบต์จะไม่มี - \0หรืออักขระ ASCII อื่น ๆ สำหรับกรณีนั้น
John Kugelman

1
ฉันเจอสิ่งนี้เมื่อพยายามกระตุ้นอัลกอริทึมในกรณีทดสอบ มีเหตุผลที่ถูกต้อง แม้ว่าจะมีน้อย
namezero

12

ความสามารถใหม่ใดที่ผู้ใช้กำหนดเองเพิ่มให้กับ C ++ นำเสนอคำตอบที่สวยงาม: กำหนด

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

จากนั้นคุณสามารถสร้างสตริงของคุณด้วยวิธีนี้:

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

หรือแม้แต่:

auto my_string = "a\0b"_s;

มีวิธี "แบบเก่า":

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

จากนั้นคุณสามารถกำหนด

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


5

คุณจะต้องระวังเรื่องนี้ หากคุณแทนที่ 'b' ด้วยอักขระตัวเลขใด ๆ คุณจะสร้างสตริงผิดโดยไม่ใช้วิธีการส่วนใหญ่ ดู: กฎสำหรับ C ++ อักษรของสตริงหนีตัวอักษร

ตัวอย่างเช่นฉันทิ้งตัวอย่างข้อมูลที่ดูไร้เดียงสานี้ลงกลางโปรแกรม

// 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";

นี่คือผลลัพธ์ของโปรแกรมนี้สำหรับฉัน:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

นั่นเป็นคำสั่งพิมพ์ครั้งแรกของฉันสองครั้งอักขระที่ไม่ได้พิมพ์หลายตัวตามด้วยขึ้นบรรทัดใหม่ตามด้วยบางสิ่งในหน่วยความจำภายในซึ่งฉันเพิ่งเขียนทับ (แล้วพิมพ์แสดงว่ามีการเขียนทับ) ที่แย่ที่สุดคือรวบรวมสิ่งนี้ด้วยคำเตือน gcc อย่างละเอียดและละเอียดทำให้ฉันไม่มีข้อบ่งชี้ว่ามีบางอย่างผิดปกติและการเรียกใช้โปรแกรมผ่าน valgrind ไม่ได้บ่นเกี่ยวกับรูปแบบการเข้าถึงหน่วยความจำที่ไม่เหมาะสม กล่าวอีกนัยหนึ่งก็คือเครื่องมือที่ทันสมัยไม่สามารถตรวจจับได้อย่างสมบูรณ์

คุณจะได้รับปัญหาเดียวกันนี้ด้วยวิธีที่ง่ายกว่ามาก std::string("0", 100);แต่ตัวอย่างข้างต้นค่อนข้างยุ่งยากกว่าเล็กน้อยและทำให้ยากที่จะดูว่ามีอะไรผิดปกติ

โชคดีที่ C ++ 11 ช่วยให้เราแก้ปัญหาได้ดีโดยใช้ไวยากรณ์รายการตัวเริ่มต้น วิธีนี้ช่วยให้คุณไม่ต้องระบุจำนวนอักขระ (ซึ่งตามที่ฉันได้แสดงไว้ข้างต้นคุณสามารถทำได้ไม่ถูกต้อง) และหลีกเลี่ยงการรวมตัวเลขที่หลีกเลี่ยง std::string str({'a', '\0', 'b'})ปลอดภัยสำหรับเนื้อหาสตริงต่างจากเวอร์ชันที่ใช้อาร์เรย์charและขนาด


2
ในการเตรียมตัวสำหรับโพสต์นี้ฉันได้ส่งรายงานข้อผิดพลาดไปยัง gcc ด้วยความหวังว่าพวกเขาจะเพิ่มคำเตือนเพื่อทำให้สิ่งนี้ปลอดภัยขึ้นเล็กน้อย: gcc.gnu.org/bugzilla/show_bug.cgi?id=54924
David Stone

4

ใน C ++ 14 ตอนนี้คุณสามารถใช้ตัวอักษรได้แล้ว

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

1
และบรรทัดที่ 2 สามารถเขียนได้อีกทางหนึ่งคือ imho ที่ดีกว่าเช่นauto s{"a\0b"s};
underscore_d

คำตอบที่ดีขอบคุณ
Joma


1

คำตอบของ anonym นั้นยอดเยี่ยม แต่ก็มีโซลูชันที่ไม่ใช่มาโครใน C ++ 98 เช่นกัน:

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

ด้วยฟังก์ชันนี้RawString(/* literal */)จะสร้างสตริงเดียวกันกับ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;

นอกจากนี้ยังมีปัญหากับมาโคร: นิพจน์ไม่ได้เป็นจริงstd::stringตามที่เขียนดังนั้นจึงไม่สามารถใช้งานได้เช่นสำหรับการกำหนดค่าเริ่มต้นอย่างง่าย:

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

... ดังนั้นจึงควรใช้:

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

เห็นได้ชัดว่าคุณควรใช้เพียงวิธีเดียวหรือวิธีอื่นในโครงการของคุณและเรียกมันว่าอะไรก็ได้ที่คุณคิดว่าเหมาะสม


-5

ฉันรู้ว่าคำถามนี้ถูกถามมานานแล้ว แต่สำหรับใครก็ตามที่มีปัญหาคล้าย ๆ กันอาจสนใจโค้ดต่อไปนี้

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

คำตอบนี้เฉพาะเจาะจงเกินไปสำหรับแพลตฟอร์ม Microsoft และไม่ได้ตอบคำถามเดิม (ซึ่งถามเกี่ยวกับ std :: string)
June Rhodes

-8

การใช้งาน std :: strings เกือบทั้งหมดถูกยกเลิกด้วย null ดังนั้นคุณอาจไม่ควรทำเช่นนี้ โปรดทราบว่าจริงๆแล้ว "a \ 0b" มีความยาวสี่อักขระเนื่องจากตัวบอกเลิกโมฆะอัตโนมัติ (a, null, b, null) หากคุณต้องการทำสิ่งนี้จริงๆและทำลายสัญญาของ std :: string คุณสามารถทำได้:

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

แต่ถ้าคุณทำเพื่อนของคุณจะหัวเราะเยาะคุณคุณจะไม่มีวันพบกับความสุขที่แท้จริง


1
std :: string ไม่จำเป็นต้องถูกยกเลิกเป็น NULL
Martin York

2
ไม่จำเป็นต้องใช้ แต่ในการใช้งานเกือบทั้งหมดอาจเป็นเพราะความต้องการตัวเข้าถึง c_str () เพื่อให้คุณมีค่าเทียบเท่าที่สิ้นสุดด้วย null
Jurney

2
เพื่อความมีประสิทธิภาพอักขระ null อาจถูกเก็บไว้ที่ด้านหลังของบัฟเฟอร์ข้อมูล แต่ไม่มีการดำเนินการใด ๆ (เช่นวิธีการ) บนสตริงที่ใช้ความรู้นี้หรือได้รับผลกระทบจากสตริงที่มีอักขระ NULL อักขระ NULL จะถูกจัดการในลักษณะเดียวกับอักขระอื่น ๆ ทุกประการ
Martin York

นี่คือเหตุผลที่มันตลกมากที่ string เป็น std :: - พฤติกรรมของมันไม่ได้ถูกกำหนดบนแพลตฟอร์มใด ๆ

ฉันหวังว่า user595447 จะยังอยู่ที่นี่เพื่อที่ฉันจะได้ถามพวกเขาว่าพวกเขาคิดว่าพวกเขากำลังพูดถึงอะไรบนโลกนี้
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.