PVOID - Массивы

Массивы

 Этот раздел является переводом туториала C++ Language


   Массив — это последовательность элементов одинакового типа, расположенных в смежных участках памяти, к каждому из которых можно обратиться отдельно, добавляя индекс к уникальному идентификатору.

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

Например, массив, содержащий 5 целочисленных значений типа int, с именем foo, может быть представлен как:


где каждая пустая панель представляет элемент массива. В данном случае, это значения типа int. Эти элементы пронумерованы от 0 до 4, где первый — 0, а последний — 4. В C++ первый элемент массива всегда имеет индекс ноль (а не 1), независимо от длины массива.
Подобно обычным переменным, массив должен быть объявлен перед использованием. Типичное объявление массива в C++:

type name [elements];

где type это корректный тип (такой, как int, float...), name это корректный идентификатор, а поле elements (которое всегда заключено в квадратные скобки []) определяет длину массива в виде числа элементов.

   Следовательно, массив foo с пятью элементами типа int, может быть объявлен как:

int foo [5];

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

Инициализация массивов


   По умолчанию, обычные локальные массивы (например, объявленные в функции) остаются неинициализированными. Это означает, что ни один из элементов массива не имеет какого-либо конкретного значения; их содержимое является неопределенным в точке объявления массива.

   Но элементы массива могут быть явно инициализированы конкретными значениями при объявлении путем заключения этих инициализирующих значений в фигурные скобки {}. Например:

int foo [5] = { 16, 2, 77, 40, 12071 };

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


   Число значений в фигурных скобках {} не должно превышать число элементов в массиве. Например, в примере выше, foo объявлен как массив из пяти элементов (на что указывает число в квадратных скобках []), а фигурные скобки {} содержат ровно пять значений, одно на каждый элемент. Если указано меньше, оставшиеся элементы принимают значения по умолчанию (для фундаментальных типов это означает, что они будут заполнены нулями). Например:

int bar [5] = { 10, 20, 30 };

создаст такой массив:

 


 Инициализатор может даже не иметь значений, только фигурные скобки:

int baz [5] = { }; 

 Это создаст массив из пяти значений типа int, инициализированных нулем.



   Когда для массива предусмотрена инициализация значений, C++ позволяет оставить квадратные скобки пустыми [ ]. В этом случае, компилятор автоматически сделает предположение о размере массива, основываясь на числе значений, заключенных в фигурные скобки { }:

int foo [] = { 16, 2, 77, 40, 12071 };

   После этого объявления массив foo будет длиной в 5 элементов типа int, т.к. мы предоставили 5 инициализирующих значений.
   Наконец, развитие C++ привело к принятию универсальной инициализации также для массивов. Следовательно, больше нет необходимости добавлять знак равенства между объявлением и инициализатором. Оба этих выражения эквивалентны:

int foo[] = { 10, 20, 30 };
int foo[] { 10, 20, 30 }; 

   Статические массивы и те, которые объявлены непосредственно в пространстве имен (вне какой-либо функции), всегда инициализированы. Если не указан явный инициализатор, все элементы инициализируются по умолчанию (нулями для фундаментальных типов).

 Доступ к значениям массива


   К значению любого элементам массива можно получить доступ точно так же, как и к значению обычной переменной того же типа. Синтаксис следующий:
 
 name[index]
 
   Следуя предыдущим примерам, в которых foo содержит 5 элементов и каждый из этих элементов имеет тип int, имя, которое может быть использовано, чтобы ссылаться на каждый элемент, будет следующим:


 
Например, следующее выражение сохраняет значение 75 в третий элемент foo:

foo [2] = 75;

 а следующий пример копирует значение третьего элемента массива foo в переменную x:

x = foo[2];

   Следовательно, выражение foo[2] является переменной типа int.

   Обратите внимание, что третий элемент массива foo указан как foo[2], так как первым является foo[0], вторым foo[1], а, следовательно, третьим будет foo[2]. По этой же причине последним элементом массива будет foo[4]. Следовательно, если мы напишем foo[5], то мы обратимся к шестому элементу массива foo, что в действительности превышает размер массива.
   В C++ синтаксически правильно превышать допустимый диапазон индексов массива. Это может создать проблему, т.к. доступ к элементам за границами массива не приводит к ошибке компиляции, но может привести к ошибке во время выполнения. Причину этого можно увидеть в следующей главе при знакомстве с указателями.
   На этом этапе важно уметь четко различать два разных использования квадратных скобок по отношению к массивам. Они выполняют две различные задачи: указание размера массива при объявлении и указание индекса для конкретного элемента массива во время доступа. Не путайте эти два возможных использования квадратных скобок [ ] с массивами.

int foo[5];         // объявление нового массива
foo[2] = 75;        // доступ к элементу массива

   Главное отличие в том, что объявлению массива предшествует тип элементов.

   Некоторые другие корректные операции над массивами:

foo[0] = a;
foo[a] = 75;
b = foo [a+2];
foo[foo[a]] = foo[2] + 5;

Например:

// пример массивов
#include <iostream>
using namespace std;

int foo [] = {16, 2, 77, 40, 12071};
int n, result=0;

int main ()
{
  for ( n=0 ; n<5 ; ++n )
  {
    result += foo[n];
  }
  cout << result;
  return 0;
}
12206


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



   Многомерные массивы можно описать как «массивы массивов». Например, двумерный массив может быть представлен в виде двумерной таблицы, состоящей из элементов, имеющих одного и того же типа данных.



 jimmy представляет двумерный массив 3 на 5 элементов типа int. Синтаксис С++ для этого такой:

int jimmy [3][5];

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

jimmy[1][3]



 (помним, что индексы массива всегда начинаются с нуля).

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

char century [100][365][24][60][60];

объявляет массив элементов типа char для каждой секунды в столетии. Это составляет более трех миллиардов элементов типа char! Таким образом, это объявление будет потреблять более 3 гигабайт памяти!

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

int jimmy [3][5];   // эквивалентно:
int jimmy [15];     // (3 * 5 = 15)

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

 Многомерный массив   Псевдо-многомерный массив 
 #define WIDTH 5
 #define HEIGHT 3

 int jimmy [HEIGHT][WIDTH];
 int n,m;

 int main ()
 {
   for (n=0; n<HEIGHT; n++)
     for (m=0; m<WIDTH; m++)
     {
       jimmy[n][m]=(n+1)*(m+1);
     }
 }
 #define WIDTH 5
 #define HEIGHT 3

 int jimmy [HEIGHT * WIDTH];
 int n,m;

 int main ()
 {
   for (n=0; n<HEIGHT; n++)
     for (m=0; m<WIDTH; m++)
     {
       jimmy[n*WIDTH+m]=(n+1)*(m+1);
     }
 }


 Ни один из двух примеров кода выше не производит какого-либо вывода на экран, но оба присваивают значения блоку памяти с именем jimmy следующим образом:


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

 Массивы как параметры


   Иногда может возникнуть необходимость передать массив в функцию как параметр. В C++ нет возможности передать целый блок памяти, представляющий массив, непосредственно в функцию как аргумент. Но вместо этого можно передать его адрес. На практике это имеет такой же эффект, а также это намного более быстрая и эффективная операция.
   Чтобы принять массив как параметр функции, параметры могут быть объявлены с типом массива, но с пустыми квадратными скобками, без указания фактического размера массива. Например:

void procedure (int arg[])

   Эта функция принимает параметр типа «массив int» с именем arg. Чтобы передать в эту функцию массив, объявленный как:

int myarray [40];

достаточно будет написать вызов, подобный этому:

procedure (myarray);

 Далее приведем полный пример:

// массивы как параметры
#include <iostream>
using namespace std;

void printarray (int arg[], int length) {
  for (int n=0; n<length; ++n)
    cout << arg[n] << ' ';
  cout << '\n';
}

int main ()
{
  int firstarray[] = {5, 10, 15};
  int secondarray[] = {2, 4, 6, 8, 10};
  printarray (firstarray,3);
  printarray (secondarray,5);
}
5 10 15
2 4 6 8 10

   В приведенном примере первый параметр (int arg[ ]) принимает любой массив, элементы которого имеют тип int, независимо от длины. По этой причине мы включили второй параметр, который сообщает функции длину каждого массива, который мы передаем в нее первым параметром. Это позволяет циклу for, который печатает массив, итерироваться по заданному диапазону, не выходя за его границы.
   В объявление функции также возможно включить многомерные массивы. Формат для параметра трехмерного массива такой:

base_type[][depth][depth]

   Например, функция с многомерным массивом в качестве аргумента может быть:

void procedure (int myarray[][3][4])

   Обратите внимание, что первые квадратные скобки [ ] оставлены пустыми, тогда как следующие указывают размеры для соответствующих измерений. Это необходимо для того, чтобы компилятор мог определять глубину каждого дополнительного измерения.
   В некотором смысле, передача массива в качестве аргумента всегда теряет одно измерение. Причина заключается в том, что по историческим причинам массивы не могут быть скопированы напрямую, и, таким образом, в действительности передается указатель. Это распространенный источник ошибок для начинающих программистов. Хотя четкое понимание указателей, описанных в следующей главе, поможет не делать подобные ошибки.

 Библиотечные массивы


   Массивы, описанные выше, реализованы непосредственно как часть языка, унаследованная от языка Си. Они являются отличной возможностью языка, но ограничивают копирование, легко распадаясь на указатели, и, возможно, страдают от избытка оптимизации.
   Чтобы преодолеть некоторые из этих проблем с помощью встроенных в язык массивов, C++ предоставляет альтернативный тип массива в качестве стандартного контейнера. Это шаблонный тип (в действительности, шаблонный класс), определенный в заголовочном файле <array>.
   Контейнеры являются частью стандартной библиотеки, которая выходит за рамки данного руководства, и поэтому класс не будет детально освещен здесь. Достаточно сказать, что они имеют схожее поведение со встроенными массивами, за исключением того, что допускают копирование (в действительности, довольно дорогая операция, которая копирует целый блок памяти, поэтому используйте ее осторожно) и распадается на указатели только при явном указании на это (с помощью члена класса data).
   В качестве примера приведем две версии кода, использующие массив, встроенный в язык, описанный в этой главе, и библиотечный контейнер:

 встроенный массив   библиотечный массив 
#include <iostream>

using namespace std;

int main()
{
  int myarray[3] = {10,20,30};

  for (int i=0; i<3; ++i)
    ++myarray[i];

  for (int elem : myarray)
    cout << elem << '\n';
}
#include <iostream>
#include <array>
using namespace std;

int main()
{
  array<int,3> myarray {10,20,30};

  for (int i=0; i<myarray.size(); ++i)
    ++myarray[i];

  for (int elem : myarray)
    cout << elem << '\n';
}

   Как вы можете видеть, оба типа массивов используют схожий синтаксис для доступа к элементам: myarray[i]. Помимо этого, основные различия заключаются в объявлении массива и включении дополнительного заголовка для библиотечного массива. Обратите также внимание на то, как легко получить доступ к размеру библиотечного массива.