Сучасний 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 ++.