Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 41 из 43)

* К сожалению, об этом присваивании легко забыть. Например, в

первом издании этой книги (английском - перев.) вторая строка

конструктор derived::derived() читалась так:

if (this == 0) this = (derived*)43;

И следовательно, для d конструктор базового класса base::base() не

вызывался. Программа была допустимой и корректно выполнялась, но

очевидно делала не то, что подразумевал автор. (прим. автора)

- стр 232 -

он мог действовать как сопрограмма. Отдельные задачи можно

определять как объекты классов, производных от класса task.

Программа, которую должна исполнять задача, модет задаваться

как виртуальная функция. Должна быть возможность передавать

новой задаче ее параметры как параметры ее конструктора(ов).

Там должен быть планировщик, реализующий концепцию

виртуального времени. Обеспечьте функцию задержки

task::delay(), которая "тратит" виртуальное время. Будет ли

планировщик отдельным или частью класса task - это один из

основных вопросов, которые надо решить при проектировании.

Задача должна передавать данные. Для этого разработайте класс

queue (очередь). Придумайте способ, чтобы задача ожидала ввода

из нескольких очередей. Ошибки в ходе выполнения обрабатывайте

единообразно. Как бы вы отлаживали программы, написанные с

помощью такой библиотеки?

* Глава 8 *

Потоки

Язык C++ не обеспечивает средств для ввода/вывода. Ему это и не

нужно; такие средства легко и элегантно можно создать с помощью

самого языка. Описанная зжесь стандартная библиотека потокового

ввода/вывода обеспечивает гибкий и эффективный с гарантией типа

метод обработки символьного ввода целых чисел, чисел с плавающей

точкой и символьных строк, а также простую модель ее расширения для

обработки типов, определяемых пользователем. Ее пользовательский

интерфейс находится в . В этой главе описывается сама

библиотека, некоторые способы ее применения и методы, которые

использовались при ее реализации.

8.1 Введение

Разработка и реализация стандартных средств ввода/вывода для

языка программирования зарекомдовала себя как заведомо трудная

работа. Традиционно средства ввода/вывода разрабатывались

исключительно для небольшого числа встроенных типов данных. Однако

в C++ программах обычно используется много типов, определенных

пользователем, и нужно обрабатывать ввод и вывод также и значений

этих типов. Очевидно, средство ввода/вывода должно быть простым,

удобным, надежным в употребелении, эффективным и гибким, и ко всему

прочему полным. Ничье решение еще не смогло угодить всем, поэтому у

пользователя должна быть возможность задавать альтернативные

средства ввода/вывода и расширять стандартные средства ввода/вывода

применительно к требованиям приложения.

C++ разработан так, чтобы у пользователя была возможность

определять новые типы столь же эффективные и удобные, сколь и

встроенные типы. Поэтому обоснованным является требование того, что

средства ввода/вывода для C++ должны обеспечиваться в C++ с

применением только тех средств, которые доступны каждому

программисту. Описываемые здесь средства ввода/вывода предсавляют

собой попытку ответить на этот вызов.

Средства ввода/вывода связаны исключительно с

обработкой преобразования типизированнных объектов в

последовательности символов и обратно. Есть и другие схемы

ввода/вывода, но эта является основополагающей в системе UNIX, и

большая часть видов бинарного ввода/вывода обрабатывается через

рассмотрение символа просто как набор бит, при этом его

общепринятая связь с алфавитом игнорируется. Тогда для программиста

ключевая проблема заключается в задании соответствия между

типизированным объектом и принципиально нетипизированной строкой.

Обработка и встроенных и определенных пользователем типов

однородным образом и с гарантией типа достигается с помощью одного

перегруженного имени функции для набора функций вывода. Например:

- стр 234 -

put(cerr,"x = "); // cerr - поток вывода обшибок

put(cerr,x);

put(cerr,"\n");

Тип параметра определяет то, какая из функций put будет вызываться

для каждого параметра. Это решение применялось в нескольких языках.

Однако ему недостает лаконичности. Перегрузка операции << значением

"поместить в" дает более хорошую запись и позволяет программисту

выводить ряд объектов одним оператором. Например:

cerr << "x = " << x << "&bsol;n";

где cerr - стандартный поток вывода ошибок. Поэтому, если x

является int со значением 123, то этот оператор напечатает в

стандартный поток вывода ошибок

x = 123

и символ новой строки. Аналогично, если X принадлежит определенному

пользователем типу complex и имеет значение (1,2.4), то приведенный

выше оператор напечатает в cerr

x = 1,2.4)

Этот метод можно применять всегда, когда для x определена

операция <<, и пользователь может определять операцию << для нового

типа.

8.2 Вывод

В этом разделе сначала обсуждаются средства форматного и

бесформатного вывода встроенных типов, потом приводится стандартный

способ спецификации действий вывода для определяемых пользователем

типов.

8.2.1 Вывод Встроенных Типов

Класс ostream определяется вместе с операцией << ("поместить в")

для обработки вывода встроенных типов:

class ostream {

// ...

public:

ostream& operator<<(char*);

ostream& operator<<(int i) { return *this<

8.2.3 Некоторые Подробности Разработки

Операция вывода используется, чтобы избежать той многословности,

которую дало бы использование функции вывода. Но почему << и >, но значения

"меньше" и "больше" настолько прочно вросли в сознание людей, что

новые операции ввода/вывода во всех реальных случаях оказались

нечитаемыми. Помимо этого, "<" находится на большинстве клавиатур

как раз на ",", и у людей получаются операторы вроде такого:

cout < x , y , z;

Для таких операторов непросто выдать хорошие сообщения об ошибках.

Операции << и >> к такого рода проблемам не приводят, они

асимметричны в том смысле, что их можно проассоциировать с "в" и

"из", а приоритет << достаточно низок, чтобы можно было не

использовать скобки для арифметических выражений в роли операндов.

Например:

cout << "a*b+c=" << a*b+c << "&bsol;n";

Естественно, при написании выражений, которые содержат операции с

более низкими приоритетами, скобки использовать надо. Например:

cout << "a^b|c=" << (a^b|c) << "&bsol;n";

Операцию левого сдвига тоже можно применять в операторе вывода:

cout << "a<

8.2.4 Форматированный Вывод

Пока << применялась только для неформатированного вывода, и на

самом деле в реальных программах она именно для этого главным

образом и применяется. Помимо этого существует также несколько

форматирующих функций, создающих представление своего параметра в

виде строки, которая используется для вывода. Их второй

(необязательный) параметр указывает, сколько символьных позиций

должно использоваться.

char* oct(long, int =0); // восьмеричное представление

char* dec(long, int =0); // десятичное представление

char* hex(long, int =0); // шестнадцатиричное представление

char* chr(int, int =0); // символ

char* str(char*, int =0); // строка

Если не задано поле нулевой длины, то будет производиться усечение

или дополнение; иначе будет использоваться столько символов

(ровно), сколько нужно. Например:

cout << "dec(" << x

<< ") = oct(" << oct(x,6)

<< ") = hex(" << hex(x,4)

<< ")";

Если x==15, то в результате получится:

dec(15) = oct( 17) = hex( f);

Можно также использовать строку в общем формате:

- стр 238 -

char* form(char* format ...);

cout<

8.2.5 Виртуальная Функция Вывода

Иногда функция вывода должна быть virtual. Рассмотрим пример

класса shape, который дает понятие фигуры (#1.18):

class shape {

// ...

public:

// ...

virtual void draw(ostream& s); // рисует "this" на "s"

};

class circle : public shape {

int radius;

public:

// ...

void draw(ostream&);

};

То есть, круг имеет все признаки фигуры и может обрабатываться

как фигура, но имеет также и некоторые специальные свойства,

которые должны учитываться при его обработке.

Чтобы поддерживать для таких классов стандартную парадигму

вывода, операция << определяется так:

ostream& operator<<(ostream& s, shape* p)

{

p->draw(s);

return s;

}

- стр 241 -

Если next - итератор типа определенного в #7.3.3, то список фигур

распечатывается например так:

while ( p = next() ) cout << p;

8.3 Файлы и Потоки

Потоки обычно связаны с файлами. Библиотека потоков создает

стандартный поток ввода cin, стандартный поток вывода cout и

стандартный поток ошибок cerr. Программист может открывать другие

файлы и создавать для них потоки.

8.3.1 Инициализация Потоков Вывода

ostream имеет конструкторы:

class ostream {

// ...

ostream(streambuf* s); // связывает с буфером потока

ostream(int fd); // связывание для файла

ostream(int size, char* p); // связывет с вектором

};

Главная работа этих конструкторов - связывать с потоком буфер.

streambuf - класс, управляющий буферами; он описывается в #8.6, как

и класс filebuf, управляющий streambuf для файла. Класс filebuf

является производным от класса streambuf.

Описание стандартных потоков вывода cout и cerr, которое

находится в исходных кодах баблиотеки потоков ввода/вывода,

выглядит так:

// описать подходящее пространство буфера

char cout_buf[BUFSIZE]

// сделать "filebuf" для управления этим пространством

// связать его с UNIX'овским потоком вывода 1 (уже открытым)

filebuf cout_file(1,cout_buf,BUFSIZE);

// сделать ostream, обеспечивая пользовательский интерфейс

ostream cout(&cout_file);

char cerr_buf[1];

// длина 0, то есть, небуферизванный

// UNIX'овский поток вывода 2 (уже открытый)

filebuf cerr_file()2,cerr_buf,0;

ostream cerr(&cerr_file);

Примеры двух других конструкторов ostream можно найти в #8.3.3 и