Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
138
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

1072

#include <iostream> int main()

{

int ival = 16; double dval = 16.0;

cout << "ival: " << ival

<< " установлен oct: " << oct << ival << "\n";

cout << "dval: " << dval

<< " установлен hex: " << hex << dval << "\n";

cout << "ival: " << ival

<< " установлен dec: " << dec << ival << "\n";

}

Эта программа печатает следующее:

ival: 16 установлен oct: 20 dval: 16 установлен hex: 16 ival: 10 установлен dec: 16

Но, глядя на значение, мы не можем понять, в какой системе счисления оно записано. Например, 20 – это действительно 20 или восьмеричное представление 16? Манипулятор showbase выводит основание системы счисления вместе со значением с помощью следующих соглашений:

0x в начале обозначает шестнадцатеричную систему (если мы хотим, чтобы вместо строчной буквы 'x' печаталась заглавная, то можем применить манипулятор uppercase, а для отмены манипулятор nouppercase);

0 в начале обозначает восьмеричную систему;

отсутствие того и другого обозначает десятичную систему.

#include <iostream> int main()

{

int ival = 16; double dval = 16.0;

cout << showbase;

cout << "ival: " << ival

<< " установлен oct: " << oct << ival << "\n";

cout << "dval: " << dval

<< " установлен hex: " << hex << dval << "\n";

cout << "ival: " << ival

<< " установлен dec: " << dec << ival << "\n"; cout << noshowbase;

Вот та же программа, но и с использованием showbase:

}

С++ для начинающих

1073

Результат:

ival: 16 установлен oct: 020 dval: 16 установлен hex: 16 ival: 0x10 установлен dec: 16

Манипулятор noshowbase восстанавливает состояние cout, при котором основание системы счисления не выводится.

По умолчанию значения с плавающей точкой выводятся с точностью 6. Эту величину можно модифицировать с помощью функции-члена precision(int) или манипулятора setprecision(); для использования последнего необходимо включить заголовочный

#include <iostream> #include <iomanip> #include <math.h>

int main()

{

cout << "Точность: "

<<cout.precision() << endl

<<sqrt(2.0) << endl;

cout.precision(12);

cout << "\nТочность: "

<<cout.precision() << endl

<<sqrt(2.0) << endl;

cout << "\nТочность: " << setprecision(3)

<<cout.precision() << endl

<<sqrt(2.0) << endl;

return 0;

файл iomanip. precision() возвращает текущее значение точности. Например:

}

После компиляции и запуска программа печатает следующее:

Точность: 6 1.41421

Точность: 12 1.41421356237

Точность: 3 1.41

Манипуляторы, принимающие аргумент, такие, как setprecision() и setw(), требуют включения заголовочного файла iomanip:

#include <iomanip>

Кроме описанных аспектов, setprecision() имеет еще два: на целые значения он не оказывает никакого влияния; значения с плавающей точкой округляются, а не

С++ для начинающих

1074

обрезаются. Таким образом, при точности 4 значение 3.14159 печатается как 3.142, а при точности 3 – как 3.14.

По умолчанию десятичная точка не печатается, если дробная часть значения равна 0. Например:

cout << 10.00

выводит

10

cout << showpoint << 10.0

Чтобы точка выводилась, воспользуйтесь манипулятором showpoint:

<< noshowpoint << '\n';

Манипулятор noshowpoint восстанавливает поведение по умолчанию.

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

cout << "научная: " << scientific

<<10.0

<<"с фиксированной точкой: " << fixed

для возврата к прежней нотации модификатор fixed:

<<10.0 << '\n';

Врезультате печатается:

научная: 1.0e+01

с фиксированной точкой: 10

Если бы мы захотели вместо буквы 'e' выводить 'E', то следовало бы употребить манипулятор uppercase, а для возврата к 'e' nouppercase. (Манипулятор uppercase не приводит к переводу букв в верхний регистр при печати.)

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

a bc d

то цикл

С++ для начинающих

1075

char ch;

while ( cin >> ch )

// ...

читает все буквы от 'a' до 'd' за четыре итерации, а пробельные разделители оператором ввода игнорируются. Манипулятор noskipws отменяет такой пропуск пробельных

char ch;

cin >> noskipws; while ( cin >> ch )

// ...

символов:

cin >> skipws;

Теперь цикл while будет выполняться семь раз. Чтобы восстановить поведение по умолчанию, к потоку cin применяется манипулятор skipws.

Когда мы пишем:

cout << "пожалуйста, введите значение: ";

то в буфере потока cout сохраняется литеральная строка. Есть ряд условий, при которых буфер сбрасывается (т.е. опустошается), – в нашем случае в стандартный вывод:

буфер может заполниться. Тогда перед чтением следующего значения его необходимо сбросить;

буфер можно сбросить явно с помощью любого из манипуляторов flush, ends

//сбрасывает буфер cout << "hi!" << flush;

//вставляет нулевой символ, затем сбрасывает буфер char ch[2]; ch[0] = 'a'; ch[1] = 'b';

cout << ch << ends;

// вставляет символ новой строки, затем сбрасывает буфер

или endl:

cout << "hi!" << endl;

при установлении внутренней переменной состояния потока unitbuf буфер сбрасывается после каждой операции вывода;

объект ostream может быть связан (tied) с объектом istream. Тогда буфер ostream сбрасывается каждый раз, когда istream читает из входного потока. cout всегда связан с cin:

cin.tie( &cout );

Инструкция

cin >> ival;

С++ для начинающих

1076

приводит к сбросу буфера cout.

В любой момент времени объект ostream разрешено связывать только с одним объектом istream. Чтобы разорвать существующую связь, мы передаем функции-члену tie()

istream is; ostream new_os;

//...

//tie() возвращает существующую связь ostream *old_tie = is.tie();

is.tie( 0 ); // разорвать существующую связь is.tie( &new_os ); // установить новую связь

// ...

is.tie( 0 ); // разорвать существующую связь

значение 0:

is.tie( old_tie ); // восстановить прежнюю связь

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

#include <iostream> #include <iomanip>

int main()

{

int ival = 16;

double dval = 3.14159;

cout << "ival: " << setw(12) << ival << '\n' << "dval: " << setw(12) << dval << '\n';

значения, с помощью манипулятора setw(). Например, программа

}

печатает:

ival: 16 dval: 3.14159

Второй модификатор setw() необходим потому, что, в отличие от других манипуляторов, setw() не изменяет состояние формата объекта ostream.

Чтобы выровнять значение по левой границе, мы применяем манипулятор left (соответственно манипулятор right восстанавливает выравнивание по правой границе). Если мы хотим получить такой результат:

16

-3

С++ для начинающих

1077

то пользуемся манипулятором internal, который выравнивает знак по левой границе, а значение по правой, заполняя пустое пространство пробелами. Если же нужен другой символ, то можно применить манипулятор setfill(). Так

cout << setw(6) << setfill('%') << 100 << endl;

печатает:

%%%100

В табл. 20.1 приведен полный перечень предопределенных манипуляторов.

Таблица 20.1. Манипуляторы

Манипулятор

Назначение

 

 

boolalpha

Представлять true и false в виде строк

*noboolalpha

Представлять true и false как 1 и 0

Showbase

Печатать префикс, обозначающий систему счисления

*noshowbase

Не печатать префикс системы счисления

showpoint

Всегда печатать десятичную точку

*noshowpoint

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

 

ненулевая

showpos

Печатать + для неотрицательных чисел

*noshowpos

Не печатать + для неотрицательных чисел

Манипулятор

Назначение

*skipws

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

noskipws

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

uppercase

Печатать 0X при выводе в шестнадцатеричной системе счисления;

*nouppercase

E при выводе в научной нотации

Печатать 0x при выводе в шестнадцатеричной системе счисления;

 

e при выводе в научной нотации

*dec

Печатать в десятичной системе

hex

Печатать в шестнадцатеричной системе

oct

Печатать в восьмеричной системе

 

 

С++ для начинающих

1078

left

 

Добавлять символ заполнения справа от значения

 

right

 

Добавлять символ заполнения слева от значения

internal

 

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

*fixed

 

Отображать число с плавающей точкой в десятичной нотации

scientific

 

Отображать число с плавающей точкой в научной нотации

flush

 

Сбросить буфер ostream

ends

 

Вставить нулевой символ, затем сбросить буфер ostream

endl

 

Вставить символ новой строки, затем сбросить буфер ostream

ws

 

Пропускать пробельные символы

 

 

 

// для этих манипуляторов требуется #include <ionamip>

setfill( ch)

 

Заполнять пустое место символом ch

Setprecision( n )

 

Установить точность вывода числа с плавающей точкой равной n

setw( w )

 

Установить ширину поля ввода или вывода равной w

setbase( b )

 

Выводить целые числа по основанию b

* обозначает состояние потока по умолчанию

20.10. Сильно типизированная библиотека

Библиотека iostream сильно типизирована. Например, попытка прочитать из объекта

класса ostream или записать в объект класса istream помечается компилятором как

#include <iostream> #include <fstream> class Screen;

extern istream& operator>>( istream&, const Screen& ); extern void print( ostream& );

нарушение типизации. Так, если имеется набор объявлений: ifstream inFile;

то следующие две инструкции приводят к нарушению типизации, обнаруживаемому во время компиляции:

С++ для начинающих

1079

int main()

{

Screen myScreen;

//ошибка: ожидается ostream& print( cin >> myScreen );

//ошибка: ожидается оператор >> inFile << "ошибка: оператор вывода";

Средства ввода/вывода включены в состав стандартной библиотеки C++. В главе 20 библиотека iostream описана не полностью, в частности вопрос о создании

определенных пользователем манипуляторов и буферных классов остался за рамками введения в язык. Мы сосредоточили внимание лишь на той части библиотеки iostream, которая имеет основополагающее значение для программного ввода/вывода.

С++ для начинающих

1080

Приложение

21. Обобщенные алгоритмы в

алфавитном порядке

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

Первыми двумя аргументами всех обобщенных алгоритмов (естественно, не без исключений) является пара итераторов, обычно first и last, обозначающих диапазон элементов внутри контейнера или встроенного массива, над которым работает алгоритм. Этот диапазон (часто называемый интервалом с включенной левой

//следует читать: включая first и все последующие

//элементы до last, но не включая сам last

границей), как правило, записывается в виде:

[ first, last )

Это означает, что диапазон начинается с first и заканчивается last, однако сам элемент last не включается. Если

first == last

то говорят, что диапазон пуст.

К паре итераторов предъявляется такое требование: last должен быть достижим, если начать с first и последовательно применять оператор инкремента. Однако компилятор не может проверить выполнение данного ограничения. Если требование не будет выполнено, поведение программы не определено; обычно это заканчивается ее крахом и дампом памяти.

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

Некоторые алгоритмы существуют в нескольких вариантах: в одном используется тот или иной встроенный оператор, а в другом объект-функция или указатель на функцию, реализующие альтернативу этому оператору. Например, алгоритм unique()

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

С++ для начинающих

1081

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

в этом классе нет оператора равенства или мы хотим сравнивать элементы иным способом, то можем передать объект-функцию или указатель на функцию, поддерживающую нужную семантику. Есть и такие алгоритмы, которые выполняют похожие действия, но имеют различные имена. Так, имена предикатных версий алгоритмов всегда заканчиваются на _if, скажем find_if(). Например, есть вариант алгоритма replace(), где используется встроенный оператор равенства, и вариант с именем replace_if(), которому передается предикатный объект-функция или указатель на функцию-предикат.

Алгоритмы, модифицирующие контейнер, обычно также существуют в двух вариантах: один осуществляет модификацию по месту, а второй возвращает копию с внесенными изменениями. Так, есть алгоритмы replace() и replace_copy(). Однако вариант с копированием (его имя всегда содержит слово _copy) имеется не для каждого алгоритма, модифицирующего контейнер. К примеру, для алгоритмов sort() его нет. В таких случаях, если нужно, чтобы алгоритм работал с копией, мы должны создать ее самостоятельно и передать в качестве аргумента.

Для использования любого обобщенного алгоритма в программу необходимо включить заголовочный файл

#include <algorithm>

Для

употребления

любого

из

четырех

численных

алгоритмов:

adjacent_difference(), accumulate(),

inner_product() и partial_sum()

нужно включить также файл

 

 

 

 

#include <numeric>

Приведенные в этом Приложении примеры программ, в которых используются алгоритмы и различные контейнерные типы, отражают существующую на момент написания книги реализацию. Применение библиотеки ввода/вывода iostream следует соглашениям, установленным до принятия стандарта; скажем, в программу включается заголовочный файл iostream.h, а не iostream. Шаблоны не поддерживают аргументы по умолчанию. Чтобы программа работала на системе, имеющейся у вас, возможно, придется изменить некоторые объявления.

Другое, более подробное, чем в этой книге, описание обобщенных алгоритмов можно найти в работе [MUSSER96], правда, оно несколько отстает от окончательного варианта стандартной библиотеки C++.

Алгоритм accumulate()