Использование sprintf с std::string в С++
Я использую функцию sprintf
в С++ 11 следующим образом:
std::string toString()
{
std::string output;
uint32_t strSize=512;
do
{
output.reserve(strSize);
int ret = sprintf(output.c_str(), "Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
FORAMT_VERSION,
contentType,
contentFormatVersion,
magic,
segmentId);
strSize *= 2;
} while (ret < 0);
return output;
}
Есть ли лучший способ сделать это, чем проверять каждый раз, если зарезервированного пространства было достаточно? Для будущей возможности добавить больше вещей.
Ответы
Ответ 1
Ваша конструкция - запись в буфер, полученный из c_str()
, - это undefined поведение, даже если вы предварительно проверили пропускную способность строки. (Возвращаемое значение является указателем на const char, а сама функция помечает const.)
Не смешивайте C и С++, особенно не для записи во внутреннее представление объекта. (Это нарушает очень простой OOP.) Используйте С++, для безопасности типов и не выполняйте ошибки спецификации/несоответствия параметров, если ничего не делать.
std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
<< " Version=" << FORMAT_VERSION
// ...and so on...
;
std::string output = s.str();
Альтернатива:
std::string output = "Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
+ " Version=" + std::to_string( FORMAT_VERSION )
// ...and so on...
;
Ответ 2
Шаблоны С++, показанные в других ответах, более приятные, но для полноты, здесь правильный путь с sprintf
:
auto format = "your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);
Обратите внимание на
- Вы должны
resize
указать свою строку. reserve
не изменяет размер буфера. В моем примере я строю правильно размерную строку напрямую.
-
c_str()
возвращает a const char*
. Вы не можете передать его sprintf
.
Буфер -
std::string
не должен быть смежным до С++ 11, и это зависит от этой гарантии. Если вам нужно поддерживать экзотические pre-С++ 11 соответствующие платформы, которые используют реализацию веревки для std::string
, то вам, вероятно, лучше сперва сначала перейти в std::vector<char>
, а затем скопировать вектор в строку.
- Это работает только в том случае, если аргументы не изменяются между вычислением размера и форматированием; используйте либо локальные копии переменных, либо примитивы синхронизации потоков для многопоточного кода.
Ответ 3
Ваш код неверен. reserve
выделяет память для строки, но не меняет ее размер. Запись в буфер, возвращаемый c_str
, также не изменяет его размер. Поэтому строка по-прежнему считает, что ее размер равен 0, и вы только что что-то написали в неиспользуемом пространстве в буфере строк. (Возможно, с технической точки зрения код имеет Undefined Behavior, потому что запись в c_str
есть undefined, так что все может случиться).
То, что вы действительно хотите сделать, - это забыть sprintf
и подобные функции C-стиля, а также использовать способ строкового форматирования строк и строк в формате С++:
std::ostringstream ss;
ss << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
<< " Version=" << FORAMT_VERSION
<< /* ... the rest ... */;
return ss.str();
Ответ 4
Лучший способ - использовать библиотеку {fmt}. Пример:
std::string message = fmt::sprintf("The answer is %d", 42);
Он также предоставляет более приятный интерфейс, чем iostreams и printf. Пример:
std::string message = fmt::format("The answer is {}", 42);'
Увидеть:
https://github.com/fmtlib/fmt
http://fmtlib.net/latest/api.html#printf-formatting-functions
Ответ 5
Мы можем смешать код отсюда fooobar.com/questions/557278/... и здесь fooobar.com/questions/44659/..., и результат будет подобен что:
template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
return output;
}
Ответ 6
Да, есть!
В C лучший способ - связать файл с нулевым устройством и сделать фиктивный printf
желаемого вывода на него, чтобы узнать, сколько места потребуется, если оно будет напечатано. Затем выделите соответствующий буфер и sprintf
те же самые данные.
В С++ вы также можете связать выходной поток с нулевым устройством и проверить количество символов, напечатанных с помощью std:: ostream:: tellp. Однако использование ostringstream
является лучшим решением - см. Ответы DevSolar или Angew.