2009-04-29 8 views
0

J'ai passé environ 4 heures hier à essayer de résoudre ce problème dans mon code. J'ai simplifié le problème à l'exemple ci-dessous.Pourquoi std :: ends provoque-t-il l'échec de la comparaison de chaînes?

L'idée est de stocker une chaîne dans un train de chaînes se terminant par std :: ends, puis de la récupérer plus tard et de la comparer à la chaîne d'origine.

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

Comme vous pouvez probablement deviner, le code ci-dessus lors de l'exécution n'imprimera rien. Bien que, s'ils sont imprimés ou regardés dans le débogueur (VS2005), HELLO et hi soient identiques, leur .length() diffère en fait de 1. C'est ce que je devine qui provoque l'opérateur "==" échouer.

Ma question est pourquoi. Je ne comprends pas pourquoi std :: ends est un caractère invisible ajouté à la chaîne hi, rendant hi et HELLO différentes longueurs même si elles ont un contenu identique. De plus, ce caractère invisible ne sera pas coupé avec la réduction de boost. Cependant, si vous utilisez strcmp pour comparer .c_str() des deux chaînes, la comparaison fonctionne correctement.

La raison pour laquelle j'ai utilisé std :: ends en premier lieu est parce que j'ai eu des problèmes dans le passé avec la stringstream retenant les données parasites à la fin du flux. std :: ends a résolu ça pour moi.

+0

D'accord, je comprends la mécanique derrière, mais je n'aime pas la sémantique. Il semble que j'ai deux choix: n'utilisez pas std :: ends et risquez d'avoir des données parasites, ou bien utilisez-le et ajoutez du code personnalisé pour vous débarrasser des caractères NULL supplémentaires. –

+1

Vous devez essayer de concevoir votre code pour connaître les attentes des chaînes. Par exemple, si vous lisez des chaînes à partir d'un périphérique réseau, elles ne sont probablement pas terminées, mais cela dépend de l'API que vous utilisiez, mais Si vous passez des chaînes dans votre application, elles le sont probablement. Ne vous mettez pas dans une situation où vous n'avez aucune idée de ce qu'il y a dans vos données. –

+0

Pourquoi utilisez-vous les extrémités de toute façon? Cela est uniquement utilisé lorsque vous créez une chaîne de style C terminée par un caractère nul à partir de données brutes. Ici, dans votre exemple, ce n'est clairement pas approprié. Vous avez déjà une chaîne C++. –

Répondre

11

std::ends insère un caractère nul dans le flux. Obtenir le contenu en tant que std::string conservera ce caractère nul et créera une chaîne avec ce caractère nul aux positions respectives.

Donc en effet une chaîne std :: peut contenir des caractères nuls incorporés. Les std :: suivants contenu de la chaîne sont différentes:

ABC 
ABC\0 

Un zéro binaire est pas un espace. Mais il n'est pas non plus imprimable, donc vous ne le verrez pas (à moins que votre terminal ne l'affiche spécialement).

La comparaison en utilisant strcmp interprétera le contenu d'un std::string comme une chaîne C lorsque vous réussirez .c_str(). Il dira

Hmm, caractères avant le premier \0 (caractère nul) sont ABC, donc je suppose que la chaîne est ABC

Et donc, il ne verra pas différence entre les deux ci-dessus. Vous rencontrez sans doute cette question:

std::stringstream s; 
s << "hello"; 
s.seekp(0); 
s << "b"; 
assert(s.str() == "b"); // will fail! 

assert échouera, car la séquence que le stringstream utilise encore l'ancien qui contient « bonjour ». Ce que vous avez fait est juste d'écraser le premier personnage. Vous voulez faire ceci:

std::stringstream s; 
s << "hello"; 
s.str(""); // reset the sequence 
s << "b"; 
assert(s.str() == "b"); // will succeed! 

lire également cette réponse: How to reuse an ostringstream

4

std::ends est tout simplement un caractère nul. Traditionnellement, les chaînes en C et C++ sont terminées avec un caractère nul (ascii 0), mais il s'avère que std::string ne nécessite pas vraiment cette chose.Quoi qu'il en soit à l'étape par votre point de code par point, nous voyons quelques choses intéressantes en cours:

int main(int argc, char** argv) 
{ 

La chaîne littérale "hello" est une chaîne terminée par zéro traditionnelle constante. Nous copions cet ensemble dans le std::string BONJOUR.

const std::string HELLO("hello"); 

    std::stringstream testStream; 

Nous mettons maintenant le string BONJOUR (y compris le 0 arrière) dans le stream, suivi d'un second nul qui est mis là par l'appel à std::ends.

testStream << HELLO << std::ends; 

On extrait une copie des choses que nous mettons dans la stream (la chaîne « hello », plus les deux nuls) terminateurs.

std::string hi = testStream.str(); 

Nous comparons ensuite les deux chaînes en utilisant la operator == de la classe std::string. Cet opérateur (probablement) compare la longueur des objets string - y compris le nombre de caractères null de fin. Notez que la classe std::string ne nécessite pas que le tableau de caractères sous-jacent se termine par une valeur nulle. En d'autres termes, il autorise la chaîne à contenir des caractères nuls. Le premier des deux caractères null finaux est donc considéré comme faisant partie de la chaîne hi.

Étant donné que les deux chaînes ont un nombre de zéros fin différent, la comparaison échoue.

if(HELLO == hi) 
    { 
     std::cout << HELLO << "==" << hi << std::endl; 
    } 

    return 0; 
} 

Bien que, si elle est imprimée sur, ou regardé dans le débogueur (VS2005), BONJOUR et salut semblent identiques, leur .length() en fait diffère de 1. C'est ce que je suis deviner provoque l'échec de l'opérateur "==" .

Raison étant, la longueur est différente d'un caractère nul arrière.

Ma question est pourquoi. Je ne comprends pas pourquoi std :: ends est un caractère invisible ajouté à la chaîne hi, ce qui rend salut et bonjour longueurs même si elles ont contenu identique. De plus, ce caractère invisible n'obtient pas le garni de l'assiette boost. Cependant, si vous utilisez strcmp pour comparer .c_str() de les deux chaînes, la comparaison fonctionne correctement .

strcmp est différent de std::string - il est écrit de retour dans les premiers jours où les chaînes ont été résiliés par un nul - alors quand il obtient le premier nul de fuite en hi il cesse de regarder.

La raison pour laquelle je std :: se termine dans la première place est parce que j'ai eu des problèmes dans le passé avec stringstream la conservation des données de déchets à la fin de le flux. std :: ends résolu que pour moi.

Parfois, c'est une bonne idée de comprendre la représentation sous-jacente.

0

Vous ajoutez un char NULL à BONJOUR avec std :: extrémités. Lorsque vous initialisez hi avec str(), vous supprimez le caractère NULL. Les chaînes sont différentes. strcmp ne compare pas std :: strings, il compare char * (c'est une fonction C).

+0

Gotta love StackOverflow - dans le temps qu'il m'a fallu pour écrire une personne de réponse 2 ligne a écrit Guerre et Paix :-) – PowerApp101

+0

Et il semble que je me trompais de toute façon sur la façon dont str() fonctionne. Retour à la planche à dessin pour moi! – PowerApp101

+0

http://steve-yegge.blogspot.com/2008/09/programmings-dirtiest-little-secret.html ;) –

0

std :: extrémités ajoute un terminateur null, (char) '\ 0'. Vous l'utiliseriez avec les classes strtrat obsolètes, pour ajouter le terminateur null. Vous n'en avez pas besoin avec stringstream, et en fait ça bloque les choses, parce que le terminateur null n'est pas "le terminateur null spécial qui termine une chaîne" à stringstream, à stringstream c'est juste un autre caractère, le zeroth personnage. stringstream l'ajoute juste, et cela augmente le nombre de caractères (dans votre cas) à sept, et fait échouer la comparaison avec "bonjour".

+0

Vous n'en avez pas non plus besoin avec 'strstream'. 'string :: c_str()' est toujours correctement terminé par NUL indépendamment de la façon dont la chaîne a été construite. –

0

Je pense avoir une bonne façon de comparer les chaînes est d'utiliser la méthode std::find. Ne pas mélanger les méthodes C et std::string ones!