Шаг 36.
Общие сведения о массивах

    С этого шага мы начинаем знакомиться с организацией и использованием массивов.

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


Рис.1. Схема размещения в памяти элементов массива b

    При определении массива ему выделяется память так же, как массивам других алгоритмических языков. Но как только память для массива выделена, имя массива воспринимается как константный указатель того типа, к которому отнесены элементы массива. Однако это справедливо не всегда. Например, при использовании имени массива в операции sizeof, ее результатом является размер в байтах участка памяти, выделенного не для указателя, а для массива в целом. Исключением является и применение операции & (получение адреса) к имени массива. Результат операции - адрес начального (с нулевым индексом) элемента массива. В остальных случаях значением имени является адрес первого элемента массива, которое нельзя изменить.

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

<тип элементов массива> <имя массива> [<количество элементов в массиве>];.
Нумерация элементов в массиве начинается с нуля!

    Массивы могут иметь те же типы и классы памяти, что и простые переменные. В некоторых случаях допустимо описание массива без указания количества его элементов, то есть без константного выражения в квадратных скобках. Например: extern unsigned long UL[]; является описанием внешнего массива, который определен в другой части программы, где ему выделена память и (возможно) присвоены начальные значения его элементам. Приведем еще несколько примеров описания массивов:

   char alpha[26]; //Внешний массив, содержащий 26 символов.
   main ()
   {
      static int nan[22];  //Статический массив, содержащий 22 целых числа.
      extern char alpha[]; //Внешний массив, размер его указан выше.
      float a[15];         //Автоматический массив, содержащий 15 
                           //вещественных  чисел. 
   }

    В языке C++ определены только одномерные массивы. Многомерные массивы строятся на основе рекурсивного правила, согласно которому элементом массива может быть массив. Таким образом, в языке C++ двумерный массив (матрица) - это одномерный массив, каждый элемент которого является одномерным массивом.

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

char СН[ ] = { 'А', 'В', 'С', 'D'}; // Массив из 4 элементов.
int pr[6]= {10,20,30,40};           // Массив из 6 элементов.
char St[ ] = "ABCD";    // Массив из 5 элементов (включая нулевой элемент).

Пример 1. Инициализация целочисленного массива внешнего класса памяти.
   #include <iostream.h>
      int n=5;
      int nd[5] = { 0,1,2,3,4 };
                  /* Инициализация целочисленного   */
                  /* массива внешнего класса памяти */
   main ()
   {
      for (int i=0; i<=n; i++)
        cout << "i=" << i << " nd[i]=" << nd[i] << endl;
   }
Текст этой программы можно взять здесь.

    Следующие примеры являются ошибочными:

     float A[ ];  // Отсутствует размер массива.
     double b[4] = {1,2,3,4,5,6,7,8};  // Количество элементов 
                                       //не совпадает с размером массива.

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

    В тех случаях, когда массив не определяется, а описывается, список начальных значений задавать нельзя. В описании массива может отсутствовать и его размер:

     extern float E[ ]; // Правильное описание внешнего массива.

Предполагается, что в месте определения массива для него выделена память и выполнена инициализация.

    Элементы массива можно задавать в цикле при выполнении программы.


    Пример 2. Ввод элементов массива при выполнении программы.
   #include <iostream.h>
   main ()
   {
      int score[10];
      /* Ввод элементов массива. */
      for  (int i=0; i<=9; i++)
        {  cout << i+1 << "-й элемент: ";
           cin >> score[i];
        }
      /* Проверка правильности ввода. */
      cout << "Введены следующие значения:\n");
      for  (i=0; i<=9; i++)
         cout << score[i] << " ";
      cout << endl;
   }
Текст этой программы можно взять здесь.

    Имя массива является указателем-константой, значением которой служит адрес первого элемента массива (с индексом 0). Таким образом, доступ к первому элементу массива может быть осуществлен так:

    <имя массива>[0]     
        или     
    *<имя массива>     .

    В общем случае доступ к заданному элементу массива можно осуществить двумя способами:

    <имя массива>[<номер элемента>] 
        или 
    *(<имя массива>+<номер элемента>).

    Проиллюстрируем различные способы доступа к элементам массива.


    Пример 3. Вывести на экран заданную строку несколькими способами.
#include <iostream.h>
void main()
{
  char x[]="Пример строки";
  int i=0;
  cout<< "\n Вывод первого символа строки (1 способ): "<< x[0];  //1
  cout<< "\n Вывод первого символа строки (2 способ):  "<< *x;   //2
  cout<< "\n Вывод строки без цикла (1 способ):  "<< x;          //3
  cout<< "\n Вывод строки без цикла (2 способ): "<< &x[0];       //4
  cout<< "\n Вывод строки с  циклом (1 способ):  ";              //5
  while (x[i]!='\0')
	  cout << x[i++];
  cout<< "\n Вывод строки с  циклом (2 способ):  ";              //6
  i=0;
  while (*(x+i)!='\0')
	  cout << *(x+i++);
  cout<<"\n Вывод строки с  циклом (3 способ): ";                //7
  i=0;
  while (i[x]!='\0')
	  cout << i++[x];
}
Текст этой программы можно взять здесь.

    Прокомментируем текст приведенной программы.

  1. Вывод первого символа строки (1 способ). Конструкция x[0] организует стандартный доступ к первому элементу массива.
  2. Вывод первого символа строки (2 способ). Конструкцию *x запишем следующим образом: *x = *(x+0) = x[0]. Здесь учитывается тот факт, что все конструкции вида x[i] перед выполнением преобразуются компилятором к виду: *(x+i).
  3. Вывод строки без цикла (1 способ). Это традиционный способ вывода значения переменной.
  4. Вывод строки без цикла (2 способ). Для символьного массива его имя содержит адрес первого элемента массива. Таким образом, запись x эквивалентна &x[0].
  5. Вывод строки с циклом (1 способ). Он является стандартным доступом к элементам массива.
  6. Вывод строки с циклом (2 способ). Проведем уже известные преобразования: x[i++] = *(x+i++).
  7. Вывод строки с циклом (3 способ). Продолжим предыдущую цепочку преобразований: x[i++] = *(x+i++) = *(i++ + x) = i++[x]. Синтаксическая правильность приведенной в этом способе конструкций еще раз свидетельствует о том, что все конструкции вида x[i] перед выполнением преобразуются компилятором к виду: *(x+i).

    В заключение отметим, что в языке C++ нет специального типа днных "строка". Вместо этого каждая символьная строка в памяти компьютера представляется в виде одномерного массива типа char, последним элементом которого является символ '\0'. Изображение строковой константы может использоваться по-разному. Если строка применяется для инициализации массива типа char, например, так: char array[ ] = "инициализирующая строка";, то адрес первого элемента строки становится значением указателя-константы (имени массива) array. Если строка используется для инициализации указателя типа char *: char *pointer = "инициализирующая строка" ;, то адрес первого элемента строки становится значением указателя-переменной pointer. Если использовать строку в выражении, где разрешено применять указатель, то используется адрес первого элемента строки:

     char * string;
     string = "строковый литерал";

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

    На следующем шаге мы продолжим изучение массивов, в частности, остановимся на многомерных массивах.


Предыдущий шаг Содержание Следующий шаг