Использование различных спецификаторов формата c в C++

Как и в c, мы можем использовать различные спецификаторы формата, такие как

  • %nd, где n — число, чтобы напечатать число, занимающее не менее n пробелов.
  • %0nd то же, что и выше, за исключением предварительного заполнения нулями " %05d ",3 => 00003
  • %.nf для установки точности n после запятой
  • так далее ....

Так есть ли способ использовать их с std::cout?

Я получил несколько отрицательных отзывов на недавнем курсе (C++ для программистов на C) на Coursera за использование printf вместо cout, потому что я хотел немного отформатировать :(


person Harry    schedule 12.12.2013    source источник
comment
Прочтите об манипуляторах потока.   -  person Some programmer dude    schedule 12.12.2013
comment
Может быть, это поможет вам. stackoverflow.com/questions/275484/cout-formatting   -  person SoulRayder    schedule 12.12.2013
comment
То, что иногда может быть лучшим из обоих миров, — это Boost.Format. Он безопасен для типов и гораздо более компактен, чем <iomanip> в C++. Возможно, это вряд ли полезно в курсе, но полезно знать об этом.   -  person chris    schedule 12.12.2013
comment
@chris К сожалению, он использует % в качестве оператора вставки, а также спецификатора формата, что приводит к практически нечитаемому коду. (Python попробовал это и, наконец, переключился на использование функций-членов string, format. Решение не работает так же хорошо в C++, потому что для него требуется средство безопасного типа varargs.) И, конечно, даже с boost::format вы бы хотите использовать манипуляторы, чтобы информация о форматировании была отделена от текста (что важно для обслуживания).   -  person James Kanze    schedule 12.12.2013


Ответы (5)


Для %nd %0nd эквивалентами C++ являются std::setw() и std::setfill().

#include <iostream>     // std::cout, std::endl
#include <iomanip>      // std::setfill, std::setw

int main () {
  std::cout << std::setfill ('x') << std::setw (10);
  std::cout << 77 << std::endl;
  return 0;
}

Выход: xxxxxxxx77

%.nf можно заменить на std::setprecision и std::fixed,

#include <iostream>     // std::cout, std::fixed, std::scientific

int main () {
    double a = 3.1415926534;
    double b = 2006.0;
    double c = 1.0e-10;

    std::cout.precision(5);

    std::cout << "fixed:\n" << std::fixed;
    std::cout << a << '\n' << b << '\n' << c << '\n';
    return 0;
}

Выход:

fixed:
3.14159
2006.00000
0.00000
person Deidrei    schedule 12.12.2013
comment
Да, но вы никогда не захотите использовать какой-либо из них (кроме, может быть, std::setw) в рабочем коде (хотя они удобны для быстрых тестов). Кроме того: в <ios> и <istream> также определен ряд манипуляторов. <iomanip> определяет только те, которые принимают аргумент. - person James Kanze; 12.12.2013
comment
@JamesKanze: я знаю это. Я думаю, что приведенных выше манипуляторов достаточно для использования ОП. - person Deidrei; 12.12.2013
comment
Одним из его требований было %.nf. Для этого вам понадобится std::fixed, который находится в <ios>. (Обратите внимание, что std::setiosflags нельзя использовать для установки базы, формата с плавающей запятой или полей настройки.) - person James Kanze; 12.12.2013
comment
@JamesKanze и user1920482, эквивалентно ли cout‹‹setprecision() cout.setprecision() ?? Есть ли разница? - person Harry; 12.12.2013
comment
@HarendraSingh Это cout << setprecision(n), но cout.precision(n). Первый — это манипулятор (думаю, я никогда им не пользовался); вторая функция-член (которую будет вызывать манипулятор), обычно используемая в пользовательском манипуляторе, как часть большего набора параметров форматирования. Функции-члены ostream (или чаще его базовых классов) и стандартные манипуляторы задаются очень низкоуровневыми физическими данными; в любом большом приложении вы захотите указать элемент logical. - person James Kanze; 12.12.2013

потоки C++ не используют спецификаторы формата, такие как функции типа printf() языка C; они используют manipulators.

Например:

#include <iostream>
#include <iomanip>


int main()
{
    std::cout << std::fixed << std::setprecision(6) << 42.0 << std::endl;
}

Выход:

42.000000

Посмотрите, как он работает!

person Johnsyweb    schedule 12.12.2013

Обычное решение в C++ состоит в том, чтобы определить манипуляторы, которые указывают, что вы пытаетесь отформатировать, а не взламывать физические значения непосредственно в точке вывода. (Одним из возможных исключений является ширина, где std::setw может быть полезен напрямую.) Таким образом, например, при фактическом выводе чего-либо вы не будете указывать нулевое заполнение или фиксированное с двумя десятичными знаками, а что-то вроде:

std::cout << temperature(2) << theTemporature;

где temperature будет что-то вроде:

class temperature
{
    int myMinWidth;
public:
    temperature( int minWidth )
        : myMinWidth( minWidth )
    {
    }
    friend std::ostream& operator<<( std::ostream& dest, temperature const& manip )
    {
        dest.setf( std::ios_base::fixed, std::ios_base::floatfield );
        dest.precision( 2 );
        dest.width( myMinWidth );
        return dest;
    }
};

Список доступных модификаций формата см. в спецификации std::ios_base и в полях std::ios_base::fmtflags.

Если вы делаете много вывода, вы можете изменить это, чтобы восстановить исходные флаги формата в конце полного выражения. (Вся информация о формате, кроме ширины, является фиксированной, поэтому принудительное фиксированное форматирование здесь оставляет вам фиксированный формат для остальной части программы, что не обязательно является тем, что вам нужно.) Я использую следующий базовый класс для всех моих манипуляторы:

class StateSavingManip
{
public:
    void operator()( std::ios& stream ) const;
protected:
    StateSavingManip() : myStream( nullptr ) {}
    ~StateSavingManip();
private:
    virtual void setState( std::ios& stream ) const = 0;
private:
    mutable std::ios* myStream;
    mutable std::ios::fmtflags mySavedFlags;
    mutable int mySavedPrec;
    mutable char mySavedFill;
};

реализация:

namespace {
int getXAlloc() ;
int ourXAlloc = getXAlloc() + 1 ;

int
getXAlloc()
{
    if ( ourXAlloc == 0 ) {
        ourXAlloc = std::ios::xalloc() + 1 ;
        assert( ourXAlloc != 0 ) ;
    }
    return ourXAlloc - 1 ;
}
}

StateSavingManip::~StateSavingManip()
{
    if ( myStream != nullptr ) {
        myStream->flags( mySavedFlags ) ;
        myStream->precision( mySavedPrec ) ;
        myStream->fill( mySavedFill ) ;
        myStream->pword( getXAlloc() ) = NULL ;
    }
}

void
StateSavingManip::operator()( 
    std::ios&           stream ) const
{
    void*&              backptr = stream.pword( getXAlloc() ) ;
    if ( backptr == nullptr ) {
        backptr      = const_cast< StateSavingManip* >( this ) ;
        myStream     = &stream ;
        mySavedFlags = stream.flags() ;
        mySavedPrec  = stream.precision() ;
        mySavedFill  = stream.fill() ;
    }
    setState( stream ) ;
}

Обратите внимание на использование поля pword, чтобы гарантировать, что только первый временный манипулятор восстановит формат; деструкторы будут вызываться в порядке, обратном построению, но порядок построения обычно не указывается, если в выражении имеется более одного такого манипулятора.

Наконец: не все возможно с использованием этой техники: если вы хотите систематически добавлять знак градуса к температуре, это невозможно сделать. В этом случае вам нужно определить класс Temperature и перегрузить для него оператор <<; это позволяет все, что только можно вообразить (намного больше, чем вы когда-либо могли бы достичь с форматированием в стиле printf).

person James Kanze    schedule 12.12.2013

Манипуляторы потока C++ (iomanip) были специально разработаны для поддержки всех стандартных форматов C. спецификаторов, только с совершенно другим интерфейсом. Например. setfill и setw для ширины и заполнения части %02d.

Конечно, если вам действительно нужны строки формата (например, потому что это упрощает i18n), вам следует взглянуть на boost::format, а если у вас есть C++11, вы можете легко написать вокруг него небольшую оболочку с переменным числом шаблонов, чтобы сделать вызов формата больше похож на printf.

Что бы вы ни делали, старайтесь не использовать printf. Он не является безопасным для типов и не расширяется для операций вывода для пользовательских типов.

person PlasmaHH    schedule 12.12.2013
comment
boost::format на самом деле вам не очень нравится. Плохой выбор оператора вставки (%) приводит к довольно нечитаемому коду. Это может быть хорошим выбором, если вам нужна позиционная обработка аргументов (эквивалент расширения X/Open для форматирования printf), но для самого формата вы захотите использовать манипуляторы, поскольку по причинам обслуживания вы не хотите физический формат перепутался в текстовой строке. - person James Kanze; 12.12.2013

Есть манипуляторы потоков, если они вам нужны.
Но я думаю, вы хотите знать вот что:
cout умнее, чем printf(). Скажем, у вас есть:

int x = 34;
cout<<x;

Теперь компилятор вызовет:

ostream& operator<<(ostream& stream, int arg);

для тебя. И эта функция выведет вывод в консоль (поскольку в вашем случае stream равно cout). Стандартная библиотека предоставляет перегрузки для этого оператора << для всех доступных основных типов данных.

person deeiip    schedule 12.12.2013
comment
Во-первых, эта функция не выводит вывод в консоль: она выводит туда, куда всегда идет stream (это может быть файл, строка или что-то еще). Во-вторых, это не касается того, что кажется его вопросом: как изменить формат, используемый для вывода. И в-третьих, вы действительно должны упомянуть, что вы можете перегружать << для ваших собственных типов; это действительно один из самых важных вопросов. - person James Kanze; 12.12.2013