. Указатели в C/C++ для новичков (Часть 2)
Указатели в C/C++ для новичков (Часть 2)

Указатели в C/C++ для новичков (Часть 2)

В предыдущей части я рассказал вам об основах указателей в С/С++. Поскольку работа с памятью немного отличается в С++(там она упрощена), эта статья будет ориентирована именно на С. Чистый С сложнее, но если его понять, С++ покажется легким. В конце статьи, я коротко расскажу о работе с памятью в С++. И так сегодня мы узнаем:

  • Что общего у массивов и указателей;
  • Как выделять память;
  • Что из себя представляют строки;

Кому интересно, прошу под спойлер!

И так. Для начала узнаем, что же такое массив. Вот небольшой код:

Странный код, правда? Создается массив. Потом мы его пытаемся вывести на экран как указатель(wtf?). Мало того, получаем значение массива, как будто он указатель. Бред? А вот и нет! Массив и есть указатель. Почти. Дело в том, что написав:

Мы скомандовали компьютеру: «выделить последовательно(один блок за одним) память, для трех элементов типа int, указатель на первый элемент поместить в переменную array«. Теперь попробуем наоборот, работать с указателем как с массивом:

С функциями malloc и free мы разберемся чуть пожже, просто запомните что они работают с памятью(выделяет malloc, free — освобождает). Поскольку указатель указывает(да да, я знаю) на область памяти, то этой области не обязательно должна быть присвоена переменная. То есть, можно как в нашем примере выделить память для указателя, и работать с ней.

Примечание: переменные и массивы при объявлении получают память в стеке, при выделении памяти вручную, блоки создаются в куче. Гугл ваш друг.

Как вы уже знаете, переменная массива тоже указатель(на первый элемент), так что нам мешает создать указатель, выделить блок памяти и присвоить ему значение первого элемента? А ничего. Функция malloc выделает указанный размер памяти в байтах и возвращает указатель на его первый элемент. Поскольку она универсальная, то возвращает указатель типа void*, его нужно явно преобразовать в тип int*. Для того, что бы вычислить необходимое количество байт я использовал оператор sizeof, он возвращает размер переменной или типа. Функция free освобождает память, выделенную под указатель.

Примечание: sizeof вычисляется на этапе компиляции, и если ее применять в указателям(то есть пытаться вычислить динамическую память) она всегда будет возвращать размер указателя.

Важно: всегда освобождайте выделенную память! Думаю все слышали о такой вещи, как «утечка памяти»? Это и есть блоки памяти, которую не освободили. То есть, создался указатель, ему выделился блок памяти(назовем его блок1). Потом указателю выделили другой блок памяти(блок2). В результате этого, блок1 попрежнему воспринимается операционной системой как используемый, но доступа к нему уже нет(указателя нет, ничего на него не указывает). Освободится он лишь в случае закрытия программы или перезагрузки компьютера. Поэтому следуйте правилу: «Использовал указатель? — Освободи память! Присвой указателю NULL!».

Все очень запутанно, поэтому я попробую графически вам показать. Память состоит из бит. 8 бит = 1 байт. Это думаю все знают. Операционная система работает с памятью, как с блоками байт. Не будем заморачиваться размерами, а представим себе что int и int* занимают одинаковый размер блока в памяти.

Примечание: на всех компьютерах реализация размеров int разная. То есть для x86 — один размер, для x64 — другой.

Вот иллюстрация нашей памяти:

Создадим переменную, типа int:

Компьютер выделит память для нашей переменной, и мы получим что-то вроде:

Теперь создадим указатель:

Получим что-то вроде:

Теперь сделаем так:

Как вы уже догадались в указатель pointer1 помещается адрес переменной var1. То есть получается вот так:

Что же происходит с массивами? Объявим массив:

В результате компьютер выделит память размером в 3 блока. Указатель на первый элемент поместит в переменную array1. Абстрактно получится так:

Блоки памяти для элементов массива выделяются последовательно, указатель на первый элемент помещается в array1. По сути, когда вы пишите array1[2] компьютер это расценивает как «переместится на две позиции в памяти из указателя array1«. Вот вам пример:

В результате получим:

Получили тоже самое. Выходит, что с указателем и с массивом можно работать почти одинаково, но нужно не забывать про осторожность. Указатель можно сдвинуть через-чур и получить ошибки в программе. Например, если под указатель выделили 4 байта, а сдвинулись на 5(5 раз сделать ++%pointer_name%) то теперь указатель указывает на блок памяти, в котором может быть системная/важная информация, вовсе не принадлежащая нашей программе.Теперь о магических сложных строках в С. Строка в С — это массив символов заканчивающийся символом конца строки (‘\0’). Строки можно инициализировать явно:

В результате будет инициализирован массив символов размером в 11 символов(10 символы строки и символ ‘\0’).

Или например вот так:

Можно создать сначала массив, а потом заполнить его строкой. Например с помощью функции strcpy:

Строки в чистом С очень отличаются от строк в паскале. Например что бы их сравнить нужно использовать функцию strcmp, потому что выражение типа:

Будет давать неверный результат, ведь str1 и str2 — указатели на первый элемент массива, и сравнивается их равенство, но никак не строки! Это необходимо запомнить. Есть функция strcmp, возвращает ноль — если строки равны. Использовать вот так:

Поскольку strcmp вернет 0, то !0(не 0) будет true.

Теперь напишем функцию, которая будет добавлять символ в конец строки. Прототип нашей функции:

Принимает в качестве аргументов строку input, модификатор const который, означает, что в процессе выполнения функции, строка изменена не будет, и символ character. Теперь реализация:

Сначала мы выделяем память для «выходной» строки. Размер памяти задаем как strlen(input) + sizeof(char) * 2, то есть размер входящей строки плюс два символа: новый символ и признак конца строки ‘\n’.

Потом мы копируем в output саму input. Следующая операция заменяет символ ‘\0’ нашим символом, а следующий(пустой) — ‘\0’.

  • Переменная массива — это указатель на его первый элемент.
  • С указателем можно работать также как и с массивом, использую квадратные скобки. Предварительно нужно выделить ему память с помощью функции malloc.
  • Строки в С представляют собой массивы символов(char), длинна массива равна длине строки +1, в конец записывается признак конца строки, символ ‘\0’. Для их использования, необходимо создавать массивы или использовать указатели.
  • После того, как указатель стал не нужным, необходимо освободить выделенную ему память с помощью функции free, а самому указателю присвоить NULL. Это обезопасит ваш код!
  • Для работы со строками в С есть специальные функции. Например strcmp — сравнивает строки, в случае совпадения возвращает ноль.
  • Оператор sizeof возвращает размер переменной/типа в байтах. Он не умеет вычислять размер динамической памяти, поэтому не используйте с указателями!

Теперь немного о С++. Что бы выделить память указателю, достаточно написать:

Указателю будет выделена память размером в 40 символов. Для очистки:

Подробнее читайте в гугле, там есть особенности связанные с классами(ООП). Чистый С не имеет классов, перегруженных функций и прочих вкусностей С++.

Спрашивайте свои ответы. Пишите пожелания/просьбы. И не забывайте сообщать мне об орфографических ошибках :D!

📎📎📎📎📎📎📎📎📎📎