std :: форматування рядків, як sprintf


454

Я маю на формат std::stringз sprintfі відправити його в файловий потік. Як я можу це зробити?


6
коротке використання довгих історій 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 ++.
Тревор Бойд Сміт

@Ockonal - ради громади (я не могла менше піклуватися про свого представника), пропоную змінити свій вибір. Вибраний на даний момент, у першому фрагменті, відображає помилку, яка чекає, коли станеться довільна максимальна довжина. Другий фрагмент повністю ігнорує ваше заявлене бажання використовувати такі ваги, як sprintf. Я пропоную вам вибрати ТІЛЬКУ відповідь тут, яка є чистою, безпечною, покладається лише на стандарти C ++, перевірена та добре прокоментована. Що це моє - не має значення. Це об'єктивно вірно. Дивіться stackoverflow.com/questions/2342162/… .
Дуглас Дазеєко

@TrevorBoydSmith a std::format додано до C ++ 20 BTW: stackoverflow.com/a/57286312/895245 Дивовижно!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@CiroSantilli Я прочитав статтю про C++20щойно вчора, і побачив, що C++20скопіював boost(уже мільйонний раз), додавши std::formatдо C++20специфікації! Я був дуже радий! Майже кожен файл C ++, який я написав за останні 9 років, використовував boost::format. додавання офіційного виводу стилю printf до потоків на C ++ пройде довгий шлях IMO для всіх C ++.
Тревор Бойд Сміт

Відповіді:


333

Ви не можете зробити це прямо, тому що у вас немає доступу до основного буферу (до C ++ 11, см Dietrich Еппи коментаря ). Вам доведеться зробити це спочатку в c-string, а потім скопіювати його в std :: string:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

Але я не впевнений, чому б ви не просто використовували потоковий рядок? Я припускаю, що у вас є конкретні причини не просто робити це:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

17
Чарівне печиво char buf[100];робить це рішення не дуже надійним. Але істотна ідея є.
Джон Дайлінг

18
Іван, потоки не повільні. Єдина причина, що потоки здаються повільними, це те, що за замовчуванням iostreams синхронізуються з виходом C FILE, щоб змішані cout і printfs виводилися правильно. Вимкнення цього посилання (із закликом до cout.sync_with_stdio (помилково)) призводить до того, що потоки c ++ перевершать stdio, щонайменше, як для MSVC10.
Джимбо

72
Причина використання форматів полягає в тому, щоб дозволити локалізатору відновити структуру речення для іноземних мов замість жорсткого кодування граматики речення.
Martijn Courteaux

216
Чомусь інші мови використовують синтаксис, схожий на printf: Java, Python (новий синтаксис ще ближче до printf, ніж до потоків). Лише С ++ завдає цієї неправдивої гидоти невинним людям.
Quant_dev

9
Ще краще використовувати asprintf, який виділяє нову рядок з достатньою кількістю місця для утримання результату. Потім скопіюйте це, std::stringякщо вам подобається, і запам’ятайте freeоригінал. Крім того, можна поставити це в макрос, щоб будь-який хороший компілятор допоміг підтвердити формат для вас - ви не хочете розміщувати місце, doubleде %sочікується
Aaron McDaid

287

Сучасний 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 можуть бути корисними. НЕ ВИКОРИСТОВУЙТЕ будь-які рішення з фіксованою довжиною буфера, це просто задає проблеми.


2
Будь ласка, підкресліть у своїй відповіді для користувачів Visual Studio, що версія VS повинна бути принаймні 2013. З цієї статті видно, що вона працює лише з версією VS2013: Якщо буфер - це нульовий покажчик, а кількість - нуль, len повертається як кількість символів, необхідних для форматування виводу, не враховуючи завершення нуля. Щоб здійснити успішний виклик з однаковими параметрами аргументу та локалі, виділіть буфер із вмістом принаймні len + 1 символу.
ча

3
@moooeeeep Кілька причин. По-перше, метою тут є повернення std :: string, а не c-string, так що ви, мабуть, мали на увазі return string(&buf[0], size);чи щось подібне. По-друге, якщо ви повернете такий c-рядок, це призведе до невизначеної поведінки, оскільки вектор, який містить значення, на які ви вказуєте, буде недійсним при поверненні. По-третє, коли я почав вивчати C ++, стандарт не визначав, в якому порядку елементи потрібно зберігати всередині std::vector, тому доступ до його сховища за допомогою покажчика був невизначеним поведінкою. Зараз це спрацювало б, але я не бачу користі в цьому.
iFreilicht

2
@iFreilicht Новий std::stringбуде побудований з неявно перетвореного вектора ( ініціалізація копії ), який потім повертається як копія, як підказує функція підпису. Крім того, елементи а std::vectorє і завжди були призначені для зберігання безперервно . Але я вважаю, що в цьому може бути ніякої користі.
moooeeeep

4
Мені дуже подобається це рішення, проте я думаю, що рядок return string(buf.get(), buf.get() + size);повинен бути return string(buf.get(), buf.get() + size - 1);іншим, ви отримаєте рядок з нульовим символом на кінці. Я виявив, що це стосується gcc 4.9.
Філ Вільямс

3
Передача std :: string до% s викликає помилку компіляції ( помилка: не можна передавати об’єкт нетривіального типу 'std :: __ cxx11 :: basic_string <char>' через різноманітну функцію; виклик буде припинено під час виконання [-Wnon-pod -varargs] ) в clang 3.9.1, але в CL 19 він складає штраф і замість цього виконує під час виконання. Якийсь попереджувальний прапор, який я можу ввімкнути, щоб він також міг під час збирання в cl?
Zitrax

241

C ++ 11 рішення, яке використовується vsnprintf()внутрішньо:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

Більш безпечний і ефективніший (я тестував це, і це швидше) підхід:

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

Значення fmt_strпередається за значенням, щоб відповідати вимогам va_start.

ПРИМІТКА: "Більш безпечна" та "швидша" версія не працює в деяких системах. Отже, обидва досі перераховані. Крім того, "швидше" повністю залежить від правильності кроку попереднього розміщення, інакше це strcpyробить повільніше.


3
повільний. чому збільшити розмір на 1? І коли цей функціонал повертається -1?
0xDEAD BEEF

27
Ви перезаписуєте str.c_str ()? Хіба це не небезпечно?
квантовий

8
va_start з аргументом посилання має проблеми з MSVC. Він мовчить і повертає покажчики на випадкову пам'ять. В якості вирішення використовуйте std :: string fmt замість std :: string & fmt або напишіть об’єкт обгортки.
Стів Ханов

6
I + 1'd тому, що я знаю, що це, ймовірно, буде працювати з урахуванням того, як реалізовано більшість std :: рядків, однак c_str насправді не призначений для того, щоб змінити нижній рядок. Його передбачається лише для читання.
Дуг Т.

6
І щоб заздалегідь отримати отриману довжину рядка, див .: stackoverflow.com/a/7825892/908336 Я не бачу сенсу в збільшенні sizeкожної ітерації, коли ви можете отримати її за першим викликом vsnprintf().
Массуд Хаарі

107

boost::format() надає потрібну вам функціональність:

З конспекту бібліотек формату Boost:

Об'єкт форматування будується з рядка формату, а потім йому надаються аргументи через повторні виклики оператору%. Кожен з цих аргументів потім перетворюється на рядки, які в свою чергу об'єднуються в одну рядок відповідно до рядка формату.

#include <boost/format.hpp>

cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
// prints "writing toto,  x=40.230 : 50-th try"

5
ви можете підрізати бібліотеки, які вам потрібні, і за поштовх. Використання сулідного інструменту.
Хасан,

7
Формат підвищення не тільки великий, але і дуже повільний. Дивіться zverovich.net/2013/09/07/… та boost.org/doc/libs/1_52_0/libs/spirit/doc/html/spirit/karma/…
vitaut

14
Увімкнення прискорення в будь-якому місці вашого проекту негайно збільшує час складання. Для великих проектів це, мабуть, не має значення. Для невеликих проектів стимул - це тягар.
Quant_dev

2
@vitaut Хоча це жахливо витрачає ресурси в порівнянні з альтернативами. Як часто ви форматуєте рядки? Враховуючи, що це займає лише кілька мікросекунд, і більшість проектів, ймовірно, використовують лише кілька десятків разів, це не помітно в проекті, який не сильно фокусується на форматуванні рядків, правда?
AturSams

2
На жаль, формат boost :: працює не так: не приймає var_args. Дехто любить, щоб весь код, пов'язаний з однією програмою, виглядав однаково / використовував ті самі ідіоми.
xor007

88

C ++ 20 буде включати, std::formatщо нагадує sprintfз точки зору API, але повністю безпечний для типу, працює з визначеними користувачем типами та використовує синтаксис струнних синтаксисів формату Python. Ось як ви зможете відформатувати std::stringта записати його в потік:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

або

std::string s = "foo";
puts(std::format("Look, a string: {}", s).c_str());

Можна також використати бібліотеку {fmt} для форматування рядка та запису до нього stdoutабо потоку файлів за один раз:

fmt::print(f, "Look, a string: {}", s); // where f is a file stream

Як для sprintf або більшості інших відповідей тут, то, на жаль, вони використовують varargs і є по суті небезпечними, якщо ви не використовуєте щось на зразок formatатрибута GCC, який працює лише з рядками прямого формату. На наступному прикладі ви можете зрозуміти, чому ці функції небезпечні:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

де string_formatреалізація з відповіді Еріка Аронесті. Цей код компілюється, але він, швидше за все, вийде з ладу при спробі його запуску:

$ g++ -Wall -Wextra -pedantic test.cc 
$ ./a.out 
Segmentation fault: 11

Відмова : Я автор {fmt} та C ++ 20 std::format.


ІМХО ви пропускаєте включити error: 'fmt' has not been declared
Серхіо

Це просто фрагмент, а не повний код. Очевидно, що вам потрібно включити <fmt / format.h> і поставити код у функцію.
vitaut

для мене це не так очевидно, IMHO, ви повинні включити його до фрагменту, дякую за відгук
Серхіо

1
Подібна fmtреалізація була додана до C ++ 20! stackoverflow.com/a/57286312/895245 fmt наразі вимагає підтримки. Дивовижна робота!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

2
@vitaut Дякую за вашу роботу над цим!
Керт Ніколс


15

Я написав свій власний, використовуючи vsnprintf, тому він повертає рядок замість того, щоб створювати власний буфер.

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

Таким чином, ви можете використовувати це як

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

Це робить повну додаткову копію даних, її можна використовувати vsnprintfбезпосередньо в рядку.
Mooing Duck

1
Використовуйте код у stackoverflow.com/a/7825892/908336, щоб отримати попередньо отриману довжину рядка. І ви можете використовувати смарт-покажчики для безпечного для винятків коду:std::unique_ptr<char[]> buffer (new char[size]);
Massood Khaari

Я не впевнений, що це правильно в резервному випадку; Я думаю, вам потрібно зробити va_copy vl для другого vsnprintf (), щоб правильно бачити аргументи. Для прикладу дивіться: github.com/haberman/upb/blob/…
Джош Хаберман

15

Для того, щоб форматувати std::stringв 'sprintf' спосіб, зателефонуйте snprintf(аргументи nullptrта 0), щоб отримати необхідну довжину буфера. Запишіть свою функцію, використовуючи такий варіант C ++ 11:

#include <cstdio>
#include <string>
#include <cassert>

template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
  int length = std::snprintf( nullptr, 0, format, args... );
  assert( length >= 0 );

  char* buf = new char[length + 1];
  std::snprintf( buf, length + 1, format, args... );

  std::string str( buf );
  delete[] buf;
  return str;
}

Компілюйте з підтримкою C ++ 11, наприклад в GCC: g++ -std=c++11

Використання:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);

std :: snprintf недоступний у VC ++ 12 (Visual Studio 2013). Замініть його замість _snprintf.
Shital Shah

чому ви не використовуєте char buf[length + 1];замість цього char* buf = new char[length + 1];?
Беруз.М

Різниця між використанням char[]і char*новим полягає в тому, що в першому випадку buf буде виділятися на стек. Для невеликих буферів це нормально, але оскільки ми не можемо гарантувати розмір отриманого рядка, його трохи краще використовувати new. Наприклад, на моїй машині string_sprintf("value: %020000000d",5)надрукуйте нецензурну кількість провідних нулів перед номером 5, основне скидання ядра при використанні масиву в стеці, але працює добре, коли використовується динамічно виділений масивnew char[length + 1]
user2622016

дуже розумна ідея отримати фактичний розмір бафу, необхідний для форматованого виводу
Кріс,

1
@ user2622016: Дякую за рішення! Зверніть увагу, що std::move це зайве .
Михай Тодор

14

[ред .: 20/05/25] ще краще ...:
У заголовку:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

Функція PRINTSTRING(r)полягає в обслуговуванні графічного інтерфейсу або терміналу або будь-яких спеціальних вихідних потреб, використовуючи #ifdef _some_flag_за замовчуванням:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[редагувати '17 / 8/31] Додавання варіативної шаблонової версії 'vtspf (..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

яка фактично обмежена комою (замість) іноді перешкоджаючих <<-операторів, використовуваних так:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);


[редагувати] Адаптовано для використання методики у відповіді Еріка Аронесті (вище):

#include <string>
#include <cstdarg>
#include <cstdio>

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

[попередня відповідь]
Дуже пізня відповідь, але для тих, хто, як і я, так подобається "спринт": я написав і використовую такі функції. Якщо вам це подобається, ви можете розширити% -пції, щоб тісніше підходити до sprintf; тих, що там зараз є, достатньо для моїх потреб. Ви використовуєте stringf () і stringfappend () так само, як і sprintf. Просто пам’ятайте, що параметри для ... повинні бути типами POD.

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

@MooingDuck: Змінено параметр функції відповідно до коментаря Дена до відповіді Аронесті. Я використовую тільки Linux / gcc, і в fmtякості посилання він працює чудово. (Але я припускаю, що люди захочуть пограти з іграшками, тож ...) Якщо є якісь інші припущені помилки, чи можете ви, будь ласка, розібратися?
slashmais

Я неправильно зрозумів, як працює його код, і подумав, що це робить для багатьох змін. Перегляд показує, що я помилився. Ваш код правильний.
Mooing Duck

Основою для відповіді Еріка Аронесті є червона оселедець. Його перший зразок коду небезпечний, а другий - неефективним і незграбним. Про чисту реалізацію чітко вказує той факт, що, якщо buf_siz будь-якої з сімейств vprintf функцій дорівнює нулю, нічого не записується, а буфер може бути нульовим покажчиком, однак повернене значення (кількість байтів, які були б записані, не включаючи нульовий термінатор) все ще обчислюється і повертається. Якість виробництва відповідь тут: stackoverflow.com/questions/2342162 / ...
Дуглас Daseeco

10

Ось як це робить Google: StringPrintf(Ліцензія BSD),
а facebook робить це досить схожим чином: StringPrintf(Ліцензія Apache)
Обидва надають і зручне StringAppendF.


10

Мої два центи на це дуже популярне питання.

Щоб процитувати статтю 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);
}

Це працює досить добре :)

Варіантні шаблони підтримуються лише в C ++ 11. Відповідь від pixelpoint показує аналогічну техніку за допомогою старих стилів програмування.

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

Редагувати

Як вказував alexk7, A +1потрібен для зворотного значення std::snprintf, оскільки нам потрібно мати простір для \0байта. Інтуїтивно зрозуміло, що для більшості архітектур, відсутніх +1волі, requiredціле число буде частково перезаписане символом a 0. Це станеться після оцінки requiredфактичного параметра для std::snprintf, тому ефект не повинен бути видимим.

Однак ця проблема може змінитися, наприклад, при оптимізації компілятора: що робити, якщо компілятор вирішить використовувати регістр requiredзмінної? Це вид помилок, які іноді призводять до проблем із безпекою.


1
snprintf завжди додає закінчуючий нульовий байт, але повертає кількість символів без нього. Чи не завжди цей код пропускає останній символ?
alexk7

@ alexk7, приємний улов! Я актуалізую відповідь. Код не пропускає останнього символу, а пише поза кінцем bytesбуфера, ймовірно, над requiredцілим числом (яке, на щастя, у цій точці вже оцінено).
Дакав

1
Лише невеликий натяк: маючи розмір буфера 0, ви можете передавати nullptrаргумент в якості буфера, виключаючи char b;рядок у вашому коді. ( Джерело )
iFreilicht

@iFreilicht, виправити. Також +1
Дакав

2
Використання "байтів char [обов'язкових]" буде виділено на стек замість купи, це може бути небезпечно для рядків великого формату. Подумайте про використання нового замість цього. Yann
Yannuth

9
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


9

Випробувано, відповідь якості виробництва

Ця відповідь обробляє загальний випадок методами, що відповідають стандартам. Такий самий підхід наводиться як приклад на 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; }

Прогнозована лінійна ефективність

Два проходи необхідні для безпечної, надійної та передбачуваної функції багаторазового використання відповідно до специфікацій питання. Припущення щодо розподілу розмірів вагів у функції багаторазового використання є поганим стилем програмування, і цього слід уникати. У цьому випадку довільно великі подання змінної довжини вагів є ключовим фактором вибору алгоритму.

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

У вищезазначеній реалізації, готовій до реалізації, перший запуск - це такий сухий цикл для визначення розміру виділення. Розподіл не відбувається. Розбір директив printf та читання ваг був зроблений надзвичайно ефективно протягом десятиліть. Код для багаторазового використання повинен бути передбачуваним, навіть якщо мала неефективність тривіальних випадків повинна бути принесена в жертву.

Безпека та надійність

Після лекції на заході в Кембриджі Ендрю Кеніг сказав невеликій групі з нас: "Користувацькі функції не повинні покладатися на експлуатацію невдач для невиключної функціональності". Як завжди, з того часу його мудрість виявилася правдою. Виправлені та закриті проблеми з помилками безпеки часто вказують на повторні взломи в описі отвору, який експлуатувався до виправлення.

Про це йдеться в офіційній пропозиції щодо перегляду формальних стандартів для функції нульового буфера в Альтернативі sprintf, Пропозиції щодо редакції C9X, Документу ISO IEC WG14 N645 / X3J11 96-008 . Довільно довгий рядок, вставлений у директиву щодо друку, "% s" в межах обмежень доступності динамічної пам'яті, не є винятком і не повинен використовуватися для створення "Невиключної функціональності".

Розгляньте пропозицію поряд із прикладом коду, наведеним у нижній частині сторінки C ++ Reference.org, посилаючись на перший абзац цієї відповіді.

Крім того, тестування випадків невдач рідко є надійним випадком успіху.

Переносність

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

Використання стека

Використання стека в 1-му дзвінку до std :: vsnprintf буде менше або рівне тому, що у другому, і воно буде звільнене до початку 2-го виклику. Якщо перший виклик перевищує доступність стеку, то і std :: fprintf теж не вдасться.


Короткий і надійний. Він може вийти з ладу для HP-UX, IRIX, Tru64, які мають невідповідні vsnprintf-s. EDIT: також, враховуючи, як два проходи можуть вплинути на виступи, особливо. Що стосується найбільш поширеного форматування невеликих рядків, чи вважали ви здогадом для початкового пропуску, який може бути достатньо великим?
Інженер

FWIW, здогад, про який я мав на увазі, використовує виділений стеком буфер, де відбувається перший запуск. Якщо вона підходить, це економить витрати на другий запуск та динамічний розподіл, який відбувається там. Імовірно, невеликі струни частіше використовуються, ніж великі. У моєму сирому еталоні ця стратегія (майже) вдвічі зменшує час роботи для невеликих рядків і знаходиться в межах декількох відсотків (можливо, фіксованих накладних витрат) вищезгаданої стратегії. Чи можете ви детальніше розробити проект C ++ 11, в якому використовується сухий пробіг тощо? Я хотів би прочитати про це.
Інженер

@Engineerist, ваші питання були вирішені в тілі відповіді, вище та нижче коду. Підтеми можна полегшити для читання таким чином.
Дуглас Дазеєко

6

C ++ 20 std::format

Це прибуло! Ця функція описана за адресою: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html та використовує подібний до Python.format() синтаксис, .

Я очікую, що використання буде таким:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

Я спробую, коли підтримка надійде до GCC, GCC 9.1.0 з g++-9 -std=c++2a ще не підтримує її.

API додасть новий std::formatзаголовок:

Пропонований API форматування визначений у новому заголовку <format>і не повинен впливати на існуючий код.

Існуюча fmtбібліотека вимагає її реалізувати, якщо вам потрібна поліфайл: https://github.com/fmtlib/fmt

Впровадження C ++ 20 std::format.

і раніше згадувалося на: std :: string formatting like sprintf


5

На основі відповіді Еріка Аронесті:

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()який був у оригінальній відповіді.


1
Основою для відповіді Еріка Аронесті є червона оселедець. Його перший зразок коду небезпечний, а другий - з циклом неефективним і незграбним. Про чисту реалізацію чітко вказує той факт, що, якщо buf_siz будь-якої з сімейств vprintf функцій дорівнює нулю, нічого не записується, а буфер може бути нульовим покажчиком, однак повернене значення (кількість байтів, які були б записані, не включаючи нульовий термінатор) все ще обчислюється і повертається. Якість виробництва відповідь тут: stackoverflow.com/questions/2342162 / ...
Дуглас Daseeco

Відповідь Еріка Аронесті була відредагована з моменту додавання моєї. Я хотів виділити можливість використання вектора <char> для зберігання рядків у міру їх побудови. Я часто використовую цю техніку під час виклику функцій C із коду C ++. Цікаво, що на запитання зараз 34 відповіді.
ЧетС

Приклад cppreference.com на сторінці vfprintf був доданий пізніше. Я вважаю, що найкраща відповідь - це прийнята на даний момент відповідь, використовуючи потокові рядки замість варіанту printf - це спосіб C ++. Однак моя відповідь зробила додаткову цінність, коли вона була надана; Це було поступово краще, ніж інші відповіді в той час. Тепер у стандарті є string_view, пакети параметрів та шаблон Variadic, нова відповідь може включати ці функції. Що стосується моєї відповіді, хоча вона, можливо, більше не заслуговує на додаткове голосування, вона не заслуговує на те, щоб бути видаленою чи знятою з голосів, тому я залишаю її як такою.
ЧетС

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

1
+1 для розумної ідеї, але не дуже зрозуміло, що _vscprintfтаке. Я думаю, ви повинні детальніше розглянути цю відповідь.
Дакав

3

рядок не має того, що вам потрібно, але std :: stringstream робить. Створіть рядок, щоб створити рядок, а потім витягніть рядок. Ось вичерпний список речей, які ви можете зробити. Наприклад:

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

дасть вам 10 десяткових знаків точності при друкуванні дублера або поплавця.


8
яка все ще не дає нічого біля контрольного printf, але дає вам ... але приємно.
Ерік Аронестій

3

Ви можете спробувати це:

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

Якщо ви працюєте в системі, яка має 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
Я б додав цей рядок як декларацію раніше format, оскільки він говорить gcc перевірити типи аргументів і дати гідне попередження з -Wall:std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
Aaron McDaid

2
Я щойно додав дзвінок до va_end. "якщо va_end не викликається перед функцією, яка викликає va_start або va_copy повертається, поведінка не визначена." - docs
Aaron McDaid

1
Ви повинні перевірити результат повернення vasprintf, оскільки значення вказівника не визначене при відмові. Тому, можливо, включіть <new> і додайте: if (size == -1) {кинь std :: bad_alloc (); }
Ніл Макгілл

Добре, я відповідним чином змінив відповідь, і вирішив просто поставити коментар, а не робити throw std::bad_alloc();, оскільки я не використовую винятки C ++ у своїй кодовій базі, і люди, які це роблять, можуть легко додати її на основі на коментар джерела та ваш коментар тут.
Томас Перл

2

Це код, який я використовую для цього у своїй програмі ... Це нічого не фантазії, але це робить трюк ... Зауважте, вам доведеться коригувати свій розмір, як це потрібно. 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;
}

4
Ініціалізація textString вже встановлює весь буфер в нуль. Не потрібно згадувати ...
EricSchaefer

Це робить повну додаткову копію даних, її можна використовувати vsnprintfбезпосередньо в рядку.
Mooing Duck

2

Ідею взяв з відповіді Дакава та піксельпойнта . Я трохи пограв і отримав таке:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

Зважаючи на практику нормального програмування, я вважаю, що коду має бути достатньо, проте я все ще відкритий для більш безпечних альтернатив, які все ще досить прості і не потребують C ++ 11.


І ось ще одна версія, яка використовує початковий буфер для запобігання другого виклику, vsnprintf()коли початкового буфера вже достатньо.

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(Виявляється, що ця версія просто схожа на відповідь Піті Онгмонгколкула , лише те, що вона не використовується newі delete[], а також визначає розмір при створенні std::string.

Ідея тут не використовувати newі delete[]полягає в тому, щоб використовувати стек над купою, оскільки йому не потрібно викликати функції розподілу та розподілу, проте, якщо їх правильно не використовувати, це може бути небезпечним переповненням буфера в деяких (можливо, старих, або можливо, просто вразливі) системи. Якщо це викликає занепокоєння, я настійно пропоную використовувати newі delete[]замість цього. Зауважте, що тут є єдине занепокоєння щодо розподілу, як vsnprintf()це вже називається з обмеженнями, тому визначення ліміту на основі розміру, виділеного на другому буфері, також перешкоджатиме цьому.)


2

Я зазвичай використовую це:

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

Недолік: не всі системи підтримують вспрінт


vasprintf приємно - проте вам потрібно перевірити код повернення. На -1 буфер матиме невизначене значення. Потрібно: якщо (розмір == -1) {кинь std :: bad_alloc (); }
Ніл Макгілл

2

Нижче трохи змінена версія відповіді @iFreilicht, оновлена ​​до C ++ 14 (використання make_uniqueфункції замість необробленої декларації) та додана підтримка std::stringаргументів (на основі статті Кенні Керра )

#include <iostream>
#include <memory>
#include <string>
#include <cstdio>

template <typename T>
T process_arg(T value) noexcept
{
    return value;
}

template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
    return value.c_str();
}

template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
    const auto fmt = format.c_str();
    const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
    auto res = std::string(buf.get(), buf.get() + size - 1);
    return res;
}

int main()
{
    int i = 3;
    float f = 5.f;
    char* s0 = "hello";
    std::string s1 = "world";
    std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}

Вихід:

i = 3, f = 5.000000, s = hello world

Не соромтеся при бажанні злити цю відповідь з оригінальною.



1

Ви можете відформатувати вихід C ++ у cout, використовуючи заголовок iomanip. Переконайтеся, що ви включили файл заголовка iomanip перед тим, як використовувати будь-яку з допоміжних функцій, таких як setpoint, setfill тощо.

Ось фрагмент коду, який я раніше використовував для друку середнього часу очікування у векторі, який я "накопичив".

#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>

...

cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;

Ось короткий опис того, як ми можемо форматувати потоки C ++. http://www.cprogramming.com/tutorial/iomanip.html


1

Можуть виникнути проблеми, якщо буфер недостатньо великий для друку рядка. Ви повинні визначити довжину відформатованого рядка, перш ніж надрукувати там відформатоване повідомлення. Я роблю власного помічника в цьому (тестується на Windows і Linux GCC ), і ви можете спробувати використовувати його.

String.cpp: http://pastebin.com/DnfvzyKP
String.h: http://pastebin.com/7U6iCUMa

String.cpp:

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

Що стосується рядка vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);- Чи безпечно припустити, що в буфері рядка є місце для закінчення нульового символу? Чи є такі реалізації, які не виділяють розмір + 1 символ. Це було б безпечніше зробитиdst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
drwatsoncode

Очевидно, відповідь на мій попередній коментар: Ні, це НЕ БЕЗПЕЧНО припустити, що є нульовий символ. Зокрема щодо специфікації C ++ 98: "Доступ до значення у data () + size () призводить до невизначеного поведінки . Немає гарантій, що нульовий символ припиняє послідовність символів, вказану на значення, повернене цією функцією. Див. Рядок :: c_str для функції, яка забезпечує таку гарантію. Програма не повинна змінювати жодного символу цієї послідовності. "Однак специфікація C ++ 11 вказує на це dataі c_strє синонімами.
drwatsoncode


1

Дуже-дуже просте рішення.

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

1

Я усвідомлюю, що на це відповіли багато разів, але це більш стисло:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

приклад:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

Дивіться також http://rextester.com/NJB14150


1

ОНОВЛЕННЯ 1 : доданоfmt::format тести

Я взяв власне дослідження щодо впроваджених тут методів і отримав діаметрально протилежні результати порівняно із згаданими тут.

Я використовував 4 функції протягом 4-х методів:

  • варіативна функція + vsnprintf+std::unique_ptr
  • варіативна функція + vsnprintf+std::string
  • функція варіативного шаблону + std::ostringstream+ std::tuple+utility::for_each
  • fmt::formatфункція з fmtбібліотеки

Для тестового бекенда googletestвикористано.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

for_eachРеалізація взята звідси: перебрати кортеж

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

Тести:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

The UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp :

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp :

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

РЕЗУЛЬТАТИ :

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

Як ви бачите, реалізація через vsnprintf+ std::stringдорівнює fmt::format, але швидше, ніж через vsnprintf+ std::unique_ptr, що швидше, ніж через std::ostringstream.

Тести, складені Visual Studio 2015 Update 3та запущені в Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.