Главная страница
Навигация по странице:

  • Упражнение 8.14. Почему переменные entry и nums были объявлены как const auto amp;

  • Язык программирования C Пятое издание


    Скачать 1.85 Mb.
    НазваниеЯзык программирования C Пятое издание
    Дата15.07.2019
    Размер1.85 Mb.
    Формат файлаpdf
    Имя файла620354-www.libfox.ru.pdf
    ТипДокументы
    #84130
    страница23 из 54
    1   ...   19   20   21   22   23   24   25   26   ...   54
    Упражнение 8.12. Почему в классе PersonInfo не использованы внутриклассовые инициализаторы?
    8.3.2. Использование класса ostringstream
    Класс ostringstream полезен тогда, когда необходимо организовать вывод небольшими частями за раз, не откладывая его на более позднее время. Например, могла бы возникнуть необходимость проверять и переформатировать номера телефонов, которые были прочитаны в коде предыдущего примера. Если все номера допустимы, необходимо переформатировать номера и вывести их в новый файл. Если у кого-нибудь будут недопустимые номера, то помещать их в новый файл не нужно. Вместо этого следует вывести сообщение об ошибке, содержащее имя человека и список его недопустимых номеров.
    Поскольку нельзя включать для человека данные с недопустимыми номерами, мы не можем произвести вывод, пока не просмотрим и не проверим все их номера. Но можно "записать"
    вывод в оперативную память объекта класса ostringstream: for (const auto &entry : people) { // для каждой записи в people ostringstream formatted, badNums; // объекты создаются на каждом
    // цикле for (const auto &nums : entry.phones) { // для каждого номера if (!valid(nums)) { badNums << " " << nums; // строка в badNums
    } else
    //
    "запись" в строку formatted
    Page 413/1103
    formatted << " " << format(nums);
    } if (badNums.str().empty()) // если плохих номеров нет os << entry.name << " " // вывести имя
    << formatted.str() << endl; // и переформатированные номера else // в противном случае вывести имя и плохие номера cerr << "input error: " << entry.name
    << " invalid number(s) " << badNums.str() << endl;
    }
    В этой программе подразумевается, что есть две функции, valid() и format(), которые проверяют и переформатируют номера телефонов. Интересная часть программы —
    использование строковых потоков formatted и badNums. Для записи в эти объекты используется обычный оператор вывода (<<). Но они действительно "пишут" строковые манипуляторы. Они добавляют символы к строкам в строковых потоках formatted и badNums соответственно. Упражнения раздела 8.3.2
    Упражнение 8.13. Перепишите программу номеров телефонов из этого раздела так, чтобы читать из именованного файла, а не из объекта cin.

    Упражнение 8.14. Почему переменные entry и nums были объявлены как const auto &?
    Резюме
    Язык С++ использует библиотечные классы для обработки потоков ввода и вывода.
    • Класс iostream отрабатывает ввод-вывод на консоль.
    • Класс fstream отрабатывает ввод-вывод в именованным файл.
    • Класс stringstream отрабатывает ввод-вывод в строки в оперативной памяти.
    Классы fstream и stringstream связаны происхождением от класса iostream. Классы ввода происходят от класса istream, а классы вывода — от класса ostream. Таким образом,
    операции, которые могут быть выполнены с объектом класса istream, могут быть также выполнены с объектом класса ifstream или istringstream. Аналогично для классов вывода,
    происходящих от класса ostream.
    Каждый объект ввода-вывода обладает набором флагов состояния, указывающих, возможен
    Page 414/1103
    ли ввод-вывод через этот объект. Если произошла ошибка (например, встретился конец файла в потоке ввода), то состояние объекта окажется таково, что никакой дальнейший ввод невозможен, пока ошибка не будет исправлена. Библиотека предоставляет набор функций для установки и проверки этих состояний.
    Термины
    Класс fstream. Файловый поток, обеспечивающий чтение и запись в тот же файл. По умолчанию объект класса ifstreams открывает файл одновременно в режимах in и out.
    Класс ifstream. Файловый поток, читающий данные из файла. По умолчанию поток ifstream открывается в режиме in.
    Класс istringstream. Строковый поток, читающий данные из строки.
    Класс ofstream. Файловый поток, записывающий данные в файл. По умолчанию поток ofstream открывается в режиме out.
    Класс ostringstream. Строковый поток, записывающий данные в строку.
    Класс stringstream. Строковый поток, читающий и записывающий данные в строку.
    Наследование (inheritance). Программное средство, позволяющее типу наследовать интерфейс другого типа. Классы ifstream и istringstream происходят от классов istream и ofstream, а класс ostringstream происходит от класса ostream. Более подробная информация о наследовании приведена в главе 15.
    Режим файла (file mode). Флаги классов заголовка fstream, устанавливаемые при открытии файла и задающие способ его применения. Строковый поток (string stream). Потоковый объект, читающий или записывающий данные в строку. Кроме возможностей, присущих классу iostream, классы строковых потоков определяют перегруженную функцию str(). Вызов функции str() без аргументов возвращает строку, с которой связан объект строкового потока,
    а ее вызов со строковым аргументом свяжет строковый поток с копией этой строки.
    Файловый поток (file stream). Потоковый объект этого класса позволяет читать и записывать данные в именованный файл. Кроме возможностей, присущих классу iostream, класс fstream обладает также функциями-членами open() и close(). Функция-член open() получает символьную строку в стиле С, которая содержит имя открываемого файла и необязательный аргумент, задающий режим. Функция-член close() закрывает файл, с которым связан поток.
    Ее следует вызвать прежде, чем может быть открыт другой файл.
    Флаг состояния (condition state). Флаги и связанные с ними функции потоковых классов позволяют выяснить, пригоден ли данный поток для использования.
    Глава 9
    Последовательные контейнеры
    Эта глава подробно останавливается на материале главы 3 и завершает обсуждение
    Page 415/1103
    последовательных контейнеров стандартной библиотеки. Порядок элементов в последовательном контейнере соответствует порядку их добавления в контейнер. В
    библиотеке определено также несколько ассоциативных контейнеров, позиция элементов которых зависит от ключа, ассоциируемого с каждым элементом. Операции, специфические для ассоциативных контейнеров, рассматриваются в главе 11.
    Классы контейнеров имеют общий интерфейс, который каждый из контейнеров дополняет собственным способом. Общий интерфейс упрощает изучение библиотечных классов; то, что стало известно о контейнере одного вида, относится к контейнеру другого. Однако каждый вид контейнеров обладает индивидуальной эффективностью и функциональными возможностями.
    Контейнер (container) содержит коллекцию объектов определенного типа.
    Последовательные контейнеры (sequential container) позволяют контролировать порядок, в котором хранятся элементы и предоставляется доступ к ним. Этот порядок не зависит от значений элементов, он соответствует позиции, в которую помещаются элементы контейнера. В отличие от них, ассоциативные контейнеры (упорядоченные и неупорядоченные) хранят свои элементы на основании значения ключа, как будет описано в главе 11.
    Библиотека предоставляет также три контейнерных адаптера, каждый из которых адаптирует определенный тип контейнера, определяя иной интерфейс к функциям контейнера. Адаптеры рассматриваются в конце этой главы.
    Эта глава основана на материале разделов 3.2–3.4. Здесь подразумевается, что читатель знаком с их материалом.
    9.1. Обзор последовательных контейнеров
    Все последовательные контейнеры, перечисленные в табл. 9.1, предоставляют быстрый последовательный доступ к своим элементам. Однако эти контейнеры обладают разной производительностью и возможностями, включая следующие:
    • цена добавления и удаления элементов из контейнера;
    • цена непоследовательного доступа к элементам контейнера.
    Таблица 9.1. Типы последовательных контейнеров vector Массив переменного размера
    (вектор). Обеспечивает быстрый произвольный доступ. Вставка и удаление элементов, кроме как в конец, могут быть продолжительными deque Двухсторонняя очередь. Обеспечивает быстрый произвольный доступ. Быстрая вставка и удаление в начало и конец list
    Двухсвязный список. Обеспечивает только двунаправленный последовательный доступ.
    Быстрая вставка и удаление в любую позицию forward_list Односвязный список.
    Обеспечивает только последовательный доступ в одном направлении. Быстрая вставка и удаление в любую позицию array Массив фиксированного размера. Обеспечивает быстрый произвольный доступ. Не позволяет добавлять или удалять элементы string
    Специализированный контейнер, подобный вектору, содержащий символы. Быстрый произвольный доступ. Быстрая вставка и удаление в конец
    За исключением контейнера array, являющегося контейнером фиксированного размера,
    Page 416/1103
    контейнеры обеспечивают эффективное, гибкое управление памятью, позволяя добавлять и удалять элементы, увеличивая и сокращая размер контейнера. Стратегии, используемые контейнерами для хранения своих элементов, имеют незначительное, а иногда существенное влияние на эффективность операций с ними. В некоторых случаях эти стратегии влияют также на то, поддерживает ли некий контейнер определенную операцию.
    Например, контейнеры string и vector содержат свои элементы в соседних областях памяти.
    Поскольку элементы расположены последовательно, их адрес по индексу вычисляется очень быстро. Но добавление или удаление элемента в середину такого контейнера занимает много времени: для обеспечения последовательности все элементы после удаления или вставки придется переместить. Кроме того, добавление элемента может иногда потребовать резервирования дополнительного места для хранения. В этом случае каждый элемент следует переместить в новое место.
    Контейнеры list и forward_list разработаны так, чтобы быстро добавлять и удалять элементы в любой позиции контейнера. Однако эти типы не поддерживают произвольный доступ к элементам: обратиться к элементу можно, только перебрав контейнер. Кроме того,
    дополнительные затраты памяти этих контейнеров зачастую являются существенными по сравнению с контейнерами vector, deque и array.
    Контейнер deque — это более сложная структура данных. Как и контейнеры string, vector и deque, они обеспечивают быстрый произвольный доступ. Как и у контейнеров string и vector,
    добавление или удаление элементов в середину контейнера deque — потенциально дорогая операция. Однако добавление и удаление элементов в оба конца контейнера deque являются быстрой операцией, сопоставимой с таковыми у контейнеров list и forward_list.
    Типы forward_list и array были добавлены согласно новому стандарту. Класс array — более безопасная и легкая в употреблении альтернатива встроенным массивам. Как и встроенные массивы, объект библиотечного класса array имеет фиксированный размер. В результате он не поддерживает операции по добавлению и удалению элементов или изменению размеров контейнера. Класс forward_list предназначен для замены наилучших самодельных односвязных списков. Следовательно, у него нет функции size(), поскольку сохранение или вычисление его размера повлекло бы дополнительные затраты по сравнению с самодельным списком. Для других контейнеров функция size() гарантированно будет выполняться быстро с постоянной скоростью.
    По причинам, изложенным в разделе 13.6, новые библиотечные контейнеры работают существенно быстрее, чем в предыдущих версиях. Библиотечные контейнеры почти наверняка работают так же (если не лучше), чем даже наиболее тщательно разработанные альтернативы. Современные программы С++ должны использовать библиотечные контейнеры, а не множество примитивных структур, подобных массивам. Решение о том,
    какой последовательный контейнер использовать
    Как правило, если нет серьезных оснований предпочесть другой контейнер, лучше использовать контейнер vector.
    Ниже приведено несколько эмпирических правил по выбору используемого контейнера.
    • Если нет причин использовать другой контейнер, используйте вектор.
    • Если имеется много небольших элементов и дополнительных затрат, не используйте контейнер list или forward_list.
    • Если нужен произвольный доступ к элементам, используйте контейнер vector или deque.
    • Если необходима вставка или удаление элементов в середину, используйте контейнер list
    Page 417/1103
    или forward_list.
    • Если необходима вставка или удаление элементов в начало или в конец, но не в середину,
    используйте контейнер deque.
    • Если вставка элементов в середину контейнера нужна только во время чтения ввода, а впоследствии нужен произвольный доступ к элементам, то:
    • сначала решите, необходимо ли фактически добавлять элементы в середину контейнера.
    Зачастую проще добавлять элементы в vector, а затем использовать библиотечную функцию sort() (рассматриваемую в разделе 10.2.3) для переупорядочивания контейнера по завершении ввода;
    • если вставка в середину необходима, рассмотрите возможность использования контейнера list на фазе ввода, а по его завершении — копирования списка в вектор.
    Но что если нужен и произвольный доступ, и вставка (удаление) элементов в середину контейнера? Решение будет зависеть от отношения цены доступа к элементам в контейнере list и forward_list цены вставки (удаления) элементов в контейнер vector или deque. Как правило, выбор типа контейнера определит преобладающая операция приложения
    (произвольный доступ или вставка и удаление). В таких случаях, вероятно, потребуется проверка производительности приложения с использованием контейнеров обоих типов.
    Если вы не уверены, какой контейнер использовать, напишите свой код так, чтобы он использовал только те операции, которые совпадают у вектора и списка: используйте итераторы, а не индексы, и избегайте произвольного доступа к элементам. Так будет удобней заменить вектор на список при необходимости. Упражнения раздела 9.1
    Упражнение 9.1. Какой из контейнеров (vector, deque или list) лучше подходит для приведенных ниже задач? Объясните, почему. Если нельзя отдать предпочтение тому или иному контейнеру, объясните, почему?
    • Чтение известного заранее количества слов и вставка их в контейнер в алфавитном порядке по мере ввода. В следующей главе будет показано, что ассоциативные контейнеры лучше подходят для этой задачи.
    • Чтение неизвестного заранее количества слов. Новые слова всегда добавляются в конец.
    Следующее значение извлекается из начала.
    • Чтение неизвестного заранее количества целых чисел из файла. Числа сортируются, а затем выводятся на стандартное устройство вывода.
    9.2. Обзор библиотечных контейнеров
    Возможные операции с контейнерами составляют своего рода иерархию.
    • Некоторые функциональные возможности (табл. 9.2) поддерживаются контейнерами всех типов.
    • Другие операции являются специфическими только для последовательных (табл. 9.3),
    ассоциативных (табл. 11.7) или неупорядоченных (табл. 11.8) контейнеров.
    • Остальные являются общими лишь для небольшого подмножества контейнеров.
    Page 418/1103

    Таблица 9.2. Средства контейнеров Псевдонимы типов iterator Тип итератора для контейнера данного типа const_iterator Тип итератора, позволяющий читать, но не изменять значение элемента size_type Целочисленный беззнаковый тип, размер которого достаточно велик,
    чтобы содержать значение размера наибольшего возможного контейнера данного типа difference_type Целочисленный знаковый тип, размер которого достаточно велик, чтобы содержать значение разницы между двумя итераторами value_type Тип элемента reference
    Тип l-значения элемента; то же, что и value_type& const_reference Тип константного l-значения элемента; аналог const value_type& Конструкторы С с; Стандартный конструктор, создающий пустой контейнер С c1(c2); Создает контейнер c1 как копию контейнера c2 С c(b, е); Копирует элементы из диапазона, обозначенного итераторами b и е ( недопустимо для массива ) C c{а, b, c...}; Списочная инициализация контейнера с Присвоение и замена c1 = c2 Заменяет элементы контейнера c1 элементами контейнера c2 c1 = {a, b, c...}
    Заменяет элементы контейнера c1 элементами списка ( недопустимо для массива ) a.swap(b) Меняет местами элементы контейнеров а и b swap(а, b)
    Эквивалент a.swap(b) Размер c.size() Возвращает количество элементов контейнера c ( недопустимо для контейнера forward_list) c.max_size() Возвращает максимально возможное количество элементов контейнера с c.empty() Возвращает логическое значение false, если контейнер c пуст. В противном случае возвращает значение true Добавление/удаление элементов (недопустимо для массива) Примечание: интерфейс этих функций зависит от типа контейнера c.insert( args ) Копирует элемент(ы), указанный параметром args , в контейнер c c.emplace( inits ) Использует параметр inits для создания элемента в контейнере с c.erase( args ) Удаляет элемент(ы), указанный параметром args , из контейнера c c.clear() Удаляет все элементы из контейнера c; возвращает значение void Операторы равенства и отношения ==, != Равенство допустимо для контейнеров всех типов <, <=, >, >= Операторы отношения ( недопустимы для неупорядоченных ассоциативных контейнеров ) Получения итераторов c.begin(), c.end() Возвращают итератор на первый и следующий после последнего элемент в контейнере с c.cbegin(), c.cend() Возвращают const_iterator Дополнительные члены реверсивных контейнеров (недопустимы для forward_list) reverse_iterator Итератор,
    обеспечивающий доступ к элементам в обратном порядке const_reverse_iterator Реверсивный итератор, не позволяющий запись в элементы с.rbegin(), c.rend() Возвращает итератор на последний и следующий после первого элементы контейнера c c.crbegin(), c.crend()
    Возвращают итератор const_reverse_iterator
    В этом разделе рассматриваются аспекты, являющиеся общими для всех контейнеров.
    Остальная часть этой главы посвящена исключительно последовательным контейнерам;
    операции, специфические для ассоциативных контейнеров, рассматриваются в главе 11.
    Обычно каждый контейнер определяется в файле заголовка, название которого совпадает с именем типа. Таким образом, тип deque определен в заголовке deque, тип list — в заголовке list и т.д. Контейнеры — это шаблоны классов (см. раздел 3.3). Подобно векторам, при
    Page 419/1103
    создании контейнера специфического типа необходимо предоставить дополнительную информацию. Для большинства контейнеров, но не всех, предоставляемой информацией является тип элемента: list<Sales_data> // список, содержащий объекты класса Sales_data deque<double> // двухсторонняя очередь переменных типа double Ограничения на типы, которые может содержать контейнер
    Типом элемента последовательного контейнера может быть практически любой тип. В
    частности, типом элемента контейнера может быть другой контейнер. Такие контейнеры определяют точно так же, как любые другие: в угловых скобках указывается тип элемента
    (которым в данном случае является другой контейнер): vector<vector<string>> lines; // вектор векторов где lines — это вектор, элементами которого являются векторы строк.
    Устаревшие компиляторы могут потребовать пробела между угловыми скобками, например vector<vector<string> >.
    Несмотря на то что в контейнере можно хранить практически любой тип, некоторые операции налагают на тип элемента собственные требования. Можно определить контейнер для типа,
    который не поддерживает определенное операцией требование, но использовать операцию можно, только если тип элемента отвечает требованиям этой операции.
    В качестве примера рассмотрим конструктор последовательного контейнера, получающий аргумент размера (см. раздел 3.3.1) и использующий стандартный конструктор типа элемента. У некоторых классов нет стандартного конструктора. Вполне можно определить контейнер, содержащий объекты такого типа, но создать такой контейнер, используя только количество элементов, нельзя:
    // тип noDefault не имеет стандартного конструктора vector<noDefault> v1(10, init); // ok: предоставлен инициализатор
    // элемента vector<noDefault> v2(10); // ошибка: необходимо предоставить
    // инициализатор элемента
    Page 420/1103

    Поскольку рассматриваются контейнерные операции, следует заметить, что тип элемента накладывает дополнительные ограничения, если таковые вообще имеются, на каждую операцию с контейнером. Упражнения раздела 9.2
    Упражнение 9.2. Определите список (list), элементами которого будут двухсторонние очереди целых чисел.
    9.2.1. Итераторы
    Подобно контейнерам, у итераторов есть общий интерфейс: если итератор поддерживает некую функцию, то аналогичным образом она работает с каждым поддерживающим ее итератором. Например, итераторы всех контейнеров стандартных типов позволяют обращаться к элементу, предоставляя оператор обращения к значению. Аналогично все итераторы библиотечных контейнеров определяют оператор инкремента для перемещения от одного элемента к следующему.
    За одним исключением контейнерные итераторы поддерживают все функции, перечисленные в табл. 3.6. Исключение в том, что итераторы контейнера forward_list не поддерживают оператор декремента (--). Операторы арифметических действий с итераторами,
    перечисленными в табл. 3.7, применимы только к итераторам контейнеров string, vector,
    deque и array. К итераторам контейнеров любых других типов эти операторы неприменимы.
    Диапазоны итераторов
    Концепция диапазона итераторов фундаментальна для стандартной библиотеки.
    Диапазон итераторов (iterator range) обозначается парой итераторов, каждый из которых указывает на элемент или на следующий элемент после последнего в том же контейнере. Эти два итератора,
    обозначающие диапазон элементов контейнера, зачастую называют begin и end или, что несколько обманчиво, first и last.
    Хоть имя last и общепринято, оно немного вводит в заблуждение, поскольку второй итератор никогда не указывает на последний элемент диапазона. Вместо этого он указывает на позицию следующего элемента после последнего. Диапазон включает элемент,
    обозначенный итератором first, и все элементы от него до обозначенного итератором last, но не включая его.
    Такой диапазон элементов называется интервал, включающий левый элемент (left-inclusive interval). Вот стандартная математическая форма записи такого диапазона:
    [ begin, end )
    Это указывает, что диапазон начинается с элемента, обозначенного итератором begin, и заканчивается элементом перед тем, который обозначен итератором end. Итераторы begin и end должны относиться к тому же контейнеру. Итератор end может быть равен итератору begin, но не должен указывать на элемент перед обозначенным итератором begin.
    Требования к итераторам, формирующим диапазон
    Два итератора, begin и end, позволяют задать диапазон при следующих условиях.
    Page 421/1103

    • Итераторы относятся к существующим элементам или к следующему элементу за концом того же контейнера.
    • Элемент end достижим благодаря последовательному приращению итератора begin.
    Другими словами, итератор end не должен предшествовать итератору begin.
    Компилятор не может сам соблюдать эти требования. Позаботиться об этом придется разработчику. Смысл использования диапазонов, включающих левый элемент
    Библиотека использует диапазоны, включающие левый элемент, потому, что они обладают двумя очень полезными качествами (напомним, что допустимый диапазон обозначают итераторы begin и end).
    • Если итератор begin равен итератору end, то диапазон пуст.
    • Если итератор begin не равен итератору end, в диапазоне содержится по крайней мере один элемент и итератор begin указывает на первый из них.
    • Можно осуществлять инкремент итератора begin до тех пор, пока он не станет равен итератору end (т.е. begin == end).
    Благодаря этим качествам можно создавать вполне безопасные циклы обработки диапазона элементов, например, такие: while (begin != end) {
    *begin = val; // ok: диапазон не пуст, begin обозначает элемент
    ++begin; // переместить итератор и получить следующий элемент
    }
    Если итераторы begin и end задают допустимый диапазон элементов, выполнение условия begin == end означает, что диапазон пуст. В данном случае это условие выхода из цикла.
    Если диапазон не пуст, значит, итератор begin указывает на элемент в этом не пустом диапазоне. Вполне очевидно, что в теле цикла while можно безопасно обращаться к значению итератора begin, поскольку оно гарантировано существует. И наконец, поскольку инкремент итератора begin осуществляется в теле цикла, последний гарантированно будет конечным.
    Упражнения раздела 9.2.1

    1   ...   19   20   21   22   23   24   25   26   ...   54


    написать администратору сайта