§9 Итераторы. Класс 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:

Программа ext_9.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. К тому же, он более безопасен, так как препятствует выходу за пределы массива:

Программа ext_9.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;
}
Примечание. Переменная этого цикла &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:

Программа ext_9.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 элементов и произвести сортировку этого массива. Вывести на экран исходный и упорядоченный массив.

Программа ext_9.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 элементов. Получить новый массив, в котором элементы исходного массива расположены в обратном порядке.

Программа ext_9.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.


Print Friendly, PDF & Email

Comments are closed.