[БЕЗ_ЗВУКА] В предыдущем видео мы с вами узнали, что вектор хранит свои, свое содержимое в памяти, выделенной в куче. Выделение и освобождение памяти из кучи — это не какая-то магия, доступная только компилятору или типам, встроенным в язык C++. Нет. Любой программист в своем коде также может обратиться в кучу и выделить в ней необходимую память. Чтобы выделить в куче память, достаточную для хранения одного значения типа int, нужно сделать следующее. Нужно объявить локальную переменную, мы ее здесь назвали pInt, но она может называться как угодно, тип у этой переменной будет int*, и проинициализировать ее вот таким выражением, new int. new — это специальный оператор, который, собственно, выполняет выделение памяти из кучи. Ну а int, он как бы подсказывает оператору new, какой тип, для хранения какого типа нам нужна память. При этом pInt — это будет указатель на значение типа int. И смотрите, какая тут интересная вещь. Мы сказали, что pInt — это локальная переменная. И, как мы уже с вами знаем, локальные переменные хранятся в стеке. Поэтому сама переменная pInt будет храниться в стеке. Но она будет указывать на область памяти в куче, как раз на ту самую часть кучи, которая может хранить одно значение типа int. А теперь я хочу обратить ваше внимание на то, что в наших курсах по C++ слово «указатель» прозвучало впервые. Вообще язык C++ имеет славу сложного языка, потому что «у них там указатели и ручное управление памятью». Вот смотрите, еще раз: слово «указатель» прозвучало впервые только сейчас, в середине третьего курса по C++. До этого вы два с половиной курса программировали на C++, решали практические достаточно сложные задачи, при этом вам ни разу не приходилось сталкиваться с указателями. Поэтому вы теперь знаете, что C++ не так уж и сложен, и не надо быть гуру указателей, чтобы на нем программировать. И, на самом деле, указатели — это не так сложно, не так страшно. Указатель — это всего лишь адрес в памяти. Память в C++. представляется как линейный массив байт. И указатель — это просто индекс в этом массиве, какое-то числовое значение, которое указывает смещение от начала этого линейного массива байт. И давайте мы на это посмотрим в отладчике. Переключимся в Eclipse и напишем то, что у нас было представлено на слайде, pInt = new int. Скомпилируем, у нас все отлично компилируется, и запустим эту команду в отладчике. Запустился отладчик, выполним эту команду, и давайте посмотрим, чему у нас равно значение переменной pInt. Вот pInt. И мы видим, что оно равно в шестнадцатеричном виде 24bd0. Или же десятичное значение ее — 150480. То есть это как раз тот самый индекс в массиве памяти, в массиве байт, который, о котором мы и говорили. Он просто указывает смещение относительно начала памяти. Хорошо. Вот мы с вами, давайте переключимся в более привычный вид, вот мы выделили память, и у нас есть на нее указатель. Но как же нам обратиться к значению, на которое этот указатель указывает? Как нам обратиться к значению, которое лежит там в куче? Мы же именно со значением хотим работать, мы для этого память выделили. И здесь, на самом деле, все очень просто. Синтаксис работы с указателями точно такой же, как и при работе с итераторами. Давайте рассмотрим чуть другой пример. Давайте выделим в куче память для хранения одного объекта типа string. Тогда, чтобы обратиться к этому объекту, мы пишем *s, где s — это указатель на строку. И мы можем присвоить ей значение Hello. Давайте выведем, что у нас в итоге получилось. Вот мы запустили нашу программу, и в консоль вывелось Hello, то самое, которое мы записали в строчку s. Дальше. Мы можем захотеть узнать размер нашей строки. Давайте его тоже выведем. И делается это с помощью того же самого оператора «->», которым вы пользовались, когда работали с итератором, с итераторами. Вот мы берем указатель и обращаемся к методу size с помощью оператора «->». И вот у нас в консоль вывелось 5. Где еще, какие еще есть аналогии с итераторами? Например, мы можем показать, что оператор «*», примененный к указателю, возвращает ссылку на объект в куче. Вот мы присвоили переменной ссылочного типа *s, и что мы можем написать? Мы можем, например, написать: «прибавить к ссылке ref_to_s ", world"». А затем вывести *s на экран. Скомпилируем, запустим. И мы видим, что вывелось Hello, world. То есть мы изменили объект, доступный по ссылке ref_to_s, и когда мы вывели *s, у нас вывелось как раз Hello, world. Таким образом, вы можете видеть, что синтаксис работы с указателями, он точно такой же, как и с итераторами. Это логично, потому что итератор, он указывает на позицию в контейнере, а указатель указывает на объект где-то в памяти. В этом есть аналогия, и синтаксис работы такой же. Хорошо. Давайте вернемся к нашему первому примеру. И зададимся вот таким вопросом. Вот мы выделили на куче одно значение типа int. А чему оно равно? Чему равно, что будет записано в той области памяти, которую нам вернул оператор new. Здесь прослеживается аналогия со стеком. Мы уже с вами вспоминали, что, когда мы объявляем неинициализированную переменную на стеке, то ее значение не определено. И мы даже теперь знаем, почему это так. Потому что она кладется в неизвестный какой-то, старый стековый фрейм. Когда мы выделяем на куче объект базового типа, — int, char, double — то происходит то же самое. Мы берем какую-то память, в которой раньше что-то хранилось, мы не знаем, что, и собираемся хранить там значения целого типа. Но какое там значение сейчас, никто не знает. Поэтому когда мы выделяем на куче базовый тип, значение базового типа, — int, bool — то, если мы ничем его не проинициализируем, значение не определено. Если же мы, как и с локальными переменными, проинициализируем это значение в операторе new, то мы будем точно знать, что там будет записано. Давайте даже вернемся в Eclipse и сделаем это. То есть мы после оператора, после типа int в операторе new в круглых скобках — круглых, это важно — напишем 42. И выполним наш код опять-таки в отладчике. Выполним строчку, перейдем вот сюда, нажмем, вот здесь есть такая стрелочка, которая позволяет посмотреть на значение, на которое указывает указатель. Вот мы на нее нажмем и увидим, что *pInt, то есть то значение, на которое указывает указатель, равно 42. То есть у нас произошла инициализация. Так что здесь есть определенная аналогия с тем, что вы уже знаете, со стековыми переменными. И аналогия выполняется также, когда мы говорим о создании объектов классовых типов. Когда мы на стеке создаем объект классового типа, вы это уже знаете, для него вызывается конструктор по умолчанию. Либо конструктор, который мы сами решили вызвать. Вот когда мы выделяем этот объект на куче, происходит то же самое. Когда мы просто пишем, как в нашем примере, new string, то для объекта класса string будет вызван конструктор по умолчанию. Если же мы передали какие-то значения, то есть как во втором примере у нас передается строчка "Hi", то строка, выделенная на куче, будет сразу проинициализирована значением "Hi". Так что здесь точно так же выполняется аналогия между созданием объектов на стеке и созданием объектов на куче. Давайте подведем итоги этого видео. В этом видео мы узнали, что для создания объекта в куче используется оператор new. Для обращения к объекту... Причем этот оператор new возвращает указатель на выделенный участок памяти. Для обращения к объекту по указателю используется синтаксис, аналогичный итераторам, то есть оператор «*» и оператор «->». И инициализация объектов, выделенных на куче, выполняется аналогично инициализации объектов, создаваемых на стеке при объявлении локальных переменных.