std:: форматирование строк как sprintf
Я должен отформатировать std::string
С sprintf
и отправить его в файловый поток. Как я могу это сделать?
30 ответов
вы не можете сделать это напрямую, потому что у вас нет доступа на запись к базовому буферу (до c++11; см.комментарий). Вам нужно будет сделать это сначала в C-строке, а затем скопировать ее в 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();
в 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
выводит его медленнее.
используя C++11 std::snprintf
, это становится довольно простой и безопасной задачей. Я вижу много ответов на этот вопрос, которые, по-видимому, были написаны до времени C++11, которые используют фиксированные длины буферов и vargs, что я бы не рекомендовал по соображениям безопасности, эффективности и ясности.
#include <memory>
#include <iostream>
#include <string>
#include <cstdio>
using namespace std; //Don't if you're in a header-file
template<typename ... Args>
string string_format( const std::string& format, Args ... args )
{
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for ''
unique_ptr<char[]> buf( new char[ size ] );
snprintf( buf.get(), size, format.c_str(), args ... );
return string( buf.get(), buf.get() + size - 1 ); // We don't want the '' inside
}
приведенный выше фрагмент кода лицензируется в разделе Лицензия CC0 1.0.
построчно объяснение:
цель: пишите на char*
С помощью std::snprintf
а затем преобразовать это в std::string
.
во-первых, мы определяем желаемую длину массива.
от cppreference.com:
возвращаемое значение
[...] Если результирующая строка усекается из-за ограничения buf_size, функция возвращает общее количество символов (не в том числе завершающий нулевой байт), который был бы написан, если бы предел был не навязывать.
это означает, что желаемый размер - это количество символов плюс один, так что нуль-Терминатор будет сидеть после всех других символов и что он может быть снова отрезан строковым конструктором. Эта проблема была объяснена @alexk7 в комментариях.
затем мы выделяем новый массив символов и назначаем его std::unique_ptr
. Это вообще советую, так как вам не придется вручную delete
это снова.
обратите внимание, что это не безопасный способ выделить unique_ptr
с пользовательскими типами, поскольку вы не можете освободить память, если конструктор выдает исключение!
после этого, мы можем, конечно, просто использовать snprintf
для его целевого использования и записи форматированной строки в char[]
а затем создать и вернуть новый std::string
от этого.
вы можете увидеть пример в действии здесь.
если вы хотите использовать std::string
в списке аргументов, взгляните на в этом суть.
дополнительная информация Visual Studio потребители:
как поясняется в ответ, Microsoft переименовала std::snprintf
to _snprintf
(да, без std::
). MS далее устанавливает его как устаревший и советует использовать _snprintf_s
вместо этого, однако _snprintf_s
не будет принимать буфер равным нулю или меньше форматированного вывода и не будет вычислять длину выходных данных, если это произойдет.
Поэтому, чтобы избавиться от предупреждений об устаревании во время компиляции, вы можете вставить следующую строку в верхней части файла, который содержит использование _snprintf
:
#pragma warning(disable : 4996)
boost::format()
обеспечивает функциональность, которую вы хотите:
Как из библиотеки формата Boost synopsis:
объект format строится из format-string, а затем получает аргументы путем повторных вызовов оператору%. Каждый из этих аргументов преобразуются в строки, которые, в свою очередь, объединяются в одну строку в соответствии с форматом строки.
#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"
к сожалению, большинство ответов здесь используют varargs, которые по своей сути небезопасны, если вы не используете что-то вроде GCC format
атрибут, который работает только со строками литерального формата. Вы можете увидеть, почему эти функции небезопасны в следующем примере:
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
возможно снабдить сейф printf
и расширить его до формата std::string
использование шаблонов (variadic). Это было сделано в библиотека fmt, который обеспечивает безопасную альтернативу sprintf
возвращение std::string
:
std::string format_str = "The answer is %d";
std::string result = fmt::sprintf(format_str, 42);
fmt отслеживает типы аргументов, и если тип не соответствует спецификации формата, нет ошибки сегментации, просто исключение.
отказ от ответственности: я автор этой библиотеки.
Если вы хотите только синтаксис, подобный printf (без вызова printf самостоятельно), посмотрите на Увеличить Формат.
Я написал свой собственный, используя 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);
[edit '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);
[edit] адаптировано для использования техники в ответе Эрика Аронести (выше):
#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" - путь: я написаны и используются следующие функции. Если вам это нравится, вы можете расширить %-options, чтобы более точно соответствовать 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;
}
для того, чтобы формат 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 std::move(str);
}
компиляция с поддержкой C++11, например в GCC:g++ -std=c++11
использование:
std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
вот как это делает google:StringPrintf
(лицензия BSD)
и facebook делает это довольно похожим образом:StringPrintf
(Лицензия Apache)
Оба обеспечивают удобный StringAppendF
тоже.
мои пять копеек на этот очень популярный вопрос.
цитировать на странице справочника о printf
функции:
после успешного возврата эти функции возвращают количество напечатанных символов (за исключением нулевого байта, используемого для завершения вывода в строки).
функции snprintf () и vsnprintf () не записывают больше байтов размера (включая завершающий нулевой байт ('\0')). Если выход был усечен из-за это ограничение, то возвращаемое значение-это количество символов (исключая завершающий нулевой байт), которые были бы записаны в конечную строку, если бы было достаточно места. Таким образом, возвращаемое значение size или more означает, что вывод был усечен.
другими словами, разумная реализация 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);
}
он работает довольно хорошо:)
Шаблоны Variadic поддерживаются только в C++11. Ответ из pixelpoint показать аналогичную технику, используя старые стили программирования.
странно, что C++ не имеет такой вещи из коробки. Они недавно добавили to_string()
, который на мой взгляд является большим шагом вперед. Мне интересно, добавят ли они .format
оператором std::string
в конце концов...
редактировать
как отметил alexk7, A +1
необходимо для возвращаемого значения std::snprintf
, так как нам нужно место для байт. Интуитивно, на большинстве архитектур отсутствует
+1
вызывает required
целое число, которое будет частично перезаписано с помощью 0
. Это произойдет после оценка required
как фактический параметр для std::snprintf
, поэтому эффект не должен быть видимым.
однако эта проблема может измениться, например, при оптимизации компилятора: что делать, если компилятор решит использовать регистр для required
переменной? Это ошибки, которые иногда приводят в вопросах безопасности.
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
на основе ответа, предоставленного Эриком Аронести:
std::string string_format(const std::string &fmt, ...) {
std::vector<char> str(100,'');
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()
что было в исходном ответе.
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);
}
string не имеет того, что вам нужно, но std::stringstream делает. Используйте stringstream для создания строки, а затем извлеките строку. здесь Это полный список того, что вы можете сделать. Например:
cout.setprecision(10); //stringstream is a stream like cout
даст вам 10 десятичных знаков точности при печати двойного или float.
вы можете попробовать это:
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 );
Это код, который я использую для этого в своей программе... Ничего особенного,но это помогает... Обратите внимание, что вам придется настроить свой размер, как это применимо. MAX_BUFFER для меня-1024.
std::string Format ( const char *fmt, ... )
{
char textString[MAX_BUFFER*5] = {''};
// -- Empty the buffer properly to ensure no leaks.
memset(textString, '', 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;
}
взял идею из Dacav и pixelpoint ответ. Я немного поиграл и получил это:
#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('');
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('');
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
и . Обратите внимание, что единственная проблема здесь заключается в распределении как vsnprintf()
уже вызывается с ограничениями, поэтому указание предела на основе размера, выделенного на втором буфере, также предотвратит их.)
Я обычно использую этот:
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;
}
недостаток: не все системы поддерживают vasprint
Если вы находитесь в системе, которая имеет 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;
}
очень-очень простое решение.
std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
ниже слегка измененная версия ответа @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
не стесняйтесь объединять этот ответ с оригинальным, если это необходимо.
Испытанный, Ответ Качества Продукции
этот ответ обрабатывает общий случай с методами, совместимыми со стандартами. Такой же подход приведен в качестве примера на 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; }
одно из решений, которое я выбрал, - это сделать это с помощью sprintf непосредственно в буфер std:: string, сделав указанный буфер достаточно большим:
#include <string>
#include <iostream>
using namespace std;
string l_output;
l_output.resize(100);
for (int i = 0; i < 1000; ++i)
{
memset (&l_output[0], 0, 100);
sprintf (&l_output[0], "\r%i", i);
cout << l_output;
cout.flush();
}
Итак, создайте строку std::, измените ее размер, получите доступ к ее буферу напрямую...
Poco Foundation библиотека имеет очень удобную функцию форматирования, которая поддерживает std:: string как в строке формата, так и в значениях:
вы можете форматировать вывод C++ в cout с помощью файла заголовка iomanip. Убедитесь, что вы включаете файл заголовка iomanip перед использованием любой из вспомогательных функций, таких как setprecision, setfill etc.
вот фрагмент кода, который я использовал в прошлом для печати среднего времени ожидания в векторе, который я "накопил".
#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
могут возникнуть проблемы, если буфер недостаточно велик для печати строки. Перед печатью отформатированного сообщения необходимо определить длину отформатированной строки. Я делаю собственный помощник для этого (протестирован на Windows и Linux GCC), и вы можете попробовать использовать его.
строку.cpp:http://pastebin.com/DnfvzyKP
Строка.ч: http://pastebin.com/7U6iCUMa
строку.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;
}
строку.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();
Я дал ему попробовать, с регулярные выражения. Я реализовал его для строк ints и const в качестве примера, но вы можете добавить любые другие типы (POD типы, но с указателями вы можете напечатать что-нибыдь).
#include <assert.h>
#include <cstdarg>
#include <string>
#include <sstream>
#include <regex>
static std::string
formatArg(std::string argDescr, va_list args) {
std::stringstream ss;
if (argDescr == "i") {
int val = va_arg(args, int);
ss << val;
return ss.str();
}
if (argDescr == "s") {
const char *val = va_arg(args, const char*);
ss << val;
return ss.str();
}
assert(0); //Not implemented
}
std::string format(std::string fmt, ...) {
std::string result(fmt);
va_list args;
va_start(args, fmt);
std::regex e("\{([^\{\}]+)\}");
std::smatch m;
while (std::regex_search(fmt, m, e)) {
std::string formattedArg = formatArg(m[1].str(), args);
fmt.replace(m.position(), m.length(), formattedArg);
}
va_end(args);
return fmt;
}
вот пример его использования:
std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;
выход:
Я Боб, и у меня есть 3 кошки
Это можно опробовать. простой. на самом деле не использует нюансы класса string.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <exception>
using namespace std;
//---------------------------------------------------------------------
class StringFormatter
{
public:
static string format(const char *format, ...);
};
string StringFormatter::format(const char *format, ...)
{
va_list argptr;
va_start(argptr, format);
char *ptr;
size_t size;
FILE *fp_mem = open_memstream(&ptr, &size);
assert(fp_mem);
vfprintf (fp_mem, format, argptr);
fclose (fp_mem);
va_end(argptr);
string ret = ptr;
free(ptr);
return ret;
}
//---------------------------------------------------------------------
int main(void)
{
string temp = StringFormatter::format("my age is %d", 100);
printf("%s\n", temp.c_str());
return 0;
}