Современный C ++ делает это очень просто.
C ++ 20
C ++ 20 вводит std::format
, что позволяет вам делать именно это. Он использует поля замены, аналогичные тем, что есть в python :
#include <iostream>
#include <format>
int main() {
std::cout << std::format("Hello {}!\n", "world");
}
Ознакомьтесь с полной документацией ! Это огромное улучшение качества жизни.
C ++ 11
С C ++ 11 с std::snprintf
, это уже стало довольно легко и безопасно задачей.
#include <memory>
#include <string>
#include <stdexcept>
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'
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
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 с помощью специального условия в snprintf
. С cppreference.com :
Возвращаемое значение
[...] Если результирующая строка усекается из-за ограничения buf_size, функция возвращает общее количество символов (не включая завершающий нулевой байт), которое было бы записано, если бы ограничение не было наложено.
Это означает, что желаемый размер равен числу символов плюс один , так что нулевой терминатор будет располагаться после всех других символов и что он может быть снова обрезан конструктором строки. Эта проблема была объяснена @ alexk7 в комментариях.
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
snprintf
вернет отрицательное число, если произошла ошибка, поэтому мы проверим, работало ли форматирование должным образом. Невыполнение этого требования может привести к тихим ошибкам или выделению огромного буфера, как указано @ead в комментариях.
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
Затем мы выделяем новый массив символов и назначаем его для std::unique_ptr
. Обычно это рекомендуется, так как вам не придется делать delete
это вручную .
Обратите внимание, что это небезопасный способ выделения unique_ptr
с пользовательскими типами, так как вы не можете освободить память, если конструктор выдает исключение!
std::unique_ptr<char[]> buf( new char[ size ] );
После этого мы, конечно, можем просто использовать snprintf
его по назначению и записать отформатированную строку в char[]
.
snprintf( buf.get(), size, format.c_str(), args ... );
Наконец, мы создаем и возвращаем новое std::string
из этого, удостоверяясь, что в конце опускается нулевой терминатор.
return std::string( buf.get(), buf.get() + size - 1 );
Вы можете увидеть пример в действии здесь .
Если вы также хотите использовать std::string
список аргументов, взгляните на эту суть .
Дополнительная информация для пользователей Visual Studio :
Как объясняется в этом ответе , Microsoft переименована std::snprintf
в _snprintf
(да, без std::
). Кроме того, MS устанавливает его как устаревшее и рекомендует использовать _snprintf_s
вместо него, однако _snprintf_s
не примет буфер равным нулю или меньшему, чем форматированный вывод, и не вычислит длину выходных данных, если это произойдет. Таким образом, чтобы избавиться от предупреждений об устаревании во время компиляции, вы можете вставить следующую строку вверху файла, которая содержит использование _snprintf
:
#pragma warning(disable : 4996)
Последние мысли
Многие ответы на этот вопрос были написаны до C ++ 11 и используют фиксированную длину буфера или vargs. Если вы не застряли в старых версиях C ++, я бы не рекомендовал использовать эти решения. В идеале идти по пути C ++ 20.
Поскольку решение C ++ 11 в этом ответе использует шаблоны, оно может генерировать довольно много кода, если его часто использовать. Однако, если вы не разрабатываете для среды с очень ограниченным пространством для двоичных файлов, это не будет проблемой, и все равно будет значительным улучшением по сравнению с другими решениями как в отношении ясности, так и безопасности.
Если эффективность использования пространства очень важна, эти два решения с помощью vargs и vsnprintf могут быть полезны.
НЕ ИСПОЛЬЗУЙТЕ какие-либо решения с фиксированной длиной буфера, которые просто вызывают проблемы.
boost::format
(как решение Kennytm использует здесь ).boost::format
уже поддерживает потоковые операторы C ++ тоже! Пример:cout << format("helloworld. a=%s, b=%s, c=%s") % 123 % 123.123 % "this is a test" << endl;
.boost::format
имеет наименьшее количество строк кода ... рецензируется и прекрасно интегрируется с потоками C ++.