2012-12-29 1 views
2

Je voudrais imprimer une série d'entiers sur 2 champs avec '0' comme caractère de remplissage. Je peux le faire mais cela conduit à la duplication de code. Comment dois-je changer le code afin que la duplication du code puisse être prise en compte?Comment imprimer une série d'entiers avec le même formatage?

#include <ctime> 
#include <sstream> 
#include <iomanip> 
#include <iostream> 

using namespace std; 

string timestamp() { 

    time_t now = time(0); 

    tm t = *localtime(&now); 

    ostringstream ss; 

    t.tm_mday = 9; // cheat a little to test it 
    t.tm_hour = 8; 

    ss << (t.tm_year+1900) 
     << setw(2) << setfill('0') << (t.tm_mon+1) // Code duplication 
     << setw(2) << setfill('0') << t.tm_mday 
     << setw(2) << setfill('0') << t.tm_hour 
     << setw(2) << setfill('0') << t.tm_min 
     << setw(2) << setfill('0') << t.tm_sec; 

    return ss.str(); 
} 

int main() { 

    cout << timestamp() << endl; 

    return 0; 
} 

J'ai essayé

std::ostream& operator<<(std::ostream& s, int i) { 

    return s << std::setw(2) << std::setfill('0') << i; 
} 

mais cela n'a pas fonctionné, les appels sont operator<< ambigous.


EDIT Je suis 4 réponses génial et j'ai choisi celui qui est peut-être le plus simple et le plus générique (qui est, ne suppose pas que nous avons affaire à horodatages). Pour le problème réel, je vais probablement utiliser std::put_time ou strftime cependant.

+0

Création d'une nouvelle 'ostream & operator <<' pour 'int' est une très mauvaise idée. :) –

+0

@LightnessRacesinOrbit Oui, je l'ai compris aussi. Des suggestions quoi faire à la place? – Ali

+1

si vous essayez de mettre en forme la date/l'heure, vous pouvez vouloir vérifier ceci: http://en.cppreference.com/w/cpp/io/manip/put_time – zahir

Répondre

3

Vous avez besoin d'un proxy pour votre flux de chaîne comme ceci:

struct stream{ 
    std::ostringstream ss; 
    stream& operator<<(int i){ 
     ss << std::setw(2) << std::setfill('0') << i; 
     return *this; // See Note below 
    } 
} ss; 

Ensuite, votre code de formatage sera juste ceci:

ss << (t.tm_year+1900) 
    << (t.tm_mon+1) 
    << t.tm_mday 
    << t.tm_hour 
    << t.tm_min 
    << t.tm_sec; 

return ss.ss.str(); 

ps. Notez le format général de mon stream :: opérateur < <() qui fait son travail en premier, puis retourne quelque chose.

+1

Upvoted, merci! Je ne suis pas certain de comprendre votre note. Oui, vous devez renvoyer un objet semblable à un flux afin que le 'operator <<' suivant puisse être appelé. Ma tentative a également retourné quelque chose, à savoir 'std :: ostream'. – Ali

+0

@Ali: À savoir 's' :) –

0

operator<<(std::ostream& s, int i) est "ambigu" car une telle fonction existe déjà.

Tout ce que vous avez à faire est de donner à cette fonction une signature qui ne soit pas en conflit.

+0

Pourriez-vous donner un exemple de travail, de préférence une fonction d'opérateur? – Ali

+0

Et comment comptez-vous faire cela, Drew? –

3

La solution «évidente» consiste à utiliser un manipulateur pour installer une facette personnalisée std::num_put<char> qui formate simplement int s comme vous le souhaitez.

La déclaration ci-dessus peut être un peu énigmatique bien qu'elle décrive entièrement la solution. Voici le code pour implémenter la logique. Le premier ingrédient est un std::num_put<char>facette qui est juste une classe dérivée de std::num_put<char> et remplaçant une de ses virtual fonctions. La facette utilisée est une facette de filtrage qui regarde un drapeau stocké avec le flux (en utilisant iword()) pour déterminer s'il doit changer le comportement ou non. Voici le code:

class num_put 
    : public std::num_put<char> 
{ 
    std::locale loc_; 
    static int index() { 
     static int rc(std::ios_base::xalloc()); 
     return rc; 
    } 
    friend std::ostream& twodigits(std::ostream&); 
    friend std::ostream& notwodigits(std::ostream&); 

public: 
    num_put(std::locale loc): loc_(loc) {} 
    iter_type do_put(iter_type to, std::ios_base& fmt, 
        char fill, long value) const { 
     if (fmt.iword(index())) { 
      fmt.width(2); 
      return std::use_facet<std::num_put<char> >(this->loc_) 
       .put(to, fmt, '0', value); 
     } 
     else { 
      return std::use_facet<std::num_put<char> >(this->loc_) 
       .put(to, fmt, fill, value); 
     } 
    } 
}; 

La partie principale est la fonction de membre do_put() qui décide comment la valeur doit être formatée: Si le drapeau dans fmt.iword(index()) est non nul, il fixe la largeur de 2 et appelle la mise en forme fonctionne avec un caractère de remplissage de 0. La largeur va être réinitialisée de toute façon et le caractère de remplissage n'est pas stocké avec le flux, c'est-à-dire qu'il n'y a aucun besoin de nettoyage.

Normalement, le code devrait probablement vivre dans une unité de traduction séparée et il ne serait pas déclaré dans un en-tête. Les seules fonctions réellement déclarées dans un en-tête sont twodigits() et notwodigits() qui sont faites friend dans ce cas pour donner accès à la fonction membre index(). La fonction membre index() alloue juste un index utilisable avec std::ios_base::iword() lorsqu'il est appelé l'heure et il retourne juste cet index.Les manipulateurs twodigits() et notwodigits() définissent principalement cet index. Si la num_put facette est pas installé pour le flux twodigits() installe également la facette:

std::ostream& twodigits(std::ostream& out) 
{ 
    if (!dynamic_cast<num_put const*>(
      &std::use_facet<std::num_put<char> >(out.getloc()))) { 
     out.imbue(std::locale(out.getloc(), new num_put(out.getloc()))); 
    } 
    out.iword(num_put::index()) = true; 
    return out; 
} 

std::ostream& notwodigits(std::ostream& out) 
{ 
    out.iword(num_put::index()) = false; 
    return out; 
} 

Le manipulateur twodigits() alloue la num_put facette en utilisant new num_put(out.getloc()). Il ne nécessite aucun nettoyage car l'installation d'une facette dans un objet std::locale effectue le nettoyage nécessaire. Le std::locale d'origine du flux est accessible en utilisant out.getloc(). Il est changé par la facette. En théorie, le notwodigits peut restaurer l'original std::locale au lieu d'utiliser un indicateur. Cependant, imbue() peut être une opération relativement coûteuse et l'utilisation d'un drapeau devrait être beaucoup moins cher. Bien sûr, s'il y a beaucoup de drapeaux de mise en forme similaires, les choses peuvent devenir différentes ...

Pour démontrer l'utilisation des manipulateurs, il existe un programme de test simple ci-dessous. Il met en place le formatage drapeau twodigits deux fois pour vérifier que la facette est créé uniquement une fois (ce serait un peu ridicule de créer une chaîne de std::locale s pour passer à travers la mise en forme:

int main() 
{ 
    std::cout << "some-int='" << 1 << "' " 
       << twodigits << '\n' 
       << "two-digits1='" << 1 << "' " 
       << "two-digits2='" << 2 << "' " 
       << "two-digits3='" << 3 << "' " 
       << notwodigits << '\n' 
       << "some-int='" << 1 << "' " 
       << twodigits << '\n' 
       << "two-digits4='" << 4 << "' " 
       << '\n'; 
} 
+0

Pourriez-vous s'il vous plaît développer là-dessus? Ce n'est pas si "évident" pour moi :( – Ali

+0

@Ali: J'ai tapé la réponse sur un téléphone mobile, je suis en train de lier le code en ce moment ... –

+0

Désolé, je ne savais pas. – Ali

1

Pour une sortie pratique mise en forme vous pouvez utiliser boost::format() avec sprintf options de formatage -comme:

#include <boost/format.hpp> 
#include <iostream> 

int main() { 
    int i1 = 1, i2 = 10, i3 = 100; 
    std::cout << boost::format("%03i %03i %03i\n") % i1 % i2 % i3; 
    // output is: 001 010 100 
} 

double emploi peu de code, les efforts de mise en œuvre est marginal supplémentaire


.

Si tout ce que vous voulez faire est la sortie formatage de votre timestamp, vous devez évidemment utiliser strftime(). C'est ce qu'il a fait pour:

#include <ctime> 
#include <iostream> 

std::string timestamp() { 
    char buf[20]; 
    const char fmt[] = "%Y%m%d%H%M%S"; 
    time_t now = time(0); 
    strftime(buf, sizeof(buf), fmt, localtime(&now)); 
    return buf; 
} 

int main() { 
    std::cout << timestamp() << std::endl; 
} 
+0

Merci, mais vous n'avez pas besoin de boost pour ça: 'snprintf (tampon, 15, "% d% 02d% 02d% 02d% 02d% 02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mjour, t.tm_heure, t.tm_min, t.tm_sec); ' Personnellement, je n'aime pas la mise en forme de style C – Ali

+0

Upvoted! Je ne savais pas que 'strftime' faisait partie du standard, je le savais mais je pensais que c'était seulement dans POSIX. – Ali

Questions connexes