До сих пор мы рассматривали примеры работы с небольшим количеством данных, для работы с которыми достаточно использовать только простые переменные. Если данных много, то использование одних простых переменных становится явно недостаточным. Гораздо удобнее объединять их в более сложную организацию данных, например, в массивы.
Массив — это совокупность данных одного типа, имеющая одно общее имя. Доступ к отдельному элементу массива осуществляется путем указания имени массива и номера (индекса) этого элемента. До использования массива его необходимо определить, т.е. дать ему имя и указать количество и тип элементов. Индексы в любом массиве на языке C/C++ всегда начинается с 0.
Пусть имеется массив из 5 действительных чисел. Графически этот массив можно изобразить так, как показано на рисунке ниже:
Для того, чтобы начать работать с массивом, необходимо дать его описание. Формальная запись описания массива такова:
Тип_элементов_массива Имя_массива[Количество_элементов];
где
Тип_элементов_массива — любой стандартный или ранее определённый пользователем тип данных;
Имя_массива — обычный идентификатор;
Количество_элементов — целое число, большее 0, возможно, именованная константа.
Для нашего массива x описание будет выглядеть так:
double x[5];
После этого можно начинать работать с элементами массива, например, присвоить второму элементу новое значение:
x[2] = 5;
или напечатать сумму значений первых двух элементов массива:
cout << x[0]+x[1] << endl;
Как нетрудно убедиться, элементы массива могут использоваться точно так же, как и простые переменные. Единственное отличие — требуется указывать индексы элементов.
Индекс (номер) элемента массива — это константа, переменная или более сложное выражение целого типа. Можно было бы написать:
int i = 2;
x[i*3-2] = x[i+1] + x[i%5] - x[3];
главное, чтобы индексы (или в более сложном случае — индексные выражения) находились в допустимых пределах. Индексы элементов нашего массива x должны лежать в пределах от 0 до 4, т.е. максимально допустимый индекс на единицу меньше количества элементов массива.
А что произойдёт, если обратиться к элементу с индексом, значение которого выходит за допустимый диапазон? Для нашего примера, запишем так:
x[5] = 12;
Внешних проявлений (например, сообщений об ошибке), как правило, не будет, просто программа обратится к «чужому» участку памяти и «испортит» значение какой-либо переменной.
Выход за допустимые пределы индексов массива — это характерная ошибка в программах на языках С/С++. К сожалению, компилятор тут не помощник — он ни как не контролирует выход за допустимые пределы. Поэтому программисту самому необходимо проявлять повышенную внимательность и аккуратность в работе с массивами. При работе с программой, в которой используются массивы, всегда задавайте себе вопрос: «Не выходит ли где-то индекс массива за допустимые пределы?».
Вернёмся снова к описанию массива. Хороша ли запись
double x[5];
Ответ один — нет, плоха. Почему? Да потому, что при работе с массивами мы постоянно используем просмотр массива, т.е. нам для нашего массива из 5 чисел придётся использовать циклы вида
for(i=0; i < 5; i++) { тело цикла }
А что делать, если количество чисел изменится? Просматривать текст всей программы и «править» все циклы? Глупо как-то, да и ошибиться легко. Нужен какой-то выход, и он есть: количество элементов массива нужно задавать не константой (числом), а именованной константой. Тогда количество элементов массива потребуется изменить только в одном месте, а остальной текст программы останется прежним.
На языке С был только один способ создания константы с именем: с помощью директивы define. Схема программы будет выглядеть так:
#define n 5
// Другие директивы
int main()
{
double x[n]; // описание массива
int i; // переменная цикла
.......................
// использование массива
for(i=0; i<n; i++)
{ тело цикла }
.........................
}
Одна проблема решена, но есть и другая, также весьма неприятная. Что произойдёт, если в директиве define пользователь задаст неверное по смыслу значение для константы, например, другого типа:
#define n 3.14
или наберёт просто какой-то абсурд:
#define n ЮЮЮ
В этом случае препроцессор «честно» выполнит подстановки, и после компиляции (а в ряде случаев — в ходе выполнения исполняемого модуля) в программе появится сообщение об ошибке, но ссылка будет не на строку, где допущена ошибка (директива define), а гораздо дальше — там где она используется, — возможно через десятки или сотни строк. Если для приведённой выше схемы написать
#define n 3.14
то ошибка будет указана в строке
double x[n];
которая совсем не причём.
Для радикального решения проблемы на языке С++ появился более надёжный способ создания константы с именем: с помощью константной переменной. Схема программы будет выглядеть так:
// директивы
int main()
{
const int n = 5;
double x[n]; // описание массива
int i; // переменная цикла
.......................
// использование массива
for(i=0; i<n; i++)
{ тело цикла }
.........................
}
Достоинство такого подхода в том, что теперь компилятор проверяет правильность оператора, в котором мы задаём размер массива:
const int n = 5;
Если в этом операторе переменной n будет задано что-то неприемлемое:
const int n = ЮЮЮ
то сообщение об ошибке будет дано именно для этого оператора, а не где-то ниже по тексту.
Для закрепления материала рассмотрим задачу.
Пример. Дан массив действительных чисел из n элементов. Найти максимальный по модулю элемент и разделить все элементы массива на полученное значение. Вывести на экран монитора массив после обработки.
Возможный текст программы:
#include <iostream>
using namespace std;
#include <cmath>
int main()
{
const int n=5;
double x[n];
double max;
int i;
// Задаём массив из n действительных чисел
cout << "Input " << n << " numbers:" << endl;
for(i = 0; i < n; i++)
cin >> x[i];
// Поиск максимального по модулю элемента массива
// Предположим, что x[0] - это и есть максимальный
// по модулю элемент массива:
max = fabs(x[0]);
// А теперь пробуем себя опровергнуть:
for(i = 1; i < n; i++)
if(fabs(x[i]) > max)
max = fabs(x[i]);
// Максимум найден:
cout << "max=" << max << endl;
// Делим все элементы на max
for(i = 0; i < n; i++)
x[i] /= max;
// Распечатка массива
cout << "Massiv:" << endl;
for(i = 0; i < n; i++)
cout << x[i] << endl;
return 0;
}
Элементы массива можно проинициализировать, т.е. задать им начальные значения на этапе выделения памяти под массив. Делается это почти так же, как для простых переменных. Зададим начальные значения элементов массива в нашем примере (смотри рисунок в начале этой темы):
const int n=5;
double x[n]={2.5, -1.1, 3, 5.7, 1.5};
Если при инициализации задано меньше данных, чем выделяется памяти (у нас — для пяти чисел), то значение элементов массива, которые не получили значение в момент выделения памяти, будет неопределённым.
Можно пойти и дальше: не задавать при инициализации массива его размер. Пусть компилятор сам определяет, сколько байт необходимо выделить под рассматриваемый массив:
double x[]={2.5, -1.1, 3, 5.7, 1.5};
Но как обрабатывать теперь массив? Нам же надо знать количество элементов! Поделим размер массива на размер одного элемента — это и будет искомым значением:
int n = sizeof(x) / sizeof(double);
Здесь sizeof() — это стандартная операция по вычислению размера какого-либо объекта.
Теперь можно применять привычный подход при обработке массива:
for(i = 0; i < n; i++) { тело цикла }