§12 Итераторы. Класс array

Класс array

Класс array является шаблонным классом последовательного контейнера. array наиболее близок к С-массивам. Он хранит фиксированное число элементов, но поддерживает и дополнительный набор методов, в том числе и универсальных для всех контейнеров. Контейнер может хранить элементы любых типов. Индексация элементов, как и в C-массивах, начинается с нуля. Преимущество данного контейнера — максимально быстрый доступ к своих элементам.

Определение

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

#include <array>

В описание типа array входит тип данных элементов (Т) и размер контейнера (N):

array<T, N> name;
Конструктор и деструктор

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

array<int, 20> mas1(mas2); // или
array<int, 20> mas1 = mas2;

Уничтожается объект класса array специальной функцией – деструктор. Все элементы массива (mas) удаляются, а память освобождается:

mas.~array();
Инициализация

Если при определении объекта типа array начальные значения не передаются, то создается массив элементов, значения которых не определены. array – является агрегатным типом. Агрегатом называется массив, структура или объединение которые не имеют конструкторов и защищённых членов. С этими понятиями мы познакомимся позже. Здесь, пока, можно уяснить следующее – array позволяет использовать для инициализации списки инициализации (list initialization). Аргументы списка инициализации заключаются в фигурные скобки:

array<int, 5> mass1 {};
array<int, 5> mass2 {8};
array<int, 5> mass3 {1, 2, 3, 4, 5};

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

mas.fill(value)
Работа с элементами

Доступ к элементам можно осуществлять с помощью операции [] – обращения по индексу или с помощью метода класса at() (предпочтительнее) и обычного цикла for:
Программа 12.1

#include <iostream>
#include <array>
#include <random>
#include <ctime>
using namespace std;

int main() {
    array<int, 10> mas {};
    default_random_engine rnd(time(0));
    uniform_int_distribution<unsigned> d(10, 99);
    for (size_t i = 0; i <= mas.size(); i++)
        mas[i] = d(rnd);
    for (size_t i = 0; i <= mas.size(); i++)
        cout << mas.at(i) << " ";
    cout << endl;
    return 0;
}

Метод size() возвращает – размер массива. Это специальный целый беззнаковый тип – size_t. Такая запись довольно громоздка, поэтому лучше использовать, упоминавшийся нами ранее, range-based for. К тому же, он более безопасен, так как препятствует выходу за пределы массива:
Программа 12.2

#include <iostream>
#include <array>
#include <random>
#include <ctime>
using namespace std;

int main() {
    array<int, 10> mas  {};
    default_random_engine rnd(time(0));
    uniform_int_distribution<unsigned> d(10, 99);
    for (auto &ar : mas)
        ar = d(rnd);
    for (auto &ar : mas)
        if (ar % 2 == 0)
            cout << ar << " ";
    cout << endl;
    for (auto &ar : mas)
        if (ar % 2 != 0)
            cout << ar << " ";
    cout << endl;
    return 0;
}

Примечание. Переменная этого цикла (в программе 12.2 – ar) может принимать значения элементов как по ссылке, так и по значению.
Для обмена элементами между массивами одного и того же типа используется метод swap:

mas1.swap(mas2);

Итераторы

Итератор (iterator, англ. перечислитель) – это такой объект, который позволяет перебирать элементы контейнера, переходя от одного элемента – к другому. Итераторы функционально схожи с указателями. Хотя итераторы и являются формой указателя, но их функциональность отличается. Разница между обычным указателем и итератором заключается в том, что последний является интеллектуальным указателем, т. е. указателем, который может обходить сложные структуры данных. Контейнеры поддерживают определенный тип итератора, который наделен своим собственным набором доступных операций. Эти наборы довольно сильно отличаются. Всего имеется пять различных категорий итераторов:

  • InputIterator (Входной)
  • OutputIterator (Выходной)
  • ForwardIterator (Однонаправленный)
  • BidirectionalIterator (Двунаправленный)
  • RandomAccessIterator (Произвольного доступа)

array использует тип итераторов, который обладает самым большим функционалом — RandomAccessIterator (итератор произвольного доступа), который позволяет обходить контейнер в любом порядке, а также изменять содержимое контейнера (array, vector) в процессе обхода.
Для этого типа итераторов определены следующие операции:

  • it -> mem
  • ++it, it++
  • --it, it--
  • it[i] (конвертирование в ссылку)
  • *it
  • +, -
  • <, <=, >, >=, ==, !=
  • +=, -=

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

array<int, 10>::iterator first;
array<int, 10>::iterator last;

Для получения итератора не нужно использовать операцию взятия адреса (&). Для этих целей, каждый класс контейнеров имеет специальные функции-члены. Наиболее важные функции-члены возвращающие итераторы - это begin() и end(). Функция begin() возвращает итератор, указывающий на начало контейнера (первый элемент). Функция end() возвращает итератор, указывающий на позицию, следующую за последним элементом. Таким образом, функции begin() и end() определяют полуоткрытый интервал [first, last).
iter1Причина использования, на первый взгляд странного, полуоткрытого интервала заключается в двух аспектах:

  1. Удобство применения в цикле для обхода элементов массива. Сравнение first == last приведет к выходу из цикла и завершению перебора элементов массива.
  2. Отсутствует необходимость специальной обработки пустого контейнера. В нем позиции first и last совпадают.

В задаче заполнения массива натуральными числами используются итераторы для обхода массива и цикл while:
Программа 12.3

#include <iostream>
#include <array>
using namespace std;

int main() {
    array<int, 10> mas {};
    array<int, 10>::iterator first;
    array<int, 10>::iterator last;
    first = mas.begin();
    last = mas.end();
    int i = 1;
    while (first != last) {
        *first = i++;
         first++;
    }
    for (auto &r : mas)
        cout << r << " ";
    return 0;
}

В диапазонном цикле for также сокрыто использование итераторов. На самом деле, цикл можно представить в виде традиционной инструкции следующим образом:

for (array<int, 10>::iterator beg = mas.begin(); beg != mas.end(); ++beg) {
    	 int &r = *beg;
    	 r = i++;
    }

Чтобы не использовать громоздкий синтаксис типа (например, array<int, 10>::iterator) удобнее использовать спецификатор auto, позволяющий вывести тип автоматически:

auto first = mas.begin();
auto last = mas.end();

где first и last - имена итераторов, а mas - имя массива.
Примечание. Чтобы эти функции стали доступны и для встроенного C-массива, необходимо включить следующую директиву:

#include <iterator>

Решим следующую задачу. Получить массив из 20 элементов и произвести сортировку этого массива. Вывести на экран исходный и упорядоченный массив.
Программа 12.4

#include <iostream>
#include <array>
#include <ctime>
#include <random>
#include <algorithm>
#include <iomanip>
using namespace std;

int main() {
	array<int, 20> mas;
	default_random_engine rnd(time(0));
	uniform_int_distribution<unsigned> d(10, 99);
	for (auto &ar: mas) {
		ar = d(rnd);
		cout << ar << setw(4);
	}
	cout << "\n";
	auto first = mas.begin();
	auto last = mas.end();
	//сортируем
	sort(first, last);
	//и выводим с помощью итераторов и while
	while (first != last) {
		cout << *first++ << setw(4);
	}
	return 0;
}

Константные и реверсивные итераторы

Реверсивные итераторы

Реверсивные итераторы предназначены для обхода массива с конца. Реверсивные итераторы возвращают методы rbegin() и rend(). В этой форме итераторов последний элемент массива представляется как если бы он стоял на первом месте в развернутой в обратную сторону последовательности. Соответственно последний элемент (гипотетический) соответствует позиции элемента предшествующего первому элементу в не развернутой последовательности. Эта форма итераторов ничем не отличается от использования обычной формы итераторов (begin() и end()), функционально они равнозначны. Удобство применения этих итераторов в полной мере раскроется при рассмотрении нами обобщенных алгоритмов.
Дан массив из 10 элементов. Получить новый массив, в котором элементы исходного массива расположены в обратном порядке.
Программа 12.5

#include <iostream>
#include <array>
using namespace std;

int main() {
	array<int, 10> mas1 {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
	array<int, 10> mas2 {};
	for (auto &r : mas1)
		cout << r << " ";
	cout << endl;
	auto first = mas1.rbegin();
	for (auto &r : mas2)
		r = *first++;
	for (auto &r : mas2)
		cout << r << " ";
	return 0;
}
Константные итераторы

В каждом контейнере определены два типа итераторов:

  • iterator
  • const_iterator

И обычная, и реверсивная форма итераторов имеют обе версии типов. Тип iterator используется, когда необходимо получить доступ к элементу и в режиме чтения, и в режиме записи, а тип const_iterator только в режиме чтения. Для получения константного итератора используются следующие методы:

  • cbegin()
  • cend()
  • crbegin()
  • crend()

Если заранее известно, что элементы не будут изменяться или тип контейнера не позволит этого сделать, то следует применять тип const_iterator.
Тип итератора будет получен автоматически, если при определении итератора используется спецификатор auto и методы без префикса "c":

auto first = mas.begin();

В этом случае, в зависимости от типа контейнера или константности объекта, произойдет автоматическое преобразование iterator в const_iterator.

Литература
  1. Прата, Стивен. Язык программирования C++. Лекции и упражнения, 6-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильяме», 2012
  2. Липпман Б. Стенли, Жози Лажойе, Барбара Э. Му. Язык программирования С++. Базовый курс. Изд. 5-е. М: ООО "И. Д. Вильямс", 2014
  3. Эллайн А. C++. От ламера до программера. СПб.: Питер, 2015
  4. Джосаттис Н. М. Стандартная библиотека C++: справочное руководство, 2-е изд.-М.: Вильямс, 2014


Print Friendly, PDF & Email

Comments are closed