web-dev-qa-db-fra.com

Comment itérer sur une carte std :: remplie de chaînes en C++

Le problème suivant concerne l'itération sur un tableau associatif de chaînes défini à l'aide de std::map.

-- snip --
class something 
{
//...
   private:
      std::map<std::string, std::string> table;
//...
}

Dans le constructeur, je remplis une table avec des paires de clés de chaîne associées aux données de chaîne. Ailleurs, j'ai une méthode toString qui retourne un objet chaîne contenant toutes les clés et les données associées contenues dans l'objet table (sous forme de clé = format de données).

std::string something::toString() 
{
        std::map<std::string, std::string>::iterator iter;
        std::string* strToReturn = new std::string("");

        for (iter = table.begin(); iter != table.end(); iter++) {
           strToReturn->append(iter->first());
           strToReturn->append('=');
           strToRetunr->append(iter->second());
           //....
        }
       //...
}

Lorsque j'essaie de compiler, j'obtiens l'erreur suivante:

error: "error: no match for call to ‘(std::basic_string<char,
    std::char_traits<char>, std::allocator<char> >) ()’".

Quelqu'un pourrait-il m'expliquer ce qui manque, ce que je fais mal? J'ai seulement discuté d'un problème similaire dans le cas de hash_map où l'utilisateur doit définir une fonction de hachage pour pouvoir utiliser hash_map avec des objets std::string. Pourrait être quelque chose de similaire aussi dans mon cas?

62
crazybyte

Votre principal problème est que vous appelez une méthode appelée first() dans l'itérateur. Ce que vous êtes censé faire est d'utiliser la propriété appelée first:

...append(iter->first) rather than ...append(iter->first())

En ce qui concerne le style, vous ne devriez pas utiliser new pour créer cette chaîne.

std::string something::toString() 
{
        std::map<std::string, std::string>::iterator iter;
        std::string strToReturn; //This is no longer on the heap

        for (iter = table.begin(); iter != table.end(); ++iter) {
           strToReturn.append(iter->first); //Not a method call
           strToReturn.append("=");
           strToReturn.append(iter->second);
           //....
           // Make sure you don't modify table here or the iterators will not work as you expect
        }
        //...
        return strToReturn;
}

edit: facildelembrar a souligné (dans les commentaires) que vous pouvez maintenant réécrire la boucle en C++ moderne 

for (auto& item: table) {
    ...
}
73
Tom Leys
  1. N'écrivez pas une méthode toString(). Ce n'est pas Java. Implémentez l'opérateur de flux pour votre classe.

  2. Préférez utiliser les algorithmes standard plutôt que d'écrire votre propre boucle. Dans cette situation, std::for_each() fournit une interface intéressante à ce que vous voulez faire.

  3. Si vous devez utiliser une boucle mais que vous n'avez pas l'intention de modifier les données, préférez const_iterator à iterator. De cette façon, si vous essayez accidentellement de modifier les valeurs, le compilateur vous en avertira.

Ensuite:

std::ostream& operator<<(std::ostream& str,something const& data)
{
    data.print(str)
    return str;
}

void something::print(std::ostream& str) const
{
    std::for_each(table.begin(),table.end(),PrintData(str));
}

Ensuite, lorsque vous souhaitez l’imprimer, diffusez simplement l’objet:

int main()
{
    something    bob;
    std::cout << bob;
}

Si vous avez réellement besoin d'une représentation sous forme de chaîne de l'objet, vous pouvez alors utiliser lexical_cast.

int main()
{
    something    bob;

    std::string  rope = boost::lexical_cast<std::string>(bob);
}

Les détails à renseigner.

class somthing
{
    typedef std::map<std::string,std::string>    DataMap;
    struct PrintData
    {
         PrintData(std::ostream& str): m_str(str) {}
         void operator()(DataMap::value_type const& data) const
         {
             m_str << data.first << "=" << data.second << "\n";
         }
         private:  std::ostream& m_str;
    };
    DataMap    table;
    public:
        void something::print(std::ostream& str);
};
19
Martin York

Changez vos appels d'appels pour dire

...append(iter->first)

et 

... append(iter->second)

De plus, la ligne

std::string* strToReturn = new std::string("");

alloue une chaîne sur le tas. Si vous souhaitez réellement renvoyer un pointeur sur cette chaîne allouée dynamiquement, le retour doit être remplacé par std :: string *.

Sinon, si vous ne voulez pas vous soucier de la gestion de cet objet sur le tas, changez la déclaration locale en 

std::string strToReturn("");

et changez les appels 'append' pour utiliser la syntaxe de référence ...

strToReturn.append(...)

au lieu de 

strToReturn->append(...)

Sachez que cela va construire la chaîne sur la pile, puis copier dans la variable de retour. Cela a des conséquences sur les performances.

4
Adam Holmberg

iter->first et iter->second sont des variables, vous essayez de les appeler en tant que méthodes.

2
Nick Lewis

Notez que le résultat de la déréférence de std :: map :: iterator est un std :: pair . Les valeurs de first et second ne sont pas des fonctions, mais des variables.

Changement:

iter->first()

à

iter->first

Idem avec iter->second.

2
E.M.

Utilisation:

std::map<std::string, std::string>::const_iterator

au lieu:

std::map<std::string, std::string>::iterator
1
Dzwiedziu-nkg

Une autre optimisation intéressante est le membre c_str () de la STL string classes, qui renvoie une chaîne immuable terminée par un caractère null qui peut être transmise sous la forme d'unLPCTSTR, e. g., à une fonction personnalisée qui attend un LPCTSTR. Bien que je ne sois pas passé par le destructeur pour le confirmer, je soupçonne que la classe string gère la mémoire dans laquelle elle crée la copie.

0
David A. Gray

en c ++ 11, vous pouvez utiliser

for ( auto iter : table ) {
     key=iter->first();
     value=iter->second();
}
0
james