Come costruisci una std :: string con un null incorporato?

Se voglio build una std :: string con una linea come:

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

Dove voglio avere tre caratteri nella stringa risultante (a, null, b), ne ottengo solo uno. Qual è la syntax corretta?

Dal momento che C ++ 14

siamo stati in grado di creare letteralmente std::string

 #include  #include  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"; } 

Prima di C ++ 14

Il problema è il costruttore std::string che prende un const char* assume che l'input sia una stringa C. Le stringhe di tipo C sono \0 terminate e quindi l'analisi si interrompe quando raggiunge il carattere \0 .

Per compensare questo, è necessario utilizzare il costruttore che crea la stringa da un array di caratteri (non una stringa C). Questo richiede due parametri: un puntatore all'array e una lunghezza:

 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. 

Nota: C ++ std::string NON \0 -terminated (come suggerito in altri post). Tuttavia, è ansible estrarre un puntatore su un buffer interno che contiene una C-String con il metodo c_str() .

Controlla anche la risposta di Doug T in merito all'uso di un vector .

Controlla anche RiaD per una soluzione C ++ 14.

Se stai facendo una manipolazione come faresti con una stringa in stile c (array di caratteri), considera l’utilizzo

 std::vector 

Hai più libertà di trattarlo come un array nello stesso modo in cui tratteresti una c-string. Puoi usare copy () per copiare in una stringa:

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

e puoi usarlo in molti degli stessi posti in cui puoi usare c-string

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

Naturalmente, tuttavia, soffri degli stessi problemi delle stringhe in c. Potresti dimenticare il tuo terminale null o scrivere oltre lo spazio allocato.

Non ho idea del perché tu voglia fare una cosa del genere, ma prova questo:

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

Quali nuove funzionalità aggiungono letterali definiti dall’utente a C ++? presenta una risposta elegante: Definisci

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

allora puoi creare la tua stringa in questo modo:

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

o anche così:

 auto my_string = "a\0b"_s; 

C’è un modo “vecchio stile”:

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

allora puoi definire

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

Quanto segue funzionerà …

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

Dovrai stare attento con questo. Se sostituisci “b” con qualsiasi carattere numerico, creerai la stringa sbagliata in modo silenzioso utilizzando la maggior parte dei metodi. Vedi: Regole per caratteri di escape di stringhe letterali C ++ .

Ad esempio, ho lasciato cadere questo snippet dall’aspetto innocente nel bel mezzo di un programma

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

Ecco cosa mi offre questo programma:

 Entering loop. Entering loop. vector::_M_emplace_ba QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ 

Quella è stata la mia prima dichiarazione di stampa due volte, diversi caratteri non stampabili, seguita da una nuova riga, seguita da qualcosa nella memoria interna, che ho appena sovrascritto (e poi stampato, mostrando che è stato sovrascritto). Peggio ancora, anche la compilazione di questo con gli avvertimenti gcc accurati e dettagliati non mi ha dato alcuna indicazione di qualcosa che non va, e l'esecuzione del programma tramite valgrind non si è lamentato di eventuali schemi di accesso alla memoria impropri. In altre parole, è completamente inosservabile con strumenti moderni.

È ansible ottenere lo stesso problema con il molto più semplice std::string("0", 100); , ma l'esempio sopra è un po 'più complicato, e quindi più difficile da capire cosa c'è che non va.

Fortunatamente, C ++ 11 ci offre una buona soluzione al problema usando la syntax dell'elenco di inizializzazione. Questo ti evita di dover specificare il numero di caratteri (che, come ho mostrato sopra, puoi fare in modo errato), evitando di combinare i numeri di escape. std::string str({'a', '\0', 'b'}) è sicuro per qualsiasi contenuto di stringa, diversamente dalle versioni che prendono un array di char e una dimensione.

In C ++ 14 ora puoi usare i letterali

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

Meglio usare std :: vector se questa domanda non è solo per scopi educativi.

la risposta di anonym è eccellente, ma c’è anche una soluzione non macro in C ++ 98:

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

Con questa funzione, RawString(/* literal */) produrrà la stessa stringa di 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; 

Inoltre, c'è un problema con la macro: l'espressione non è in realtà una std::string come scritta, e quindi non può essere usata ad esempio per l'inizializzazione semplice dell'assegnazione:

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

... quindi potrebbe essere preferibile usare:

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

Ovviamente dovresti usare l'una o l'altra soluzione nel tuo progetto e chiamarla come ritieni appropriato.

So che è da molto tempo che questa domanda è stata posta. Ma per chiunque abbia un problema simile potrebbe essere interessato al seguente codice.

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

Quasi tutte le implementazioni di std :: string sono terminate da null, quindi probabilmente non dovresti farlo. Si noti che “a \ 0b” è in realtà composto da quattro caratteri a causa del terminatore null automatico (a, null, b, null). Se vuoi davvero fare questo e interrompere il contratto di std :: string, puoi fare:

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

ma se lo fai, tutti i tuoi amici rideranno di te, non troverai mai la vera felicità.