web-dev-qa-db-fra.com

Comment utiliser C++ std :: ostream avec un formatage semblable à printf?

J'apprends le C++. cout est une instance de la classe std::ostream. Comment puis-je imprimer une chaîne formatée avec elle?

Je peux toujours utiliser printf, mais je souhaite en savoir plus sur les méthodes de style C++ pouvant prendre tous les avantages de C++. Je pense que cela devrait être possible avec std::ostream mais je ne trouve pas le moyen approprié.

27
Eonil

La seule chose que vous pouvez faire avec std::ostream directement est la syntaxe bien connue de <<-:

int i = 0;
std::cout << "this is a number: " << i;

Et il existe divers IO manipulateurs qui peuvent être utilisés pour influencer le formatage, le nombre de chiffres, etc. d'entiers, les nombres à virgule flottante, etc.

Cependant, ce n'est pas la même chose que les chaînes formatées de printf. C++ 11 n'inclut aucune fonctionnalité vous permettant d'utiliser le formatage de chaîne de la même manière qu'il est utilisé avec printf (à l'exception de printf lui-même, que vous pouvez bien sûr utiliser en C++ si vous le souhaitez).

En ce qui concerne les bibliothèques fournissant une fonctionnalité de style printf-, il existe boost::format , qui active un code tel que celui-ci (copié à partir du synopsis):

std::cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50;

Notez également qu'il existe une mise en forme de style proposition d'inclusion de printf- dans une version future de la norme. Si cela est accepté, une syntaxe telle que celle ci-dessous peut devenir disponible:

std::cout << std::putf("this is a number: %d\n",i);
29
jogojapan

Pour implémenter printf, on pourrait utiliser les paramètres de modèle c ++ 11:

#include <iostream>
#include <string>

inline std::ostream & mprintf(std::ostream & ostr, const char * fstr) throw()
{
    return ostr << fstr;
}

template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
        const char * fstr, const T & x) throw()
{
    size_t i=0;
    char c = fstr[0];

    while (c != '%')
    {
        if(c == 0) return ostr; // string is finished
        ostr << c;
        c = fstr[++i];
    };
    c = fstr[++i];
    ostr << x;

    if(c==0) return ostr; // 

    // print the rest of the stirng
    ostr << &fstr[++i];
    return ostr;
}


template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr,
        const char * fstr, const T & x, Args... args) throw()
{
    size_t i=0;
    char c = fstr[0];

    while (c != '%')
    {
        if(c == 0) return ostr; // string is finished
        ostr << c;
        c = fstr[++i];
    };
    c = fstr[++i];
    ostr << x;

    if(c==0) return ostr; // string is finished

    return mprintf(ostr, &fstr[++i], args...);
}

int main()
{
    int c = 50*6;
    double a = 34./67.;
    std::string q = "Hello!";

    // put only two arguments
    // the symbol after % does not matter at all
    mprintf(std::cout, "%f + %f = %a \n", c, a);

    // print string object: for real printf one should write q.c_str() 
    mprintf(std::cout, "message: \"%s\". \n", q);

    // the last argument will be ignored
    mprintf(std::cout, "%z + %f\n", (long)a, 12, 544 );

}

Sortie

300 + 2 = %a 
message: "Hello!". 
2 + 12

C'est un code très simple et il peut être amélioré.

1) L'avantage est qu'il utilise << pour imprimer des objets dans le flux, de sorte que vous pouvez définir des arguments arbitraires pouvant être générés via <<.

2) Il ignore le type de l'argument dans la chaîne formatée: after% peut contenir un symbole arbitraire, même un espace. Le flux de sortie décide comment imprimer l'objet correspondant. Il est également compatible avec printf.

3) Un inconvénient est qu’il ne peut pas imprimer le symbole de pourcentage '%', il est nécessaire d’améliorer légèrement le code.

4) Il ne peut pas imprimer de nombres formatés, comme% 4.5f

5) Si le nombre d'arguments est inférieur à celui prédit par la chaîne mise en forme, la fonction affiche simplement le reste de la chaîne.

6) Si le nombre d'arguments est supérieur à celui prédit par la chaîne formatée, les arguments restants sont ignorés.

On peut améliorer le code pour que 2) -6) imite complètement le comportement printf . Cependant, si vous suivez les règles de printf, seuls 3) et 4) doivent essentiellement être corrigés.

3
user3754430

Largeur du champ

La définition de la largeur du champ est très simple. Pour chaque variable, faites-la simplement précéder de "setw (n)". Comme ça:

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
  const int max = 12;
  const int width = 6;
  for(int row = 1; row <= max; row++) {
      for(int col = 1; col <= max; col++) {
          cout << setw(width) << row * col;
      }
      cout << endl;
  }
  return 0;
}

Remarquez comment "setw (n)" contrôle la largeur du champ, donc chaque nombre est imprimé à l’intérieur d’un champ dont la largeur est la même quel que soit le largeur du nombre lui-même.

- De "Didacticiel de programmation/C++" de P. Lutus .

1
Zephyr

Je suggère d'utiliser ostringstream au lieu de ostream Voir l'exemple suivant:

#include <vector>
#include <string>
#include <iostream>
#include "CppUnitTest.h"

#define _CRT_NO_VA_START_VALIDATION

std::string format(const std::string& format, ...)
{
    va_list args;
    va_start(args, format);
    size_t len = std::vsnprintf(NULL, 0, format.c_str(), args);
    va_end(args);
    std::vector<char> vec(len + 1);
    va_start(args, format);
    std::vsnprintf(&vec[0], len + 1, format.c_str(), args);
    va_end(args);
    return &vec[0];
}

exemple d'utilisation: 

std::ostringstream ss;
ss << format("%s => %d", "Version", Version) << std::endl;
Logger::WriteMessage(ss.str().c_str()); // write to unit test output
std::cout << ss.str() << std::endl; // write to standard output
1
serup

Exemple de sortie:

2017-12-20T16:24:47,604144+01:00 Hello, World!

Code (avec l'utilisation de put_printf démontrée dans put_timestamp):

#include <assert.h>
#include <chrono>
#include <iomanip>
#include <iostream>

class put_printf {
    static constexpr size_t failed = std::numeric_limits<size_t>::max(); // for any explicit error handling
    size_t stream_size; // excluding '\0'; on error set to 0 or to "failed"
    char buf_stack[2048+1]; // MAY be any size that fits on the stack (even 0), SHOULD be (just) large enough for most uses (including '\0')
    std::unique_ptr<char[]> buf_heap; // only used if the output doesn't fit in buf_stack
public:
    explicit put_printf(const char *format, ...)
            #if __GNUC__
            __attribute__ ((format (printf, 2, 3))) // most compelling reason for not using a variadic template; parameter 1 is implied "this"
            #endif
            {
        va_list args;
        va_start(args, format);
        const int res = vsnprintf(buf_stack, sizeof(buf_stack), format, args);
        va_end(args);
        if (res < 0) { // easily provoked, e.g., with "%02147483646i\n", i.e., more than INT_MAX-1 significant characters (only observed, no guarantee seen)
            stream_size = failed;
        } else if (res < sizeof(buf_stack)) { // preferred path
            stream_size = res;
        } else { // not artificially constrained
            try {
                const size_t buf_size = static_cast<size_t>(res) + 1; // avoids relying on "res < INT_MAX" (only observed, no guarantee seen)
                buf_heap.reset(new char[buf_size]); // observed to work even beyond INT_MAX=2^32-1 bytes
                va_start(args, format);
                if (vsnprintf(buf_heap.get(), buf_size, format, args) == res) stream_size = res;
                else stream_size = failed; // can't happen
                va_end(args);
            } catch (const std::bad_alloc&) { // insufficient free heap space (or an environment-specific constraint?)
                stream_size = failed;
            }
        }
    }
    friend std::ostream& operator<<(std::ostream& os, const put_printf& self) {
        if (self.stream_size == failed) {
            // (placeholder for any explicit error handling)
            return os;
        } else {
            // using write() rather than operator<<() to avoid a separate scan for '\0' or unintentional truncation at any internal '\0' character
            return os.write((self.buf_heap ? self.buf_heap.get() : self.buf_stack), self.stream_size);
        }
    }
};

class put_timestamp {
    const bool basic = false;
    const bool local = true;
public:
    friend std::ostream& operator<<(std::ostream& os, const put_timestamp& self) {
        const auto now = std::chrono::system_clock::now();
        const std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
        struct tm tm; if ((self.local ? localtime_r(&now_time_t, &tm) : gmtime_r(&now_time_t, &tm)) == nullptr) return os; // TODO: explicit error handling?
        static_assert(4 <= sizeof(int), "");
        const int microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_Epoch() % std::chrono::seconds(1)).count();
        assert(0 <= microseconds && microseconds < 1000000); // TODO: (how) do we know?
        // TODO: doesn't "point" in "decimal_point()" imply "dot"/"full stop"/"period", unlike an obviously neutral term like "mark"/"separator"/"sign"?
        const char decimal_sign = std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point() == '.' ? '.' : ','; // full stop accepted, comma preferred
        // TODO: all well and good for a locale-specific decimal sign, but couldn't the locale also upset microseconds formatting by grouping digits?
        os << std::put_time(&tm, self.basic ? "%Y%m%dT%H%M%S" : "%FT%T") << put_printf("%c%06i", decimal_sign, microseconds);
        if (! self.local) return os << "Z";
        const int tz_minutes = std::abs(static_cast<int>(tm.tm_gmtoff)) / 60;
        return os << put_printf(self.basic ? "%c%02i%02i" : "%c%02i:%02i", 0 <= tm.tm_gmtoff ? '+' : '-', tz_minutes / 60, tz_minutes % 60);
    }
};

int main() {
    // testing decimal sign
    ///std::cout.imbue(std::locale("en_GB"));
    ///std::cout.imbue(std::locale("fr_FR"));

    std::cout << put_timestamp() << " Hello, World!\n";
    #if 0
    typedef put_printf pf; // just to demo local abbreviation
    std::cout << "1: " << pf("%02147483646i\n"  , 1     ) << std::endl; // res < 0
    std::cout << "2: " << pf("%02147483643i%i\n", 1, 100) << std::endl; // res < 0
    std::cout << "3: " << pf("%02147483643i%i\n", 1,  10) << std::endl; // works
    std::cout << "4: " << pf("%02147483646i"    , 1     ) << std::endl; // works
    #endif
    return 0;
}

Commentaires sur put_printf:

// Reasons for the name "put_printf" (and not "putf" after all):
// - put_printf is self-documenting, while using the naming pattern also seen in std::put_time;
// - it is not clear whether the proposed std::putf would support exactly the same format syntax;
// - it has a niche purpose, so a longer name is not an objection, and for frequent local uses
//     it is easy enough to declare an even shorter "typedef put_printf pf;" or so.
// Evaluation of delegating to vsnprintf() with intermediate buffer:
// (+) identical result without implementation and/or maintenance issues,
// (?) succeeds or fails as a whole, no output of successful prefix before point of failure
// (-) (total output size limited to INT_MAX-1)
// (-) overhead (TODO: optimal buf_stack size considering cache and VM page locality?)
// Error handling (an STL design problem?):
// - std::cout.setstate(std::ios_base::failbit) discards further std::cout output (stdout still works),
//     so, to be aware of an error in business logic yet keep on trucking in diagnostics,
//     should there be separate classes, or a possibility to plug in an error handler, or what?
// - should the basic or default error handling print a diagnostic message? throw an exception?
// TODO: could a function "int ostream_printf(std::ostream& os, const char *format, ...)"
//           first try to write directly into os.rdbuf() before using buf_stack and buf_heap,
//           and would that significantly improve performance or not?
1
RFST

J'ai écrit de façon indépendante, mais j'ai eu une réponse similaire à user3283405. 

Ma solution utilise vasprintf () pour atteindre le formatage et utilise la surcharge de l'opérateur de << de std :: ostream pour libérer la mémoire au bon endroit. 

Usage: 

std::cout << putf(const char *format, ...); //Same format as C printf(3)

Code:

#define _GNU_SOURCE
#include <cstdarg>
#include <iostream>
#include <cstdio>

struct putf_r{
        char *s;
};

putf_r putf(const char *fmt, ...){
        va_list ap;
        va_start(ap, fmt);
        putf_r a;
#pragma GCC diagnostic Push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
        vasprintf(&a.s, fmt, ap);
#pragma GCC diagnostic pop
        va_end(ap);
        return a;
}

std::ostream& operator<<(std::ostream& os, putf_r a){
        os<<a.s;
        free(a.s);
        return os;
}

int main(){
        std::cout << putf("%3d\n", 23) << putf("%a\n", 256.);
}

Notez que le compilateur ne vérifie pas le format dans putf (), donc le drapeau du compilateur -Wformat-nonliteral ne vous avertira pas du code suspect dans putf () et vous devez vous soucier de du format incontrôlé problem par vous-même.
Des informations détaillées peuvent être trouvées sur GitHub

0
WingTillDie

Quand j'ai besoin à la fois de la sécurité typographique de cout et du formatage simple et rapide de variables simples de printf (), je mélange les deux comme ceci. C’est une mauvaise solution, mais cela me permet d’obtenir des résultats optimaux lorsque je dois produire des éléments tels que "02/07/2014 10:05", ainsi que des entités plus complexes:

#include <stdio>
#include <stdarg>
#include <stdlib>
#include <iostream>

#pragma hdrstop

using namespace std;


char* print(char* fmt, ...)
{
    static char buffer[80] = "";

    va_list argptr;
    va_start(argptr,fmt);

    vsprintf(buffer, fmt, argptr);

    va_end(argptr);

    return buffer;
}

#pragma argsused
int main(int argc, char* argv[])
{

cout << print("\n%06d\n%6d\n%6d\n%010.3f",1,12,123,123.456);

system("PAUSE>NUL");

return 0;

}
0
user3283405