Найти тему

Указатели, массивы и аримфетика указателей (2 часть) в C++


В этой статье будет рассказано:

  • Динамическое и статическое связывание для массивов
  • Нотация массивов и нотация указателей
  • Указатели и строки
  • Использование операции new для создания динамических структур
  • Пример использования операций new и delete
  • Автоматическое, статическое и динамическое хранилище
  • Автоматическое хранилище
  • Статическое хранилище
  • динамическое хранилище
  • Стеки, кучи и утечка памяти

Динамическое и статическое связывание для массивов

Объявление массива можно использовать для создания массива со статическим связыванием — т.е. массива, размер которого фиксирован на этапе компиляции:

int tacos[10]; // статическое связывание, размер фиксирован во время компиляции

Для создания массива с динамическим связыванием (динамического массива) используется операция new [ ]. Память для этого массива выделяется в соответствии с размером, указанным во время выполнения программы. Когда работа с таким массивом завершена, выделенная ему память освобождается с помощью операции delete []:

int size;
cin >> size;
int * pz = new int [size] ; // динамическое связывание, размер
// устанавливается во время выполнения
delete [] pz; // освобождение памяти по окончании работы с массивом

Нотация массивов и нотация указателей

Использование нотации массивов с квадратными скобками эквивалентно разыменованию указателя:

tacos [0] означает *tacos и означает значение, находящееся по адресу tacos tacos [3] означает * (tacos+ 3) и означает значение, находящееся по адресу tacos+ 3

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

int * pt = new int [10]; // pt указывает на блок из 10 значений int
*pt =5; // присваивает элементу 0 значение 5
pt[0] = 6; // присваивает элементу 0 значение 6
pt[9] = 44; // устанавливает десятый элемент (элемент номер 9) в 44
int coats[10];
* (coats + 4) = 12; // устанавливает coats [4] в 12

Указатели и строки

Специальные отношения между массивами и указателями расширяют строки в стиле С. Рассмотрим следующий код:

char flower[10] = "rose";
cout << flower << "s are red\n";

Имя массива — это адрес его первого элемента, потому flower в операторе cout представляет адрес элемента char, содержащего символ г. Объект cout предполагает, что адрес char — это адрес строки, поэтому печатает символ, расположенный по этому адресу, и затем продолжает печать последующих символов до тех пор, пока не встретит нулевой символ (\0). Короче говоря, вы сообщаете cout адрес символа, он печатает все, что находится в памяти, начиная с этого символа и до нулевого. Ключевым фактором здесь является не то, что flower — имя массива, а то, что flower трактуется как адрес значения char. Это предполагает, что вы можете использовать переменную-указатель на char в качестве аргумента для cout, потому что она тоже содержит адрес char. Разумеется, этот указатель должен указывать на начало строки. Чуть позже мы проверим это. Но как насчет финальной части предыдущего оператора cout? Если flower — на самом деле адрес первого символа строки, что собой представляет выражение "s are red\n"? Согласно принципам, которыми руководствуется cout при выводе строк, эта строка в кавычках также должна быть адресом. Так оно и есть: в C++ строка в кавычках, как и имя массива, служит адресом его первого элемента. Предыдущий код на самом деле не посылает полную строку объекту cout; он просто передает адрес строки. Это значит, что строки в массиве, строковые константы в кавычках и строки, описываемые указателями — все обрабатываются одинаково. Каждая из них передается в виде адреса. Конечно, это уменьшает объем работ компьютеру по сравнению с тем, который потребовался бы в случае передачи каждого символа строки.

На заметку!
С объектом cout, как и в большинстве других выражений C++, имя массива char, указатель на char, а также строковая константа в кавычках — все интерпретируются как адрес первого символа строки.

В коде ниже демонстрируется применение различных форм строк. Код в этом листинге использует две функции из библиотеки обработки строк. Функция strlen(), которую вы уже применяли ранее, возвращает длину строки. Функция strcpy () копирует строку из одного места в другое. Обе функции имеют прототипы в файле заголовков cstring (или string, h — в устаревших реализациях). В программе также присутствуют комментарии, предупреждающие о возможных случаях неправильного применения указателей, которых следует избегать.

*знак решетки*include <iostream>
*знак решетки*include <cstring> // объявление strlen(), strcpy()
int main()
{
using namespace std;
char animal[20] = "bear"; // animal содержит bear
const char * bird = "wren"; // bird содержит адрес строки
char * ps; //не инициализировано
cout « animal << " and "; // отображение bear
cout « bird << "\n"; // отображение wren
// cout << ps << "\n"; // может отобразить мусор, но может вызвать
// и аварийное завершение программы
cout « "Enter a kind of animal: ";
cin » animal; // нормально, если вводится меньше 20 символов
// cin >> ps; очень опасная ошибка, чтобы пробовать;
// ps не указывает на выделенное пространство
ps = animal; // установка ps в указатель на строку
cout « ps « "!\n"; // нормально; то же, что и применение animal
cout « "Before using strcpy():\n";
cout << animal << " at " << (int *) animal << endl;
cout « ps « " at " « (int *) ps « endl;
ps = new char[strlen(animal) +1]; // получение нового хранилища
strcpy(ps, animal); // копирование строки в новое хранилище
cout « "After using strcpy():\n";
cout << animal << " at " << (int *) animal << endl;
cout « ps « " at " « (int *) ps « endl;
delete [] ps;
return 0;
}

Результат

bear and wren
Enter a kind of animal: fox
fox!
Before using strcpy() :
fox at 0x0065fd30
fox at 0x0065fd30
After using strcp() :
fox at 0x0065fd30
fox at 0x004301c8

Замечания по программе

Программа выше создает один массив char (animal) и две переменных типа "указатель на char" (bird и ps). Программа начинается с инициализации массива animal строкой "bear" — точно так же, как вы инициализировали массивы и раньше. Затем программа делает нечто новое. Она инициализирует указатель на char строкой:

const char * bird = "wren"; // bird содержит адрес строки

Вспомните, что "wren" на самом деле представляет собой адрес строки, поэтому приведенный выше оператор присваивает адрес "wren" указателю bird. (Обычно компилятор выделяет область памяти для размещения строк в кавычках, указанных в исходном коде, ассоциируя каждую сохраненную строку с ее адресом.) Это значит, что указатель bird можно применять так же, как использовалась бы строка "wren", например:

cout << "A concerned " « bird « " speaks\n";

Строковые литералы являются константами; именно поэтому в объявлении присутствует ключевое слово const. Применение const, таким образом, означает, что вы можете использовать bird для доступа к строке, но не можете изменять ее. И, наконец, указатель ps остается неинициализированным, поэтому он не может указывать ни па какую строку (Как вы знаете, это плохая идея, и данный пример — не исключение.)

Далее программа иллюстрирует тот факт, что имя массива animal и указатель bird можно использовать с cout совершенно одинаково. В конце концов, оба они являются адресами строк, и cout отображает две строки ("bear" и "wren"), расположенные по указанным адресам. Если вы удалите знаки комментария с кода, который пытается отобразить ps, то можете получить пустую строку, какой-нибудь мусор или даже привести программу к аварийному завершению. Создание неинициализированных указателей подобно выдаче незаполненного чека с подписью: вы утрачиваете контроль над его использованием.

Что касается ввода, то здесь ситуация несколько отличается. Использовать массив animal для ввода безопасно до тех пор, пока ввод достаточно краток, чтобы уместиться в отведенный массив. Однако было бы неправильно применять для ввода указатель bird.

• Некоторые компиляторы трактуют строковые литералы как константы, доступные только для чтения, что ведет к ошибкам времени выполнения при попытках записи в них данных. То, что строковые литералы являются константами — обязательное поведение C++, однако пока не все разработчики компиляторов отказались от старого подхода при их обработке.
• Некоторые компиляторы используют только одну копию строкового литерала для представления всех его вхождений в тексте программы.

Давайте проясним второй пункт. C++ не гарантирует уникальное сохранение строкового литерала. То есть, если вы используете литерал "wren" несколько раз в программе, компилятор может сохранить либо несколько копий этой строки, либо всего одну. Если он сохраняет одну, то установка в bird адреса "wren" заставляет его указать на единственную копию этой строки. Чтение нового значения в эту одну строку может повлиять на строки, которые изначально имели то же самое значение, но логически были совершенно независимыми, встречаясь в других местах программы. В любом случае, поскольку указатель bird объявлен как const, компилятор предотвратит любые попытки изменить содержимое, расположенное по адресу, на который указывает bird.

Еще хуже обстоят дела с попытками чтения информации в место, на которое указывает ps. Поскольку указатель ps не инициализирован, вы не можете знать, куда попадет введенная информация. Она может даже перезаписать ту информацию, которая уже имеется в памяти. К счастью, такой проблемы легко избежать: вы просто применяете достаточно большой массив char, чтобы принять ввод, но не используете для ввода строковых констант и неинициализированных указателей. (Или же можете обойти все эти проблемы и работать вместо массивов с объектами std: : string.)

Внимание!
При вводе строки внутри программы всегда необходимо использовать адрес ранее распределенной памяти. Этот адрес может иметь форму имени массива либо указателя, инициализированного с помощью операции new.

Далее обратите внимание на то, что делает следующий код:

ps = animal; // установить в ps указатель на строку
cout « animal « " at " « (int *) animal << endl;
cout « ps « " at " « (int *) ps « endl;
Он генерирует следующий вывод:
fox at 0x0065fd30
fox at 0x0065fd30

Обычно если объекту cout передается указатель, он печатает адрес. Но если указатель имеет тип char *, то cout отображает строку, на которую установлен указатель. Если вы хотите увидеть адрес строки, для этого потребуется выполнить приведение типа к указателю на другой тип, такой как int *, что и делает показанный код. Поэтому ps отображается как строка "fox", но (int *) ps выводится как адрес, по которому эта строка находится. Обратите внимание, что присваивание animal переменной ps не копирует строку; оно копирует только адрес. В результате два указателя (animal и ps) указывают на одно и то же место в памяти — т.е. на одну строку.

Чтобы получить копию строки, потребуется сделать кое-что еще. Первый подход предусматривает распределение памяти для хранения копии строки. Это можно сделать либо за счет объявления еще одного массива, либо с помощью операции new. Второй подход позволяет точно настроить размер хранилища для строки:

ps = new char[strlen(animal) + 1]; // получить новое хранилище

Строка "fox" не полностью заполняет массив animal, поэтому при таком подходе память расходуется непроизводительно. Здесь же мы видим использование strlen ()для нахождения длины строки, а затем к найденной длине прибавляется единица, чтобы получить длину, включающую нулевой символ. Далее программа применяет new для выделения достаточного пространства под хранение строки.

Вам необходим способ копирования строки из массива animal во вновь выделенное пространство. Установка ps в animal не работает, потому что это только изменяет адрес, сохраненный в ps, при этом утрачивается единственная возможность доступа к выделенной памяти. Поэтому взамен необходимо применять библиотечную функцию strcpy():

strcpy(ps, animal); // скопировать строку в новое хранилище

Функция strcpy () принимает два аргумента. Первый представляет собой целевой адрес, а второй — адрес строки, которую следует скопировать. Ваша обязанность — обеспечить, чтобы место назначения действительно смогло вместить копируемую строку. Здесь это достигается использованием функции strlen () для определения корректного размера и применением операции new для получения свободной памяти. Обратите внимание, что за счет использования strlen () и new получены две отдельных копии "fox":

fox at 0x0065fd30
fox at 0x004301c8

Также обратите внимание, что новое хранилище располагается в памяти довольно далеко от того места, где хранится содержимое массива animal.

Вам часто придется сталкиваться с необходимостью размещения строки в массиве. Для этого можно воспользоваться операцией = при инициализации массива; иначе придется иметь дело с функцией strcpy () или strncpy (). Вы уже видели функцию strcpy (); она работает следующим образом:

char food[20] = "carrots"; // инициализация
strcpy(food, "flan"); // альтернатива

Обратите внимание, что следующий подход может послужить причиной проблем, если массив food окажется меньше, чем строка:

strcpy(food, "a picnic basket filled with many goodies");

В этом случае функция копирует остаток строки в байты памяти, непосредственно следующие за массивом, при этом перезаписывая ее содержимое, несмотря на то, что, возможно, программа ее использует для других целей. Чтобы избежать такой проблемы, вместо strcpy () вы должны применять strncpy (). Эта функция принимает третий аргумент — максимальное количество копируемых символов. Однако при этом имейте в виду, что если данная функция исчерпает свободное пространство еще до достижения конца строки, то нулевой символ она не добавит. Потому применять ее нужно так:

strncpy(food, "a picnic basket filled with many goodies", 19) ;
food[19] = ' \0';

Этот код копирует до 19 символов в массив, после чего устанавливает последний элемент массива в нулевой символ. Если строка короче, чем 19 символов, то strncpy () добавит нулевой символ ранее, пометив им действительный конец строки.

На заметку!
Для копирования строки в массив применяйте strcpy () или strncpy (), а не операцию присваивания.

Теперь, когда вы ознакомились с некоторыми аспектами использования строк в стиле С и библиотеки сstring, вы сможете по достоинству оценить сравнительную простоту работы с типом string в C++. Обычно вам не следует беспокоиться о переполнении массива строкой, и вы можете применять операцию присваивания вместо strcpy() или strncpy ().

Использование операции new для создания динамических структур

Вы уже видели, насколько выгодным может быть создание массивов во время выполнения по сравнению с этапом компиляции. То же самое касается структур. Вам нужно выделить пространство для стольких структур, сколько понадобится программе при каждом конкретном запуске. Инструментом для этого, опять-таки, послужит операция new. С ее помощью можно создавать динамические структуры. Динамические здесь снова означает выделение памяти во время выполнения, а не во время компиляции. Кстати, поскольку классы очень похожи на структуры, вы сможете использовать изученные приемы как для структур, так и для классов.

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

inflatable * ps = new inflatable;

Это присвоит указателю ps адрес участка памяти достаточного размера, чтобы вместить тип inflatable. Обратите внимание, что синтаксис в точности такой же, как и для встроенных типов C++.

Более сложная часть — доступ к членам. Когда вы создаете динамическую структуру, то не можете применить операцию членства к имени структуры, поскольку эта структура безымянна. Все, что у вас есть — ее адрес. В C++ предусмотрена специальная операция для этой ситуации — операция членства через указатель (->). Эта операция, оформленная в виде тире с последующим значком "больше", означает для указателей на структуры то же самое, что операция точки для имен структур. Например, если ps указывает на структуру типа inflatable, Tops->price означает член price структуры, на которую указывает ps.

Совет
Иногда новички в C++ путаются в том, когда при обращении к членам структуры нужно применять операцию ., а когда — операцию ->. Правило простое: если идентификатор структуры представляет собой имя структуры, используйте операцию принадлежности; если же идентификатор является указателем на структуру, применяйте операцию членства через указатель.

Существует еще один, довольно неуклюжий подход для обращения к членам структур; он принимает во внимание, что если ps — указатель на структуру, то *ps — сама структура. Поэтому, если ps — структура, то (*ps) .price — ее член price. Правила приоритетов операций C++ требуют применения скобок в этой конструкции.

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

-2

*знак решётки* include <iostream>
struct inflatable // определение структуры
{
char name[20];
float volume;
double price;
};
int main ()
{
using namespace std;
inflatable * ps = new inflatable;
cout << "Enter name of inflatable item
cin.get(ps->name, 20);
cout « "Enter volume in cubic feet: ";
cin >> (*ps).volume;
cout « "Enter price: $";
cin >> ps->price;
cout « "Name: " « (*ps).name « endl;
cout « "Volume: " << ps->volume << " cubic feet\n";
cout << "Price: $" << ps->price << endl;
delete ps;
return 0;
}

Результат

Enter name of inflatable item: Fabulous Frodo
Enter volume in cubic feet: 1.4
Enter price: $27.99
Name: Fabulous Frodo
Volume: 1.4 cubic feet
Price: $27.99

Пример использования операций new и delete

Давайте рассмотрим пример, в котором используются операции new и delete для управления сохранением строкового ввода с клавиатуры. В программе выше определяется функция getname (), которая возвращает указатель на входную строку. Эта функция читает ввод в большой временный массив, а затем использует new [ ] с указанием соответствующего размера, чтобы выделить фрагмент памяти в точности такого размера, который позволит вместить входную строку. После этого функция возвращает указатель на этот блок. Такой подход может сэкономить огромный объем памяти в программе, читающей большое количество строк. (В реальности было бы проще воспользоваться классом string, в котором операции new и delete применяются внутренне.)

Предположим, что ваша программа должна прочитать 1000 строк, самая длинная из которых может составлять 79 символов, но большинство строк значительно короче. Если вы решите использовать массивы char для хранения строк, то вам понадобится 1000 массивов по 80 символов каждый, т.е. 80 000 байт, причем большая часть этого блока памяти останется неиспользованной. В качестве альтернативы можно создать массив из 1000 указателей на char и применить new для выделения ровно такого объема памяти, сколько необходимо для каждой строки. Это может сэкономить десятки тысяч байт. Вместо того чтобы создавать большой массив для каждой строки, вы выделяете память, достаточную для размещения ввода. И более того, вы можете использовать new для выделения памяти лишь для стольких указателей, сколько будет входных строк в действительности. Да, это несколько амбициозно для начала. Даже массив из 1000 указателей — довольно амбициозное решение для настоящего момента, но в листинге в программе ниже демонстрируется этот прием. К тому же, чтобы проиллюстрировать работу операции delete, программа использует ее для освобождения выделенной памяти.

*знак решетки*include <iostream>
*знак решетки*include <cs.tring> // или string, h
using namespace std;
char * getname(void); // прототип функции
int main()
{
char * name; // создание указателя, но без хранилища
name = getname(); // присваивание name адреса строки
cout « name « " at " << (int *) name << "\n";
delete [] name; // освобождение памяти
name = getname(); // повторное использование освобожденной памяти
cout « name « " at " « (int *) name « "\n";
delete [] name; // снова освобождение памяти
return 0;
}
char * getname() // возвращает указатель на новую строку
{
char temp[80]; // временное хранилище
cout « "Enter last name:'4"; // ввод фамилии
cin » temp;
char * pn = new char [strlen (temp) + 1] ;
strcpy(pn, temp); // копирование строки в меньшее пространство
return pn; // по завершении функции temp теряется
}

Результат

Enter last name: Fredeldurapkin
Fredeldumpkin at 0x004326b8
Enter last name: Pook
Pook at 0x004301c8

Замечания по программе

Рассмотрим функцию getname (), представленную в программе выше. Она использует сіn для размещения введенного слова в массив temp. Далее она обращается к new для выделения памяти, достаточной, чтобы вместить это слово. С учетом нулевого символа программе требуется сохранить в строке strlen (temp) + 1 символов, поэтому именно это значение передается new. После получения пространства памяти getname () вызывает стандартную библиотечную функцию strcpy (), чтобы скопировать строку temp в выделенный блок памяти. Функция не проверяет, поместится ли строка, но getname () гарантирует выделение блока памяти подходящего размера. В конце функция возвращает ps — адрес копии строки.

Внутри main () возвращенное значение (адрес) присваивается указателю name. Этот указатель объявлен в main (), но указывает на блок памяти, выделенный в функции getname (). Затем программа печатает строку и ее адрес.

Далее, после освобождения блока, на который указывает name, функция main () вызывает getname () второй раз. C++ не гарантирует, что только что освобожденная память будет выделена при следующем вызове new, и, как видно из вывода программы, это и не происходит.

Обратите внимание, что в рассматриваемом примере getname () выделяет память, a main () освобождает ее. Обычно это не слишком хорошая идея — размещать new и delete в разных функциях, потому что в таком случае очень легко забыть вызвать delete. В этом примере мы разделили эти две операции просто для того, чтобы продемонстрировать, что подобное возможно.

Благодаря некоторым тонким аспектам данной программы, вы должны узнать немного больше о том, как C++ управляет памятью. Поэтому давайте предварительно ознакомимся с материалом

Автоматическое, статическое и динамическое хранилище

В C++ предлагаются три способа управления памятью для данных — в зависимости от метода ее выделения: автоматическое хранилище, статическое хранилище и динамическое хранилище, иногда называемое свободным хранилищем или кучей. Объекты данных, выделенные этими тремя способами, отличаются друг от друга тем, насколько долго они существуют. Рассмотрим кратко каждый из них. (В C++11 добавляется четвертая форма, которая называется хранилищем потока)

Автоматическое хранилище

Обычные переменные, объявленные внутри функции, используют автоматическое хранилище и называются автоматическими переменными. Этот термин означает, что они создаются автоматически при вызове содержащей их функции и уничтожаются при ее завершении. Например, массив temp в программе выше существует только во время работы функции getname (). Когда управление программой возвращается main (), то память, используемая temp, освобождается автоматически. Если бы getname () возвращала указатель на temp, то указатель name в main () остался бы установленным на адрес памяти, которая скоро может быть использована повторно. Вот почему внутри getname () необходимо вызывать new. На самом деле автоматические значения являются локальными по отношению к блоку, в котором они объявлены. Блок — это раздел кода, ограниченный фигурными скобками. До сих пор все наши блоки были целыми функциями. Если вы объявите переменную внутри одного из таких блоков, она будет существовать только в то время, когда программа выполняет операторы, содержащиеся внутри этого блока.

Автоматические переменные обычно хранятся в стеке. Это значит, что когда выполнение программы входит в блок кода, его переменные последовательно добавляются к стеку в памяти и затем освобождаются в обратном порядке, когда выполнение покидает данный блок. (Этот процесс называется UFO (last-in, first-out — "последним пришел — первым ушел".) Таким образом, по мере продвижения выполнения стек растет и уменьшается.

Статическое хранилище

Статическое хранилище — это хранилище, которое существует в течение всего времени выполнения программы. Доступны два способа для того, чтобы сделать переменные статическими. Один заключается в объявлении их вне функций. Другой предполагает использование при объявлении переменной ключевого слова static:

static double fee = 56.50;

Согласно правилам языка С стандарта K&R, вы можете инициализировать только

статические массивы и структуры, в то время как C++ Release 2.0 (и более поздние), а также ANSI С позволяют также инициализировать автоматические массивы и структуры. Однако, как вы, возможно, обнаружите, в некоторых реализациях C++ до сих пор не реализована инициализация таких массивов и структур. Главный момент, который вы должны запомнить сейчас относительно автоматического и статического хранилищ — то, что эти методы строго определяют время жизни переменных. Переменные могут либо существовать на протяжении всего выполнения программы (статические переменные), либо только в период выполнения функции или блока (автоматические переменные).

Динамическое хранилище

Операции new и delete предлагают более гибкий подход, нежели использование автоматических и статических переменных. Они управляют пулом памяти, который в C++ называется свободным хранилищем или кучей. Этот пул отделен от области памяти, используемой статическими и автоматическими переменными. Как было показано в программе выше, операции new и delete позволяют выделять память в одной функции и освобождать в другой. Таким образом, время жизни данных при этом не привязывается жестко к времени жизни программы или функции.

Совместное применение new и delete предоставляет возможность более тонко управлять использованием памяти, чем в случае обычных переменных. Однако управление памятью становится более сложным. В стеке механизм автоматического добавления и удаления приводит к тому, что части памяти всегда являются смежными. Тем не менее, чередование операций new и delete может привести к появлению промежутков в свободном хранилище, усложняя отслеживание места, где будут распределяться новые запросы памяти.

Стеки, кучи и утечка памяти
Что произойдет, если не вызвать delete после создания переменной с помощью операции new в свободном хранилище (куче)? Если не вызвать delete, то переменная или конструкция, динамически выделенная в области свободного хранилища, останется там, даже при условии, что память, содержащая указатель, будет освобождена в соответствии с правилами видимости и временем жизни объекта. По сути, после этого у вас не будет никакой возможности получить доступ к такой конструкции, находящейся в области свободного хранилища, поскольку уже не будет существовать указателя, который помнит ее адрес. В этом случае вы получите утечку памяти. Такая память остается недоступной на протяжении всего сеанса работы программы. Она была выделена, но не может быть освобождена. В крайних случаях (хотя и нечастых), утечки памяти могут привести к тому, что они поглотят всю память, выделенную программе, что вызовет ее аварийное завершение с сообщением об ошибке переполнения памяти. Вдобавок такие утечки могут негативно повлиять на некоторые операционные системы и другие приложения, использующие то же самое пространство памяти, приводя к их сбоям. Даже лучшие программисты и программистские компании допускают утечки памяти. Чтобы избежать их, лучше выработать привычку сразу объединять операции new и delete, тщательно планируя создание и удаление конструкций, как только вы собираетесь обратиться к динамической памяти. Помочь автоматизировать эту задачу могут интеллектуальные указатели C++.

На заметку!
Указатели — одно из наиболее мощных средств C++. Однако они также и наиболее опасны, потому что открывают возможность недружественных к компьютеру действий, таких как использование неинициализированных указателей для доступа к памяти либо попыток освобождения одного и того же блока дважды. Более того, до тех пор, пока вы не привыкнете в нотации указателей и к самой концепции указателей на практике, они будут приводить к путанице. Но поскольку указатели — важнейшая часть программирования на C++, они постоянно будут присутствовать во всех дальнейших обсуждениях. К теме указателей мы еще будем обращаться не раз. Мы надеемся, что каждое объяснение поможет вам чувствовать себя все более уверенно при работе с указателями.

Конец

Спасибо, если вы прочитали эту статью. Надеюсь вы что-то новое узнали для себя, и конечно же поняли. Подпишитесь, поставьте лайк, напишите комментарий, поддержите меня. Хотелось бы увидеть как улучшать статьи и чего не хватает. Буду анализировать и улучшать контент. Еще раз спасибо, до свидания! :)

#c++ #ооп #программирование #разработка #языкипрограммирования #компиляторы #указатель #динамическое #статическое #стек