std:: string formatowanie jak sprintf

Muszę sformatować std::string z sprintf i wysłać go do strumienia plików. Jak mogę to zrobić?

Author: thecoshman, 2010-02-26

30 answers

Nie możesz tego zrobić bezpośrednio, ponieważ nie masz prawa zapisu do bufora bazowego (do C++11; Zobacz komentarz Dietricha EPPA ). Najpierw musisz to zrobić w C-string, a następnie skopiować do std:: string:

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

Ale nie jestem pewien, dlaczego nie użyłeś strunowego strumienia? Zakładam, że masz konkretne powody, by tego nie robić:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
 252
Author: Doug T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:11

C++11 rozwiązanie, które używa vsnprintf() wewnętrznie:

#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;
}

Bezpieczniejsze i wydajniejsze (testowałem i jest szybsze) podejście:

#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());
}

fmt_str jest przekazywana przez wartość w celu spełnienia wymagań va_start.

Uwaga: wersja "bezpieczniejsza" i "szybsza" nie działa na niektórych systemach. Dlatego oba są nadal wymienione. Ponadto, "szybszy" zależy całkowicie od poprawności kroku prealokacji, w przeciwnym razie strcpy czyni go wolniejszym.

 230
Author: Erik Aronesty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-26 14:57:22

C++11 std::snprintf, staje się to dość łatwe i bezpieczne zadanie. Widzę wiele odpowiedzi na to pytanie, które najwyraźniej zostały napisane przed czasem C++11, które używają stałych długości buforów i Varg, czego nie polecam ze względów bezpieczeństwa,wydajności i jasności.

#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
}

powyższy fragment kodu jest licencjonowany pod CC0 1.0 .

Wyjaśnienie linii po linii:

cel: napisz do a char* używając std::snprintf, a następnie przekonwertuj to na std::string.

Najpierw określamy żądaną długość tablicy znaków.

Z cppreference.com :

Return value

[...] Jeśli wynikowy ciąg zostanie obcięty z powodu limitu buf_size, funkcja zwraca całkowitą liczbę znaków (nie wliczając kończący null-bajt), który zostałby zapisany, gdyby limit był nie narzucony.

Oznacza to że pożądany rozmiar to liczba znaków plus jeden , tak że null-terminator będzie siedział po wszystkich innych znakach i że może być ponownie odcięty przez konstruktor łańcuchów. Ta kwestia została wyjaśniona przez @alexk7 w komentarzach.

Następnie przydzielamy nową tablicę znaków i przypisujemy ją do std::unique_ptr. Jest to ogólnie zalecane, ponieważ nie będziesz musiał ręcznie delete to ponownie.

należy pamiętać, że nie jest to bezpieczny sposób przydzielania unique_ptr z typami zdefiniowanymi przez użytkownika ponieważ nie można dealokacji pamięci, jeśli konstruktor rzuca wyjątek!

Po tym, możemy oczywiście użyć snprintf zgodnie z jego przeznaczeniem i zapisać sformatowany ciąg znaków do char[], a następnie utworzyć i zwrócić Nowy std::string z tego.


Możesz zobaczyć przykład w akcji tutaj .


Jeśli chcesz również użyć std::string na liście argumentów, spójrz na ten gist.


Dodatkowe informacje dla Visual Studio użytkownicy:

[[19]}Jak wyjaśniono w tej odpowiedzi , Microsoft zmienił nazwę std::snprintf na _snprintf (tak, bez std::). MS ustawił go jako przestarzały i radzi używać _snprintf_s zamiast tego _snprintf_s nie zaakceptuje bufora jako zerowego lub mniejszego niż sformatowane wyjście i nie obliczy długości wyjść, jeśli tak się stanie. Aby pozbyć się ostrzeżeń o deprecacji podczas kompilacji, możesz wstawić następujący wiersz u góry z pliku, który zawiera użycie _snprintf:
#pragma warning(disable : 4996)
 171
Author: iFreilicht,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-04 21:51:32

boost::format() zapewnia pożądaną funkcjonalność:

Z bibliotek formatu Boost:

Obiekt formatu jest zbudowany z ciągu znaków formatu, a następnie jest podawany jako argument przez powtarzające się wywołania do operatora%. Każdy z tych argumentów jest następnie konwertowany na ciągi znaków, które są z kolei połączone w jeden ciąg znaków, zgodnie z formatem-string.

#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"
 97
Author: kennytm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-10 12:01:46

Niestety, większość odpowiedzi tutaj używa varargs, które są z natury niebezpieczne, chyba że używasz czegoś takiego jak atrybut GCC format, który działa tylko z literalnymi ciągami formatów. Możesz zobaczyć, dlaczego te funkcje są niebezpieczne na poniższym przykładzie:

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

Gdzie string_format jest implementacją odpowiedzi Erika Aronesty. Ten kod kompiluje się, ale najprawdopodobniej zawiesi się, gdy spróbujesz go uruchomić:

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

Możliwe jest zaimplementowanie bezpiecznego printf i rozszerzenie go do formatu std::string używanie (wariacyjnych) szablonów. Zostało to zrobione w bibliotece FMT , która zapewnia bezpieczną alternatywę dla sprintf zwracania std::string:

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

Fmt śledzi typy argumentów i jeśli Typ nie pasuje do specyfikacji formatu, nie występuje błąd segmentacji, tylko wyjątek.

Disclaimer: jestem autorem tej biblioteki.

 27
Author: vitaut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-01 20:24:07

Jeśli potrzebujesz tylko składni podobnej do printf (bez samodzielnego wywoływania printf), spójrz na Boost Format.

 18
Author: Timo Geusch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-26 14:20:23

Napisałem swój własny używając vsnprintf, więc zwraca string zamiast tworzyć własny bufor.

#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;
}

Więc możesz go używać jak

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
 15
Author: Piti Ongmongkolkul,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-20 14:06:18

[edytuj '17/8/31] dodawanie wariacyjnej wersji szablonów' 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;
}

, który w rzeczywistości jest rozdzielaną przecinkami wersją (zamiast) czasami utrudniających <<-operatorów, używanych w następujący sposób:

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


[edytuj] przystosowane do wykorzystania techniki w odpowiedzi Erika Aronesty (powyżej):

#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;
}

[poprzednia odpowiedź]
Bardzo późna odpowiedź, ale dla tych, którzy, podobnie jak ja, lubią 'sprintf' - sposób: napisałem i korzystają z następujących funkcji. Jeśli podoba Ci się to, możesz rozszerzyć opcje%, aby bardziej pasowały do sprintf-ów; te, które są tam obecnie, są wystarczające dla moich potrzeb. Używasz stringf () i stringfappend () tak samo jak sprintf. Pamiętaj tylko, że parametry dla ... to MUSZĄ BYĆ kapsuły.

//=============================================================================
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;
}
 14
Author: slashmais,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-31 07:06:25

Aby sformatować std::string w sposób 'sprintf', wywołaj snprintf (argumenty nullptr i 0), aby uzyskać wymaganą długość bufora. Napisz swoją funkcję używając szablonu wariadycznego C++11 w następujący sposób:

#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);
}

Skompilować z obsługą C++11, na przykład w GCC: g++ -std=c++11

Użycie:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
 12
Author: user2622016,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-11 11:23:54

Tak to robi google: StringPrintf (licencja BSD)
a facebook robi to w dość podobny sposób: StringPrintf (Licencja Apache)
Oba zapewniają również wygodny StringAppendF.

 10
Author: PW.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-08 08:05:22

Moje dwa grosze na to bardzo popularne pytanie.

Aby zacytować stronę podręcznika printf-podobne funkcje :

Po pomyślnym powrocie, funkcje te zwracają liczbę wydrukowanych znaków(z wyłączeniem bajtu null używanego do zakończenia wyjścia do łańcuchów).

Funkcje snprintf() i vsnprintf() nie zapisują więcej niż rozmiar bajtów (włączając kończący bajt null ('\0')). Jeżeli wynik został okrojony z powodu tego ograniczenia, to wartość zwracana jest liczbą znaków (z wyłączeniem kończącego bajtu null), która zostałaby zapisana do końcowego łańcucha, gdyby była dostępna wystarczająca ilość spacji. W ten sposób zwracana wartość size lub większa oznacza, że wyjście zostało obcięte.

Innymi słowy, rozsądna implementacja C++11 powinna być następująca:

#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);
}

Działa całkiem nieźle:)

Zmienne szablony są obsługiwane tylko w C++11. Odpowiedź z pixelpoint pokazuje podobną technikę przy użyciu starszych style programowania.

To dziwne, że C++ nie ma czegoś takiego po wyjęciu z pudełka. Ostatnio dodane to_string(), co moim zdaniem jest wielkim krokiem naprzód. Zastanawiam się, czy w końcu dodadzą operator .format do std::string...

Edit

Jak zauważył alexk7, +1 jest potrzebne na zwracanej wartości std::snprintf, ponieważ musimy mieć miejsce na \0 bajt. Intuicyjnie, na większości architektur brak +1 spowoduje, że liczba całkowita required będzie częściowo nadpisane 0. Stanie się to po ocenie required jako rzeczywistego parametru dla std::snprintf, więc efekt nie powinien być widoczny.

Ten problem może się jednak zmienić, na przykład przy optymalizacji kompilatora: co jeśli kompilator zdecyduje się użyć rejestru dla zmiennej required? Jest to rodzaj błędów, które czasami powodują problemy z bezpieczeństwem.

 10
Author: Dacav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-12 14:03:08
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;
}

Using C99 snprintf and C++11

 7
Author: emerge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-11 02:06:15

Na podstawie odpowiedzi udzielonej przez Erika 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();
}

Pozwala to uniknąć konieczności odrzucenia const z wyniku .c_str(), który był w pierwotnej odpowiedzi.

 6
Author: ChetS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-01 19:36:30
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);
}
 6
Author: pixelpoint,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-13 10:14:45

String nie ma tego, czego potrzebujesz, ale STD::stringstream tak. Użyj strumienia stringstream, aby utworzyć łańcuch, a następnie wyodrębnić łańcuch. Tutaj {[3] } jest obszerna lista rzeczy, które możesz zrobić. Na przykład:

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

Daje 10 miejsc po przecinku dokładności podczas drukowania podwójnego lub zmiennoprzecinkowego.

 4
Author: Hassan Syed,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-26 14:27:40

Możesz spróbować tego:

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
Author: EddieV223,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-24 06:02:53

Jest to kod, którego używam do tego w moim programie... To nic wymyślnego, ale działa... Uwaga, będziesz musiał dostosować swój rozmiar w zależności od przypadku. MAX_BUFFER jak dla mnie to 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
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 04:35:23

Wziął pomysł z Dacav i pixelpoint ' s answer . Pobawiłem się trochę i dostałem to:

#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);
}

Z sane praktyka programowania uważam, że kod powinien wystarczyć, jednak nadal jestem otwarty na bezpieczniejsze alternatywy, które są nadal wystarczająco proste i nie wymagałyby C++11.


A oto kolejna wersja, która używa bufora początkowego, aby zapobiec drugiemu wywołaniu vsnprintf(), gdy bufor początkowy jest już wystarczy.

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);
}

(okazuje się, że ta wersja jest po prostu podobna do Piti Ongmongkolkul ' s answer, tyle że nie używa new i delete[], a także określa rozmiar podczas tworzenia std::string.

Ideą nieużywania new i delete[] jest sugerowanie użycia stosu nad stertą, ponieważ nie musi on wywoływać funkcji alokacji i dealokacji, jednak jeśli nie zostanie prawidłowo użyty, może być niebezpieczne buforowanie przepełnień w niektórych (być może starych, a może po prostu wrażliwych) systemów. Jeśli jest to problemem, sugeruję użycie new i delete[] zamiast tego. Zauważ, że jedynym problemem jest tutaj alokacja, ponieważ vsnprintf() jest już wywoływana z limitami, więc określenie limitu na podstawie rozmiaru przydzielonego w drugim buforze również zapobiegłoby tym.)

 3
Author: konsolebox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:28

Zwykle używam tego:

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;
}

Wada: nie wszystkie systemy obsługują vasprint

 3
Author: Folkert van Heusden,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-23 13:01:06

Jeśli korzystasz z systemu, który ma asprintf(3) , możesz go łatwo zawinąć:

#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;
}
 3
Author: Thomas Perl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-28 08:51:35

Bardzo proste rozwiązanie.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
 2
Author: Pasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-25 10:38:15

Poniżej nieco zmodyfikowana wersja odpowiedzi @iFreilicht, zaktualizowana do C++14 (użycie funkcji make_unique zamiast deklaracji surowej) i dodana obsługa argumentów std::string (na podstawie artykułu Kenny Kerr )

#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";
}

Wyjście:

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

W razie potrzeby możesz połączyć tę odpowiedź z oryginalną.

 2
Author: Pawel Sledzikowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-11 07:52:10

Testowane, Odpowiedź Jakości Produkcji

Ta odpowiedź obsługuje ogólny przypadek z technikami zgodnymi ze standardami. To samo podejście podano jako przykład na CppReference.com na dole strony.

#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; }
 2
Author: Douglas Daseeco,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-21 18:30:34

Moim ulubionym rozwiązaniem jest zrobienie tego z sprintf bezpośrednio do bufora std::string, po uczynieniu tego bufora wystarczająco dużym:

#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();
}

Więc utwórz std:: string, zmień jego rozmiar, uzyskaj bezpośredni dostęp do jego bufora...

 1
Author: Xelous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-11 14:34:45

Biblioteka poco Foundation posiada bardzo wygodną funkcję formatowania, która obsługuje std:: string zarówno w łańcuchu formatu, jak i w wartościach:

 1
Author: riot_starter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-29 20:12:36

Możesz sformatować wyjście C++ w cout używając pliku nagłówka iomanip. Upewnij się, że zawiera plik nagłówka iomanip przed użyciem którejkolwiek z funkcji pomocniczych, takich jak setprecision, setfill itp.

Oto fragment kodu, którego używałem w przeszłości do drukowania średniego czasu oczekiwania w wektorze, który "zgromadziłem".

#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;

Oto krótki opis formatowania strumieni C++. http://www.cprogramming.com/tutorial/iomanip.html

 1
Author: vinkris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-04 14:03:02

Mogą wystąpić problemy, jeśli bufor nie jest wystarczająco duży, aby wydrukować ciąg znaków. Przed wydrukowaniem sformatowanej wiadomości należy określić długość sformatowanego ciągu znaków. Robię własny helper do tego (testowany na Windows i Linux GCC), i możesz spróbować go użyć.

String.cpp: http://pastebin.com/DnfvzyKP
Sznurek.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
Author: Valdemar_Rudolfovich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 04:42:17

Spróbowałem z wyrażeniami regularnymi . Zaimplementowałem go jako przykład dla ciągów ints i const, ale możesz dodać inne typy (typyPOD, ale za pomocą wskaźników możesz wydrukować wszystko).

#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;
}

Oto przykład jego użycia:

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

Wyjście:

Jestem bob i mam 3 Koty

 1
Author: ElefEnt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 04:47:12

To można wypróbować. proste. naprawdę nie używa niuansów klasy 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
Author: ksridhar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-14 05:28:47
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
 1
Author: user5685202,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-16 06:32:14