int day= 31,
mon= 1,
year=1974;
printf("%02d.%02d.%d\n", day, mon, year); // 31.01.1974
cout<<setfill('0')<<setw(2)<<day<<'.'<<setw(2)<<mon<<setfill(' ')<<'.'
<<year<<"\n"; // тоже 31.01.1974
Думаю, что комментарии излишни.
За что же не любят потоки C и чем потоки C++ могут быть удобнее? У потоков C++ есть только одно существенное достоинство -- типобезопасность. Т.к. потоки C++ все же нужно использовать, я написал специальный манипулятор, который, оставаясь типобезопасным, позволяет использовать формат ...printf()
. Он не вызывает существенных накладных расходов и с его помощью приведенный выше пример будет выглядеть следующим образом:
cout<<c_form(day,"02")<<'.'<<c_form(mon,"02")<<'.'<<year<<'\n';
Вот исходный код заголовочного файла:
#include <ostream>
/** личное пространство имен функции c_form, содержащее детали реализации */
namespace c_form_private {
typedef std::ios_base::fmtflags fmtflags;
typedef std::ostream ostream;
typedef std::ios_base ios;
/**
* Вспомогательный класс для осуществления форматирования.
*/
class Formatter {
/** флаги для установки */
fmtflags newFlags;
/** ширина */
int width;
/** точность */
int prec;
/** символ-заполнитель */
char fill;
/** сохраняемые флаги */
fmtflags oldFlags;
public:
/**
* Создает объект, использующий переданное форматирование.
*/
Formatter(const char* form, int arg1, int arg2);
/**
* Устанавливает новое форматирование для переданного потока, сохраняя
* старое.
*/
void setFormatting(ostream& os);
/**
* Восстанавливает первоначальное форматирование, сохраненное в функции
* setFormatting().
*/
void restoreFormatting(ostream& os);
};
/**
* Вспомогательный класс.
*/
template <class T>
class Helper {
/** выводимое значение */
const T& val;
/** объект для форматирования */
mutable Formatter fmtr;
public:
/**
* Создает объект по переданным параметрам.
*/
Helper(const T& val_, const char* form, int arg1, int arg2) :
val(val_), fmtr(form, arg1, arg2) {}
/**
* Функция для вывода в поток сохраненного значения в заданном формате.
*/
void putTo(ostream& os) const;
};
template <class T>
void Helper<T>::putTo(ostream& os) const
{
fmtr.setFormatting(os);
os<<val;
fmtr.restoreFormatting(os);
}
/**
* Оператор для вывода объектов Helper в поток.
*/
template <class T>
inline ostream& operator<<(ostream& os, const Helper<T>& h)
{
h.putTo(os);
return os;
}
}
/**
* Функция-манипулятор, возвращающая объект вспомогательного класса, для
* которого переопределен оператор вывода в ostream. Переопределенный оператор
* вывода осуществляет форматирование при выводе значения.
* @param val значение для вывода
* @param form формат вывода: [-|0] [число|*] [.(число|*)] [e|f|g|o|x]
* @param arg1 необязательный аргумент, задающий ширину или точность.
* @param arg2 необязательный аргумент, задающий точность.
* @throws std::invalid_argument если передан аргумент form некорректного
* формата.
*/
template <class T>
inline c_form_private::Helper<T> c_form(const T& val, const char* form,
int arg1=0, int arg2=0)
{
return c_form_private::Helper<T>(val, form, arg1, arg2);
}
и файла-реализации:
#include "c_form.hpp"
#include <stdexcept>
#include <cctype>
namespace {
/**
* Вспомогательная функция для чтения десятичного числа.
*/
int getval(const char*& iptr)
{
int ret=0;
do ret=ret*10 + *iptr-'0';
while (std::isdigit(*++iptr));
return ret;
}
}
c_form_private::Formatter::Formatter(const char* form, int arg1, int arg2) :
newFlags(fmtflags()), width(0), prec(0), fill(0)
{
const char* iptr=form; // текущий символ строки формата
if (*iptr=='-') { // выравнивание влево
newFlags|=ios::left;
iptr++;
}
else if (*iptr=='0') { // добавляем '0'ли только если !left
fill='0';
iptr++;
}
if (*iptr=='*') { // читаем ширину, если есть
width=arg1;
iptr++;
arg1=arg2; // сдвигаем агрументы влево
}
else if (std::isdigit(*iptr)) width=getval(iptr);
if (*iptr=='.') { // есть точность
if (*++iptr=='*') {
prec=arg1;
iptr++;
}
else if (std::isdigit(*iptr)) prec=getval(iptr);
else throw std::invalid_argument("c_form");
}
switch (*iptr++) {
case 0: return; // конец строки формата
case 'e': newFlags|=ios::scientific; break;
case 'f': newFlags|=ios::fixed; break;
case 'g': break;
case 'o': newFlags|=ios::oct; break;
case 'x': newFlags|=ios::hex; break;
default: throw std::invalid_argument("c_form");
}
if (*iptr) throw std::invalid_argument("c_form");
}
void c_form_private::Formatter::setFormatting(ostream& os)
{
oldFlags=os.flags();
// очищаем floatfield и устанавливаем свои флаги
os.flags((oldFlags & ~ios::floatfield) | newFlags);
if (width) os.width(width);
if (fill) fill=os.fill(fill);
if (prec) prec=os.precision(prec);
}
void c_form_private::Formatter::restoreFormatting(ostream& os)
{
os.flags(oldFlags);
if (fill) os.fill(fill);
if (prec) os.precision(prec);
}
Принцип его работы основан на следующей идее: функция
c_form<>()
возвращает объект класса c_form_private::Helper<>
, для которого определена операция вывода в ostream
.
Для удобства использования, c_form<>()
является функцией, т.к. если бы мы сразу использовали конструктор некоторого класса-шаблона c_form<>
, то нам пришлось бы явно задавать его параметры:
cout<<c_form<int>(day,"02");
что, мягко говоря, неудобно. Далее. Мы, в принципе, могли бы не использовать нешаблонный класс
Formatter
, а поместить весь код прямо в Helper<>
, но это привело бы к совершенно ненужной повторной генерации общего (не зависящего от параметров шаблона) кода.
Как можно видеть, реализацию манипулятора c_form
вряд ли можно назвать тривиальной. Тем не менее, изучить ее стоит хотя бы из тех соображений, что в процессе разработки было использовано (неожиданно) большое количество полезных приемов программирования.
"Programming Optimization". Не очень глубокий, а местами и откровенно "слабый", но, тем не менее, практически полезный обзор более высокоуровневых техник представлен в книге Steve Heller "Optimizing C++".
Макросы
В C++ макросы не нужны! До боли знакомое высказывание, не так ли? Я бы его немного уточнил: не нужны, если вы не хотите существенно облегчить себе жизнь.
Я полностью согласен с тем, что чрезмерное и необдуманное использование макросов может вызвать большие неприятности, особенно при повторном использовании кода. Вместе с тем, я не знаю ни одного средства C++, которое могло бы принести пользу при чрезмерном и необдуманном его использовании.
Итак, когда макросы могут принести пользу?
Макрос как надъязыковое средство. Хороший примером является простой, но удивительно полезный отладочный макрос_VAL_
, выводящий имя и значение переменной:
#define _VAL_(var) #var "=" << var << " "
Надъязыковой частью здесь является работа с переменной как с текстом, путем перевода имени переменной (оно существует только в исходном коде программы) в строковый литерал, реально существующий в коде бинарном. Данную возможность могут предоставить только макросы.
Информация о текущем исходном файле и строке -- ее пользу при отладке трудно переоценить. Для этого я использую специальный макрос_ADD_
. Например:
cout<<_ADD_("Ошибка чтения");
выведет что-то вроде
Ошибка чтения <file.cpp:34>
А если нужен перевод строки, то стоит попробовать
cout<<"Ошибка чтения" _ADD_("") "\n";
Такой метод работает, потому что макрос_ADD_
возвращает строковый литерал. Вроде бы эквивалентная функция
char* _ADD_(char*);
вполне подошла бы для первого примера, но не для второго. Конечно, для вывода вcout
это не имеет никакого значения, но в следующем пункте я покажу принципиальную важность подобного поведения.