Указатель это переменная которая содержит в качестве своего значения другой переменной
Перейти к содержимому

Указатель это переменная которая содержит в качестве своего значения другой переменной

  • автор:

C++: Указатели

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

Объявление указателя

Указатель — это переменная, которая в качестве значения хранит адрес памяти.

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

Синтаксически C++ принимает звездочку рядом с типом данных, рядом с именем переменной или даже в середине.

При объявлении переменной-указателя нужно ставить звездочку рядом с типом, чтобы его было легче отличить от косвенного обращения.

Как и обычные переменные, указатели не инициализируются при объявлении. Если они не инициализированы значением, они будут содержать мусор.

Указатель X (где X – какой-либо тип) — это обычно используемое сокращение для «указателя на X». Поэтому, когда мы говорим «указатель int», мы на самом деле имеем в виду «указатель на значение типа int».

Хорошей практикой считается инициализировать указатель значением.

Присвоение значения указателю

Поскольку указатели содержат только адреса, когда мы присваиваем значение указателю, это значение должно быть адресом. Одна из самых распространенных вещей, которые делают с указателями, – это хранение в них адреса другой переменной.

Чтобы получить адрес переменной, мы используем оператор адреса:

Эта программа создает следующий вывод:

ptr содержит адрес значения переменной, поэтому мы говорим, что ptr «указывает на» num .

Тип указателя должен соответствовать типу переменной, на которую он указывает:

Тип double не может указывать на адрес переменной типа int . Следующее также некорректно:

Это связано с тем, что указатели могут содержать только адреса, а целочисленный литерал 5 не имеет адреса памяти. Если попробовать сделать это, компилятор сообщит, что он не может преобразовать int в указатель int .

Вопрос на засыпку: Можно ли инициализировать указатель, явно указав адрес ячейки памяти?

Ответ — нет, компиляция этого кода завершится с ошибкой! Хотя казалось бы, почему, ведь оператор адреса & , так же возвращает адрес? Тут есть отличие — оператор & возвращает тоже указатель.

Возвращение указателя оператором адреса

Оператор адреса (&) не возвращает адрес своего операнда в виде литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого является производным от аргумента. Например, взятие адреса значения int вернет адрес в указателе int .

Мы можем увидеть это в следующем примере:

В Visual Studio этот код напечатал:

При компиляции gcc вместо этого выводит "pi" («pointer to int», указатель на int).

Одной из основных операций является получение значения переменной через указатель — косвенное обращение.

Косвенное обращение через указатели

У нас есть переменная-указатель, которая указывает на что-то. Значит, другая распространенная вещь, которую мы делаем с ней, — это косвенное обращение через указатель. Это нужно, чтобы получить значение того, на что он указывает.

Косвенное обращение через указатель вычисляет содержимое адреса, на который он указывает:

Эта программа создает следующий вывод:

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

После присваивания значению указателя можно присвоить другое значение:

Когда адрес переменной value присваивается указателю ptr , верно следующее:

  • ptr равен &value
  • *ptr обрабатывается так же, как value

Поскольку *ptr обрабатывается так же, как value , можно присваивать ему значения, как если бы это была переменная value .

Следующая программа напечатает 7:

Обратите внимание, через указатель мы можем работать с переменной value — получить значение, и даже изменить его.

Такой мощьный механизм имеет свои минусы.

Предупреждение о косвенном обращении через недействительные указатели

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

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

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

Следующая программа иллюстрирует это и вероятнее всего упадет с ошибкой:

Для хранения указателей так же как и для обычных приманных выделяется память.

Размер указателей

Размер указателя зависит от архитектуры, для которой скомпилирован исполняемый файл — 32-битный исполняемый файл использует 32-битные адреса памяти. Следовательно, указатель на 32-битной машине занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет 64-битным (8 байтов). Это независимо от размера объекта, на который он указывает:

Размер указателя всегда один и тот же. Это связано с тем, что указатель — это просто адрес памяти, а количество битов, необходимых для доступа к адресу памяти на данной машине, всегда постоянно.

Что хорошего в указателях:

  • Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву
  • Указатели в C++ — это единственный способ динамического выделения памяти
  • Их можно использовать для передачи функции в качестве параметра другой функци
  • Их можно использовать для достижения полиморфизма при работе с наследованием
  • Их можно использовать, чтобы иметь указатель на одну структуру/класс в другой структуре/классе, чтобы сформировать цепочку. Это полезно в некоторых более сложных структурах данных, таких как связанные списки и деревья

В этом уроке мы познакомились с указателями, узнали как их объявлять, как присваивать им значения и как безопасно работать с ними.

Задание

Поменяйте значения переменных first_num и second_num местами. Попробуйте это сделать с помощью уже созданных указателей.

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

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

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Это нормально ��, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.

Для чего нужен указатель в Си?

Elman Mamedov

Указатель в Си — это переменная, содержащая адрес другой переменной. Сложность указателей заключается в понимании где и для чего они могут пригодиться.

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

Указатель

В Си указателем называется переменная, содержащая адрес другой переменной. Его можно использовать с любым типом данных, написав:

Оператор « & » (амперсанд) определяет адрес переменной, а оператор « * » разыменования позволяет получить значение по адресу, указанным указателем. В примере выше адрес i присваивается к указателю ptr и получается, что ptr указывает на i .

Для чего нужен указатель в Си

Функции в Си принимают аргументы, передавая или копируя значения в стек функции. Такой метод иногда называется передачей по значению. Поскольку функции в Си и переменные, переданные им, в действительности не связываются, любые внесённые изменения в эти переменные не будут сохраняться за пределами действия функции. Это может вызвать сложности, потому что в некоторых функциях необходимо изменять текущие переменные. Здесь-то нам и пригодится указатель. С его помощью можно получить доступ к памяти, находящейся за пределами стекового кадра. Однако важно отметить, что с помощью указателя можно получить доступ лишь к переменным, расположенным ниже текущего кадра.

В примере выше простая функция с задачей — увеличить на единицу число, проходящее через параметры. Функция написана следующим образом:

Однако при запуске кода никаких изменений с переменной не происходит. Связанно это с тем, что в функцию increment (увеличения) копируется только значение переменной, и остальная часть программы не видит изменений, внесённых в эту переменную. Другими словами, переменная i находится внутри функции increment() , и несмотря на одинаковые названия, это не одна и та же переменная, что int i , расположенная за пределами этой функции.

Для того, чтобы решить эту проблему, нужно вместо самой переменной передать в функцию increment указатель этой переменной. Таким образом мы предоставим текущей функции доступ к переменной i , которая не попадает в область действия при выполнении этой функции. Выглядит она вот так:

Разница между указателем и ссылкой

«Указатель» и «ссылка» используются для указания или ссылки на другую переменную. Но основное различие между ними состоит в том, что переменная-указатель указывает на переменную, чья ячейка памяти хранится в ней. Ссылочная переменная является псевдонимом для переменной, которая ей назначена. В приведенной ниже сравнительной таблице рассматриваются другие различия между указателем и ссылкой.

Сравнительная таблица

Основа для сравнения Указатель Ссылка
основной Указатель является адресом памяти переменной. Ссылка является псевдонимом для переменной.
Возвращает Переменная указателя возвращает значение, расположенное по адресу, хранящемуся в переменной указателя, которому предшествует знак указателя '*'. Ссылочная переменная возвращает адрес переменной, перед которой стоит ссылочный знак '&'.
операторы *, -> &
Нулевая ссылка Переменная-указатель может ссылаться на NULL. Ссылочная переменная никогда не может ссылаться на NULL.
инициализация Неинициализированный указатель может быть создан. Неинициализированная ссылка никогда не может быть создана.
Время инициализации Переменная-указатель может быть инициализирована в любой момент времени в программе. Ссылочная переменная может быть инициализирована только во время ее создания.
переинициализация Переменная-указатель может быть повторно инициализирована столько раз, сколько требуется. Переменная-ссылка никогда не может быть повторно инициализирована в программе.

Определение указателя

«Указатель» — это переменная, которая содержит место в памяти другой переменной. Операторы, используемые переменной-указателем — это * и ->. Объявление переменной указателя содержит базовый тип данных, за которым следует знак «*» и имя переменной.

Давайте разберем указатель с помощью примера.

Давайте изучим это на примере.

Здесь типом является тип данных, а оператор & подтверждает, что это ссылочная переменная. Refer_var_name — это имя ссылочной переменной. Var_name — это имя переменной, на которую мы хотим ссылочную переменную ссылаться.

Давайте разберем справочную переменную с помощью примера.

Здесь переменной типа int присваивается значение 4. Ссылочной переменной присваивается переменная a, то есть b является псевдонимом a. Теперь, когда мы присваиваем другое значение b, мы модифицируем значение a. Следовательно, можно сказать, что изменения, внесенные в эталонную переменную, также будут происходить в переменной, на которую ссылается эта эталонная переменная.

Наиболее важным моментом является то, что ссылочная переменная должна быть инициализирована во время ее создания. Как только ссылочная переменная инициализируется с помощью переменной, ее нельзя повторно инициализировать для ссылки на другую переменную. В тот момент, когда вы присваиваете значение ссылочной переменной, вы присваиваете это значение переменной, на которую указывает ссылочная переменная. Ссылочная переменная никогда не может ссылаться на NULL. Арифметика не может быть выполнена для ссылочной переменной.

Ссылочная переменная может использоваться тремя способами:

  • В качестве возвращаемого значения функции.
  • В качестве параметра функции.
  • Как отдельная ссылка.

Ключевые различия между указателем и ссылкой

  1. Ссылка похожа на создание другого имени для ссылки на переменную, чтобы на нее можно было ссылаться под разными именами. С другой стороны, указатель — это просто адрес памяти переменной.
  2. Переменная-указатель, если ей предшествует '*', возвращает значение переменной, адрес которой хранится в указателе varaible. Ссылочная переменная, когда ей предшествует '&', возвращает адрес этой переменной.
  3. Операторы указателя * и ->, тогда как оператор ссылки &.
  4. Переменная-указатель if не содержит адреса какой-либо переменной и указывает на ноль. С другой стороны, ссылочная переменная никогда не может ссылаться на Null.
  5. Вы всегда можете создать унитизированную переменную указателя, но мы создаем ссылку, когда нам нужен псевдоним некоторой переменной, чтобы вы никогда не могли создать унитализованную ссылку.
  6. Вы можете повторно инициализировать указатель, но после инициализации arefernce вы не сможете повторно инициализировать его снова.
  7. Вы можете создать пустой указатель и инициализировать его в любое время, но вы должны инициализировать ссылку только при создании ссылки.

Замечания:

Java не поддерживает указатели.

Заключение

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

10.8 – Знакомство с указателями

В уроке «1.3 – Знакомство с переменными в C++», мы отметили, что переменная – это имя части памяти, которая содержит значение. Когда наша программа создает экземпляр переменной, ей автоматически присваивается адрес свободной памяти, и любое значение, которое мы присваиваем переменной, сохраняется в памяти с этим адресом.

Когда эта инструкция выполняется процессором, будет выделена часть памяти из ОЗУ. В качестве примера предположим, что переменной x присвоена ячейка памяти 140. Всякий раз, когда программа видит переменную x в выражении или инструкции, она знает, что она должна искать значение в ячейке памяти 140.

Что хорошо в переменных, так это то, что нам не нужно беспокоиться о том, какой конкретный адрес памяти назначен. Мы просто ссылаемся на переменную по ее заданному идентификатору, и компилятор переводит это имя в соответствующий адрес памяти.

Однако у этого подхода есть некоторые ограничения, которые мы обсудим в этом и будущих уроках.

Оператор адреса ( & )

Оператор адреса ( & ) позволяет нам увидеть, какой адрес памяти назначен переменной. Это довольно просто:

На машине автора показанная выше программа напечатала:

Примечание. Хотя оператор адреса выглядит так же, как оператор побитового И, их можно различить, поскольку оператор адреса является унарным, тогда как оператор побитового И является бинарным.

Оператор косвенного обращения ( * )

Получение адреса переменной само по себе не очень полезно.

Оператор косвенного обращения ( * ) (также называемый оператором разыменования) позволяет нам получить доступ к значению по определенному адресу:

На машине автора показанная выше программа напечатала:

Примечание. Хотя оператор косвенного обращения выглядит так же, как оператор умножения, вы можете различить их, поскольку оператор косвенного обращения является унарным, а оператор умножения – бинарным.

Указатели

Теперь, когда в наши инструменты добавлены оператор адреса и оператор косвенного обращения, мы можем поговорить об указателях. Указатель – это переменная, которая в качестве значения хранит адрес памяти.

Указатели обычно считаются одной из самых запутанных частей языка C++, но при правильном объяснении они удивительно просты.

Объявление указателя

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

Синтаксически C++ принимает звездочку рядом с типом данных, рядом с именем переменной или даже в середине.

Лучшая практика

При объявлении переменной-указателя ставьте звездочку рядом с типом, чтобы его было легче отличить от косвенного обращения.

Как и обычные переменные, указатели не инициализируются при объявлении. Если они не инициализированы значением, они будут содержать мусор.

Одно замечание по номенклатуре указателей: «указатель X» (где X – какой-либо тип) – это обычно используемое сокращение для «указателя на X». Поэтому, когда мы говорим «указатель int », мы на самом деле имеем в виду «указатель на значение типа int ».

Присвоение значения указателю

Поскольку указатели содержат только адреса, когда мы присваиваем значение указателю, это значение должно быть адресом. Одна из самых распространенных вещей, которые делают с указателями, – это хранение в них адреса другой переменной.

Чтобы получить адрес переменной, мы используем оператор адреса:

Концептуально вы можете представить приведенный выше фрагмент так:

Рисунок 1 – Значение, хранимое указателем Рисунок 1 – Значение, хранимое указателем

Отсюда указатели и получили свое название – ptr (от англ. «pointer», «указатель») содержит адрес значения переменной, поэтому мы говорим, что ptr «указывает на» v .

Это также легко увидеть с помощью кода:

На машине автора эта программа напечатала:

Тип указателя должен соответствовать типу переменной, на которую он указывает:

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

Это связано с тем, что указатели могут содержать только адреса, а целочисленный литерал 5 не имеет адреса памяти. Если вы попробуете это сделать, компилятор сообщит вам, что он не может преобразовать int в указатель int .

C++ также не позволит вам напрямую преобразовать литеральные адреса памяти в указатель:

Оператор адреса возвращает указатель

Стоит отметить, что оператор адреса ( & ) не возвращает адрес своего операнда в виде литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого является производным от аргумента (например, взятие адреса значения int вернет адрес в указателе int ).

Мы можем увидеть это в следующем примере:

В Visual Studio 2013 этот код напечатал:

gcc вместо этого выводит "pi" («pointer to int », указатель на int ).

Затем этот указатель по желанию можно распечатать в консоль или присвоить.

Косвенное обращение через указатели

Когда у нас есть переменная-указатель, указывающая на что-то, другая распространенная вещь, которую мы делаем с ней, – это косвенное обращение через указатель для получения значения того, на что он указывает. Косвенное обращение через указатель вычисляет содержимое адреса, на который он указывает.

На машине автора этот код напечатал:

Вот почему указатели должны иметь тип. Без типа, при косвенном обращении через указатель, указатель не знал бы, как интерпретировать содержимое, на которое он указывает. По этой же причине тип указателя и тип переменной, адрес которой ему присваивается, должны совпадать. Если бы это было не так, косвенное обращение через указатель неверно интерпретировало бы биты как другой тип.

После присваивания значению указателя можно присвоить другое значение:

Когда адрес переменной value присваивается указателю ptr , верно следующее:

  • ptr равен &value
  • *ptr обрабатывается так же, как value

Поскольку *ptr обрабатывается так же, как value , вы можете присваивать ему значения, как если бы это была переменная value ! Следующая программа напечатает 7:

Предупреждение о косвенном обращении через недействительные указатели

Указатели в C++ по своей сути небезопасны, и неправильное использование указателей – один из лучших способов вывести ваше приложение из строя.

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

Следующая программа иллюстрирует это и, вероятно, выйдет со сбоем, когда вы запустите ее (попробуйте, вы не навредите своей машине):

Размер указателей

Размер указателя зависит от архитектуры, для которой скомпилирован исполняемый файл – 32-битный исполняемый файл использует 32-битные адреса памяти – следовательно, указатель на 32-битной машине занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет 64-битным (8 байтов). Обратите внимание, что это верно независимо от размера объекта, на который он указывает:

Как видите, размер указателя всегда один и тот же. Это связано с тем, что указатель – это просто адрес памяти, а количество битов, необходимых для доступа к адресу памяти на данной машине, всегда постоянно.

Что хорошего в указателях?

На этом этапе указатели могут показаться немного глупыми, чисто теоретическими или бесполезными. Зачем использовать указатель, если мы можем просто использовать исходную переменную?

Оказывается, указатели полезны во многих разных случаях:

  1. Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву (как альтернатива индексам массива) (рассматривается в уроке «10.24 – Введение в итераторы»).
  2. В C++ это единственный способ динамического выделения памяти (рассматривается в уроке «10.13 – Динамическое выделение памяти с помощью new и delete »). Это, безусловно, наиболее распространенный вариант использования указателей.
  3. Их можно использовать для передачи функции в качестве параметра другой функции (рассматривается в уроке «11.9 – Указатели на функции»).
  4. Их можно использовать для достижения полиморфизма при работе с наследованием (рассматривается в уроке «18.1 – Указатели и ссылки базового класса на объекты производных классов»).
  5. Их можно использовать, чтобы иметь указатель на одну структуру/класс в другой структуре/классе, чтобы сформировать цепочку. Это полезно в некоторых более сложных структурах данных, таких как связанные списки и деревья.

Так что на самом деле существует удивительное количество применений указателей. Но не волнуйтесь, если вы еще не понимаете что-то из этого. Теперь, когда вы на базовом уровне понимаете, что такое указатели, мы можем начать подробно рассматривать различные случаи, в которых они могут быть полезны; что мы и сделаем в следующих уроках.

Заключение

Указатели – это переменные, которые содержат адреса памяти. К значениям, на которые они указывают, можно получить доступ с помощью оператора косвенного обращения ( * ). Косвенное обращение через мусорный указатель вызывает неопределенное поведение.

Небольшой тест

Вопрос 1

Какие значения выводит эта программа? Предположим, что short занимает 2 байта, а машина 32-битная.

Краткое объяснение про 4 и 2. 32-битная машина означает, что указатели будут иметь длину 32 бита, но sizeof() всегда выводит размер в байтах. 32 бита – это 4 байта. Таким образом, sizeof(ptr) равен 4. Поскольку ptr является указателем на short , *ptr – это short . Размер short в этом примере составляет 2 байта. Таким образом, sizeof(*ptr) равен 2.

Вопрос 2

Что не так с этим фрагментом кода?

Последняя строка приведенного выше фрагмента не компилируется.

Разберем эту программу подробнее.

Первая строка содержит определение обычной переменной вместе со значением инициализации. Здесь ничего особенного.

Во второй строке мы определяем новый указатель с именем ptr и сохраняем в нем адрес value . Помните, что в этом контексте звездочка является частью синтаксиса объявления указателя, а не косвенным обращением через указатель. Так что с этой строкой всё в порядке.

В третьей строке звездочка представляет собой косвенное обращение, которое используется для получения значения, на которое указывает указатель. Итак, эта строка говорит: «Получить значение, на которое указывает ptr (целочисленное значение типа int ), и перезаписать его адресом value . В этом нет никакого смысла – вы не можете присвоить адрес значению типа int !

Третья строка должна быть:

Это правильно присваивает указателю адрес переменной value .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *