форматирование std :: string как sprintf - PullRequest
390 голосов
/ 26 февраля 2010

Я должен отформатировать std::string с sprintf и отправить его в поток файлов. Как я могу это сделать?

Ответы [ 38 ]

288 голосов
/ 26 февраля 2010

Вы не можете сделать это напрямую, потому что у вас нет доступа на запись в базовый буфер (до C ++ 11; см. комментарий Дитриха Эппа ). Сначала вам нужно будет сделать это в c-строке, а затем скопировать в std :: string:

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

Но я не уверен, почему бы вам просто не использовать поток строк? Я предполагаю, что у вас есть конкретные причины не просто делать это:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();
238 голосов
/ 11 ноября 2011

C ++ 11 решение, которое использует vsnprintf() внутренне:

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

Более безопасный и эффективный (я проверял, и он быстрее) подход:

#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 передается по значению в соответствии с требованиями va_start.

ПРИМЕЧАНИЕ. «Более безопасная» и «более быстрая» версия не работает на некоторых системах. Следовательно, оба все еще перечислены. Кроме того, «быстрее» полностью зависит от правильности шага предварительного распределения, в противном случае strcpy делает его медленнее.

197 голосов
/ 06 октября 2014

Использование C ++ 11 std::snprintf, это становится довольно простой и безопасной задачей. Я вижу много ответов на этот вопрос, которые, по-видимому, были написаны до C ++ 11, в которых используются фиксированные длины буфера и переменные, что я бы не рекомендовал из соображений безопасности, эффективности и ясности.

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

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

Фрагмент кода выше лицензирован под CC0 1.0 .

Объяснение построчно:

Цель: Запишите char*, используя std::snprintf, а затем преобразуйте его в std::string.

Сначала мы определим желаемую длину массива char.

С cppreference.com :

Возвращаемое значение

[...] Если результирующая строка усекается из-за ограничения buf_size, функция возвращает общее количество символов (не включая завершающий нулевой байт), который был бы записан, если бы предел был не навязывается.

Это означает, что желаемый размер равен числу символов плюс один , так что нулевой терминатор будет располагаться после всех других символов и что он может быть снова обрезан конструктором строки. Эта проблема была объяснена @ alexk7 в комментариях.

Затем мы выделяем новый массив символов и присваиваем его std::unique_ptr. Обычно это рекомендуется, так как вам не придется вручную delete снова.

Обратите внимание, что это не безопасный способ выделения unique_ptr с пользовательскими типами, поскольку вы не можете освободить память, если конструктор выдает исключение!

После этого мы, конечно, можем просто использовать snprintf по прямому назначению и записать отформатированную строку в char[], а затем создать и вернуть новое std::string из этого.


Вы можете увидеть пример в действии здесь .


Если вы также хотите использовать std::string в списке аргументов, взгляните на this gist .


Дополнительная информация для Visual Studio пользователей:

Как объяснено в этом ответе , Microsoft переименовала std::snprintf в _snprintf (да, без std::). Далее MS устанавливает его как устаревшее и рекомендует использовать вместо него _snprintf_s, однако _snprintf_s не примет буфер равным нулю или меньше отформатированного вывода и не рассчитает длину выходных данных, если это произойдет , Таким образом, чтобы избавиться от предупреждений об устаревании во время компиляции, вы можете вставить следующую строку в верхней части файла, который содержит использование _snprintf:

#pragma warning(disable : 4996)
100 голосов
/ 26 февраля 2010

boost::format() обеспечивает необходимую функциональность:

Как из краткого обзора библиотек формата Boost:

Объект формата создается из строки формата, а затем ему передаются аргументы через повторные вызовы оператора%. Каждый из этих аргументов затем преобразуется в строки, которые в свою очередь объединяются в одну строку в соответствии со строкой формата.

#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"
35 голосов
/ 22 августа 2014

К сожалению, большинство ответов здесь используют varargs, которые по своей сути небезопасны, если вы не используете что-то вроде атрибута format GCC, который работает только со строками литерального формата. Вы можете увидеть, почему эти функции небезопасны на следующем примере:

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

где string_format - реализация из ответа Эрика Аронести. Этот код компилируется, но, скорее всего, он будет зависать при попытке его запустить:

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

Можно реализовать безопасный printf и расширить его до формата std::string, используя (вариационные) шаблоны. Это было сделано в библиотеке {fmt} , которая предоставляет безопасную альтернативу sprintf, возвращающему std::string:

#include <fmt/printf.h>

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

{fmt} отслеживает типы аргументов, и если тип не соответствует спецификации формата, то нет ошибки сегментации, только исключение или ошибка времени компиляции, если используются constexpr проверки строки формата.

Отказ от ответственности : я являюсь автором {fmt}.

18 голосов
/ 26 февраля 2010

Если вам нужен только синтаксис, похожий на printf (без вызова printf самостоятельно), посмотрите на Boost Format .

15 голосов
/ 14 апреля 2012

Я написал свой собственный, используя vsnprintf, поэтому он возвращает строку вместо того, чтобы создавать собственный буфер.

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

Так что вы можете использовать его как

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
15 голосов
/ 28 сентября 2015

Чтобы отформатировать std::string способом 'sprintf', вызовите snprintf (аргументы nullptr и 0) для получения необходимой длины буфера. Напишите свою функцию, используя шаблон C ++ 11 variadic, например:

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

Компиляция с поддержкой C ++ 11, например, в GCC: g++ -std=c++11

Использование:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
14 голосов
/ 18 сентября 2010

[править '17 / 8/31] Добавление вариативно-шаблонной версии '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;
}

, который фактически является разделенной запятыми версией (вместо) иногда мешающих << -операторов, используемых следующим образом:

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


[править] Адаптирован для использования техники в ответе Эрика Аронести (выше):

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

[предыдущий ответ]
Очень поздний ответ, но для тех, кому, как и мне, нравится путь sprintf: я написал и использую следующие функции. Если вам это нравится, вы можете расширить% -опции, чтобы они более близко подходили к sprintf; в настоящее время там достаточно для моих нужд. Вы используете stringf () и stringfappend () так же, как и sprintf. Просто помните, что параметры для ... должны быть типа 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;
}
10 голосов
/ 10 апреля 2013

Вот как это делает Google: StringPrintf (лицензия BSD)
и facebook делает это очень похожим образом: StringPrintf (Apache License)
Оба обеспечивают с удобным StringAppendF тоже.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...