Що таке динамічні масиви C++?

652 0 Новини високих технологій

Визначимося для початку з тим, що таке динамічний масив. З часів Сі існують масиви, але їх особливістю був фіксований розмір, який зазначався при створенні і більше не змінювався. Завдяки цьому вони отримали назву статичних масивів. Очевидно, динамічний масив означає, що він може змінювати свій розмір в ході роботи програми. А також може створюватися тоді, коли кількість передбачуваних елементів навіть приблизно невідомо, той є пустим.

Управління динамічною пам'яттю

Що таке динамічні масиви C++?
Існує таке поняття, як динамічне управління пам'яттю. Такий підхід в програмуванні дозволяє максимально ефективно використовувати пам'ять комп'ютера. В C++ цей процес управляється операціями new і delete. Операція new резервує пам'ять в області динамічної пам'яті або також званої купи (free store або heap на англ.). Відповідно, операція delete звільняє резервацію.


За стандартами програмування за динамічною пам'яттю потрібно стежити і вчасно очищати, тому операції new і delete часто використовуються парно. Цей принцип давно застарів. Його коріння росте з тих часів, коли операційні системи погано стежили за пам'яттю або просто не вміли самостійно її очищати. Тепер ОС завжди очищає пам'ять після роботи програми. Проте явна очищення пам'яті є ознакою хорошого тону в програмуванні. Операція new резервує пам'ять під об'єкт певного типу і повертає адресу на цю пам'ять. Якщо виділення пам'яті з яких-небудь причин неможливо здійснити, то операція поверне нульовий покажчик (покажчик, який не посилається ні на що) і викине виняток. Оператор new працює з об'єктами будь-яких типів даних: double, char, int і т. д. Виділення пам'яті і її видалення виглядають наступним чином.




int *p = new int; //* - означає, що змінна є покажчиком. Покажчики зберігають адреси.
*p = 9;
delete p;

Одновимірний масив

Створення одномірного динамічного масиву відбувається так само, як створення змінної в купі.

  double *a = new double[10]; //a - вказівник на пам'ять, виділену під масив з 10 елементів типу double 
a[5]= 2.5;
delete[]a; //уважно поставтеся до цієї конструкції! Вона хитра!

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

Двовимірний масив

Створення одномірного динамічного масиву - тривіальна задача. А якщо нам потрібен багатовимірний масив

  double **ma = new double *[5]; //крок 1 
for(int I = 0; I < 5; i++) //шаг 2
ma[i]= new double[10];

Таким чином, відбувається створення динамічного масиву в C++ розміром 5 на 10. Буквально це означає на першому кроці виділення в пам'яті масиву з 5 елементів. А потім, на другому кроці, виділення пам'яті під масив з 10 елементів і запис адреси на нього в попередній масив, і так для кожного стовпця.

Що таке динамічні масиви C++?

Чому використовується покажчик другого порядку? Це покажчик на покажчик. Зрозуміти це трохи складно. Хоча код буквально нам говорить, щоб добратися до якого-небудь значення, що зберігається в масиві, нам потрібно пройти за адресою, отримати там ще один адреса і тоді ми вийдемо на значення.


<script type="text/javascript">
var blockSettings2 = {blockId:"R-A-70350-2",renderTo:"yandex_rtb_R-A-70350-2",async:!0};

if(document.cookie.indexOf("abmatch=") >= 0){
blockSettings2 = {blockId:"R-A-70350-2",renderTo:"yandex_rtb_R-A-70350-2",statId:70350async:!0};
}

!function(a,b,c,d,e){a[c]=a[c]||[],a[c].push(function(){Ya.Context.AdvManager.render(blockSettings2)}),e=b.getElementsByTagName("script")[0],d=b.createElement("script"),d.type="text/javascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");

Пам'ятайте про те, що конструкцію звільнення пам'яті для масиву потрібно було запам'ятати? Двовимірний масив знищується наступним чином.

  for(int I = 0; i < 5l i++)
delete[]ma[i];
//здається, ми щось забули
delete[]ma;

Тепер можете спокійно створити навіть пятимерный масив.

Де ж «динаміка»?

На початку було сказано, що розмір динамічного масиву змінюється під час роботи програми, але в прикладах вище ніде цього явно вказано не було. Зміни розмірності масиву виробляються за наступним алгоритмом:

  1. В пам'яті створюється новий масив необхідних розмірів.
  2. Дані старого масиву переписуються в новий масив.
  3. Старий масив знищується.

STL vector – новий динамічний масив

Для використання векторів необхідно підключити .

Як відомо, стандартна бібліотека шаблонів (STL) забезпечена набором контейнерів, які керують колекціями елементів. Серед контейнерів є послідовні контейнери. Вони відрізняються головним чином впорядкованістю елементів за часом вставки. Іншими словами, перший елемент завжди буде першим, другий завжди другим і т. д. Є й інші види контейнерів – асоціативні, впорядковані за значенням елементів і неврегульовані зовсім.

Одним з таких послідовних контейнерів є вектор. Він керує елементами масиву C++ в динамічній пам'яті. Доступ до цих елементів здійснюється безпосередньо за індексом. Завдяки тому, що вектор – послідовний контейнер, додавання і видалення елементів відбувається в кінці масиву, і ці операції виробляються дуже швидко. Проте вставка нового елемента в середину або початок вектора відбувається значно повільніше, оскільки для здійснення цієї процедури доведеться перемістити всі попередні елементи до поточного номера вставки. Розглянемо приклад.

  #include  
#include
int main() {
std::vector v; //створення порожнього вектора для зберігання елементів типу int
//визначення вектора – шаблон простору std, тому std::
for(int i = 0; i <= 5; ++i) {
v.push_back(i);
}
for(int i = 0; i < v.size(); ++i) {
std::cout v[i]", ";
}
std::cout std::endl;
system("pause");
}

Як видно з коду, робота з векторами здійснюється точно так само, як з масивами. При цьому вектори забезпечені корисними додатковими властивостями, такими як функції З++ для динамічних масивів .size ().push_back(). Строго кажучи, дані функції-члени належать контейнера.


<script type="text/javascript">
var blockSettings3 = {blockId:"R-A-70350-3",renderTo:"yandex_rtb_R-A-70350-3",async:!0};

if(document.cookie.indexOf("abmatch=") >= 0){
blockSettings3 = {blockId:"R-A-70350-3",renderTo:"yandex_rtb_R-A-70350-3",statId:70350async:!0};
}

!function(a,b,c,d,e){a[c]=a[c]||[],a[c].push(function(){Ya.Context.AdvManager.render(blockSettings3)}),e=b.getElementsByTagName("script")[0],d=b.createElement("script"),d.type="text/javascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
Що таке динамічні масиви C++?

Доступ до елементів вектора

Отримання доступу до елементів вектора – це тема, про яку варто поговорити окремо. Існують наступні способи звернутися до елементів вектора.

V[index]

Стандартне звернення за індексом

V. at(index)

Звернення до елементу з індексом, але при виході за межі діапазону генерується виключення

V. front()

Звернення до першого елементу вектора

V. back()

Звернення до останнього елемента вектора

Очевидно, що найбільш вірним способом звернення до елементу вектора є виклик функції .at(), так як вона генерує виняток out_of_range, яке можна обробити в блоці try-catch. Операція[]і функції .front ().back() у разі виходу за межі допустимого діапазону працюють непередбачуваним чином.

Двовимірний вектор

Розглянемо приклад створення двовимірного динамічного масиву С++ - простий матриці 5 на 5.


Що таке динамічні масиви C++?

  vector < vector > v(5 vector  (5)); 
v[2] [3]= 10;
int a = v[2] [3];
v[2].push_back(5); //створення нового елемента в кінець вектора
v[2].pop_back(); //видалення останнього елемента


Двовимірний вектор являє собою вектор векторів. І з кожним окремим вектором можна працювати: додавати і видаляти елементи, використовувати функції-члени, як показано в прикладі вище в останньому рядку. Також можна використовувати ітератори. На перший погляд конструкція здається страшною. Але тільки на перший. Вектори мають куди більшими можливостями, ніж описано в цій статті. З ними детально можна ознайомитися в документації.
Що таке динамічні масиви C++?

Висновок

У недалекому минулому програмісти змушені були створювати динамічні масиви самостійно, включаючи всі необхідні функції та алгоритми для роботи з ними. Але з появою STL вони можуть використовувати готові, продумані і ефективні класи. Тому слід не винаходити велосипед заново, а просто використовувати вектори (або інший відповідний контейнер). І тільки при необхідності працювати старими методами.