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

2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп

.pdf
Скачиваний:
9
Добавлен:
16.07.2023
Размер:
31.34 Mб
Скачать

190

Глава

1О.

Ввод

и

вывод

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

ветствующую информацию, если она вам понадобится, самостоятельно.

Как и сору ( ) , все операции имеют две версии.

соот­

Основная версия, показанная в

таблице, например exists (р). Эта

функция генерирует исключение

filesystem_error в случае неудачи

операции.

 

Версия с дополнительным аргументом error_code&, например exists

(р, е) . Проверяйте значение е, чтобы убедиться в успешности опера­

ции или выяснить причины ее неудачи.

Мы используем

зовании операции

коды ошибок, когда ожидается, что при обычном исполь­

будут часто неудачны, и генерацию исключений, когда

ошибка рассматривается как исключительная ситуация. Зачастую использование функции с запросом информации

-

самый

про­

стой

и

прямой

подход

к

изучению

свойств

файла.

Библиотека

<filesystem>

знает

о

нескольких

распространенных

типах

файлов

и

классифицирует

остальные

как

"иные".

Типы

файлов

(f

представляет

собой

path

или

file_status)

is Ыосk file(f) is_character_file(f) is_directory(f) is_empty(f) is_fifo(f) is_other(f) is_regular_file(f) is_socket(f) is_symlink(f) status known(f)

Является ли f блочным устройством?

Является ли f символьным устройством?

Является ли f каталогом?

Является ли f пустым файлом или каталогом?

Является ли f именованным каналом?

Является ли f некоторой иной разновидностью файла?

Является ли f обычным файлом?

Является ли f именованным сокетом IPC?

Является ли f символической ссылкой?

Известно ли состояние файла, связанного с f?

10.11.

Советы

[1]

[2]

[3]

Потоки ios tream безопасны с точки

зрения

типов,

чувствительны

к

 

типам и расширяемы; §10.1.

 

 

 

 

 

Используйте

ввод

на уровне символов,

только

когда

вы вынуждены

к

нему прибегнуть;

§10.3; [CG:SL.io.1 ].

 

 

 

 

 

При

чтении

всегда учитывайте возможность неверно

отформатирован

­

 

ных

данных;

§10.3; [CG:SL.io.2].

 

 

 

 

 

[4]

[5]

[6]

[7]

[8] [9]

[10]

[ 11]

[12] [13]

[14]

[15]

[16]

[17] [18]

[19]

[20]

[21]

[22]

 

 

 

 

 

 

 

 

 

 

 

 

 

10.11.

Советы

191

Избегайте

endl (если вы

не знаете, что

такое

endl,

вы ничего

не про­

пустили);

[CG:SL.io.50].

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Определяйте<< и>> для

пользовательских типов со

значениями, имею­

щими значимые текстовые представления;

§ 10.1, §10.2, § 10.3.

 

Используйте

cout для обычного вывода

и

cerr -

для

вывода инфор­

мации об ошибках; § 10.1.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Имеются потоки iostream

как для

обычных,

так и

 

для

широких сим­

волов; кроме

того, можно определить

 

iostream для

символов

любого

вида; §10.1.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Поддерживается бинарный ввод-вывод;

§10.1.

 

 

 

 

 

Имеются стандартные iostream для

стандартных потоков ввода-выво­

да, файлов и строк string; §10.2, §10.3, §10.7, §10.8.

 

 

 

Объединяйте

операции<< в цепочки

для

более краткой и ясной

записи;

§10.2.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Объединяйте

операции >> в

цепочки

для

более краткой и ясной

записи;

§10.3.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Ввод данных

в string не приводит к

переполнению;

§10.3.

 

Оператор>>

по умолчанию

пропускает

ведущие пробельные символы;

§10.3.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Используйте

состояние потока fail для

обработки ошибок ввода-выво­

да с потенциальным восстановлением

 

после ошибки;

§10.4.

 

Можно определить операторы

<< и

 

>>

для

пользовательских

типов;

§10.5.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Не требуется

модифицировать

istream

или ostream для добавления

новых операторов<< и>>; §10.5.

 

 

 

 

 

 

 

 

 

 

 

Для управления форматированием используйте манипуляторы; §10.6.

Спецификация

precision () применяется ко

всем

последующим опе­

рациям вывода

значений с плавающей

точкой;

§ 10.6.

 

 

 

 

Спецификации

формата для

значений

с

 

плавающей

точкой (например,

scientific)

применяются

ко

всем

 

последующим

операциям

вывода

значений с плавающей точкой; §10.6.

 

 

 

 

 

 

 

 

 

 

Включите

#include<ios>

для

использования стандартных манипуля­

торов; § 10.6.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Включите #include<iomanip> для

использования

стандартных мани­

пуляторов, принимающих аргументы;

 

§ 10.6.

 

 

 

 

 

Не пытайтесь копировать файловый

поток.

 

 

 

 

 

 

11 Контейнеры

+ +

Это было ново.

Это было необычно.

Это было просто.

Это должно быть успешным!

- Горацио Нельсон

Введение vector Элементы

+ + + + +

Проверка выхода за границы диапазона list map unordered_map

Обзор контейнеров Советы

11.1.

Введение

Большинство

вычислений

предусматривают

создание

коллекций

значений

с

последующим

манипулированием

такими

коллекциями.

Простейший

при­

мер

-

чтение

символов

в

строку

s

tr

ing

и

вывод

этой

строки.

Класс

с

ос­

новной

целью

хранения

объектов

обычно

называется

контейнером.

Важным

шагом

в

построении

любой

программы

является

создание

подходящих

кон­

тейнеров

для

данной

задачи

и

их

поддержка

с

помощью

полезных

фундамен­

тальных операций.

Чтобы проиллюстрировать

контейнеры

стандартной

библиотеки,

рассмот­

рим

просrую

программу

для

хранения

имен

и

телефонных

номеров.

Это

та

разновидность

программ,

для

которой

"простыми

и

очевидными"

для

людей

с

разными

базовыми

знаниями

кажутся

совершенно

разные

подходы.

Для

хра­

нения

простой

записи

телефонной

книги

может

использоваться

класс

Entry

из

§10.5.

Здесь

мы

сознательно игнорируем

многие

сложности

реального

мира,

такие

как

то,

что

многие

номера

телефонов

не

имеют

простого

пред­

ставления

в

виде

32-битного

целого

числа

типа

int.

194

Глава

11. Контейнеры

11.2.

vector

Наиболее

полезным

контейнером

стандартной

библиотеки

является

vector.

Вектор

представляет

собой

последовательность

элементов

данного

типа.

Элементы

хранятся

в

памяти

последовательно.

Типичная

реализация

vector

(§4.2.2,

§5.2)

будет

состоять

из

дескриптора,

хранящего

указатель

на

первый

элемент,

на

элемент,

следующий

за

последним,

и

на

элемент,

следую­

щий

за

выделенной

памятью

(§12.1)

(или

эквивалентную

информацию,

пред­

ставленную

как указатель

плюс

смещения).

vecto:r

elem

 

 

 

space

r

----.:\-___

 

last

 

 

 

alloc

 

 

 

 

 

Элементы

- - До~о~~и-,.ёЛьн0е-п-р~~тран~~во- ~

 

 

~~~~~~----------------------

~

Кроме

того,

он

содержит

аллокатор

(распределитель

памяти,

здесь

alloc),

от

которого

вектор

может

получать

память

для

своих

элементов.

Ал­

локатор

по

умолчанию

для

получения

и

освобождения

памяти

использует

операторы new и delete (§ 13.6).

Мы можем инициализировать

vector

с

набором

значений

его

типа

эле­

мента:

vector<Entry> phone_book =

{

{"David

Hume",123456),

 

{"Karl

Popper",234567),

 

{"Bertrand Arthur William

/;

 

 

Russell",345678)

Доступ к элементам осуществляется через

определили<< для Entry, можно написать:

индексы.

Предполагая,

что

мы

void {

print_book{const

vector<Entry>&

book)

for

(int cout

i <<

=О;

i!=book.size();

book[i]

<< '\n';

++i)

Как

обычно,

индексы

начинаются

с

О,

так

что

book

[О]

содержит

запись

для

David

Hurne.

Функция-член

вектора

size

()

возвращает

количество

эле­

ментов в векторе.

Элементы вектора составляют цикл for для диапазона(§1.7):

диапазон,

поэтому

мы

можем использовать

void

print_book(const

vector<Entry>&

book)

{

for

(const cout <<

auto& х <<

х

: book)

'\n';

//

Об

"auto"

см.

11.2.

vector

§1. 4

195

При определении vector

количество элементов):

мы

указываем

его

начальный

размер

(начальное

vector<int> vl

= (1,

2,

3, 4};

//

Размер

равен

4

vector<string>

 

v2;

 

 

//

Размер

равен

О

// Размер равен

23;

начальное

значение

элементов:

vector<Shape*>

 

v3(23);

 

 

 

 

 

 

11 Размер равен

32;

начальное

значение

элементов:

vector<douЫe>

 

v4(32,9.9);

 

 

 

 

 

nullptr: 9.9:

Явно

указанный

размер

заключается

в

обычные

круглые

скобки,

например

(2 3)

;

по

умолчанию

элементы

инициализируются

значением

по умолчанию

для

типа

элемента

(например,

nullptr

-

для указателей

и

О

-

для

чисел).

Если

вам

не

нужно

значение

по

умолчанию,

можете

указать

требуемое

значе­

ние

в

качестве

второго

аргумента

(например,

9.

9

для

32

элементов

v4).

Исходный

размер

можно

изменить.

Одной

из

наиболее

полезных

операций

над

вектором

является

push

_

back

(),

которая

добавляет

новый

элемент

в

конец

вектора,

увеличивая

его

размер

на

единицу.

Например,

если

предполо­

жить,

что

мы

определили

оператор>>

для

Entry,

можно

написать

void (

input()

for

(Entry phone_

е; cin>>e; book.push_back(e);

Здесь

выполняется

чтение

записей

Entry

из

стандартного

ввода

в

phone

book

до

тех

пор,

пока

не

будет

достигнут

конец

ввода (например,

конец

фай­

ла)

или

операция

ввода

не

встретит

ошибку

формата.

vector

стандартной

библиотеки

реализован

таким

образом,

что

увеличе­

ние

вектора путем

многократного

применения

push

_

back

()

оказывается

эф­

фективным.

Чтобы

показать,

почему

это

так,

рассмотрим

разработку

просто­

го

Vector

из

глав

4

и

6,

используя

представление,

показанное

на

схеме

выше:

template<typename class Vector

Т>

Т*

elem;

11

Т*

space;

11

 

 

11

Т*

last;

11

puЬlic:

 

11 ...

int

size();

Указатель

на

первый

элемент

 

Указатель

на

первый

неиспользуемый

(и неинициализированный)

слот

Указатель

на

последний слот

 

// Количество элементов

(space-elem)

11.2.

vector

197

нет

веской

причины

использовать

какой-либо

другой

контейнер.

Если

вы

из­

бегаете

вектора

из-за

сомнений

в

его

эффективности,

выполните

измерения.

Наша

интуиция

в

особенности

ошибочна

в

вопросах

эффективности

исполь­

зования

контейнеров.

11.2.1.

Элементы

Как

и

все

контейнеры

стандартной

библиотеки,

vector

представляет

со­

бой

контейнер элементов

некоторого

типа

т,

т.е.

vector<T>.

Почти

любой

тип

может

быть

типом

элемента:

встроенные

числовые типы

(такие,

как

char,

int

или

douЫe),

пользовательские

типы

(такие,

как

string,

Entry,

list<int> или

 

Matrix<douЫe,

2>)

char*, Shape*

и

douЫe*). Когда

вы

или указатели (такие, как const

вставляете новый элемент, его зна­

чение

копируется

в

контейнер.

Например,

когда

вы

помещаете

в

контейнер

целое

число

со

значением

7,

результирующий

элемент

действительно

имеет

значение

7.

Этот элемент

не

является

ссылкой

или

указателем

на

какой-либо

объект,

содержащий

7.

Это

свойство

обеспечивает

нас

компактными

контей­

нерами с

быстрым

доступом.

Для

программистов,

которые

заботятся

о

разме­

рах

памяти

и

производительности

во

время

выполнения,

это

очень

важно.

Если

у

вас

есть

иерархия

классов

(§4.5),

которая

основана

на

виртуальных

функциях

для

получения

полиморфного

поведения,

не

храните

объекты

непо­

средственно

в

контейнере.

Вместо

этого

храните

указатель

(или

интеллекту­

альный

указатель;

§

13.2.1

).

Например:

vector<Shape>

vs;

 

vector<Shape*>

vps;

 

vector<unique_ptr<Shape>>

vups;

//

Нет! Здесь

мало места

11

размещения

Circle или

//Лучше, но

см. §4.5.3

//

ОК

 

для Smiley

11.2.2. Проверка выхода за rраницы диапазона

vector стандартной библиотеки не гарантирует проверки

ницы диапазона. Например:

выхода

за

гра­

void

silly(vector<Entry>& book)

(

 

 

 

 

int i

=

book[book.size()] .numЬer;

 

// ...

 

 

// book.size() за

//границами диапазона

Такая

инициализация,

вероятно,

приведет к

некоторому

случайному

значе­

нию

в

i,

а

не

к

ошибке.

Это

нежелательно,

и

ошибки

выхода

за границы

ди­

апазона

являются

распространенной

проблемой.

Поэтому

я

часто

использую

следующую

простую

адаптацию

вектора:

198

Глава

11.

Контейнеры

template<typename class Vec : puЫic

Т>

std:

:vector<T>

puЬlic: using

vector<T>::vector;

//

//

Используем

конструктор

 

из vector

(под именем

Vec)

Т&

operator [] { return

(int i) vector<T>::at(i);

11 11

С

проверкой выхода

за

границы диапазона

const

1;

Т& operator[] (int i)

const

return vector<T>::at(i);

}

// Проверка

для константных

//объектов;

§4.2.1

Vec

наследует

от

vector

все,

за

исключением

операций

индексации,

ко­

торые

он

переопределяет

с

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

проверки

выхода

за

границу

ди­

апазона.

Функция

at

()

представляет

собой

операцию

индексации

vector,

которая

генерирует

исключение

out

_

of

_

range,

если

ее

аргумент

выходит

за

пределы диапазона вектора (§3.5. 1).

 

Для Vec доступ за границами

диапазона

сгенерирует

пользователь может перехватить.

Например:

 

исключение, которое

void

checked(Vec<Entry>&

book)

{

 

 

 

try

 

 

{

 

 

book[book.size()]

{"Joe", 9999991;

 

/ / ...

 

11

Генерирует исключение

catch

(out_of_range&)

{

 

 

cerr <<

"Выход за

границы

диапазона\n";

Здесь

будет

сгенерировано,

а

затем

перехвачено

исключение

(§3.5.1

).

Если

пользователь

не

перехватит

исключение,

программа

будет

завершена

в

точ­

но

определенном

порядке,

а

не

будет

продолжать

выполнение

или

сбоить

некоторым

неопределенным

образом.

Один

из

способов

свести

к

миниму­

му

сюрпризы

из-за

неперехваченных

исключений

-

использовать

main

()

с

t

rу-блоком

в

качестве

тела.

Например:

int try {

main

()

11

Ваш

код

11.3.

list

catch

(out_of_range&)

 

 

 

 

{

 

 

 

 

 

 

cerr <<

"Ошибка выхода

за

границы

диаnазона\n";

catch

( ... )

 

 

 

 

 

{

 

 

 

 

 

 

cerr <<

"Перехвачено

неизвестное

исключение\n";

199

Этот

подход

обеспечивает

обработчик

исключений

по

умолчанию,

поэто­

му,

если

мы

не

сможем

перехватить

какое-то

из

исключений,

в

стандартный

поток

диагностики

ошибок

cerr

(§10.2)

будет

выведено

сообщение

о

неиз­

вестном

исключении.

Почему стандарт

не гарантирует проверку выхода за

Многие критически

важные приложения используют

всех индексов подразумевает снижение эффективности

границы

диапазона?

vector,

и проверка

на величину порядка

10%.

Очевидно,

что

эта

стоимость

может

сильно

различаться

в

зависимости

от

используемого

аппаратного

обеспечения,

оптимизаторов

и

приложения,

которое

использует

индексы.

Однако

опыт

показывает,

что

такие

накладные

расходы

могут

заставить

людей

предпочесть

гораздо

более

опасные

встроен­

ные

массивы. Привести

к

отказу

от

векторов

может

даже

простой

страх

пе­

ред

такими

накладными

расходами.

Как

минимум

вектор

легко

проверяется

во

время

отладки,

а

при

необходимости

можно

легко

создать

версию

вектора

с

проверкой

поверх

версии

по

умолчанию

-

без

проверки

выхода

за

границы

диапазона.

Некоторые

реализации

избавляют

вас

от

необходимости

опреде­

лять

собственный

класс

Vec

(или

его

эквивалент),

предоставляя

версию

век­

тора

с

проверкой

выхода

за

границы

диапазона

(например,

в

качестве

опции

компилятора). Цикл for для

диапазона

позволяет

отказаться

от

каких-либо

проверок

без

каких-либо

затрат,

обеспечивая

доступ

к

элементам

через

итераторы

в

диапа­

зоне

[begin (),

end

()).

То

же

самое

относится

к

алгоритмам

стандартной

библиотеки:

пока

их

аргументы,

являющиеся

итераторами,

корректны,

гаран­

тируется

отсутствие

ошибок

выхода

за

границы

диапазона.

Если

вы

можете

использовать

vector:

:

а

t

()

непосредственно

в

коде,

то

обходное

решение

Vec

вам

не

понадобится.

Кроме

того,

в

некоторых

стан­

дартных

библиотеках

реализованы

реализации

vector

с

проверкой

выхода

за

границы

диапазона,

которые

предлагают

более

полную

проверку,

чем

Vec.

11.3. list Стандартная

библиотека

предлагает

двусвязный

список

под названием

list.