Программирование C++/Массивы

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

int a[5];


Таким образом мы инициализировали целочисленных массив из 5-и элементов. Также можно сразу инициализировать элементы массива

int a[5] {1,2,3,4,5};


Тут нужно обратить внимание, что тип массива находится по обе стороны от имени переменной int [5]. В данном случае в С++ тип окаймляет имя переменной. Иногда количество переменных можно не писать. В этом случае нужно обязательно описывать все значения массива.

int a[]{1,2,3,4,5,6};


Как же пройтись по массиву? Казалось все просто, но не тут то было. Есть две значительные проблемы:

1) массив не помнит свой размер. Здесь нельзя, как в Паскале, после имени массива поставить точку и выбрать length. Здесь такого нет. Правда размер бруска памяти, который отведен под массив известен.

sizeof(a)


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

sizeof(a)/n


Где n - кол-во байт, отводимое под тип элемента массива a. Если Вы точно не уверены в размере памяти, отводимое под одну переменную данного типа, то можно воспользоваться

sizeof(a)/sizeof(type)


Где type - это имя типа элементов массива a. В моем случае этот тип является типом int, поэтому мне подойдет код

sizeof(a)/sizeof(int)


Таким способом Вы получите кол-во элементов в массиве. Обратите внимание, что операция sizeof работает не только с переменными и выражениями, но и с типами.

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

2) массив - это брусок памяти, который пронумерован от 0 до n-1. Если мы попытаемся получить доступ к элементу под номером n, то этого элемента не существует и будет выход за границы массива, но ошибки не будет. В данном языке выход за границы массива не контролируется на этапе выполнения (И Вы скоро узнаете почему). Сделано это для эффективности. Если вы попытаетесь получить доступ к элементу под номером n, то вы влезете в память другой переменной, которая будет описана сразу за вашим массивом. При этом, если за памятью массива будет находится переменная другого типа, то ее значение будет обрезано (если за массивом целых (4 бита) будет описана вещественна переменная (8 байт), то значение переменной будет обрезано да 4 бит) и Вы получите непонятно что. Программа на этом не остановится и ошибки не будет.

Раньше таким способом взламывали операционные системы. Объявляли массив и через него пытались получить доступ к системной памяти операционной системы и через нее меняли ядро операционной системы. На данный момент такой взлом не пройдет. Такую уязвимость очень быстро обнаружили разработчики ОС и сделали защиту от этого. Если Вы попытаетесь получить доступ к памяти, которая не принадлежит вашей программе (попытаетесь залезть в память другой программы или в системную память), то ОС предотвратит эту попытку и выдаст страшное исключение (несанкционированный доступ к памяти). Это исключение означает, что Вы попытались пробить первое кольцо защиты. Его пробить очень трудно, не говоря уже о том, чтобы пробить все три кольца защиты.

Цикл по массиву

Поскольку массив не помнит свой размер, то будем его размер хранить в отдельной переменной

int n=5;
int a[n]{1,2,3,4,5};


Напишем очевидный код для данного случая

for(int i=0;i&ltn;i++)

              a[i]+=2; 


Цикл по массиву через цикл for пишется таким образом: от 0 и до n не включая.

В современном С++ появился цикл foreach, которого раньше не было.

for(int x:a)

              cout&lt&ltx&lt&lt" ";


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

for(auto&x:a)

              x+=2; 


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

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

Как же скопировать массив?

Пусть у нас есть два массива

 

int a[5]{5,4,3,2,1};

int b[5];


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

b=a;


Если Вы так сделаете, то Вы получите ошибку компиляции. Дело в том, что имя массива это просто ссылка на весь брусок памяти (Подробнее Вы узнаете чуть дальше (через статью или через две статьи)). Поэтому таким образом нельзя копировать массив. От сюда возникает вопрос: а как же можно копировать массив? Ответ, наверное, Вы и сами знаете: только в цикле.

for(int i=0;i < 5;i++)
        b[i]=a[i];


Можно это сделать по-другому. В стандартной библиотеке С++ есть функция copy, которая это сделает за нас

copy(a,a+n,b)


Здесь n - кол-во элементов в массиве a. Эта функция принимает следующие параметры:

1) от куда копировать

2)Пока что примите этот параметр как должное. (Повторюсь, вы через статью или две узнаете, что это такое).

3)куда копировать

Так тоже можно копировать, только потребуется подключить заголовочный файл algorithm

#include <algorithm>


Возникает вполне интересный вопрос:

Можно ли сравнить массивы на равенство?

a == b


В Паскале можно было присваивать, но нельзя было сравнивать на равенство. А в данном языке нельзя ни сравнивать, ни присваивать. Сравнивать можно только в цикле и никак больше. (Почему, вы узнаете чуть позже, как было сказано)

Передача массива в функции

Допустим, мы хотим написать функцию, которая напечатает наш массив.

void print(int a[


При написании данного заголовка подпрограммы, когда мы дойдем до int a[] у нас, естественно, возникнет вопрос: нужно ли писать размер массива или нет? Давайте пока не будем дописывать этот заголовок, а пока напишем тело самой функции

for(int i=0; i < 5; i++)
        cout << a[i] << " ";


Свободные места в заголовке и в теле функции связаны с размером массива. Нужно ли его указывать? Что нужно писать вместо размера? Нужно четко понимать, что в С++ массив в функцию передается ПО ССЫЛКЕ(на самом деле передается адрес на его первый элемент), что эффективно, поскольку не копируется весь брусок памяти массива, но следствия этого ужасны. В функцию никак не передается размер этого массива. То есть в основной программе мы знали кол-во элементов по другим объявленным переменным, а, войдя в функцию, мы это в принципе узнать никак не можем. Можно попробовать в заголовке подпрограммы указать размер массива

void print(int a[5])


Так можно сделать, но компилятор это значение проигнорирует. Вернее не проигнорирует, а просто выделит новую память под 5 элементов и забудет про размер данного массива.

И поэтому выход один: передавать в подпрограмму кол-во элементов в массиве

void print(int a[],int n) {
    for(int i=0;i < n; i++)
        cout << a[i] << \' \';

}


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

void print(const int a[],int n)


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

Если все же Вы будете писать подпрограмму,где нужно будет изменить элементы массива, то ключевое слово const указывать не нужно.

Двумерные массивы

Если говорить честно, то двумерных массивов нет. В С++ двумерные массивы можно смоделировать как массив массивов.

int m[3][4];


Давайте попробуем прочесть эту запись. Во многих языках русском, английском, немецком ... чтение идет слева направо. На арабском справа - налево. Когда мы читаем код на С и на С++, то он читается с середины. И это очень интересно. Ни в одном языке этого нет. Давайте прочтем эту запись: м принадлежит к типу массива из 3-х элементов и каждый из этих элементов принадлежит к типу массива из четырех элементов, каждый из которых является целым значением. Весь этот "двумерный массив" последовательно располагается в памяти. Вначале располагается первый элемент m[0].
m[0]    m[1]    m[2]
                                                       

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

cout << m[0][2];


Это хоть и не является двумерным массивом, но все же очень похоже на двумерный массив. Таким же методом можно реализовать трехмерный массив или более мерный массив.
Также, как и одномерные массивы, двумерые массивы копировать нельзя (можно, но только в цикле).
Как же передавать двумерные массивы в подпрограммы?

//в самом начале при описании подпрограммы, дойдя до описания массива возникает вопрос: что писать далее?
// Поскольку массивы, да и двумерные массивы не помнят свой размер, то нам нужно передавать в подпрограмму их размерность. 
// Но что писать в скобки пока не понятно
void print(int m[?][?], int k, int n) {
    for(int i=0; i < k;i++)     {
        for(int j=0; j < n; j++) {
            cout << m[i][j];
        }
        cout << endl;
    }
}


Теперь рассмотрим заголовок подпрограммы. При передаче одномерных массивов в подпрограммы их размерность не учитывается, даже если мы ее пишем. Также, как и в одномерных массивах, первая размерность не учитывается и этот параметр можно не писать. От сюда возникает вопрос: А можно ли не писать вторую размерность? Ответ очень простой: НЕЛЬЗЯ. Вторая размерность участвует в образовании типа, которая сообщает сколько нужно выделить память под одну строку массива. Поэтому вторую размерность нужно писать обязательно. Так, если у нас в массиве будет 4 строки, то заголовок программы примет следующее значение

void print(int m[] [4], int k, int n)


Это все нижайший уровень программирования. Такими массивами уже давно никто не пользуется, поскольку есть более современный типа массива vector, но о нем немного позже.
Пока что о массивах все. Для передачи их в подпрограммы можно пользоваться приведенными выше способами, но через несколько статей будет показан более удобный способ для передачи массивов в подпрограммы.