Как использовать массивы и векторы
Ниже приведены первые восемь элементов из шести числовых последовательностей:
Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21 Lucas: 1# 3, 4, 7, 11, 18, 29, 47 Pell: 1, 2, 5, 12, 29, 70, 169, 408 Triangular: 1, 3, 6, 10, 15, 21, 28, 36 Square: 1, 4, 9, 16, 25, 36, 49, 64 Pentagonal: 1, 5, 12, 22, 35, 51, 70, 92
Наша программа должна выводить на дисплей пары элементов из последовательности и позволить пользователю угадать следующий элемент. Если пользователь угадывает и желает продолжить, программа должна вывести на дисплей следующую пару элементов, затем третью, и так далее. Как мы можем это сделать?
Если следующая пара берется из той же последовательности, пользователь, разгадавший одну пару, угадает их все. Это не интересно. Так что будем брать следующую пару из другой числовой последовательности при каждом проходе основного цикла программы.
Теперь будем выводить на дисплей максимум шесть пар элементов за сессию: по одной паре из каждой из шести последовательностей. Мы постараемся сделать это так, чтобы при выводе на дисплей пары элементов не знать, из какой числовой последовательности будет взята пара при следующем проходе цикла. Каждый проход должен иметь доступ к трем значениям: паре элементов и элементу, следующему за ней в последовательности.
Решение, которое мы обсудим в этом разделе, использует контейнерный тип, способный поддержать смежную последовательность целых значений, которые могут быть доступны не по имени, а по позиции в контейнере. Мы запомнили 18 значений в контейнере как подборку шести групп: первые два в группе представляют пару для вывода на дисплей, третий - следующий элемент в последовательности. При каждом проходе цикла мы добавляем три индексных значения, проходя по шести группам по очереди.
В С++ мы можем определить контейнер либо как встроенный массив, либо как вектор класса стандартной библиотеки. В основном, я рекомендую использовать класс векторов, а не встроенные массивы. Однако есть резон использовать массив, и важно понять, как использовать оба варианта.
Для определения встроенного массива мы должны обозначить тип элементов массива, дать ему имя и обозначить размер - количество элементов, которые массив должен содержать. Размер должен быть константой, то есть выражением, которое не меняется во время выполнения программы. Например, следующий код объявляет pell_jseq массивом из 18 целых элементов.
Const int seq_size = 18; int pell_seq[seq_jsize] ;
Для определения объектов класса векторов мы должны вначале включить файл заголовка vector. Класс векторов - шаблон, поэтому мы определяем тип элементов в угловых скобках, следующих за именем класса. Размер помещается в круглых скобках, он не обязательно должен быть постоянным выражением. Следующий код определяет pell_seq как объект класса векторов, содержащий 18 элементов типа int. По определению каждый элемент инициализируется в «О».
#include <vector>
Vector<int> pell_seq(seq_size);
Мы добираемся до элементов: массива или вектора, определяя его позицию в контейнере. Этот элемент индексируется с использованием оператора списка индексов ([ ]). Одна потенциальная «незадача» в том, что первый элемент находится в позиции «О», а не «1». Последний элемент индексируется на 1 меньше, чем размер контейнера. Для pell_seq правильные индексы - от 0 до 17, а не от 1 до 18. (Эта ошибка настолько распространена, что заслуживает иметь собственное имя: печально известная off-by-one-ошибка). Например, для получения первых двух элементов последовательности Pell мы пишем:
Pell_seq[0] = 1; // присваиваем 1 первому элементу pell_seq[l] =2; // присваиваем 2 второму элементу
Вычислим следующие десять элементов последовательности Pell. Для прохождения через элементы вектора или массива мы обычно используем цикл for - другой базовый цикл С++. Например,
For (int ix = 2; ix < seq_size; ++ix)
Pell_seq[ix] = pell_seq[ix - 2] + 2*pell_seq[ix - 1] ;
Цикл for состоит из следующих элементов:
For (начальное значение; условие; индексация) выражение;
Начальное значение выполняется единожды перед выполнением цикла. В нашем примере ix инициализируется как «2» перед началом выполнения цикла.
Условие служит для контроля над циклом. Оно вычисляется перед каждой итерацией цикла. Так что, сколько итераций при вычисленном условии равном true, столько раз выражение выполняется. Выражение может быть единственным или блочным. Если первое условие не удовлетворяется, выражение не выполнится никогда. В нашем примере условие проверяет, меньше ли ix, чем seq_size?
Индексация вычисляется после каждой итерации цикла. Она обычно используется для модификации начального значения объекта и проверяется в условии. Если первое вычисление условия принимает значение false, индексация не выполнится никогда. В нашем случае ix увеличивается с каждой итерацией цикла.
Для вывода элементов мы проходим через следующие операции:
Cout « "The first " « seq_size« " elements of the Pell Series:nt";
For (int ix = 0; ix < seq_size; ++ix)cout « pell_seq[ix] « " "; cout « "n";
При желании мы можем обойтись без начального значения, индексации или (реже) условия для цикла for. Например, мы можем переписать предыдущий цикл как:
Int ix = 0; // ...
For (; ix < seq_size; ++ix)// ...
Точка с запятой необходима, чтобы показать пустое начальное значение.
Наш контейнер содержит второй, третий и четвертый элементы каждой из шести последовательностей. Как мы заполним контейнер подходящими значениями? Встроенный массив может специфицироваться инициализационным списком, содержащим список значений, разделенных запятыми для всех элементов или подмножества элементов:
Int elem_seq[seq_size] = {
1, 2, 3, // Fibonacci
3, 4, 7, // Lucas
2, 5, 12, // Pell
3, 6, 10, //Triangular
4, 9, 16, // Square
5, 12, 22 // Pentagonal };
Количество значений, записанных в список инициализации, не должно превышать размера массива. Если мы предлагаем меньше значений, чем размер массива, недостающие элементы инициализируются как «О». При жедании мы можем позволить компилятору вычислить размер массива на основании количества значений, которые включаем в список:
// компилятор рассчитает размер в 18 элементов
Int elem_seq[] = {1, 2, З, 3, 4, 7, 2, 5, 12,3, 6, 10,
4, 9, 16, 5, 12, 22};
Класс векторов не поддерживает список инициализации. Несколько нудным решением будет присвоение значения каждому элементу отдельно:
Vector<int> elem_seq(seq_size); elem_seq[0] = 1; elem_seq[l] =2; // ...
Elem_seq[17] =22;
Еще одна альтернатива - инициализировать встроенный массив и использовать его для инициализации вектора:
Int elem_vals[seq_size] = {1, 2, З, 3, 4, 1, 2, 5, 12,3,
6, 10, 4, 9, 16, 5, 12, 22 };
// инициализация elem_seq значениями elem_vals vector<int> elem_seq(elem_vals, elem_vals+seq_size);
Elem__seq получает два значения. Эти значения в действительности адресованные - они отмечают диапазон элементов, с которым вектор будет инициализирован. В этом случае мы отметили 18 элементов, содержащихся в elem_vals и копируемых в elem_seq.
Д теперь посмотрим, как мы можем использовать elem__seq. Одно различие между встроенными массивами и классом векторов состоит в том, что вектор знает свой размер. Наш предыдущий цикл for проходил через встроенный массив. Посмотрим, велика ли разница при использовании вектора:
// elem_seq. size() возвращает число элементов // содержащихся в векторе elem_seq
Cout « "The first " « elem_seq. size()« " elements of the Pell Series:nt";
For (int ix = 0; ix < elem_seq. size(); ++ix) cout « pell_seq[ix] « " ";
Cur_tuple представляет собой индекс в текущей последовательности, выводимой на дисплей. Мы инициализируем его в «0». С каждым проходом цикла мы добавляем «3» в cur_tuple, устанавливая его для индексации первого элемента следующей последовательности для вывода на дисплей:
Int cur_tuple = 0;
While (next_seq == true &&cur_tuple < seq_size) { cout « "The first two elements of the sequence are: " « elem_seq[cur_tuple] « ", " « elem_seq[cur_tuple + 1] « "nWhat is the next element? "; // ...
If (usr_guess == elem_seq[cur_tuple + 2]) // правильно!
If (usr_rsp == "N" II usr_rsp == "n")next_seq = false; else cur_tuple += 3;
Полезно было бы сохранять путь к последовательности, которая в настоящий момент активна. Запомним имя каждой последовательности как строку:
Const int max_seq = 6;
String seq_names[max_seq] = {"Fibonacci","Lucas","Pell", "Triangular","Square","Pentagonal"};
Мы можем использовать seq_names следующим образом:
If (usr_guess == elem_seq[cur_tuple + 2]) { ++num_cor;
Cout « "Very good. Yes, " « elem_seq[cur__tuple + 2] « " is the next element in the " « seq_names [cur_tuple / 3] « "sequence.n";
}
Выражение cur_tuple/3 получает по очереди 0, 1, 2, 3, 4 и 5, индексируя в массиве строк элементы, которые идентифицируют активную последовательность.