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

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

Ответы [ 38 ]

0 голосов
/ 10 января 2014

Для Визуальный 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 голосов
/ 17 августа 2014

Могут возникнуть проблемы, если буфер недостаточно велик для печати строки. Вы должны определить длину отформатированной строки перед печатью отформатированного сообщения там. Я сделал собственный помощник для этого (проверено на Windows и Linux GCC ), и вы можете попробовать использовать его.

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();
0 голосов
/ 20 октября 2014

Я попробовал, с регулярными выражениями . Я реализовал это для ints и const-строк в качестве примера, но вы можете добавить любые другие типы ( POD типы, но с помощью указателей вы можете печатать все что угодно).

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

Вот пример использования этого:

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

Выход:

Я Боб, и у меня есть 3 кошки

0 голосов
/ 29 мая 2016

Похоже, что во всех ответах на данный момент здесь есть одна или несколько из следующих проблем: (1) он может не работать на VC ++ (2) он требует дополнительных зависимостей, таких как boost или fmt (3), его слишком сложная пользовательская реализация и, вероятно, нет хорошо проверено.

Ниже код устраняет все вышеперечисленные проблемы.

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

Примечания:

  1. Отдельная ветвь кода VC ++ необходима, потому что VC ++ решил отказаться от snprintf, что сгенерирует предупреждения компилятора для других ответов с высоким рейтингом выше. Так как я всегда работаю в режиме «предупреждения как ошибки», мне это не подходит.
  2. Функция принимает char * вместо std::string. Это потому, что большую часть времени эта функция вызывается с литеральной строкой, которая действительно char *, а не std::string. Если у вас есть std::string в качестве параметра формата, просто позвоните .c_str().
  3. Имя функции - stringf вместо таких вещей, как string_format для поддержки с printf, scanf и т. Д.
  4. Это не решает проблему безопасности (т. Е. Плохие параметры могут вызвать ошибку сегмента вместо исключения). Если вам это нужно, то лучше использовать библиотеки boost или fmt . Здесь я предпочитаю fmt, потому что это всего лишь один заголовок и исходный файл, который нужно добавить в проект, при этом синтаксис форматирования будет менее странным, чем boost. Однако оба они несовместимы со строками формата printf, поэтому ниже все равно полезно в этом случае.
  5. Код stringf проходит через Компиляция строгого режима GCC . Это требует дополнительных #pragma макросов для подавления ложных срабатываний в предупреждениях GCC.

Выше был проверен код,

0 голосов
/ 20 января 2019

Это специальное решение для Windows, разработанное для предотвращения предупреждений компилятора в Visual Studio без их отключения. Рассматриваемые предупреждения предназначены для использования std :: string с va_start, которая выдает ошибочное предупреждение, и для использования устаревших вариантов printf.

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 голосов
/ 08 марта 2013

Вот мое (простое решение):

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 голосов
/ 27 февраля 2019

Я сейчас напишу версию для Visual Studio, надеюсь, когда-нибудь кто-нибудь сделает ее переносимой.(Подозреваю, что нужно заменить _vsnwprintf на vsnwprintf и что-то вроде этого.)

Вам нужно отключить предупреждения об устаревших, используя определение _CRT_SECURE_NO_WARNINGS из конфигурации проекта.

Я использую_vsnwprintf с первым параметром в качестве nullptr, чтобы иметь возможность оценить размер буфера, зарезервировать буфер wstring, а затем форматировать строку непосредственно в буфер.

Не уверен, почему необходимо отключить устаревшее предупреждение, как безопасные версиивызова того же метода (_vsnwprintf_s) не может использовать nullptr в качестве входа.О подозрении необходимо сообщить в команду Microsoft C ++.

Эта версия должна работать с обоими классами - string или wstring.

Если вы обнаружите какую-либо ошибку или несоответствие, пожалуйста, спросите еще разЯ постараюсь это исправить.

stringHelpers.h:

#pragma once
#include <string>

//
//  Formats string/wstring according to format, if formatting fails (e.g. invalid %s pointer - returns empty string)
//
template <typename T>
std::basic_string<T> sFormat(const T* format, ...)
{
    va_list args;
    va_start(args, format);
    int size;

    if constexpr (std::is_same_v<T, char>)
        size = vsnprintf(nullptr, 0, format, args);
    else
        size = _vsnwprintf(nullptr, 0, format, args);

    size++; // Zero termination
    std::basic_string<T> s;
    s.resize(size);

    if constexpr (std::is_same_v<T, char>)
        vsnprintf(&s[0], size, format, args);
    else
        _vsnwprintf(&s[0], size, format, args);

    va_end(args);
    return s;
}

Выше приведен пример кода, который можно скопировать как таковой.Я буду поддерживать рабочую версию в своем собственном репозитории в github:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/helpers.h#L12

0 голосов
/ 11 марта 2013

Одно из решений, которое я предпочел, - это сделать это с помощью sprintf непосредственно в буфере std :: string, после того как буфер стал достаточно большим:

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

Итак, создайте std :: string, измените ее размер, получите прямой доступ к ее буферу ...

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