double* jack_high; // jack_high — указатель на наибольший элемент
double* jill_high; // jill_high — указатель на наибольший элемент
for (int i=0; i<jack_count; ++i)
if (h<jack_data[i])
jack_high = &jack_data [i]; // сохраняем адрес наибольшего
// элемента
h = –1;
for (int i=0; i< jill_data –>size; ++i)
if (h<(*jill_data)[i])
jill_high = &(*jill_data)[i]; //
сохраняем адрес наибольшего
// элемента
cout << "Максимум Джилл: " << *jill_high
<< "; максимум Джека: " << *jack_high;
// ...
Обратите внимание на уродливую конструкцию, используемую для доступа к данным Джилл:
(*jill_data)[i]
. Функция
get_from_jill
возвращает указатель на вектор, а именно
vector<double>*
. Для того чтобы получить данные, мы сначала должны его разыменовать, получив доступ к вектору с помощью указателя
*jill_ data
, а затем применить к нему операцию индексирования. Однако выражение
*jill_data[i]
— не совсем то, что мы хотели; оно означает
*(jill_data[i])
, так как оператор
[]
имеет более высокий приоритет, чем
*
, поэтому нам необходимы скобки вокруг конструкции
*jill_data
, т.е. выражение
(*jill_data)[i]
.
ПОПРОБУЙТЕ
Как вы изменили бы интерфейс, чтобы избежать неуклюжих конструкций, если бы могли изменить код Джилл?
20.1.2. Обобщение кода
Нам нужен единообразный способ доступа и манипуляции данными, чтобы не переписывать программу каждый раз, когда представление данных немного изменяется. Посмотрим на коды Джека и Джилл и попробуем сделать их более абстрактными и единообразными.
Разумеется, все, что мы сделаем с данными Джека, относится и к данным Джилл. Однако между их программами есть два досадных различия: переменные
jack_count
и
jill_data–>size
, а также конструкции
jack_data[i]
и
(*jill_data)[i]
. Последнее различие можно устранить, введя ссылку.
vector<double>& v = *jill_data;
for (int i=0; i<v.size; ++i)
if (h<v[i])
{
jill_high = &v[i];
h = v[i];
}
Это очень похоже на код для обработки данных Джека. Может быть, стоит написать функцию, которая выполняла бы вычисления как с данными Джилл, так и с данными Джека? Возможны разные пути (см. упр. 3), но, стремясь к более высокой степени обобщения кода (см. следующие две главы), мы выбрали решение, основанное на указателях.
double* high(double* first, double* last)
// возвращает указатель на наибольший элемент в диапазоне [first,last]
Он выглядит получше. Мы не ввели слишком много переменных и написали только один цикл (в функции
high
). Если мы хотим найти наибольший элемент, то можем посмотреть на значения
*jack_high
и
*jill_high
. Рассмотрим пример.
cout << "Максимум Джилл: " << *jill_high
<< "; максимум Джека: " << *jack_high;
Обратите внимание на то, что функция
high
использует тот факт, что вектор хранит данные в массиве, поэтому мы можем выразить наш алгоритм поиска максимального элемента в терминах указателей, ссылающихся на элементы массива.
ПОПРОБУЙТЕ
В этой маленькой программе мы оставили две потенциально опасные ошибки. Одна из них может вызвать катастрофу, а другая приводит к неправильным ответам, если функция
high
будет использоваться в других программах. Универсальный прием, который описывается ниже, выявит обе эти ошибки и покажет, как их устранить. Пока просто найдите их и предложите свои способы их исправления.
Функция
high
решает одну конкретную задачу, поэтому она ограничена следующими условиями.
• Она работает только с массивами. Мы считаем, что элементы объекта класса
vector
хранятся в массиве, но наряду с этим существует множество способов хранения данных, таких как списки и ассоциативные массивы (см. разделы 20.4 и 21.6.1).
• Ее можно применять только к объектам класса
vector
и массивам типа
double
, но не к векторам и массивам с другими типами элементов, например
vector<double*>
и
char[10]
.
• Она находит элемент с максимальным значением, но с этими данными можно выполнить множество других простых вычислений.
Попробуем обеспечить более высокую общность вычислений над нашими наборами данных.
Обратите внимание на то, что, решив выразить алгоритм поиска наибольшего элемента в терминах указателей, мы “случайно” уже обобщили решение задачи: при желании мы можем найти наибольший элемент массива или вектора, но, помимо этого, можем найти максимальный элемент части массива или вектора. Рассмотрим пример.
// ...
vector<double>& v = *jill_data;
double* middle = &v[0]+v.size/2;
double* high1 = high(&v[0], middle); // максимум первой
// половины
double* high2 = high(middle, &v[0]+v.size); // максимум второй
// половины
// ...
Здесь указатель
high1
ссылается на максимальный элемент первой половины вектора, а указатель
high2
— на максимальный элемент второй половины. Графически это можно изобразить следующим образом: