Указатели в C++: адрес и удаление Всем привет! В этом уроке мы разберём то, что очень тесно связано с памятью в компьютере. С помощью этого можно улучшать работу своей программы. Как вы догадались с названия урока, это — указатели. Адрес переменной в C++ Поголовно у каждой переменной имеется свой индивидуальный адрес. Адрес переменной — это путь, по которому находится значение самой переменной. Он записывается в шестнадцатеричном виде. Так, компьютер может записать переменную, как в такой адрес 0x155, так и в такой 0x212. Давайте приведем аналогию с круизным лайнером. В нем, как и в отеле, имеются номера. Вот, например, при покупке номера вам могут дать номер — 0x155 (да, мы понимаем, что не в одном лайнере или отеле не станут записывать номера в шестнадцатеричном виде, но давайте все таки немного отвлечемся). А друг может оказаться в номере 0x212 — так и с переменными, они могут получить разный путь. И только сам компьютер при создании переменной знает, где она находится. Переменные, которые вы создаете в программе, по её завершению автоматически удаляются, чтобы не нагружать операционную память вашего компьютера. Пример удаления переменных В играх присутствует хорошая графика, различные спецэффекты. Например, тот же дым. Все это — переменная (может не одна!), которой в будущем придётся уничтожиться навсегда. А вот, если бы она не удалилась, то она бы своей фоновой работой понемножку нагружала бы наш компьютер. Поэтому в C/C++ присутствует возможность обратиться к переменной, и, если требует ситуация, удалить и создать её вовсе в другом участке программы, когда это, конечно, нам будет нужно. Что такое указатели в C++ Указатели — это с самого начала переменные, уже в которых хранится адрес других переменных. Чтобы пользоваться указателями, вам нужно использовать два оператора: * — показывает значение переменной по заданному адресу (показывает, кто живет в этом номере). Если вы используете оператор *, то вы занимаетесь операцией разыменование указателя. & — показывает адрес переменной (говорит, по какому адресу проживает этот человек). Как создать указатели в C++ Давайте посмотрим, какую конструкцию нужно использовать, чтобы создать указатели: 1 *<имя переменной> = &<имя другой переменной> Давайте подробно разберем, как эта она работает: В самом начале мы ставим оператор * (звездочку). Так мы говорим компилятору, что хотим использовать тип данных — указатель. Дальше мы должны указать имя нашей переменной. После знака равно нам нужно передать указателю адрес какой-то переменной, что мы и делаем с помощью оператора & (амперсанд). Чтобы передать адрес какой-то переменной, от одного указателя другому, нужно опускать оператор * для одного указателя (от которого мы передаем второму): 1 int a = 15 + 5; 2 3 int *ykazatel = &a; 4 int *ykazatel_second = ykazatel; // присвоили адрес переменной a 5 6 cout << *ykazatel_second; 7 В нашем случае мы опустили оператор * для ykazatel. Используем указатели на примере Чтобы получше понять указатели, давайте их разберем на примере ниже. Отец работает программистом в крупной компании, которая находится в 0x145 городе, и ему предложили поехать в командировку в 0x195 город, которую он так долго ждал. Он смог оповестить только своего сына о том, что уезжает. Поэтому сыну придется передать это маме самому. Пример выше мы сейчас реализуем на C++ с помощью указателей. 1 #include <iostream> 2 3 using namespace std; 4 5 int main() { 6 setlocale(0, ""); 7 8 int dad_gorod; 9 int *dad_son = &dad_gorod; 10 11 int *mama = dad_son; 12 13 system("pause"); 14 return 0; 15 } Давайте подробно разберем код выше: В строке 8: создали переменную dad_gorod, в которой находится адрес (город, в который уехал отец). В строке 9: создали указатель dad_son (это сын), он узнает имя города. В строке 11: объявили указатель mama, которая уже от сына получает имя города. Так сын (dad_son) в нашем примере является указателем. Часто у новичков есть некоторое недопонимание, как работает указатель. Они начинают путать операторы *, &. Вам нужно помнить, что: * — используется, когда вам нужно значение переменной. & — используется, когда вам понадобилось узнать адрес переменной. Как передать указатели функциям Вы, наверное, заметили, передавая функции аргумент и изменяя его в функции, переменная, значение которой мы передавали, никак не изменятся. И это понятно, потому что мы передаем значение одной переменной — другой переменной. Вот на примере ниже мы решили изменить значение передаваемой переменной: 1 #include <iostream> 2 3 using namespace std; 4 5 void func(int func_number) { 6 func_number = 5; 7 } 8 9 int main() { 10 setlocale(0, ""); 11 12 int main_number = 10; 13 14 cout << "Значение main_number до использования функции: " << main_number << endl; 15 16 func(main_number); 17 18 cout <<"А это значение main_number после использования функции: "<< main_number; 19 20 system("pause"); 21 return 0; 22 } Давайте запустим эту программу: ykazately.cpp Значение main_number до использования функции: 10 А это значение main_number после использования функции: 10 Process returned 0 (0x0) execution time : 0.010 s Press any key to continue. Как видно выше, у нас ничего не получилось, что и сразу было понятно. А вот если нам понастоящему нужно изменить значение переменной в функции, нам понадобится оперировать указателями. Чтобы изменить значение переменной, нам понадобится: В аргументах функции создать указатель. Далее при вызове функции передать адрес переменной, которую мы собираемся изменить. Вот и все! На примере ниже вы можете увидеть, как это реализовать: 1 #include <iostream> 2 3 using namespace std; 4 5 void func(int *func_number) { 6 *func_number = 5; 7 } 8 9 int main() { 10 setlocale(0, ""); 11 12 int main_number = 10; 13 14 func(&main_number); 15 16 cout << main_number; 17 18 system("pause"); 19 return 0; 20 } Что такое динамические переменные Динамические переменные — это переменные, которые созданы напрямую с помощью указателей. Для них существует функция удаление (это мы разберем ниже). Чтобы мы могли полноценно создавать динамические переменные, нам понадобится изучить конструктор — new, после его использования в оперативной памяти компьютера выделяются ячейки на тот тип данных, который мы указали. На каждый тип данных выделяется разное количество ячеек. Как создать динамические переменные в C++ Для создания динамических переменных нам понадобится применять конструкцию ниже: 1 <тип данных указателя> *<имя указателя> = new <тип данных>(<первоначальное значение>); Давайте подробно ее разберем: — указанный тип данных почти ни на что не повлияет. Читайте ниже. new — это конструктор, который и будет заключительным звеном для создания нашей переменной. <тип данных> — здесь нам понадобится указать тип, какой будет храниться в переменной. Он необязательно должен совпадать с типом указателя. <первоначальное значение> — с помощью круглых скобок можно указать значение переменной еще при ее инициализации. Использование круглых скобок в этой конструкции необязательно. <тип данных указателя> Вы должны знать! Если тип переменной отличается от типа указателя — то эта динамическая переменная будет весить больше в оперативной памяти, чем такая же переменная с одинаковыми типами! Пример использования динамических переменных Внизу мы решили использовать динамические переменные: 1 #include <iostream> 2 3 using namespace std; 4 5 int main() { 6 setlocale(0, ""); 7 int *a = new int; 8 9 int b = 10; 10 11 *a = b; 12 13 cout <<"Теперь переменная a равна "<< *a << endl; 14 15 cout <<"Пришло время удалить эту переменную!"; 16 17 system("pause"); 18 return 0; 19 } В строке 7: мы объявили переменную, оперируя конструктором new. Дальше в строке 11: значение нашей переменной становится равно 10. И в самом конце, в строке 15: выводим значение нашей переменной на экран. Важно помнить! Динамические переменные — это указатели, и поэтому перед ними обязательно должен стоять оператор *. Удаление динамических переменных Как мы говорили выше, у нас есть возможность освобождать память переменной или, если понятным языком, удалять переменную из оперативной памяти ПК. Конечно, эта переменная и так удалится из оперативной памяти компьютера при завершении программы. Но если нам захотелось удалить ее еще в середине программы, то это будет возможно благодаря оператору delete. Чтобы его использовать, нужно применить конструкцию ниже: 1 delete <имя переменной>; В самом начале мы используем оператор delete. Дальше идет имя переменной. Вы должны обратить внимание на отсутствие оператора * перед именем переменной. Многие начинающие прогеры забывают про это и в дальнейшем пытаются найти ошибку часами. Статическое и динамическое объявление переменных Статическое объявление переменных имеет такой вид: int number; Использование динамических переменных имеет маленький плюс. Он заключается в освобождении памяти переменной до завершения программы. Благодаря этому мы можем сначала удалить переменную, а потом ее снова создать в другом участке программы (когда это нам будет нужно). Что такое динамические массивы Мы уже знакомы с миром массивов в C++. Мы не раз создавали их на определенное количество ячеек и при этом использовали статическое создание массивов. 1 int array[100]; Но еще ни разу не затрагивали их использование с указателями! Мы создавали массивы на сто тысяч элементов, а то и больше. И не один раз бывало, что большое количество ячеек оставались неиспользованными. Это является неправильным применением оперативной памяти в ПК. Чтобы мы бесполезно не использовали оперативную память в компьютере, нам понадобится оперировать с указателями в свете массивов. Нам нужно вспомнить, что для создания статического массива количество ячеек нужно задавать числовой константой (а не переменной). Это очень неприятно, потому что в программе мы не знаем, сколько нам может понадобится ячеек. Например, пользователь захотел вписать 1000 чисел в массив, а мы из-за незнания этого факта сделали массив всего лишь на 500 ячеек. Динамический массив — это массив, у которого количество ячеек можно задавать и переменной, и числовой константой. Это большой плюс перед использованием статического массива. Как работают динамические массивы Для работы динамических массивов нам понадобится при инициализации указатель (всего лишь при инициализации!) и уже знакомый конструктор new. Как создать динамический массив в C++ Чтобы создать динамический массив мы будем использовать конструкцию ниже: 1 <тип данных> *<имя массива> = new <тип переменных> [<количество ячеек>]; — без разницы какой тип данных тут будет находиться, но лучше тот, который будет совпадать с типом переменных. <тип переменных> — указанный сюда тип и будут иметь ячейки массива. <количество ячеек> — здесь мы задаем размер массива (например [n] или [25]). <тип данных> Динамический массив полностью идентичен обычному массиву, кроме: Своей инициализации Возможностью своевременно освободить память. Давайте рассмотрим пример с использованием динамического массива: 1 int main() { 2 setlocale(0, ""); 3 4 int n; 5 6 cout << "Введите количество чисел, которое вы хотите ввести: "; 7 cin >> n; 8 9 cout << "Введите " << n << " чисел: "; 10 11 int *dinamich_array = new int [n]; // создаем 12 // динамический массив 13 for (int i = 0; i < n; i++) { 14 cin >> dinamich_array[i]; // считываем числа в ячейки массива 15 } 16 17 cout << "Теперь давайте выведем элементы массива в обратном порядке: "; 18 19 for (int i = n - 1 ; i >= 0; i--) { 20 cout << dinamich_array[i] << " "; // выводим значение всех ячеек 21 } 22 23 cout << endl << "Удаляем массив!"; 24 25 delete [] dinamich_array; // удаляем динамический массив 26 27 return 0; 28 } Вот что будет при выполнении программы: dinamic_array.cpp Задайте количество чисел, которое вы хотите ввести: 5 Введите 5 чисел: 2 4 6 8 16 Теперь давайте выведем элементы массива в обратном порядке: 16 8 6 4 2 Удаляем массив! Process returned 0 (0x0) execution time : 0.010 s Press any key to continue. Удаление динамического массива Для удаления динамического массива нам понадобится уже знакомый оператор — delete. 1 delete [] <имя массива>; Важно запомнить, что квадратные скобки нужно ставить перед <именем массива>. Как создать двумерный динамический массив в C++ Для создания двумерного динамического массива мы будем использовать похожую конструкцию (как и в одномерном динамическом массиве): 1 <тип данных> **<имя массива> = new <тип данных массива>* [<количество ячеек>]; Вам нужно обратить внимание на: Дополнительный оператор * перед <имя массива> и после <тип данных массива>. Дальше для каждой ячейки мы должны создать одномерный массив. Чтобы это сделать, нам понадобится цикл for и конструктор new. 1 for (int i = 0; i < n; i++) { 2 3 <имя массива>[i] = new <тип ячеек> [<количество ячеек>]; 4 5} В <количество ячеек> можно задавать разные значения. Поэтому сначала для первого массива можно задать длину 1 (new int [1]), потом для второго — длину 2 (new int [2]), как в примере ниже. Внизу находится пример двумерного динамического массива: 1 #include <iostream> 2 3 using namespace std; 4 5 int main() { 6 setlocale(0, ""); 7 8 int **dinamic_array2 = new int* [5]; // создаем 9 for (int i = 0; i < 5; i++) { // двумерный 10 dinamic_array2[i] = new int [i + 1]; // массив 11 } // ! 12 13 for (int i = 0; i < 5; i++) { 14 cout << "Введите числа" << "(" << i + 1 << ")" << ":"; 15 for (int j = 0; j < i + 1; j++) { 16 cin >> dinamic_array2[i][j]; 17 } 18 } 19 20 for (int i = 0; i < 5; i++) { 21 int sum = 0; 22 for (int j = 0; j < i + 1; j++) { 23 sum += dinamic_array2[i][j]; 24 } 25 cout << "Сумма " << i + 1 << " массива равна " << sum << endl; 26 } 27 28 for (int i = 0; i < 5; i++) { 29 delete [] dinamic_array2[i]; // удаляем массив 30 } 31 32 system("pause"); 33 return 0; 34 } В строках 8 — 11: создали двумерный динамический массив. В строках 13 — 18: заполнили массив. В строках 20 — 26: подсчитали и вывели по отдельности на экран сумму всех массивов. В строках 28 — 30: происходит удаление массива (об этом ниже). Удаление двумерного динамического массива Для удаление двумерного динамического массива мы будем использовать уже похожую схему. Но в ней присутствует цикл for, и после <имя массива> находится индекс того массива который будет удален. 1 for (int i = 0; i < <количество элементов в массиве>; i++) { 2 3 delete [] <имя массива>[i]; 4 5}