Массив — это сложный (составной, структурированный) тип данных, который характеризуется следующим:
· элементы массива имеют одинаковый тип в отличие от структур, поэтому каждый элемент массива занимает одинаковый объём памяти;
· массив располагается в оперативной памяти, а не на внешнем устройстве, как файлы (2-й семестр);
· элементы массива занимают подряд идущие ячейки, в отличие, например, от списков (2-й семестр).
Доступ к элементам массива в языке С++ осуществляется двумя способами.
Первый, с помощью порядкового номера элемента массива, который называется индексом, характерен для многих языков программирования и рассматривается в первом семестре. Он более простой и привычный для тех, кто изучал язык Pascal. В качестве индекса можно использовать выражение целого или совместимого с ним типа, в том числе константу или переменную. В качестве индекса нельзя использовать выражение вещественного типа.
Кроме того, в языке С++ есть возможность обрабатывать массивы, используя указатели (адреса), так как в С++ существует связь между массивами и указателями. Несмотря на то, что в первом способе в программе отсутствует специальный тип для работы с адресами, указатели всё равно используются.
Массивы могут иметь одну или несколько размерностей. В этом параграфе рассматривается одномерный массив, который иногда называют вектором, подразумевая вектор в n-мерном пространстве. Работа с двумерными массивами (матрицами) рассматривается в гл. 5. Три и более размерностей на практике используются редко, так как такие массивы занимают большой объём оперативной памяти.
Везде в дальнейшем под словом “массив” будем понимать одномерный массив.
С точки зрения времени (этапа), когда распределяется память под массив, существуют два их вида. Память для динамического массива выделяется во время выполнения программы, и если массив не нужен, память для него можно освободить. Такие массивы рассматриваются во втором семестре.
Одномерный массив с фиксированной размерностью (назовём его статический) объявляется в общем виде следующим образом:
тип имя [N];
Здесь тип — тип элементов массива. Вначале будем рассматривать простые типы (int, float, char), но можно использовать и сложные, например, структуры. Имя записывается по правилам идентификаторов. Каждый элемент массива имеет одно и то же имя, меняется только индекс или номер элемента. N — размерность (или размер) массива в виде целочисленной константы или константного выражения. Эта величина определяет количество ячеек оперативной памяти, зарезервированной для массива. Например:
float A[10]; или const n=10; float A[n];
Преимущество второго способа c предварительным объявлением размерности в виде константы заключается в следующем. Если надо будет изменить размерность массива, то это достаточно сделать в одном месте программы при определении константы.
В отличие от динамического массива, для статического на этапе компиляции резервируется память для размещения N чисел указанного типа (10 вещественных чисел). Для массива требуется память объёмом k*N байт (4*10), где k — необходимое количество байт для размещения одного элемента указанного типа (одного числа типа float). Эта память сохраняется на всё время выполнения программы, а точнее, функции или блока, где описан массив. Программно необходимый объём памяти определяется с помощью операции sizeofследующим образом:
M=sizeof (тип)*N; или M= sizeof (имя); или M= sizeof имя;
где M — переменная целого типа, определяющая размер массива в байтах. Тип обязательно записывается в скобках, а имя может быть без скобок. Следующая программа выведет дважды число 40.
float A[10]; int M1, M2;
M1=sizeof(float)*10; // но M1=sizeof float *10;— ошибка!
M2=sizeof(A); // или M2=sizeof A;
cout<<M1<<endl<<M2;
Во многих современных системах программирования, в том числе и в С++, нумерация элементов массива начинается с 0. Тогда A[n-1] — последний элемент массива. Это связано с использованием указателей при работе с массивами (см. 2-й семестр). Поэтому в нашем примере индекс изменяется от 0 до 9 включительно, то есть индекс последнего элемента массива на единицу меньше его размерности. Объявленные 10 элементов массива обозначаются следующим образом: A[0], A[1], A[2] ,…, A[9]. В С++ отсутствует проверка границ массивов. Можно выйти за его границу и записать значение в некоторую переменную или даже в код программы. О таком контроле должен позаботиться программист.
При использовании статических массивов возникают проблемы в случае, если размер массива заранее мы не знаем. В таком случае объявляем массив максимальной размерности, которая, как правило, известна. Реальную размерность вводим и используем далее, например, в циклах и для других целей:
const nmax=100; float X[nmax];
int n; cout<<”Input the size of array ”; cin>>n;
/* Дальше работаем с n (а не с nmax) элементами массива, например, вводим их.*/
for (int i=0; i<n; i++)
{ // Эту строку можно опустить вместе с фигурными скобками.
cout<<”X[“<<i<<”]=”;
cin>>X[i];
}
Такой способ проще, но неэффективен с точки зрения распределения памяти, так как “заказываем” больше памяти, чем реально используем. В таких случаях профессионально используются более эффективные динамические массивы (см. 2-й семестр).
6.2. Способы определения массивов
· Ввод элементов массива с экрана (см. выше) или с заранее подготовленного файла (2-й семестр).
· Значения элементов массива можно задать (проинициализировать) во время объявления следующим образом:
тип имя [N]={список значений};
где в фигурных скобках записываются константы соответствующего типа, разделённые запятыми. Например:
const N=5; float A[N]={-1.1, 22, 3, -4.4, 50};
При этом если в списке меньше N значений, то недостающие элементы массива примут нулевое значение. Наоборот, если указать больше N значений, “компилятору это не понравится”.
Массив символов (строку) без явного использования указателей можно объявить и инициализировать по-разному. Можно указать размерность, достаточную для размещения текста и символа конца строки (‘\0’). Этот символ надо явно записать в конце списка, например:
char T[11]={‘м’,’а’,’т’,’е’,’м’,’а’,’т’,’и’,’к’,’а’,’\0’};
Второй способ проще и удобнее: char T[11]=“математика”; В этом случае нулевой символ добавляется к концу строки автоматически.
Кроме того, как для числовых, так и для символьных массивов необязательно указывать размерность. Она будет определена в зависимости от количества записанных элементов или длины строки. Например, int V[]={11, 2, -3, 44, -5}; объявляет и инициализирует целочисленный массив из пяти элементов, а
сhar S[]=”ММФ”; — строку из четырёх символов, так как добавлен символ конца строки.
Указанный способ определения массива удобен для отладки программы, так как не надо тратить время на многократный ввод его элементов. При тестировании достаточно изменить несколько элементов массива.
· Для некоторых, но не для всех, задач массив можно определить с помощью датчика случайных чисел:
const n=5; int Y[n]; randomize();
for (int j=0; j<n; j++)
{ Y[j]=random(100);
cout<<Y[j]<<" ";
}
Функцию randomize() рекомендуют использовать, чтобы массив был “более случайным”. В противном случае массив может быть таким же, каким был при предыдущем выполнении программы. Параметр функции random (в нашем примере 100) означает, что числа массива будут целыми на промежутке от 0 до 99 включительно. Если надо, чтобы числа были не только положительными, можно записать, например, так: Y[j]=random(100)-20; Тогда числа будут на промежутке от –20 до 79, то есть положительных чисел будет по теории вероятностей больше.
Если надо получить вещественные числа, можно в цикле записать, например, так: A[i]=random(5)/10.-0.25; При этом массив A объявляется как float, а константу 10 надо записать обязательно с символом “.”, то есть как вещественное число. Иначе получатся все одинаковые числа (-0.25), так как при делении любого целого числа, меньшего 5, на целое число 10 получится целая часть результата, то есть нуль.
· Массив можно построить по некоторому правилу, например:
for ( int j=0; j<n; j++)
if (j%2) A[j]= j*10;
else A[j]=j/100.;
Упражнение. Как работает этот фрагмент программы?