web-dev-qa-db-fra.com

modification du délimiteur pour cin (c ++)

J'ai redirigé "cin" pour lire à partir d'un flux de fichiers cin.rdbug(inF.rdbug()) Lorsque j'utilise l'opérateur d'extraction, il lit jusqu'à ce qu'il atteigne un caractère d'espace blanc.

Est-il possible d'utiliser un autre délimiteur? J'ai parcouru l'API sur cplusplus.com, mais je n'ai rien trouvé.

42
yotamoo

Il est possible de changer le délimiteur inter-Word pour cin ou tout autre std::istream , en utilisant std::ios_base::imbue pour ajouter un --- ctypefacet personnalisé.

Si vous lisez un fichier dans le style de/etc/passwd, le programme suivant lira chaque :- Word délimité séparément.

#include <locale>
#include <iostream>


struct colon_is_space : std::ctype<char> {
  colon_is_space() : std::ctype<char>(get_table()) {}
  static mask const* get_table()
  {
    static mask rc[table_size];
    rc[':'] = std::ctype_base::space;
    rc['\n'] = std::ctype_base::space;
    return &rc[0];
  }
};

int main() {
  using std::string;
  using std::cin;
  using std::locale;

  cin.imbue(locale(cin.getloc(), new colon_is_space));

  string Word;
  while(cin >> Word) {
    std::cout << Word << "\n";
  }
}
42
Robᵩ

Pour les chaînes, vous pouvez utiliser std::getline surcharges à lire à l'aide d'un délimiteur différent.

Pour l'extraction de nombres, le délimiteur n'est pas vraiment un "espace" pour commencer, mais tout caractère invalide dans un nombre.

20
Ben Voigt

Il s'agit d'une amélioration par rapport à la réponse de Robᵩ , car c'est la bonne (et je suis déçu qu'elle n'ait pas été acceptée.)

Ce que vous devez faire est de modifier le tableau que ctype examine pour décider ce qu'est un délimiteur.

Dans le cas le plus simple, vous pouvez créer le vôtre:

const ctype<char>::mask foo[ctype<char>::table_size] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ctype_base::space};

Sur ma machine '\n' est 10. J'ai défini cet élément du tableau sur la valeur du délimiteur: ctype_base::space. Un ctype initialisé avec foo ne délimiterait que sur '\n' ne pas ' ' ou '\t'.

Maintenant, c'est un problème parce que le tableau passé dans ctype définit plus que ce qu'est un délimiteur, il définit également des lettres, des nombres, des symboles et d'autres ordures nécessaires pour la diffusion en continu. ( Ben Voigt la réponse touche à ce sujet.) Donc ce que nous voulons vraiment faire est modifier un mask, pas en créer un à partir de zéro.

Cela peut être accompli comme ceci:

const auto temp = ctype<char>::classic_table();
vector<ctype<char>::mask> bar(temp, temp + ctype<char>::table_size);

bar[' '] ^= ctype_base::space;
bar['\t'] &= ~(ctype_base::space | ctype_base::cntrl);
bar[':'] |= ctype_base::space;

Un ctype initialisé avec bar délimiterait sur '\n' et ':' mais non ' ' ou '\t'.

Vous allez configurer cin, ou tout autre istream, pour utiliser votre ctype personnalisé comme ceci:

cin.imbue(locale(cin.getloc(), new ctype<char>(data(bar))));

Vous pouvez également basculer entre ctypes et le comportement changera en cours de route:

cin.imbue(locale(cin.getloc(), new ctype<char>(foo)));

Si vous devez revenir au comportement par défaut, procédez comme suit:

cin.imbue(locale(cin.getloc(), new ctype<char>));

Live example

15
Jonathan Mee

Il s'agit d'une amélioration de la réponse de Jon et de l'exemple de cppreference.com . Cela suit donc la même prémisse que les deux, mais les combine avec des délimiteurs paramétrés.

struct delimiter_ctype : std::ctype<char> {
    static const mask* make_table(std::string delims)
    {
        // make a copy of the "C" locale table
        static std::vector<mask> v(classic_table(), classic_table() + table_size);
        for(mask m : v){
            m &= ~space;
        }
        for(char d : delims){
            v[d] |= space;
        }
        return &v[0];
    }
    delimiter_ctype(std::string delims, ::size_t refs = 0) : ctype(make_table(delims), false, refs) {}
};

À votre santé!

0
Josh C