2 семестр / Литература / Язык программирования С++. Краткий курс. Страуструп
.pdf190
Глава
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.