Сучасний 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 .
Пояснення за рядком:
Мета: Напишіть у achar*за допомогою, std::snprintfа потім перетворіть його на astd::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 і використовували фіксовану довжину буфера або ваги. Якщо ви не зациклюєтесь на старих версіях 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 ++.