Массивы в C++

При использовании простых переменных каждой области памяти для хранения данных соответствует свое имя. Если с группой величин одинакового типа требуется выполнять однообразные действия, им дают одно имя, а различают по порядковому номеру. Это позволяет компактно записывать множество операций с помощью циклов. Конечная именованная последовательность однотипных величин называется массивом.

Описание массива в программе отличается от описания простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размерность):

float а [10]; // описание массива из 10 вещественных чисел

Элементы массива нумеруются с нуля. При описании массива используются те же модификаторы (класс памяти, const и инициализатор), что и для простых переменных. Инициализирующие значения для массивов записываются в фигурных скобках. Значения элементам присваиваются по порядку. Если элементов в массиве больше, чем инициализаторов, элементы, для которых значения не указаны, обнуляются:

int b[5] = {3, 2, 1}; / / b[0]=3, b[l]=2, b[2]=l, b[3]=0, b[4]=0

Размерность массива вместе с типом его элементов определяет объем памяти, необходимый для размещения массива, которое выполняется на этапе компиляции, поэтому размерность может быть задана только целой положительной константой или константным выражением. Если при описании массива не указана размерность, должен присутствовать инициализатор, в этом случае компилятор выделит память по количеству инициализирующих значений. В дальнейшем мы увидим, что размерность может быть опущена также в списке формальных параметров.

Внимание
При описании массивов квадратные скобки являются элементом синтаксиса, а не указанием на необязательность конструкции.

Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.

#include <iostream.h>
int main(){
const int n = 10;
int i,. sum;
int marks[n] = {3, 4, 5, 4, 4};
for (i = 0, sum = 0; i<n; i++) sum += marks[1];
cout << "Сумма элементов: " << sum;
return 0;
}

Размерность массивов предпочтительнее задавать с помощью именованных констант, как это сделано в примере, поскольку при таком подходе для ее изменения достаточно скорректировать значение константы всего лишь в одном месте программы. Обратите внимание, что последний элемент массива имеет номер, на единицу меньший заданной при его описании размерности.

Внимание
При обращении к элементам массива автоматический контроль выхода индекса за границу массива не производится, что может привести к ошибкам.

Пример. Сортировка целочисленного массива методом выбора. Алгоритм состоит в том, что выбирается наименьший элемент массива и меняется местами с первым элементом, затем рассматриваются элементы, начиная со второго, и наименьший из них меняется местами со вторым элементом, и так далее n-1 раз (при последнем проходе цикла при необходимости меняются местами предпоследний и последний элементы массива).

#include <iostream.h>;
int main(){
 const int n = 20; // количество элементов массива
 int b[n]; // описание массива
 int i;
 for (i = 0; i> b[i]; // ввод массива
 for (i = 0; i<n-1; i++){ // n-1 раз ищем наименьший элемент
   // принимаем за наименьший первый из рассматриваемых элементов:
   int imin = 1;
   // поиск номера минимального элемента из неупорядоченных:
   for (int j = i + 1; j<n; j++)
      // если нашли меньший элемент, запоминаем его номер:
      if (b[j] < b[imin]) imin = j;
   int a = b[i]; // обмен элементов
   b[i] = b[imin]; // с номерами
   b[imin] = a; // i и imin
 }
 // вывод упорядоченного массива:
 for (i = 0; 1<n; i++) cout << b[i] << ' ';
 return 0;
}

Идентификатор массива является константным указателем на его нулевой элемент. Например, для массива из предыдущего листинга имя b — это то же самое, что &Ь[0], а к i-му элементу массива можно обратиться, используя выражение *(b+1). Можно описать указатель, присвоить ему адрес начала массива и работать с массивом через указатель. Следующий фрагмент программы копирует все элементы массива а в массив b:

int а[100], b[100]:
int *ра = а; // или int *р = &а[0];
int *pb = b;
for (int i = 0; i<100; i++) *pb++ =>*pa++; // или pb[1] = pa[1];

Динамические массивы

Динамические массивы создают с помощью операции new, при этом необходимо указать тип и размерность, например:

int п = 100:
float *р = new float [n];

В этой строке создается переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес ее начала записывается в указатель р. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.

Преимущество динамических массивов состоит в том, что размерность может быть переменной, то есть объем памяти, выделяемой под массив, определяется на этапе выполнения программы. Доступ к элементам динамического массива осуществляется точно так же, как к статическим, например, к элементу номер 5 приведенного выше массива можно обратиться как р[5] или *(р+5).

Альтернативный способ создания динамического массива — использование функции mallос библиотеки С:

int n = 100;
float *q = (float *) malloc(n * sizeof(float));

Операция преобразования типа, записанная перед обращением к функции mallос, требуется потому, что функция возвращает значение указателя тина void*, а инициализируется указатель на float.

Память, зарезервированная под динамический массив с помощью new [], должна освобождаться оператором delete [], а память, выделенная функцией mallос — посредством функции free, например:

delete [] р; free (q);

При несоответствии способов выделения и освобождения памяти результат не определен. Размерность массива в операции delete не указывается, но квадратные скобки обязательны.

Многомерные массивы

Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор

int matr [6][8];

задает описание двумерного массива из 6 строк и 8 столбцов. В памяти такой массив располагается в последовательных ячейках построчно. Многомерные массивы размещаются так, что при переходе к следующему элементу быстрее всего изменяется последний индекс. Для доступа к элементу многомерного массива указываются все его индексы, например, matr[i][j], или более экзотическим способом: *(matr[i]+j) или *(*(matr+i)+j). Это возможно, поскольку matr[i] является адресом начала i-й строки массива.

При инициализации многомерного массива он представляется либо как массив из массивов, при этом каждый массив заключается в свои фигурные скобки (в этом случае левую размерность при описании можно не указывать), либо задается общий список элементов в том порядке, в котором элементы располагаются в памяти:

int mass2 [][2] = { {1, 1}, {0, 2}, {1, 0} }:
int mass2 [3][2] = {1, 1, 0, 2, 1, 0}

Пример. Программа определяет в целочисленной матрице номер строки, которая содержит наибольшее количество элементов, равных нулю.

#include <stdio.h>
int main(){
   const int nstr = 4, nstb = 5; // размерности массива
   int b[nstr][nstb]; // описание массива
   int i, j;
   for (i = 0; i<nstr; i++) // ввод массива
   for (j = 0; j<nstb; j++) scanf("%d", &b[i][j]);
   int istr = -1, MaxKol = 0;
   for (i = 0; i<nstr; i++){ // просмотр массива по строкам
      int Коl = 0:
      for (j = 0; j<nstb; j++) if (b[i][j] == 0) Kol++;
      if (Ko1 > MaxKol) {istr = i ; MaxKol = Kol;}
   }
   printf(" Исходный массив:\n");
   for (i = 0; i<nstr; i++){
      for (j = 0; j<nstb; j++) printf("%d ", b[i][j]);
      printf ("\n");}
   if (istr == -1) printf("Нулевых элементов нет");
   else printf("Hoмep строки: %d", istr);
   return 0;
}

Номер искомой строки хранится в переменной istr, количество нулевых элементов в текущей (i-й) строке — в переменной Kol, максимальное количество нулевых элементов — в переменной MaxKol. Массив просматривается по строкам, в каждой из них подсчитывается количество нулевых элементов (обратите внимание, что переменная Kol обнуляется перед просмотром каждой строки). Наибольшее количество и номер соответствующей строки запоминаются.

Для создания динамического многомерного массива необходимо указать в операции new все его размерности (самая левая размерность может быть переменной), например:

int nstr = 5:
int ** m = (int **) new int [nstr][10];

Более универсальный и безопасный способ выделения памяти под двумерный массив, когда обе его размерности задаются на этапе выполнения программы, приведен ниже:

int nstr, nstb;
cout << " Введите количество строк и столбцов: ";
cin >> nstr >> nstb;
int **a = new int *[nstr]; // 1
for (int i = 0; i<nstr; i++) // 2
a[i] = new int [nstb]; // 3
...

В операторе 1 объявляется переменная типа «указатель на указатель на int» и выделяется память под массив указателей на строки массива (количество строк — nstr). В операторе 2 организуется цикл для выделения памяти под каждую строку массива. В операторе 3 каждому элементу массива указателей на строки присваивается адрес начала участка памяти, выделенного под строку двумерного массива. Каждая строка состоит из nstb элементов типа int.

Выделение памяти поод двумерный массиив в C++Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete [ ] . Указатель на константу удалить нельзя.

Примечание
Для правильной интерпретации объявлений полезно запомнить мнемоническое правило: «суффикс привязан крепче префикса». Если при описании переменной используются одновременно префикс * (указатель) и суффикс [] (массив), то переменная интерпретируется как массив указателей, а не указатель на массив: int *р[10]; — массив из 10 указателей на int.

Строки как массивы символов

Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ — это символ с кодом, равным О, что записывается в виде управляющей последовательности ‘\0’. По положению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литералом.

char str[10] = "Vasia";
// выделено 10 элементов с номерами от 0 до 9
// первые элементы - 'V', 'а', 's', 'i', 'а', '\0'

В этом примере под строку выделяется 10 байт, 5 из которых занято под символы строки, а шестой — под нуль-символ. Если строка при определении инициализируется, ее размерность можно опускать (компилятор сам выделит соответствующее количество байт):

char str [] = "Vasia"; // выделено и заполнено 6 байт

Оператор

char *str = "Vasia"

создает не строковую переменную, а указатель на строковую константу, изменить которую невозможно (к примеру, оператор str[l]=’o’ не допускается). Знак равенства перед строковым литералом означает инициализацию, а не присваивание. Операция присваивания одной строки другой не определена (поскольку строка является массивом) и может выполняться с помощью цикла или функций стандартной библиотеки. Библиотека предоставляет возможности копирования, сравнения, объединения строк, поиска подстроки, определения длины строки и т. д., а также содержит специальные функции ввода строк и отдельных символов с клавиатуры и из файла.

При работе со строками часто используются указатели.

Внимание
Распространенные ошибки при работе со строками — отсутствие пуль-символа и выход указателя при просмотре строки за ее пределы.

Существует несколько способов работы со строками (через массивы или указатели) и они имеют свои плюсы и минусы, но в общем случае лучше не изобретать велосипед, а пользоваться функциями библиотеки или определенным в стандартной библиотеке C++ классом string, который обеспечивает индексацию, присваивание, сравнение, добавление, объединение строк и поиск подстрок, а также преобразование из С-строк, то есть массивов типа char, в string, и наоборот.


По материалам книги Т.А. Павловской «С\С++. Программирование на языке высокого уровня».

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.