На этом шаге мы рассмотрим использование срезов.
Срез определяется набором индексов, который характеризуется тремя свойствами:
Порядок передачи этих трех свойств точно соответствует порядку следования параметров конструктора класса slice. Например, следующее выражение определяет четыре элемента, начиная с индекса 2, находящихся на расстоянии 3 друг от друга:
std::slice(2,4,3)
Другими словами, выражение определяет такой набор индексов:
2 5 8 11
Шаг может быть отрицательным. Например, рассмотрим следующее выражение:
std::slice(9,5,-2)
Это выражение определяет такой набор индексов:
9 7 5 3 1
Чтобы определить подмножество элементов массива значений, достаточно передать срез в качестве аргумента оператора индексирования. Например, следующее выражение определяет подмножество массива va, содержащее элементы с индексами 2, 5, 8 и 11:
va[std::slice(2,4,3)]
Перед вызовом необходимо проверить правильность всех индексов.
Если подмножество, заданное в виде среза, принадлежит константному массиву значений, то оно образует новый массив значений. Если массив значений не является константным, то подмножество предоставляет ссылочную семантику для работы с исходным массивом значений. Для этой цели определяется вспомогательный класс slice_array:
namespace std { class slice; template <class T> class slice_array; template <class T> class valarray { public: // Срез константного массива значений // возвращает новый массив значений valarray<T> operator[] (slice) const; // Срез неконстантного массива значений возвращает slice_array slice_array<T> operator[] (slice); ... }; }
Для объектов slice_array определены следующие операции:
Для остальных операций подмножество необходимо преобразовать в массив значений (смотри 433 шаг). Учтите, что класс slice_array проектировался исключительно как внутренний вспомогательный класс для работы со срезами, поэтому он должен оставаться невидимым для внешних пользователей. По этой причине все конструкторы и операторы присваивания класса slice_array<> объявлены закрытыми.
Например, представленная ниже команда присваивает 2 третьему, шестому, девятому и двенадцатому элементам массива значений va:
va[std::slice(2,4,3)] = 2;
Она эквивалентна следующему набору команд:
va[2] = 2; va[5] = 2; va[8] = 2; va[11] = 2;
Другая команда возводит в квадрат элементы с индексами 2, 5, 8 и 11:
va[std::slice(2,4,3)] *= std::valarray<double>(va[std::slice(2,4,3)]);
Как упоминалось на 433 шаге, следующая запись является ошибочной:
va[std::slice(2,4,3)] *= va[std::slice(2,4,3)]; // ОШИБКА
Но если воспользоваться шаблонной функцией VA() (смотри 433 шаг), запись принимает следующий вид:
va[std::slice(2,4,3)] *= VA(va[std::slice(2,4,3)]); // OK
Передавая разные срезы одного массива значений, можно объединить подмножества и сохранить результат в другом подмножестве массива. Например, рассмотрим такую команду:
va[std::slice(0,4,3)] = VA(va[std::slice(1,4,3)]) * VA(va[std::slice(*2,4,3)]);
Эта команда эквивалентна следующему набору команд:
va[0] = va[1] * va[2]; va[3] = va[4] * va[5]; va[6] = va[7] * va[8]; va[9] = va[10] * va[11];
Если рассматривать массив значений как двухмерную матрицу, этот пример представляет собой не что иное, как умножение векторов (рисунок 1).
Рис.1. Умножение векторов с использованием срезов
Однако следует учитывать, что порядок выполнения отдельных присваиваний не определен. Следовательно, если приемное подмножество содержит элементы, используемые в исходном подмножестве, последствия могут быть непредсказуемыми.
Также возможны и более сложные команды. Например:
va[std::slice(0,100,3)] = std::pow(VA(va[std::slice(l,100,3)]) * 5.0, VA(va[std::slice(2,100,3)]));
Еще раз обратите внимание: отдельное значение (5.0 в данном случае) должно точно соответствовать типу элементов массива значений.
На следующем шаге мы приведем пример использования срезов.