Подтвердить что ты не робот

Как использовать С++ std:: ostream с форматированием в формате printf?

Я изучаю С++. cout - это экземпляр класса std::ostream. Как я могу напечатать форматированную строку с ней?

Я все еще могу использовать printf, но я хочу узнать больше о методе стиля C++, который может использовать все преимущества С++. Я думаю, что это возможно с помощью std::ostream, но я не могу найти правильный путь.

4b9b3361

Ответ 1

Единственное, что вы можете сделать с std::ostream напрямую, - это хорошо известный << -syntax:

int i = 0;
std::cout << "this is a number: " << i;

И существуют различные IO-манипуляторы, которые могут использоваться для влияния на форматирование, количество цифр и т.д. целых чисел, чисел с плавающей запятой и т.д..

Однако это не то же самое, что форматированные строки printf. С++ 11 не включает средства, позволяющие использовать форматирование строк так же, как оно используется с printf (кроме printf, которое вы, конечно, можете использовать на С++, если хотите).

В терминах библиотек, которые обеспечивают функциональность printf -style, существует boost::format, который позволяет использовать такой код (скопированный из синопсис):

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

Также обратите внимание, что есть предложение для включения форматирования printf в будущем в версии Standard. Если это будет принято, синтаксис, такой как ниже, может стать доступным:

std::cout << std::putf("this is a number: %d\n",i);

Ответ 2

Для реализации printf можно использовать параметры шаблона С++ 11:

#include <iostream>
#include <string>

inline std::ostream & mprintf(std::ostream & ostr, const char * fstr) throw()
{
    return ostr << fstr;
}

template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr, 
        const char * fstr, const T & x) throw()
{
    size_t i=0;
    char c = fstr[0];

    while (c != '%')
    {
        if(c == 0) return ostr; // string is finished
        ostr << c;
        c = fstr[++i];
    };
    c = fstr[++i];
    ostr << x;

    if(c==0) return ostr; // 

    // print the rest of the stirng
    ostr << &fstr[++i];
    return ostr;
}


template<typename T, typename... Args> 
std::ostream & mprintf(std::ostream & ostr,
        const char * fstr, const T & x, Args... args) throw()
{
    size_t i=0;
    char c = fstr[0];

    while (c != '%')
    {
        if(c == 0) return ostr; // string is finished
        ostr << c;
        c = fstr[++i];
    };
    c = fstr[++i];
    ostr << x;

    if(c==0) return ostr; // string is finished

    return mprintf(ostr, &fstr[++i], args...);
}

int main()
{
    int c = 50*6;
    double a = 34./67.;
    std::string q = "Hello!";

    // put only two arguments
    // the symbol after % does not matter at all
    mprintf(std::cout, "%f + %f = %a \n", c, a);

    // print string object: for real printf one should write q.c_str() 
    mprintf(std::cout, "message: \"%s\". \n", q);

    // the last argument will be ignored
    mprintf(std::cout, "%z + %f\n", (long)a, 12, 544 );

}

Выход

300 + 2 = %a 
message: "Hello!". 
2 + 12

Это очень простой код, и его можно улучшить.

1) Преимуществом является то, что он использует < для печати объектов в поток, поэтому вы можете помещать произвольные аргументы, которые могут быть выведены через < lt;

2) Он игнорирует тип аргумента в отформатированной строке: после того, как% может помещать произвольный символ даже в пробел. Выходной поток решает, как печатать соответствующий объект. Он также совместим с printf.

3) Недостатком является то, что он не может печатать процентный символ "%", нужно немного улучшить код.

4) Он не может печатать отформатированные номера, например% 4.5f

5) Если количество аргументов меньше, чем было предсказано форматированной строкой, тогда функция просто распечатает оставшуюся часть строки.

6) Если количество аргументов больше, чем прогнозируется форматированной строкой, оставшиеся аргументы игнорируются

Можно улучшить код, чтобы сделать 2) -6), чтобы полностью имитировать поведение printf. Однако, если вы следуете правилам printf, тогда только 3) и 4) должны быть исправлены.

Ответ 3

Ширина поля

Установить ширину поля очень просто. Для каждой переменной просто добавьте к ней setw (n). Как это:

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
  const int max = 12;
  const int width = 6;
  for(int row = 1; row <= max; row++) {
      for(int col = 1; col <= max; col++) {
          cout << setw(width) << row * col;
      }
      cout << endl;
  }
  return 0;
}

Обратите внимание, как "setw (n)" контролирует ширину поля, поэтому каждое число печатается внутри поля, которое остается одинаковым по ширине, независимо от ширины самого числа.

- Из "Учебника по программированию /C++" П. Лютуса.

Ответ 4

Пример вывода:

2017-12-20T16:24:47,604144+01:00 Hello, World!

Код (с использованием put_printf, продемонстрированным в put_timestamp):

#include <assert.h>
#include <chrono>
#include <iomanip>
#include <iostream>

class put_printf {
    static constexpr size_t failed = std::numeric_limits<size_t>::max(); // for any explicit error handling
    size_t stream_size; // excluding '\0'; on error set to 0 or to "failed"
    char buf_stack[2048+1]; // MAY be any size that fits on the stack (even 0), SHOULD be (just) large enough for most uses (including '\0')
    std::unique_ptr<char[]> buf_heap; // only used if the output doesn't fit in buf_stack
public:
    explicit put_printf(const char *format, ...)
            #if __GNUC__
            __attribute__ ((format (printf, 2, 3))) // most compelling reason for not using a variadic template; parameter 1 is implied "this"
            #endif
            {
        va_list args;
        va_start(args, format);
        const int res = vsnprintf(buf_stack, sizeof(buf_stack), format, args);
        va_end(args);
        if (res < 0) { // easily provoked, e.g., with "%02147483646i\n", i.e., more than INT_MAX-1 significant characters (only observed, no guarantee seen)
            stream_size = failed;
        } else if (res < sizeof(buf_stack)) { // preferred path
            stream_size = res;
        } else { // not artificially constrained
            try {
                const size_t buf_size = static_cast<size_t>(res) + 1; // avoids relying on "res < INT_MAX" (only observed, no guarantee seen)
                buf_heap.reset(new char[buf_size]); // observed to work even beyond INT_MAX=2^32-1 bytes
                va_start(args, format);
                if (vsnprintf(buf_heap.get(), buf_size, format, args) == res) stream_size = res;
                else stream_size = failed; // can't happen
                va_end(args);
            } catch (const std::bad_alloc&) { // insufficient free heap space (or an environment-specific constraint?)
                stream_size = failed;
            }
        }
    }
    friend std::ostream& operator<<(std::ostream& os, const put_printf& self) {
        if (self.stream_size == failed) {
            // (placeholder for any explicit error handling)
            return os;
        } else {
            // using write() rather than operator<<() to avoid a separate scan for '\0' or unintentional truncation at any internal '\0' character
            return os.write((self.buf_heap ? self.buf_heap.get() : self.buf_stack), self.stream_size);
        }
    }
};

class put_timestamp {
    const bool basic = false;
    const bool local = true;
public:
    friend std::ostream& operator<<(std::ostream& os, const put_timestamp& self) {
        const auto now = std::chrono::system_clock::now();
        const std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
        struct tm tm; if ((self.local ? localtime_r(&now_time_t, &tm) : gmtime_r(&now_time_t, &tm)) == nullptr) return os; // TODO: explicit error handling?
        static_assert(4 <= sizeof(int), "");
        const int microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch() % std::chrono::seconds(1)).count();
        assert(0 <= microseconds && microseconds < 1000000); // TODO: (how) do we know?
        // TODO: doesn't "point" in "decimal_point()" imply "dot"/"full stop"/"period", unlike an obviously neutral term like "mark"/"separator"/"sign"?
        const char decimal_sign = std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point() == '.' ? '.' : ','; // full stop accepted, comma preferred
        // TODO: all well and good for a locale-specific decimal sign, but couldn't the locale also upset microseconds formatting by grouping digits?
        os << std::put_time(&tm, self.basic ? "%Y%m%dT%H%M%S" : "%FT%T") << put_printf("%c%06i", decimal_sign, microseconds);
        if (! self.local) return os << "Z";
        const int tz_minutes = std::abs(static_cast<int>(tm.tm_gmtoff)) / 60;
        return os << put_printf(self.basic ? "%c%02i%02i" : "%c%02i:%02i", 0 <= tm.tm_gmtoff ? '+' : '-', tz_minutes / 60, tz_minutes % 60);
    }
};

int main() {
    // testing decimal sign
    ///std::cout.imbue(std::locale("en_GB"));
    ///std::cout.imbue(std::locale("fr_FR"));

    std::cout << put_timestamp() << " Hello, World!\n";
    #if 0
    typedef put_printf pf; // just to demo local abbreviation
    std::cout << "1: " << pf("%02147483646i\n"  , 1     ) << std::endl; // res < 0
    std::cout << "2: " << pf("%02147483643i%i\n", 1, 100) << std::endl; // res < 0
    std::cout << "3: " << pf("%02147483643i%i\n", 1,  10) << std::endl; // works
    std::cout << "4: " << pf("%02147483646i"    , 1     ) << std::endl; // works
    #endif
    return 0;
}

Комментарии о put_printf:

// Reasons for the name "put_printf" (and not "putf" after all):
// - put_printf is self-documenting, while using the naming pattern also seen in std::put_time;
// - it is not clear whether the proposed std::putf would support exactly the same format syntax;
// - it has a niche purpose, so a longer name is not an objection, and for frequent local uses
//     it is easy enough to declare an even shorter "typedef put_printf pf;" or so.
// Evaluation of delegating to vsnprintf() with intermediate buffer:
// (+) identical result without implementation and/or maintenance issues,
// (?) succeeds or fails as a whole, no output of successful prefix before point of failure
// (-) (total output size limited to INT_MAX-1)
// (-) overhead (TODO: optimal buf_stack size considering cache and VM page locality?)
// Error handling (an STL design problem?):
// - std::cout.setstate(std::ios_base::failbit) discards further std::cout output (stdout still works),
//     so, to be aware of an error in business logic yet keep on trucking in diagnostics,
//     should there be separate classes, or a possibility to plug in an error handler, or what?
// - should the basic or default error handling print a diagnostic message? throw an exception?
// TODO: could a function "int ostream_printf(std::ostream& os, const char *format, ...)"
//           first try to write directly into os.rdbuf() before using buf_stack and buf_heap,
//           and would that significantly improve performance or not?

Ответ 5

Я предлагаю использовать ostringstream вместо ostream, см. Следующий пример:

#include <vector>
#include <string>
#include <iostream>
#include "CppUnitTest.h"

#define _CRT_NO_VA_START_VALIDATION

std::string format(const std::string& format, ...)
{
    va_list args;
    va_start(args, format);
    size_t len = std::vsnprintf(NULL, 0, format.c_str(), args);
    va_end(args);
    std::vector<char> vec(len + 1);
    va_start(args, format);
    std::vsnprintf(&vec[0], len + 1, format.c_str(), args);
    va_end(args);
    return &vec[0];
}

пример использования:

std::ostringstream ss;
ss << format("%s => %d", "Version", Version) << std::endl;
Logger::WriteMessage(ss.str().c_str()); // write to unit test output
std::cout << ss.str() << std::endl; // write to standard output

Ответ 6

Когда мне нужны как типы безопасности cout, так и быстрое и простое форматирование простых переменных printf(), я смешиваю их так. Это уродливое исправление, но для меня это делается, когда мне нужно выводить такие вещи, как "02/07/2014 10:05 утра" вместе с некоторыми более сложными объектами:

#include <stdio>
#include <stdarg>
#include <stdlib>
#include <iostream>

#pragma hdrstop

using namespace std;


char* print(char* fmt, ...)
{
    static char buffer[80] = "";

    va_list argptr;
    va_start(argptr,fmt);

    vsprintf(buffer, fmt, argptr);

    va_end(argptr);

    return buffer;
}

#pragma argsused
int main(int argc, char* argv[])
{

cout << print("\n%06d\n%6d\n%6d\n%010.3f",1,12,123,123.456);

system("PAUSE>NUL");

return 0;

}

Ответ 7

Я написал самостоятельно, но придумал ответ, похожий на user3283405

Мое решение использует vasprintf() для получения форматирования и использует перегрузку операторов << std :: ostream для освобождения памяти в нужном месте.

Использование:

std::cout << putf(const char *format, ...); //Same format as C printf(3)

Код:

#define _GNU_SOURCE
#include <cstdarg>
#include <iostream>
#include <cstdio>

struct putf_r{
        char *s;
};

putf_r putf(const char *fmt, ...){
        va_list ap;
        va_start(ap, fmt);
        putf_r a;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
        vasprintf(&a.s, fmt, ap);
#pragma GCC diagnostic pop
        va_end(ap);
        return a;
}

std::ostream& operator<<(std::ostream& os, putf_r a){
        os<<a.s;
        free(a.s);
        return os;
}

int main(){
        std::cout << putf("%3d\n", 23) << putf("%a\n", 256.);
}

Обратите внимание, что компилятор не проверяет формат внутри putf(), поэтому флаг компилятора -Wformat-nonliteral не будет предупреждать о подозрительном коде в putf(), и вам нужно самостоятельно позаботиться о неконтролируемой проблеме строки формата.
Подробную информацию можно найти на GitHub