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

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

Ответы [ 38 ]

9 голосов
/ 05 октября 2014

Мои два цента по этому очень популярному вопросу.

Цитировать справочную страницу из printf -подобных функций :

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

Функции snprintf () и vsnprintf () записывают не больше байтов размера (включая завершающий нулевой байт ('\ 0')). Если выходные данные были усечены из-за этого ограничения, тогда возвращаемое значение - это количество символов (исключая завершающий нулевой байт), которое было бы записано в конечную строку, если бы было достаточно места. Таким образом, возвращаемое значение размера или более означает, что вывод был усечен.

Другими словами, нормальная реализация C ++ 11 должна быть следующей:

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

Работает довольно хорошо:)

Шаблоны Variadic поддерживаются только в C ++ 11. Ответ от pixelpoint показывает похожую технику с использованием более старых стилей программирования.

Странно, что в C ++ нет такой штуки из коробки. Недавно они добавили to_string(), что, на мой взгляд, является большим шагом вперед. Мне интересно, добавят ли они оператор .format к std::string в конце концов ...

Редактировать

Как указал alexk7, A +1 требуется для возвращаемого значения std::snprintf, поскольку нам нужно пространство для байта \0. Интуитивно понятно, что на большинстве архитектур, пропущенных +1, целое число required будет частично заменено на 0. Это произойдет после оценки required как фактического параметра для std::snprintf, поэтому эффект не должен быть видимым.

Однако эта проблема может измениться, например, при оптимизации компилятора: что если компилятор решит использовать регистр для переменной required? Это ошибки, которые иногда приводят к проблемам с безопасностью.

7 голосов
/ 11 октября 2014
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;
}

Использование C99 snprintf и C ++ 11

6 голосов
/ 13 апреля 2018

Проверено, ответ качества продукции

Этот ответ обрабатывает общий случай с помощью методов, соответствующих стандартам. Тот же подход приведен в качестве примера на CppReference.com в нижней части их страницы. В отличие от их примера, этот код соответствует требованиям вопроса и испытан в полевых условиях в робототехнике и спутниковых приложениях. Это также улучшило комментирование. Качество дизайна обсуждается ниже.

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

Предсказуемая линейная эффективность

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

Повторная попытка переполнения является экспоненциально неэффективной, что является еще одной причиной, обсуждаемой, когда комитет по стандартам C ++ 11 обсуждал вышеупомянутое предложение, чтобы обеспечить пробный запуск, когда буфер записи равен нулю.

В приведенной выше реализации готового производства первый прогон является таким пробным прогоном для определения размера распределения. Распределение не происходит. Парсинг директив printf и чтение vargs были сделаны чрезвычайно эффективными в течение десятилетий. Повторно используемый код должен быть предсказуемым, даже если нужно пожертвовать небольшой неэффективностью для тривиальных случаев.

Безопасность и надежность

Эндрю Кениг сказал небольшой группе из нас после своей лекции на мероприятии в Кембридже: «Функции пользователя не должны полагаться на использование отказа из-за исключительной функциональности». Как обычно, его мудрость была показана в записи с тех пор. Исправленные и закрытые ошибки безопасности часто указывают на повторные попытки в описании дыры, использованной до исправления.

Это упоминается в официальном предложении о пересмотре стандартов для функции нулевого буфера в Альтернатива sprintf, Предложение по пересмотру C9X , Документ ИСО МЭК WG14 N645 / X3J11 96-008 . Строка произвольной длины, вставленная в директиву печати «% s» в рамках ограничений доступности динамической памяти, не является исключением и не должна использоваться для создания «Необычайной функциональности».

Рассмотрим предложение вместе с примером кода, приведенным внизу страницы C ++ Reference.org, на которую есть ссылка в первом абзаце этого ответа.

Кроме того, тестирование случаев отказа редко бывает таким же успешным.

Портативность

Все основные О.С. поставщики предоставляют компиляторы, которые полностью поддерживают std :: vsnprintf как часть стандартов c ++ 11. Хосты, использующие продукты поставщиков, которые больше не поддерживают дистрибутивы, должны быть снабжены g ++ или clang ++ по многим причинам.

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

Использование стека при первом вызове std :: vsnprintf будет меньше или равно использованию второго, и оно будет освобождено до начала второго вызова. Если первый вызов превысит доступность стека, то std :: fprintf тоже не удастся.

5 голосов
/ 01 февраля 2013

На основании ответа, предоставленного Эриком Аронести:

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

Это исключает необходимость отбрасывать const из результата .c_str(), который был в исходном ответе.

5 голосов
/ 13 января 2014
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);
}
3 голосов
/ 17 января 2013

Вы можете попробовать это:

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 );
3 голосов
/ 26 февраля 2010

строка не имеет то, что вам нужно, но std :: stringstream делает. Используйте поток строк, чтобы создать строку, а затем извлечь строку. Здесь - это полный список того, что вы можете сделать. Например:

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

даст вам 10 знаков после запятой при печати двойного или плавающего числа.

3 голосов
/ 20 января 2015

Если вы работаете в системе с asprintf (3) , вы можете легко обернуть ее:

#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;
}
2 голосов
/ 26 февраля 2012

Это код, который я использую, чтобы сделать это в моей программе ... Ничего особенного, но он делает свое дело ... Обратите внимание, вам придется корректировать свой размер в зависимости от ситуации.MAX_BUFFER для меня - 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;
}
2 голосов
/ 23 июня 2016

Я обычно использую это:

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

Недостаток: не все системы поддерживают vasprint

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