web-dev-qa-db-fra.com

std :: string formatant comme sprintf

Je dois formater std::string avec sprintf et l'envoyer dans le flux de fichiers. Comment puis-je faire ceci?

365
Max Frai

Vous ne pouvez pas le faire directement, car vous n'avez pas d'accès en écriture au tampon sous-jacent (jusqu'à C++ 11; voir comment de Dietrich Epp). Vous devrez d'abord le faire dans une c-chaîne, puis le copier dans un std :: string:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Mais je ne suis pas sûr de savoir pourquoi vous n'utilisez pas simplement un flux de chaîne? Je suppose que vous avez des raisons spécifiques de ne pas simplement faire ceci:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
273
Doug T.

Solution C++ 11 utilisant vsnprintf() en interne:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Une approche plus sûre et plus efficace (je l'ai testée et plus rapide):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

Le fmt_str est passé par valeur pour se conformer aux exigences de va_start.

REMARQUE: les versions "plus sûre" et "plus rapide" ne fonctionnent pas sur certains systèmes. Par conséquent, les deux sont toujours répertoriés. En outre, "plus rapide" dépend entièrement de l’étape de préallocation, sinon la strcpy la ralentit.

235
Erik Aronesty

En utilisant C++ 11std::snprintf , cela devient une tâche assez facile et sûre. Je vois beaucoup de réponses à cette question qui avaient apparemment été écrites avant l’époque de C++ 11 et qui utilisaient des longueurs de tampon fixes et des variables, ce que je ne recommanderais pas pour des raisons de sécurité, d’efficacité et de clarté.

#include <memory>
#include <iostream>
#include <string>
#include <cstdio>

using namespace std; //Don't if you're in a header-file

template<typename ... Args>
string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    unique_ptr<char[]> buf( new char[ size ] ); 
    snprintf( buf.get(), size, format.c_str(), args ... );
    return string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

L'extrait de code ci-dessus est sous licence CC0 1.0 .

Explication ligne par ligne:

But: Ecrivez dans un char* en utilisant std::snprintf, puis convertissez-le en std::string.

Tout d'abord, nous déterminons la longueur souhaitée du tableau de caractères.

De cppreference.com :

Valeur de retour

[...] Si la chaîne résultante est tronquée à cause de la limite buf_size, La fonction retourne le nombre total de caractères (sans inclure le qui termine le octet nul) qui auraient été écrits si la limite était pas imposé.

Cela signifie que la taille souhaitée est le nombre de caractères plus un, de sorte que le terminateur nul se place après tous les autres caractères et qu'il peut être à nouveau coupé par le constructeur de chaînes. Ce problème a été expliqué par @ alexk7 dans les commentaires.

Ensuite, nous allouons un nouveau tableau de caractères et l'attribuons à un std::unique_ptr. Ceci est généralement conseillé, car vous n'avez plus à delete manuellement.

Notez que ce n'est pas un moyen sûr d'allouer un unique_ptr avec des types définis par l'utilisateur car vous ne pouvez pas libérer la mémoire si le constructeur lève une exception!

Après cela, nous pouvons bien sûr simplement utiliser snprintf pour son utilisation prévue et écrire la chaîne formatée dans le char[], puis créer et retourner un nouveau std::string à partir de celui-ci.


Vous pouvez voir un exemple en action ici .


Si vous souhaitez également utiliser std::string dans la liste d'arguments, jetez un coup d'œil à this Gist


Informations supplémentaires pour Visual Studio users:

Comme expliqué dans cette réponse , Microsoft a renommé std::snprintf en _snprintf (oui, sans std::). MS le définit en outre comme obsolète et conseille d'utiliser _snprintf_s à la place, cependant _snprintf_s n'acceptera pas que la mémoire tampon soit inférieure ou égale à la sortie formatée et ne calculera pas la longueur des sorties si cela se produit. Ainsi, afin de supprimer les avertissements de dépréciation lors de la compilation, vous pouvez insérer la ligne suivante en haut du fichier contenant l’utilisation de _snprintf:

#pragma warning(disable : 4996)
185
iFreilicht

boost::format() fournit les fonctionnalités souhaitées:

À partir du synopsis des bibliothèques de formats Boost:

Un objet de format est construit à partir d'une chaîne de format, puis reçoit des arguments par le biais d'appels répétés à l'opérateur% . Chacun de ces arguments est ensuite converti en chaînes, qui sont à leur tour combinées en une chaîne, en fonction de la chaîne de formatage.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"
100
kennytm

Malheureusement, la plupart des réponses ici utilisent des variables qui sont intrinsèquement dangereuses, sauf si vous utilisez quelque chose comme l'attribut format de GCC qui fonctionne uniquement avec des chaînes de format littéral. Vous pouvez voir pourquoi ces fonctions sont dangereuses dans l'exemple suivant:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

string_format est une implémentation de la réponse d'Erik Aronesty. Ce code est compilé, mais il va probablement planter lorsque vous essayez de l'exécuter:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Il est possible d'implémenter une printf sûre et de l'étendre au format std::string à l'aide de modèles (variadiques). Cette opération a été effectuée dans la bibliothèque {fmt} , qui constitue une alternative sûre à sprintf renvoyant std::string:

std::string format_str = "The answer is %d";
std::string result = fmt::sprintf(format_str, 42);

{fmt} garde la trace des types d'argument et si le type ne correspond pas à la spécification de format, il n'y a pas d'erreur de segmentation, mais une exception ou une erreur de compilation si les vérifications de chaîne de format constexpr sont utilisées.

Disclaimer: je suis l'auteur de {fmt}.

30
vitaut

Si vous voulez seulement une syntaxe semblable à printf (sans appeler printf vous-même), jetez un coup d'œil à Format Boost .

18
Timo Geusch

[edit '17/8/31] Ajout d'une version variadique basée sur un modèle 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

qui est en réalité une version délimitée par des virgules (à la place) des opérateurs parfois gênants <<-, utilisée comme ceci:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[edit] Adapté pour utiliser la technique dans la réponse d'Erik Aronesty (ci-dessus):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[réponse précédente]
Une réponse très tardive, mais pour ceux qui, comme moi, aiment la voie 'sprintf': j'ai écrit et utilise les fonctions suivantes. Si vous l'aimez, vous pouvez développer les options% pour qu'elles correspondent davantage à celles du sprintf; ceux qui y sont actuellement suffisent pour mes besoins . Vous utilisez stringf () et stringfappend () comme vous le feriez avec sprintf Rappelez-vous simplement que les paramètres pour ... doivent être des types POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}
15
slashmais

J'ai écrit le mien en utilisant vsnprintf afin qu'il renvoie une chaîne au lieu de devoir créer mon propre tampon.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Donc vous pouvez l'utiliser comme

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
15
Piti Ongmongkolkul

Afin de formater std::string de manière 'sprintf', appelez snprintf (arguments nullptr et 0) pour obtenir la longueur de tampon nécessaire. Ecrivez votre fonction en utilisant un modèle variadique C++ 11 comme ceci:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return std::move(str);
}

Compiler avec le support C++ 11, par exemple dans GCC: g++ -std=c++11

Usage:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
13
user2622016

Mes deux cents sur cette question très populaire.

Pour citer la page de manuel de printf- like function :

En cas de retour réussi, ces fonctions renvoient le nombre de caractères imprimés (à l'exclusion de l'octet nul utilisé pour terminer la sortie en chaînes).

Les fonctions snprintf () et vsnprintf () n'écrivent pas plus que des octets de taille (y compris l'octet nul final ('\ 0')). Si la sortie a été tronquée en raison de cette limite, la valeur de retour est le nombre de caractères (à l'exclusion de l'octet nul final) qui auraient été écrits dans la chaîne finale si suffisamment d'espace avait été disponible. Ainsi, une valeur de retour de taille ou plus signifie que la sortie a été tronquée.

En d'autres termes, une implémentation saine de C++ 11 devrait être la suivante:

#include <string>
#include <cstdio>

template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
        // See comments: the +1 is necessary, while the first parameter
        //               can also be set to nullptr

    char bytes[required];
    std::snprintf(bytes, required, fmt.c_str(), vs...);

    return std::string(bytes);
}

Ça marche plutôt bien :)

Les modèles variadiques ne sont pris en charge que dans C++ 11. La réponse de pixelpoint montre une technique similaire utilisant des styles de programmation plus anciens.

C'est bizarre que C++ n'ait pas une telle chose hors de la boîte. Ils ont récemment ajouté to_string() , ce qui, à mon avis, constitue un grand pas en avant. Je me demande s'ils vont éventuellement ajouter un opérateur .format au std::string ...

Modifier

Comme l'a souligné alexk7, un +1 est nécessaire pour la valeur de retour de std::snprintf, car nous avons besoin d'espace pour l'octet \0. Intuitivement, sur la plupart des architectures manquantes du +1, le nombre entier required sera partiellement remplacé par un 0. Cela se produira après l'évaluation de required comme paramètre réel pour std::snprintf, de sorte que l'effet ne devrait pas être visible.

Ce problème pourrait toutefois changer, par exemple avec l'optimisation du compilateur: que se passera-t-il si le compilateur décide d'utiliser un registre pour la variable required? C’est le genre d’erreur qui entraîne parfois des problèmes de sécurité.

10
Dacav

Voici comment Google le fait: StringPrintf (Licence BSD)
et facebook le fait de manière assez similaire: StringPrintf (Licence Apache)
Les deux fournissent une StringAppendF pratique aussi.

10
PW.
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

Utilisation de C99 snprintf et C++ 11 

7
emerge

Basé sur la réponse fournie par Erik Aronesty:

std::string string_format(const std::string &fmt, ...) {
    std::vector<char> str(100,'\0');
    va_list ap;
    while (1) {
        va_start(ap, fmt);
        auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
        va_end(ap);
        if ((n > -1) && (size_t(n) < str.size())) {
            return str.data();
        }
        if (n > -1)
            str.resize( n + 1 );
        else
            str.resize( str.size() * 2);
    }
    return str.data();
}

Cela évite d'avoir à jeter const du résultat de .c_str() qui figurait dans la réponse d'origine. 

6
ChetS

Testé, Qualité de la production Réponse

Cette réponse traite le cas général avec des techniques conformes aux normes. La même approche est donnée à titre d'exemple sur CppReference.com près du bas de leur page. Contrairement à leur exemple, ce code répond aux exigences de la question et est testé sur le terrain dans les applications de robotique et de satellite. Il a également amélioré les commentaires. La qualité de la conception est discutée plus loin.

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

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

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

Efficacité linéaire prévisible

Deux passes sont nécessaires pour une fonction réutilisable sécurisée, fiable et prévisible selon les spécifications de la question. Les présomptions relatives à la distribution des tailles de vargs dans une fonction réutilisable constituent un mauvais style de programmation et doivent être évitées. Dans ce cas, des représentations de vargs de longueur variable arbitrairement grandes sont un facteur clé dans le choix de l'algorithme.

Réessayer en cas de dépassement de capacité est inefficace de manière exponentielle, ce qui est une autre raison évoquée lorsque le comité de normalisation C++ 11 a examiné la proposition ci-dessus visant à fournir une exécution sèche lorsque le tampon d'écriture est nul.

Dans l'implémentation prête pour la production ci-dessus, le premier cycle est un tel cycle pour déterminer la taille d'allocation. Aucune allocation ne se produit. L'analyse des directives printf et la lecture des variables ont été rendues extrêmement efficaces au fil des décennies. Le code réutilisable doit être prévisible, même si une faible inefficacité doit être sacrifiée pour des cas triviaux.

Sécurité et fiabilité

Andrew Koenig a déclaré à un petit groupe d'entre nous après sa conférence lors d'un événement à Cambridge: "Les fonctions utilisateur ne devraient pas dépendre de l'exploitation d'un échec pour des fonctionnalités exceptionnelles." Comme d'habitude, sa sagesse a été démontrée dans le dossier depuis. Les problèmes de bogues de sécurité résolus et fermés indiquent souvent des tentatives de piratage dans la description du trou exploité avant le correctif.

Ceci est mentionné dans la proposition de révision des normes formelles pour la fonction de mémoire tampon nulle dans Alternative à la proposition de révision sprintf, C9X, Document ISO IEC WG14 N645/X3J11 96-008 . Une chaîne arbitrairement longue insérée par directive d'impression, "% s", dans les limites des contraintes de disponibilité dynamique de la mémoire, ne constitue pas une exception et ne doit pas être exploitée pour produire une "fonctionnalité inattendue".

Considérez la proposition à côté de l'exemple de code donné au bas de la page de référence C++ référencée dans le premier paragraphe de cette réponse.

En outre, le test des cas d'échec est rarement aussi robuste que celui des cas de réussite.

Portabilité

Tous les principaux États-Unis Les fournisseurs fournissent des compilateurs qui prennent totalement en charge std :: vsnprintf dans le cadre des normes c ++ 11. Les hôtes exécutant des produits de fournisseurs qui ne gèrent plus les distributions devraient être fournis avec g ++ ou clang ++ pour de nombreuses raisons.

Utilisation de la pile

La pile utilisée dans le 1er appel à std :: vsnprintf sera inférieure ou égale à celle du 2e et sera libérée avant le début du deuxième appel. Si le premier appel dépasse la disponibilité de la pile, std :: fprintf échouera également.

5
Douglas Daseeco

C++ 20 std::format

C'est arrivé! La fonctionnalité est décrite à l'adresse suivante: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html et utilise une syntaxe Python-like .format().

Je m'attends à ce que l'utilisation ressemble à:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Je vais essayer quand le support arrive à GCC, GCC 9.1.0 avec g++-9 -std=c++2a ne le supporte toujours pas.

L'API ajoutera un nouvel en-tête std::format:

L'API de formatage proposée est définie dans le nouvel en-tête <format> et ne devrait avoir aucune incidence sur le code existant.

La bibliothèque existante fmt prétend la mettre en œuvre si vous avez besoin du polyfill: https://github.com/fmtlib/fmt

Implémentation de C++ 20 std::format.

et a été précédemment mentionné à: std :: string formating comme sprintf

inline void format(string& a_string, const char* fmt, ...)
{
    va_list vl;
    va_start(vl, fmt);
    int size = _vscprintf( fmt, vl );
    a_string.resize( ++size );
    vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
    va_end(vl);
}
5
pixelpoint

Si vous êtes sur un système qui a asprintf (3) , vous pouvez facilement l’envelopper:

#include <iostream>
#include <cstdarg>
#include <cstdio>

std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));

std::string format(const char *fmt, ...)
{
    std::string result;

    va_list ap;
    va_start(ap, fmt);

    char *tmp = 0;
    int res = vasprintf(&tmp, fmt, ap);
    va_end(ap);

    if (res != -1) {
        result = tmp;
        free(tmp);
    } else {
        // The vasprintf call failed, either do nothing and
        // fall through (will return empty string) or
        // throw an exception, if your code uses those
    }

    return result;
}

int main(int argc, char *argv[]) {
    std::string username = "you";
    std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
    return 0;
}
4
Thomas Perl

Vous pouvez essayer ceci:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );
4
EddieV223

string n'a pas ce dont vous avez besoin, mais std :: stringstream en a. Utilisez un stringstream pour créer la chaîne, puis extrayez-la. Ici est une liste complète de ce que vous pouvez faire. Par exemple:

cout.setprecision(10); //stringstream is a stream like cout

vous donnera 10 décimales de précision lorsque vous imprimez un double ou un float.

3
Hassan Syed

Ci-dessous, version légèrement modifiée de la réponse @iFreilicht, mise à jour vers C++ 14 (utilisation de la fonction make_unique au lieu de la déclaration brute) et prise en charge des arguments std::string (basée sur Kenny Kerr article )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Sortie:

i = 3, f = 5.000000, s = hello world

N'hésitez pas à fusionner cette réponse avec l'original si vous le souhaitez.

3
Pawel Sledzikowski

A pris l'idée de Dacav et réponse de pixelpoint . J'ai joué un peu et j'ai eu ceci:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Avec sane _ pratique de la programmation, j'estime que le code devrait suffire, mais je suis toujours ouvert à des alternatives plus sûres qui sont encore assez simples et ne nécessiteraient pas le C++ 11.


Et voici une autre version qui utilise un tampon initial pour empêcher le second appel à vsnprintf() quand le tampon initial est déjà suffisant.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Il s’avère que cette version est simplement similaire à la réponse de Piti Ongmongkolkul , mais elle n’utilise pas new et delete[] et spécifie également une taille lors de la création de std::string

L’idée ici de ne pas utiliser new et delete[] est d’impliquer l’utilisation de la pile au dessus du tas, car elle n’a pas besoin d’appeler les fonctions d’allocation et de désallocation. Toutefois, s’il n’est pas utilisé correctement, il peut être dangereux de tamponner , ou peut-être simplement vulnérable). Si cela pose un problème, je suggère fortement d'utiliser plutôt new et delete[]. Notez que la seule préoccupation ici concerne les allocations, étant donné que vsnprintf() est déjà appelé avec des limites. Par conséquent, spécifier une limite basée sur la taille allouée sur le second tampon les empêcherait également.)

3
konsolebox

C'est le code que j'utilise pour faire cela dans mon programme ... Ce n'est pas compliqué, mais ça fait l'affaire ... Remarque, vous devrez ajuster votre taille selon les cas. MAX_BUFFER est pour moi 1024.

std::string Format ( const char *fmt, ... )
{
    char textString[MAX_BUFFER*5] = {'\0'};

    // -- Empty the buffer properly to ensure no leaks.
    memset(textString, '\0', sizeof(textString));

    va_list args;
    va_start ( args, fmt );
    vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
    va_end ( args );
    std::string retStr = textString;
    return retStr;
}
3
Dave

J'utilise habituellement ceci:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

Inconvénient: tous les systèmes ne supportent pas vasprint

3

Solution très très simple.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
2
Pasha
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
1
user5685202

Vous pouvez formater la sortie C++ dans cout à l'aide du fichier d'en-tête iomanip . Assurez-vous d'inclure le fichier d'en-tête iomanip avant d'utiliser les fonctions d'assistance telles que Setprecision, setfill, etc.

Voici un extrait de code que j'ai utilisé dans le passé pour imprimer le temps d'attente moyen dans le vecteur, que j'ai "accumulé".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Voici une brève description de la manière dont nous pouvons formater les flux C++ . http://www.cprogramming.com/tutorial/iomanip.html

1
vinkris

Il peut y avoir des problèmes si la mémoire tampon n’est pas assez grande pour imprimer la chaîne. Vous devez déterminer la longueur de la chaîne formatée avant d’imprimer un message formaté ici . Je crée sa propre aide (testée sous Windows et Linux GCC ) et vous pouvez essayer de l’utiliser.

String.cpp: http://Pastebin.com/DnfvzyKP
String.h: http://Pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#Elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();
1

Je me rends compte que cela a été répondu à plusieurs reprises, mais ceci est plus concis

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

exemple:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Voir aussi http://rextester.com/NJB14150

1
Patrick Beard

cela peut être essayé. simple. n'utilise pas vraiment les nuances de la classe string.

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <string>
#include <exception>
using namespace std;

//---------------------------------------------------------------------

class StringFormatter
{
public:
    static string format(const char *format, ...);
};

string StringFormatter::format(const char *format, ...)
{
    va_list  argptr;

    va_start(argptr, format);

        char   *ptr;
        size_t  size;
        FILE   *fp_mem = open_memstream(&ptr, &size);
        assert(fp_mem);

        vfprintf (fp_mem, format, argptr);
        fclose (fp_mem);

    va_end(argptr);

    string ret = ptr;
    free(ptr);

    return ret;
}

//---------------------------------------------------------------------

int main(void)
{
    string temp = StringFormatter::format("my age is %d", 100);
    printf("%s\n", temp.c_str());

    return 0;
}
1
ksridhar

À ce jour, toutes les réponses semblent présenter un ou plusieurs de ces problèmes: (1) cela peut ne pas fonctionner sur VC++ (2), il nécessite des dépendances supplémentaires telles que boost ou fmt (3), son implémentation personnalisée trop compliquée et probablement mal testée.

Le code ci-dessous aborde tous les problèmes ci-dessus.

#include <string>
#include <cstdarg>
#include <memory>

std::string stringf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    #ifndef _MSC_VER

        //GCC generates warning for valid use of snprintf to get
        //size of result string. We suppress warning with below macro.
        #ifdef __GNUC__
        #pragma GCC diagnostic Push
        #pragma GCC diagnostic ignored "-Wformat-nonliteral"
        #endif

        size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'

        #ifdef __GNUC__
        # pragma GCC diagnostic pop
        #endif

        std::unique_ptr<char[]> buf(new char[ size ] ); 
        std::vsnprintf(buf.get(), size, format, args);
        return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    #else
        int size = _vscprintf(format, args);
        std::string result(++size, 0);
        vsnprintf_s((char*)result.data(), size, _TRUNCATE, format, args);
        return result;
    #endif
    va_end(args);
}    

int main() {
    float f = 3.f;
    int i = 5;
    std::string s = "hello!";
    auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
    printf("%s", rs.c_str());
    return 0;
}

Remarques:

  1. Une branche de code VC++ distincte est nécessaire car VC++ a décidé de rendre obsolète snprintf, ce qui générera les avertissements du compilateur pour les autres réponses à vote élevé mentionnées ci-dessus. Comme je suis toujours en mode "avertissements en tant qu'erreurs", ce n'est pas pour moi.
  2. La fonction accepte char * au lieu de std::string. Ceci parce que la plupart du temps, cette fonction est appelée avec une chaîne littérale qui est bien char *, pas std::string. Si vous avez std::string comme paramètre de format, appelez simplement .c_str().
  3. Le nom de la fonction est stringf au lieu d'éléments tels que string_format pour maintenir avec printf, scanf, etc.
  4. Il ne résout pas le problème de sécurité (c’est-à-dire que de mauvais paramètres peuvent potentiellement causer une erreur de segmentation au lieu d’une exception). Si vous en avez besoin, utilisez plutôt boost ou fmt libraries. Ma préférence ici serait fmt car il ne s'agit que d'un en-tête et d'un fichier source à insérer dans le projet, avec une syntaxe de formatage moins étrange que celle de boost. Cependant, les deux étant incompatibles avec les chaînes de format printf, la procédure ci-dessous reste utile dans ce cas. 
  5. Le code stringf passe par compilation GCC en mode strict . Cela nécessite des macros #pragma supplémentaires pour supprimer les faux positifs dans les avertissements GCC.

Le code ci-dessus a été testé,

1
Shital Shah

Une solution que j’ai privilégiée est de faire cela avec sprintf directement dans le tampon std :: string, après avoir rendu ce tampon suffisamment grand:

#include <string>
#include <iostream>

using namespace std;

string l_output;
l_output.resize(100);

for (int i = 0; i < 1000; ++i)
{       
    memset (&l_output[0], 0, 100);
    sprintf (&l_output[0], "\r%i\0", i);

    cout << l_output;
    cout.flush();
}

Donc, créez le std :: string, redimensionnez-le, accédez directement à son tampon ...

1
Xelous

J'ai essayé, avec expressions régulières . Je l'ai implémenté pour les chaînes ints et const à titre d'exemple, mais vous pouvez ajouter n'importe quel autre type ( POD mais avec des pointeurs, vous pouvez tout imprimer).

#include <assert.h>
#include <cstdarg>

#include <string>
#include <sstream>
#include <regex>

static std::string
formatArg(std::string argDescr, va_list args) {
    std::stringstream ss;
    if (argDescr == "i") {
        int val = va_arg(args, int);
        ss << val;
        return ss.str();
    }
    if (argDescr == "s") {
        const char *val = va_arg(args, const char*);
        ss << val;
        return ss.str();
    }
    assert(0); //Not implemented
}

std::string format(std::string fmt, ...) {
    std::string result(fmt);
    va_list args;
    va_start(args, fmt);
    std::regex e("\\{([^\\{\\}]+)\\}");
    std::smatch m;
    while (std::regex_search(fmt, m, e)) {
        std::string formattedArg = formatArg(m[1].str(), args);
        fmt.replace(m.position(), m.length(), formattedArg);
    }
    va_end(args);
    return fmt;
}

Voici un exemple d'utilisation de celui-ci:

std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;

Sortie:

Je suis bob et j'ai 3 chats

1
ElefEnt

Poco Foundation library a une fonction de format très pratique, qui supporte std :: string à la fois dans la chaîne de format et les valeurs:

1
riot_starter

Mise à jour d'une réponse, la différence est - la fonction acceptera correctement std :: string pour% s

namespace format_helper
{

    template <class Src>
    inline Src cast(Src v)
    {
        return v;
    }

    inline const char *cast(const std::string& v)
    {
        return v.c_str();
    }
};

template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
    using namespace format_helper;
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
    std::string result;
    //because we use string as container, it adds extra 0 automatically
    result.resize(required , 0);
    //and snprintf will use n-1 bytes supplied
    std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);

    return result;
}

Live: http://cpp.sh/5ajsv

1
Alex Zaharov

Pour Visual C :

std::wstring stringFormat(const wchar_t* fmt, ...)
{
    if (!fmt) {
        return L"";
    }

    std::vector<wchar_t> buff;
    size_t size = wcslen(fmt) * 2;
    buff.resize(size);
    va_list ap;
    va_start(ap, fmt);
    while (true) {
        int ret = _vsnwprintf_s(buff.data(), size, _TRUNCATE, fmt, ap);
        if (ret != -1)
            break;
        else {
            size *= 2;
            buff.resize(size);
        }
    }
    va_end(ap);
    return std::wstring(buff.data());
}
0
Jichao

Je vais maintenant écrire la version pour Visual Studio. J'espère qu'un jour, quelqu'un le rendra portable. (Le suspect doit remplacer _vsnwprintf par vsnwprintf et quelque chose comme ça.)

Vous devez désactiver les avertissements obsolètes en utilisant define _CRT_SECURE_NO_WARNINGS dans la configuration du projet.

J'utilise _vsnwprintf avec le premier paramètre en tant que nullptr pour pouvoir estimer la taille du tampon, réserver le tampon wstring, puis formater la chaîne directement dans le tampon.

Vous ne savez pas pourquoi vous devez désactiver l'avertissement déconseillé, car les versions sûres du même appel de méthode (_vsnwprintf_s) ne peuvent pas utiliser nullptr comme entrée. Le suspect doit être signalé à l'équipe Microsoft C++.

Ceci est seulement la version unicode, en supprimant 'w' et en remplaçant wchar_t par char, vous pouvez probablement le faire fonctionner également avec ascii.

//
//  Formats wstring according to format.
//
wstring wformat(const wchar_t* format, ...)
{
    va_list args;
    va_start(args, format);
    int size = _vsnwprintf(nullptr, 0, format, args);
    size++; // Zero termination
    wstring ws;
    ws.resize(size);
    _vsnwprintf(&ws[0], size, format, args);
    va_end(args);
    return ws;
}
0
TarmoPikaro

Il s'agit d'une solution spécifique à Windows conçue pour éviter les avertissements du compilateur dans Visual Studio sans les désactiver. Les avertissements en question concernent l'utilisation de std :: string avec va_start, qui génère un avertissement par erreur, et l'utilisation de variantes printf déconseillées. 

template<typename ... va>
std::string Format( const std::string& format, va ... args )
{
    std::string s;
    s.resize( _scprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

template<typename ... va>
std::wstring Format( const std::wstring& format, va ... args )
{
    std::wstring s;
    s.resize( _scwprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snwprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

std::string s = Format( "%hs %d", "abc", 123 );
std::wstring ws = Format( L"%hs %d", "abc", 123 );
0
Lose

Voici ma (solution simple):

std::string Format(const char* lpszFormat, ...)
{
    // Warning : "vsnprintf" crashes with an access violation
    // exception if lpszFormat is not a "const char*" (for example, const string&)

    size_t  nSize     = 1024;
    char    *lpBuffer = (char*)malloc(nSize);

    va_list lpParams;

    while (true)
    {
        va_start(lpParams, lpszFormat);

        int nResult = vsnprintf(
            lpBuffer,
            nSize,
            lpszFormat,
            lpParams
        );

        va_end(lpParams);

        if ((nResult >= 0) && (nResult < (int)nSize) )
        {
            // Success

            lpBuffer[nResult] = '\0';
            std::string sResult(lpBuffer);

            free (lpBuffer);

            return sResult;
        }
        else
        {
            // Increase buffer

            nSize =
                  (nResult < 0)
                ? nSize *= 2
                : (nResult + 1)
            ;

            lpBuffer = (char *)realloc(lpBuffer, nSize);
        }
    }
}
0
Antonio Petricca