Шаблон классов valarray



Шаблон классов valarray

Этот шаблон разработан для оптимизации вычислений, производимых над массивами чисел фиксиррванного размера. Valarray похож на контейнер, но он им не является. Вы не можете динамически и эффективно наращивать его размер. Он, как и контейнер, может изменять свои размеры, используя метод resize, но при этом имеющиеся данные разрушаются. Главным преимуществом использования valarray является эффективность проведения операций сразу над всеми элементами последовательности. Предположим, вы хотите построить график функции у = sin(x) и имеете процедуру, которая сделает это с учетом масштабирования, оцифровки осей и всяких других удобств. Вашей задачей является лишь сформировать данные для графика и подать их на вход этой процедуры. Использование valarray даст преимущество в легкости манипулирования данными и эффективности выполнения. Для простоты выберем шаг изменения координаты х, равный л/3,

Примечание
Примечание

C целью экономии места я обычно не привожу директивы препроцессора, которые, конечно же, должны предшествовать каждому из рассматриваемых фрагментов. Большинство читателей, я уверен, успешно решают эту проблему сами, так как сообщения об ошибках обычно довольно ясно указывают на недостающее описание. Но при работе с библиотекой STL окно сообщений ведет себя не совсем так, как при работе с MFC. Незначительный пропуск или неточность со стороны программиста порой приводят к лавине предупреждений и ошибок, анализ которых превращается в испытание для нервной системы. Здесь у компании Microsoft еще довольно много работы. Учитывая сказанное, следующий фрагмент приведен со списком директив, необходимых для его работы.

#include <iostream>

#include <algorithm>

#include <valarray>

#include <limits>

using namespace std;


void main() { //======== Вспомогательные переменные

double PI = atan(l.)*4.,

dx = PI/3., // Шаг изменения

xf = 2*PI - dx/2.;

// Барьер

int i = 0,

size = int(ceil(xf/dx)); // Количество точек

//======== Создаем два объекта типа valarray

valarray<double> vx(size), vy(size);

//======== Абсциссы точек вычисляются в цикле

for (double х=0.;

х < xf; х += dx) vx[i++] = х;

//======== Ординаты вычисляются без помощи цикла

vy = sin(vx);

cout«"Valarrays of x and sin(x)\n";

for (i=0; i < size; i++)

cout«"\nx = " « vx[i] «" у = "« vy[i];

}

Теперь усложним задачу. Представим, что надо численно продифференцировать функцию, заданную в дискретном множестве точек. Вы знаете, что конечные разности позволяют аппроксимировать производные, то есть производить численное дифференцирование. В STL есть алгоритм adjacent_dif ference, который вычисляет первые конечные разности в указанном диапазоне последовательности. Здесь важно вспомнить, что valarray не является контейнером и поэтому не поддерживает итераторов. Но алгоритмы STL принимают в качестве аргументов как итераторы, так обычные указатели. Мы воспользуемся этим фактом, а также тем, что элементы valarray расположены в памяти подряд.

Результат дифференцирования надо поместить в другую последовательность типа valarray, которую после этого можно эффективно нормировать, поделив сразу все ее элементы на шаг дискретизации вдоль оси х. Добавьте директиву # include <numeric>, вставьте следующий текст в конец предыдущего фрагмента и, пожалуй, увеличьте количество точек, заменив присвоение dx = Pi/З. на dx = Pi/10:

//======= Конструктор создает valarray нужного размера

valarray<double> vd(size);

//======= Алгоритм вычисляет конечные разности

adjacent_difference(&vy[0], &vy[size], &vd[0]);

//======= Все элементы valarray делятся на dx

vd /= dx;

//======= Мы проверяем результат

cout«"\n\nValarray of differences\n";

for (i=l; i < size; i++)

cout«"\nx = " « vx[i] «" у = "« vd[i];

Отметьте, что в первой точке (с нулевым индексом) будет ошибка, поэтому мы ее не выводим. Остальные элементы результирующей последовательности чисел (valarray vd) должны вести себя как у = cos(x). В качестве третьего параметра функции adjacent_dif ference нельзя задать просто vd, так как в отличие от обычного массива имя vd не является адресом его первого элемента. Шаблон классов valarray имеет некоторое, весьма ограниченное количество методов, которые позволяют производить манипуляции с данными, среди которых стоит отметить: min, max, sum, shift, cshift, apply. Приведем фрагмент, иллюстрирующий их использование:

//======= Функциональный объект, применяемый к каждому

//======= элементу valarray

double Sharp (double x)

{

return x != 0. ? l/(x*x) : DBL_MAX;

}

//======= Функция для вывода valarray

void out(char* head, valarray<double>& v)

{

cout « '\n' « head << '\n';

for (unsigned i=0; i < v.size(); i++)

cout«"\nv[" « i « "] = " « v[i];

cout «'\n';

}

void main()

{

int size = 11;

valarray<double> vx(size), vy(size);

//======== Заполняем диапазон от -1 до 1

for (int i=0; i < size; i++)

{

vx[i] = i/5. - 1.;

}

out("Initial valarray", vx);

//======== Вычисляем сумму всех элементов

cout « "\nsum = " « vx.sum() « endl;

//======== Применяем свое преобразование

vy = vx.apply (Sharp);

//======== Получили "острую" функцию

out("After apply", vy);

//======== Вычисляем min и max

cout « "\n\nmin = " « vy.min() « " max = " « vy.max();

}

При положительных значениях аргумента метод shift используется для сдвига всей последовательности влево или при отрицательных значениях — вправо. Метод cshif t представляет собой циклическую модификацию метода shift. Заметьте, что все рассмотренные методы возвращают новую последовательность типа valarray и не имеют модификаций, работающих в режиме in-place, что, на мой взгляд, является ощутимым недостатком этого типа данных. Вы можете проверить работу сдвигов, добавив такие строки:

//======== Циклический сдвиг на 2 позиции влево

valarray<double> r =vy.cshift(2);

out("After cyclic 2 digits left shift", r) ;

//======== Сдвиг на 2 позиции вправо

r =r.shift(-2);

out("After 2 digits right shift", r);



Содержание раздела