Що таке динамічні масиви 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 елементів і запис адреси на нього в попередній масив, і так для кожного стовпця.
Чому використовується покажчик другого порядку? Це покажчик на покажчик. Зрозуміти це трохи складно. Хоча код буквально нам говорить, щоб добратися до якого-небудь значення, що зберігається в масиві, нам потрібно пройти за адресою, отримати там ще один адреса і тоді ми вийдемо на значення.
<script type="text/jаvascript">
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/jаvascript",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;
Тепер можете спокійно створити навіть пятимерный масив.
Де ж «динаміка»?
На початку було сказано, що розмір динамічного масиву змінюється під час роботи програми, але в прикладах вище ніде цього явно вказано не було. Зміни розмірності масиву виробляються за наступним алгоритмом:
- В пам'яті створюється новий масив необхідних розмірів.
- Дані старого масиву переписуються в новий масив.
- Старий масив знищується.
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/jаvascript">
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/jаvascript",d.src="//an.yandex.ru/system/context.js",d.async=!0e.parentNode.insertBefore(d,e)}(this,this.document,"yandexContextAsyncCallbacks");
Доступ до елементів вектора
Отримання доступу до елементів вектора – це тема, про яку варто поговорити окремо. Існують наступні способи звернутися до елементів вектора.
V[index] | Стандартне звернення за індексом |
V. at(index) | Звернення до елементу з індексом, але при виході за межі діапазону генерується виключення |
V. front() | Звернення до першого елементу вектора |
V. back() | Звернення до останнього елемента вектора |
Очевидно, що найбільш вірним способом звернення до елементу вектора є виклик функції .at(), так як вона генерує виняток out_of_range, яке можна обробити в блоці try-catch. Операція[]і функції .front ().back() у разі виходу за межі допустимого діапазону працюють непередбачуваним чином.
Двовимірний вектор
Розглянемо приклад створення двовимірного динамічного масиву С++ - простий матриці 5 на 5.
vector < vector > v(5 vector (5));
v[2] [3]= 10;
int a = v[2] [3];
v[2].push_back(5); //створення нового елемента в кінець вектора
v[2].pop_back(); //видалення останнього елемента
Двовимірний вектор являє собою вектор векторів. І з кожним окремим вектором можна працювати: додавати і видаляти елементи, використовувати функції-члени, як показано в прикладі вище в останньому рядку. Також можна використовувати ітератори. На перший погляд конструкція здається страшною. Але тільки на перший. Вектори мають куди більшими можливостями, ніж описано в цій статті. З ними детально можна ознайомитися в документації.