Шаблон классов 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);