Perché std :: End fa fallire il confronto delle stringhe?
Domanda
Ieri ho passato circa 4 ore a cercare di risolvere questo problema nel mio codice. Ho semplificato il problema nell'esempio seguente.
L'idea è di memorizzare una stringa in una stringa che termina con std :: End, quindi recuperarla in un secondo momento e confrontarla con la stringa originale.
#include <sstream> #include <iostream> #include <string> int main( int argc, char** argv ) { const std::string HELLO( "hello" ); std::stringstream testStream; testStream << HELLO << std::ends; std::string hi = testStream.str(); if( HELLO == hi ) { std::cout << HELLO << "==" << hi << std::endl; } return 0; }
Come probabilmente intuirai, il codice sopra quando eseguito non stamperà nulla.
Anche se, se stampato o guardato nel debugger (VS2005), HELLO e hi sembrano identici, la loro .length () in realtà differisce di 1. Questo è ciò che suppongo stia causando il " == " operatore fallito.
La mia domanda è: perché. Non capisco perché std :: Ends è un carattere invisibile aggiunto alla stringa hi, che rende hi e HELLO lunghezze diverse anche se hanno un contenuto identico. Inoltre, questo personaggio invisibile non viene tagliato con il boost boost. Tuttavia, se si utilizza strcmp per confrontare .c_str () delle due stringhe, il confronto funziona correttamente.
Il motivo per cui ho usato std :: End in primo luogo è perché ho avuto problemi in passato con stringstream che conservava i dati inutili alla fine del flusso. std :: End l'ha risolto per me.
Soluzione
std :: End
inserisce un carattere null nello stream. Ottenere il contenuto come std :: string
manterrà quel carattere nullo e creerà una stringa con quel carattere nullo nelle rispettive posizioni.
Quindi una stringa std :: può contenere caratteri null incorporati. I seguenti contenuti std :: string sono diversi:
ABC
ABC\0
Uno zero binario non è uno spazio bianco. Ma non è nemmeno stampabile, quindi non lo vedrai (a meno che il tuo terminale non lo visualizzi appositamente).
Il confronto con strcmp
interpreterà il contenuto di un std :: string
come una stringa C quando si passa .c_str ()
. Dirà
Hmm, i caratteri prima del primo
\ 0
(terminando il carattere null) sono ABC , quindi presumo che la stringa sia ABC
E così, non vedrà alcuna differenza tra i due sopra. Probabilmente stai riscontrando questo problema:
std::stringstream s;
s << "hello";
s.seekp(0);
s << "b";
assert(s.str() == "b"); // will fail!
L'asserzione fallirà, perché la sequenza che utilizza lo stringstream è ancora quella precedente che contiene "ciao". Quello che hai fatto è solo sovrascrivere il primo personaggio. Vuoi fare questo:
std::stringstream s;
s << "hello";
s.str(""); // reset the sequence
s << "b";
assert(s.str() == "b"); // will succeed!
Leggi anche questa risposta: Come riutilizzare un ostringstream
Altri suggerimenti
std :: End
è semplicemente un carattere nullo. Tradizionalmente, le stringhe in C e C ++ sono terminate con un carattere null (ascii 0), tuttavia risulta che std :: string
non richiede davvero questa cosa. Ad ogni modo, per esaminare il tuo codice punto per punto vediamo alcune cose interessanti che accadono:
int main( int argc, char** argv )
{
La stringa " hello "
letterale di stringa è una costante di stringa con terminazione zero tradizionale. Copiamo tutto questo nel std :: string
CIAO.
const std::string HELLO( "hello" );
std::stringstream testStream;
Ora inseriamo la stringa
CIAO (compreso lo 0 finale) nello stream
, seguito da un secondo null che viene inserito dalla chiamata a std :: estremità
.
testStream << HELLO << std::ends;
Estraiamo una copia delle cose che inseriamo nel stream
(la stringa letterale " hello " ;, più i due terminatori null).
std::string hi = testStream.str();
Confrontiamo quindi le due stringhe utilizzando operator ==
nella classe std :: string
. Questo operatore (probabilmente) confronta la lunghezza degli oggetti string
, incluso il numero di caratteri null finali. Si noti che la classe std :: string
non richiede che l'array di caratteri sottostante termini con un null finale - in altri termini, consente alla stringa di contenere caratteri null, quindi il primo dei due caratteri null finali è trattato come parte della stringa hi
.
Poiché le due stringhe sono diverse nel numero di null finali, il confronto fallisce.
if( HELLO == hi )
{
std::cout << HELLO << "==" << hi << std::endl;
}
return 0;
}
Anche se, se stampato, o guardato nel debugger (VS2005), CIAO e ciao sembrano identici, il loro .length () in il fatto differisce di 1. Ecco cosa sono indovinare sta causando il " == " operatore fallire.
Essendo la ragione, la lunghezza è diversa per un carattere null finale.
La mia domanda è: perché. Io non capire perché std :: End è un carattere invisibile aggiunto alla stringa ciao, rendendo ciao e CIAO diversi lunghezze anche se hanno contenuto identico. Inoltre, questo il personaggio invisibile non ottiene rifinito con rifinitura boost. Tuttavia, se usi strcmp per confrontare .c_str () di le due stringhe, il confronto funziona correttamente.
strcmp
è diverso da std :: string
- è scritto da dietro all'inizio quando le stringhe erano terminate con un valore nullo - quindi quando arriva al primo trascinando null in hi
smette di cercare.
Il motivo per cui ho usato std :: termina in il primo posto è perché ho avuto problemi in passato con stringstream conservazione dei dati inutili alla fine di il flusso. std :: End ha risolto questo problema per me.
A volte è una buona idea capire la rappresentazione sottostante.
Stai aggiungendo un carattere NULL a HELLO con std :: End. Quando si inizializza hi con str () si rimuove il carattere NULL. Le stringhe sono diverse. strcmp non confronta std :: stringhe, confronta char * (è una funzione C).
std :: end aggiunge un terminatore nullo, (char) '\ 0'. Lo useresti con le classi strstream deprecate, per aggiungere il terminatore null.
Non è necessario con stringstream, e in effetti rovina le cose, perché il terminatore null non è lo speciale terminatore null che termina una stringa " stringstream, stringstream è solo un altro personaggio, il personaggio di zeroth. stringstream lo aggiunge e questo aumenta il numero di caratteri (nel tuo caso) a sette, e fa il confronto a "ciao". fallire.
Penso che avere un buon modo per confrontare le stringhe sia usare il metodo std :: find
. Non mischiare i metodi C e std :: stringne
!