Язык программирования C++.

9.2. Обзор библиотечных контейнеров. Глава 9. Последовательные контейнеры. Часть II. Библиотека С++ Язык программирования C++.9.2. Обзор библиотечных контейнеров.

Возможные операции с контейнерами составляют своего рода иерархию.

• Некоторые функциональные возможности (табл. 9.2) поддерживаются контейнерами всех типов.

• Другие операции являются специфическими только для последовательных (табл. 9.3), ассоциативных (табл. 11.7) или неупорядоченных (табл. 11.8) контейнеров.

• Остальные являются общими лишь для небольшого подмножества контейнеров.

Таблица 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). Подобно векторам, при создании контейнера специфического типа необходимо предоставить дополнительную информацию. Для большинства контейнеров, но не всех, предоставляемой информацией является тип элемента:

list<Sales_data> // список, содержащий объекты класса Sales_data

deque<double>    // двухсторонняя очередь переменных типа double

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

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

vector<vector<string>> lines; // вектор векторов

Где lines — это вектор, элементами которого являются векторы строк.

Язык программирования C++.Устаревшие компиляторы могут потребовать пробела между угловыми скобками, например vector<vector<string> >.

Несмотря на то что в контейнере можно хранить практически любой тип, некоторые операции налагают на тип элемента собственные требования. Можно определить контейнер для типа, который не поддерживает определенное операцией требование, но использовать операцию можно, только если тип элемента отвечает требованиям этой операции.

В качестве примера рассмотрим конструктор последовательного контейнера, получающий аргумент размера (см. раздел 3.3.1) и использующий стандартный конструктор типа элемента. У некоторых классов нет стандартного конструктора. Вполне можно определить контейнер, содержащий объекты такого типа, но создать такой контейнер, используя только количество элементов, нельзя:

// тип noDefault не имеет стандартного конструктора

vector<noDefault> v1(10, init); // ok: предоставлен инициализатор

                                // элемента

vector<noDefault> v2(10);       // ошибка: необходимо предоставить

                                // инициализатор элемента

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

Упражнения раздела 9.2.

Упражнение 9.2. Определите список ( list), элементами которого будут двухсторонние очереди целых чисел.

9.2.1. Итераторы. 9.2. Обзор библиотечных контейнеров. Глава 9. Последовательные контейнеры. Часть II. Библиотека С++ Язык программирования C++.9.2.1. Итераторы.

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

За одним исключением контейнерные итераторы поддерживают все функции, перечисленные в табл. 3.6. Исключение в том, что итераторы контейнера forward_list не поддерживают оператор декремента ( --). Операторы арифметических действий с итераторами, перечисленными в табл. 3.7, применимы только к итераторам контейнеров string, vector, deque и array. К итераторам контейнеров любых других типов эти операторы неприменимы.

Диапазоны итераторов.

Язык программирования C++.Концепция диапазона итераторов фундаментальна для стандартной библиотеки.

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

Хоть имя last и общепринято, оно немного вводит в заблуждение, поскольку второй итератор никогда не указывает на последний элемент диапазона. Вместо этого он указывает на позицию следующего элемента после последнего. Диапазон включает элемент, обозначенный итератором first, и все элементы от него до обозначенного итератором last, но не включая его.

Такой диапазон элементов называется интервал, включающий левый элемент (left-inclusive interval). Вот стандартная математическая форма записи такого диапазона:

[ begin, end )

Это указывает, что диапазон начинается с элемента, обозначенного итератором begin, и заканчивается элементом перед тем, который обозначен итератором end. Итераторы begin и end должны относиться к тому же контейнеру. Итератор end может быть равен итератору begin, но не должен указывать на элемент перед обозначенным итератором begin.

Требования к итераторам, формирующим диапазон. Два итератора, begin и end, позволяют задать диапазон при следующих условиях. • Итераторы относятся к существующим элементам или к следующему элементу за концом того же контейнера. • Элемент end достижим благодаря последовательному приращению итератора begin. Другими словами, итератор end не должен предшествовать итератору begin. Язык программирования C++.Компилятор не может сам соблюдать эти требования. Позаботиться об этом придется разработчику.

Смысл использования диапазонов, включающих левый элемент.

Библиотека использует диапазоны, включающие левый элемент, потому, что они обладают двумя очень полезными качествами (напомним, что допустимый диапазон обозначают итераторы 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.

Упражнение 9.3. Каким условиям должны удовлетворять итераторы, обозначающие диапазон?

Упражнение 9.4. Напишите функцию, которая получает два итератора вектора vector<int> и значение типа int. Организуйте поиск этого значения в диапазоне и возвратите логическое значение (тип bool), указывающее, что значение найдено.

Упражнение 9.5. Перепишите предыдущую программу так, чтобы она возвращала итератор на найденный элемент. Функция должна учитывать случай, когда элемент не найден.

Упражнение 9.6. Что не так со следующей программой? Как ее можно исправить?

list<int> lst1;

list<int>::iterator iter1 = lst1.begin(),

iter2 = lst1.end();

while (iter1 < iter2) /* ... */

9.2.2. Типы-члены классов контейнеров.

Класс каждого контейнера определяет несколько типов, представленных в табл. 9.2. Три из них уже использовались: size_type (см. раздел 3.2.2), iterator и const_iterator (см. раздел 3.4.1).

Кроме итераторов уже использовавшихся типов, большинство контейнеров предоставляет реверсивные итераторы ( reverse_iterator). Другими словами, реверсивный итератор — это итератор, перебирающий контейнер назад и инвертирующий значение его операторов. Например, оператор ++ возвращает реверсивный итератор к предыдущему элементу. Более подробная информация о реверсивных итераторах приведена в разделе 10.4.3.

Остальные псевдонимы типов позволяют использовать тип хранящихся в контейнере элементов, даже не зная его конкретно. Если необходим тип элемента, используется тип value_type контейнера. Если необходима ссылка на этот тип, используется член reference или const_reference. Эти связанные с элементами псевдонимы типов весьма полезны в обобщенном программировании, которое рассматривается в главе 16.

Чтобы использовать один из этих типов, следует указать класс, членами которого они являются.

// iter имеет тип iterator, определенный классом list<string>

list<string>::iterator iter;

// count имеет тип difference_type, определенный классом vector<int>

vector<int>::difference_type count;

В этих объявлениях оператор области видимости (см. раздел 1.2) позволяет указать, что используется тип-член iterator класса list<string> и тип-член difference_type, определенный классом vector<int>, соответственно.

Упражнения раздела 9.2.2.

Упражнение 9.7. Какой тип следует использовать в качестве индекса для вектора целых чисел?

Упражнение 9.8. Какой тип следует использовать для чтения элементов в списке строк?

9.2.3. Функции-члены begin() и end(). 9.2. Обзор библиотечных контейнеров. Глава 9. Последовательные контейнеры. Часть II. Библиотека С++ Язык программирования C++.9.2.3. Функции-члены begin() и end().

Функции-члены begin() и end() (см. раздел 3.4.1) возвращают итераторы на первый и следующий после последнего элементы контейнера соответственно. Эти итераторы, как правило, используют при создании диапазона итераторов, охватывающего все элементы контейнера.

Как показано в табл. 9.2, есть несколько версий этих функций: имена которых начинаются с буквы r возвращают реверсивные итераторы (рассматриваются в разделе 10.4.3), а с буквы c — возвращают константную версию соответствующего итератора:

list<string> a = {"Milton", "Shakespeare", "Austen"};

auto it1 = a.begin();  // list<string>::iterator

auto it2 = a.rbegin(); // list<string>::reverse_iterator

auto it3 = a.cbegin(); // list<string>::const_iterator

auto it4 = a.crbegin();// list<string>::const_reverse_iterator

Функции, имена которых не начинаются с буквы c, перегружены. Таким образом, фактически есть две функции-члена begin(). Одна является константной (см. раздел 7.1.2) и возвращает тип const_iterator контейнера. Вторая не константна и возвращает тип iterator контейнера. Аналогично для функций rbegin(), end() и rend(). При вызове такой функции-члена для неконстантного объекта используется версия, возвращающая тип iterator. Константная версия итераторов будет получена только при вызове этих функций для константного объекта. Подобно указателям и ссылкам на константу, итератор типа iterator можно преобразовать в соответствующий итератор типа const_iterator, но не наоборот.

Язык программирования C++.Версии этих функций, имена которых не начинаются с буквы с, были введены согласно новому стандарту для обеспечения использования ключевого слова auto с функциями begin() и end() (см. раздел 2.5.2). Прежде не было никакого иного выхода, кроме как явно указать необходимый тип итератора:

// тип указан явно

list<string>::iterator it5 = a.begin();

list<string>::const_iterator it6 = a.begin();

// iterator или const_iterator в зависимости от типа а

auto it7 = a.begin();  // const_iterator только если a константа

auto it8 = a.cbegin(); // it8 - const_iterator

Когда с функциями begin() или end() используется ключевое слово auto, тип возвращаемого итератора зависит от типа контейнера. То, как предполагается использовать итератор, несущественно. Версии c позволяют получать итератор типа const_iterator независимо от типа контейнера.

Язык программирования C++.Когда доступ на запись не нужен, используйте версии cbegin() и cend().

Упражнения раздела 9.2.3.

Упражнение 9.9. В чем разница между функциями begin() и cbegin()?

Упражнение 9.10. Каковы типы следующих четырех объектов?

vector<int> v1;

const vector<int> v2;

auto it1 = v1.begin(), it2 = v2.begin();

auto it3 = v1.cbegin(), it4 = v2.cbegin();

9.2.4. Определение и инициализация контейнера. 9.2. Обзор библиотечных контейнеров. Глава 9. Последовательные контейнеры. Часть II. Библиотека С++ Язык программирования C++.9.2.4. Определение и инициализация контейнера.

Каждый контейнерный тип определяет стандартный конструктор (см. раздел 7.1.4). За исключением контейнера array стандартный конструктор создает пустой контейнер определенного типа. Также за исключением контейнера array другие конструкторы получают аргументы, которые определяют размер контейнера и исходные значения его элементов.

Инициализация контейнера как копии другого контейнера.

Существуют два способа создать новый контейнер как копию другого: можно непосредственно скопировать контейнер или (за исключением контейнера array) скопировать только диапазон его элементов, обозначенный парой итераторов.

Таблица 9.3. Определение и инициализация контейнера

С c; Стандартный конструктор. Если C — массив, то элементы контейнера с инициализируются значением по умолчанию; в противном случае контейнер с пуст
С c1(c2) С c1 = c2 Контейнер c1 — копия c2. Контейнеры c1 и c2 должны быть одинакового типа (т.е. должны иметь тот же тип контейнера и содержать элементы того же типа; у массивов должен также совпадать размер)
С с{a, b, с...} С с = {a, b, с...} Контейнер  c содержит копию элементов из списка инициализации. Тип элементов в списке должен быть совместимым с типом элементов C. В случае массива количество элементов списка не должно превышать размер массива, а все недостающие элементы инициализируются значением по умолчанию (см. раздел 3.3.1)
С с(b, е) Контейнер  c содержит копию элементов из диапазона, обозначенного итераторами b и е. Тип элементов должен быть совместимым с типом элементов С. (Недопустимо для массива.)
Получающие размер конструкторы допустимы только для последовательных контейнеров (исключая массив)
С seq(n) Контейнер seq содержит n элементов, инициализированных значением по умолчанию; этот конструктор является явным (см. раздел 7.5.4). (Недопустимо для строки.)
С seq(n,t) Контейнер seq содержит n элементов со значением t

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

// каждый контейнер имеет три элемента, инициализированных

// предоставленными инициализаторами

list<string> authors = {"Milton", "Shakespeare", "Austen"};

vector<const char*> articles = {"a", "an", "the"};

list<string> list2(authors);     // ok: типы совпадают

deque<string> authList(authors); // ошибка: типы контейнеров

                                 // не совпадают

vector<string> words(articles);  // ошибка: типы элементов не совпадают

// ok: преобразует элементы const char* в string

forward_list<string> words(articles.begin(), articles.end());

Язык программирования C++.При копировании содержимого одного контейнера в другой типы контейнеров и их элементов должны точно совпадать.

Конструктор, получающий два итератора, использует их для обозначения диапазона копируемых элементов. Как обычно, итераторы отмечают первый и следующий элемент после последнего копируемого. Размер нового контейнера будет совпадать с количеством элементов в диапазоне. Каждый элемент в новом контейнере инициализируется значением соответствующего элемента в диапазоне.

Поскольку итераторы обозначают диапазон, этот конструктор можно использовать для копирования части последовательности. С учетом того что it является итератором, обозначающим элемент в контейнере authors, можно написать следующее:

// копирует до, но не включая элемент, обозначенный итератором it

deque<string> authList(authors.begin(), it);

Списочная инициализация.

Язык программирования C++.По новому стандарту контейнер допускает списочную инициализацию (см. раздел 3.3.1):

// каждый контейнер имеет три элемента, инициализированных

// предоставленными инициализаторами

list<string> authors = {"Milton", "Shakespeare", "Austen"};

vector<const char*> articles = {"a", "an", "the"};

Это определяет значение каждого элемента в контейнере явно. Кроме контейнеров таких типов, как array, список инициализации неявно определяет также размер контейнера: у контейнера будет столько элементов, сколько инициализаторов в списке.

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

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

vector<int> ivec(10, -1);     // десять элементов типа int; значение -1

list<string> svec(10, "hi!"); // десять строк; значение "hi!"

forward_list<int> ivec(10);   // десять элементов; значение 0

deque<string> svec(10);       // десять элементов; все пустые строки

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

Язык программирования C++.Конструкторы, получающие размер, допустимы только для последовательных контейнеров; ассоциативные контейнеры их не поддерживают.

Библиотечные массивы имеют фиксированный размер.

Подобно тому, как размер встроенного массива является частью его типа, размер библиотечного контейнера array тоже входит в состав его типа. Когда определяется массив, кроме типа элемента задается также размер контейнера:

array<int, 42>    // тип: массив, содержащий 42 целых числа

array<string, 10> // тип: массив, содержащий 10 строк

Чтобы использовать контейнер array, следует указать тип элемента и его размер:

array<int, 10>::size_type i; // тип массива включает тип элемента

                             // и размер

array<int>::size_type j;     // ошибка: array<int> - это не тип

Поскольку размер является частью типа массива, контейнер array не поддерживает обычные конструкторы контейнерных классов. Эти конструкторы, явно или неявно, определяют размер контейнера. В случае массива разрешение пользователям передавать аргумент размера было бы избыточно (в лучшем случае) и приводило бы к ошибкам.

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

array<int, 10> ia1;        // десять целых чисел, инициализированных

                           // значением по умолчанию

array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9}; // списочная инициализация

array<int, 10> ia3 = {42}; // ia3[0] содержит значение 42, остальные

                           // элементы - значение 0

Следует заметить, что хоть и нельзя копировать или присваивать объекты массивов встроенных типов (см. раздел 3.5.1), для контейнеров array такого ограничения нет:

int digs[10] = {0,1,2,3,4,5,6,7,8,9};

int cpy[10] = digs; // ошибка: встроенные массивы не допускают

                    // копирования и присвоения

array<int, 10> digits = {0,1,2,3,4,5,6,7,8,9};

array<int, 10> copy = digits; // ok: если типы массивов совпадают

Как обычно, инициализирующий контейнер должен иметь тот же тип, что и создаваемый. Для контейнера array совпадать должны тип элемента и размер, поскольку размер массива — часть его типа.

Упражнения раздела 9.2.4.

Упражнение 9.11. Приведите пример каждого из шести способов создания и инициализации контейнеров vector. Объясните, какие значения будет содержать каждый вектор.

Упражнение 9.12. Объясните различие между конструктором, получающим контейнер для копирования, и конструктором получающим два итератора.

Упражнение 9.13. Как инициализировать контейнер vector<double> из контейнера list<int> и контейнера vector<int>? Напишите код для проверки ответов.

9.2.5. Присвоение и функция swap().

Связанные с присвоением операторы, перечисленные в табл. 9.4, воздействуют на весь контейнер. Оператор присвоения заменяет весь диапазон элементов в левом контейнере копиями элементов из правого:

c1 = c2;      // заменяет содержимое контейнера c1 копией

              // элементов контейнера c2

c1 = {a,b,c}; // после присвоения контейнер c1 имеет размер 3

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

Таблица 9.4. Операторы присвоения контейнеров

c1 = c2 Заменяет элементы контейнера c1 копиями элементов контейнера c2. Контейнеры c1 и c2 должны иметь тот же тип
с = {a, b, с...} Заменяет элементы контейнера c1 копиями элементов списка инициализации. (Недопустимо для массива.)
swap(c1, c2) c1.swap(c2) Обменивает элементы контейнеров c1 и c2. Контейнеры c1 и c2 должны иметь тот же тип. Обычно функция swap() выполняется намного быстрее, чем процесс копирования элементов из контейнера c2 в c1
Операторы присвоения недопустимы для ассоциативных контейнеров и массива
seq.assign(b,е) Заменяет элементы в контейнере seq таковыми из диапазона, обозначенного итераторами b и е. Итераторы b и е не должны ссылаться на элементы в контейнере seq
seq.assign(il) Заменяет элементы в контейнере seq таковыми из списка инициализации il
seq.assign(n,t) Заменяет элементы в контейнере seq набором из n элементов со значением t

В отличие от встроенных массивов, библиотечный тип array поддерживает присвоение. У левых и правых операндов должен быть одинаковый тип:

array<int, 10> a1 = {0,1,2,3,4,5,6,7,8,9};

array<int, 10> а2 = {0}; // все элементы со значением 0

a1 = а2;  // замена элементов в a1

а2 = {0}; // ошибка: нельзя присвоить массиву значения из списка

Поскольку размер правого операнда может отличаться от размера левого операнда, тип array не поддерживает функцию assign() и это не позволяет присваивать значения из списка.

Язык программирования C++.Связанные с присвоением операторы делают недопустимыми итераторы, ссылки и указатели на контейнер слева.

Применение функции assign() (только последовательные контейнеры).

Оператор присвоения требует совпадения типов левых и правых операндов. Он копирует все элементы правого операнда в левый. Последовательные контейнеры (кроме array) определяют также функцию-член assign(), обеспечивающую присвоение разных, но совместимых типов, или присвоение контейнерам последовательностей. Функция assign() заменяет все элементы в левом контейнере копиями элементов, указанных в ее аргументе. Например, функцию assign() можно использовать для присвоения диапазона значений char* из вектора в список строк:

list<string> names;

vector<const char*> oldstyle;

names = oldstyle; // ошибка: типы контейнеров не совпадают

// ok: преобразование из const char* в string возможно

names.assign(oldstyle.cbegin(), oldstyle.cend());

Вызов функции assign() заменяет элементы в контейнере names копиями элементов из диапазона, обозначенного итераторами. Аргументы функции assign() определяют количество элементов контейнера и их значения.

Язык программирования C++.Поскольку существующие элементы заменяются, итераторы, переданные функции assign(), не должны относиться к контейнеру, для которого вызвана функция assign().

Вторая версия функции assign() получает целочисленное значение и значение элемента. Она заменяет указанное количество элементов контейнера заданным значением:

// эквивалент slist1.clear();

// сопровождается slist1.insert(slist1.begin(), 10, "Hiya!");

list<string> slist1(1);     // один элемент; пустая строка

slist1.assign(10, "Hiya!"); // десять элементов; со значением "Hiya!"

Применение функции swap().

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

vector<string> svec1(10); // вектор из 10 элементов

vector<string> svec2(24); // вектор из 24 элементов

svec1.swap(svec2);

После выполнения функции swap() вектор svec1 содержит 24 строки, а вектор svec2 — 10. За исключением массивов смена двух контейнеров осуществляется очень быстро — сами элементы не меняются; меняются лишь внутренние структуры данных.

Язык программирования C++.За исключением массивов функция swap() не копирует, не удаляет и не вставляет элементы, поэтому она гарантированно выполняется за постоянное время.

Благодаря тому факту, что элементы не перемещаются, итераторы, ссылки и указатели в контейнере, за исключением контейнера string, остаются допустимыми. Они продолжают указывать на те же элементы, что и перед перестановкой. Однако после вызова функции swap() эти элементы находятся в другом контейнере. Предположим, например, что итератор iter обозначал в векторе строк позицию svec1[3]. После вызова функции swap() он обозначит элемент в позиции svec2[3]. В отличие от других контейнеров, вызов функции swap() для строки делает некорректными итераторы, ссылки и указатели.

В отличие от поведения функции swap() с другими контейнерами, когда дело доходит до массивов, элементы действительно обмениваются. Обмен двух массивов потребует времени, пропорционального количеству их элементов.

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

Язык программирования C++.Новая библиотека предоставляет функцию swap() в версии члена класса контейнера и в версии, не являющейся членом какого-либо класса. Прежние версии библиотеки определили только версию функции-члена swap(). Функция swap() не являющаяся членом класса контейнера, имеет большое значение в обобщенных программах. Как правило, лучше использовать именно эту версию.

Упражнения раздела 9.2.5.

Упражнение 9.14. Напишите программу, присваивающую значения элементов списка указателей на символьные строки в стиле С (тип char*) элементам вектора строк.

9.2.6. Операции с размером контейнера. 9.2. Обзор библиотечных контейнеров. Глава 9. Последовательные контейнеры. Часть II. Библиотека С++ Язык программирования C++.9.2.6. Операции с размером контейнера.

За одним исключением у классов контейнеров есть три функции, связанные с размером. Функция-член size() (см. раздел 3.2.2) возвращает количество элементов в контейнере; функция-член empty() возвращает логическое значение true, если контейнер пуст, и значение false в противном случае; функция-член max_size() возвращает число, большее или равное количеству элементов, которые может содержать контейнер данного типа. По причинам, рассматриваемым в следующем разделе, контейнер forward_list предоставляет функции max_size() и empty(), но не функцию size().

9.2.7. Операторы сравнения.

Для сравнения используется тот же реляционный оператор, который определен для типа элементов: при сравнении двух контейнеров на неравенство ( !=) используется оператор != типа их элементов. Если тип элемента не поддерживает определенный оператор, то для сравнения контейнеров такого типа данный оператор использовать нельзя.

Сравнение двух контейнеров осуществляется на основании сравнения пар их элементов. Эти операторы работают так же, как и таковые у класса string (см. раздел 3.2.2):

• Если оба контейнера имеют одинаковый размер и все их элементы совпадают, контейнеры равны, в противном случае — не равны.

• Если контейнеры имеют различный размер, но каждый элемент короткого совпадает с соответствующим элементом длинного, считается, что короткий контейнер меньше длинного.

• Если значения элементов контейнеров не совпадают, результат их сравнения зависит от значений первых неравных элементов.

Проще всего понять работу операторов, рассмотрев их на примерах.

vector<int> v1 = { 1, 3, 5, 7, 9, 12 };

vector<int> v2 = { 1, 3, 9 };

vector<int> v3 = { 1, 3, 5, 7 };

vector<int> v4 = { 1, 3, 5, 7, 9, 12 };

v1 < v2  // true; v1 и v2 отличаются элементом [2]: v1[2] меньше,

         // чем v2[2]

v1 < v3  // false; все элементы равны, но у v3 их меньше;

v1 == v4 // true; все элементы равны и размер v1 и v4 одинаков

v1 == v2 // false; v2 имеет меньше элементов, чем v1

При сравнении контейнеров используются операторы сравнения их элементов.

Язык программирования C++.Сравнить два контейнера можно только тогда, когда используемый оператор сравнения определен для типа элемента контейнера.

Операторы равенства контейнеров используют оператор == элемента, а операторы сравнения — оператор < элемента. Если тип элемента не предоставляет необходимый оператор, то не получится использовать соответствующие операторы и с содержащими их контейнерами. Например, определенный в главе 7 тип Sales_data не предоставлял операторов == и <. Поэтому нельзя сравнить два контейнера, содержащих элементы типа Sales_data:

vector<Sales_data> storeA, storeB;

if (storeA < storeB) // ошибка: Sales_data не имеет оператора <

Упражнения раздела 9.2.7.

Упражнение 9.15. Напишите программу, выясняющую, равны ли два вектора vector<int>.

Упражнение 9.16. Перепишите предыдущую программу, но сравните элементы списка list<int> и вектора vector<int>.

Упражнение 9.17. Допустим, c1 и c2 являются контейнерами. Какие условия налагают типы их элементов в следующем выражении?

if (c1 < c2)