что такое ptr в программировании

Указатели

Что такое указатели

Указатели представляют собой объекты, значением которых служат адреса других объектов (переменных, констант, указателей) или функций. Как и ссылки, указатели применяются для косвенного доступа к объекту. Однако в отличие от ссылок указатели обладают большими возможностями.

Для определения указателя надо указать тип объекта, на который указывает указатель, и символ звездочки *. Например, определим указатель на объект типа int:

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

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

Консольный вывод программы:

И указатель p будет ссылаться на адрес, по которому располагается переменная x, то есть на адрес 0x60FE98.

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

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

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

Так как по адресу, на который указывает указатель, располагается переменная x, то соответственно ее значение изменится.

Создадим еще несколько указателей:

В моем случае я получу следующий консольный вывод:

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

Источник

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

Так как указатель содержит адрес объекта, это дает возможность «косвенного» доступа к этому объекту через указатель.

Указатели и массивы

приводит к тому, что pa указывает на нулевой элемент массива a. Это означает, что pa содержит адрес элемента a[0]. Теперь присваивание

будет копировать содержимое a[0] в x.

Если ра указывает на некоторый определенный элемент массива a, то по определению pa+1 указывает на следующий элемент, и вообще pa-i указывает на элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i позиций после. Таким образом, если pa указывает на a[0], то *(pa+1)

Эти замечания справедливы независимо от типа переменных в массиве a. Суть определения «добавления 1 к указателю», а также его распространения на всю арифметику указателей, состоит в том, что приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель. Таким образом, i в pa+i перед прибавлением умножается на размер объектов, на которые указывает pa.

Очевидно существует очень тесное соответствие между индексацией и арифметикой указателей. В действительности компилятор преобразует ссылку на массив в указатель на начало массива. В результате этого имя массива является указательным выражением. Отсюда вытекает несколько весьма полезных следствий. Так как имя массива является синонимом местоположения его нулевого элемента, то присваивание pa = &a[0]

можно записать как pa = a.

Когда имя массива передается функции, то на самом деле ей передается местоположение начала этого массива. Внутри вызванной функции такой аргумент является точно такой же переменной, как и любая другая, так что имя массива в качестве аргумента действительно является указателем, т.е. переменной, содержащей адрес. Мы можем использовать это обстоятельство для написания нового варианта функции strlen, вычисляющей длину строки: /* возвращает длину строки s */

Операция увеличения s совершенно законна, поскольку эта переменная является указателем, s++ никак не влияет на символьную строку в обратившейся к strlen функции, а только увеличивает локальную для функции strlen копию адреса.

Описания формальных параметров в определении функции в виде char s[];

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

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

Как описывать ссылки (указатели) на двумерные массивы?

Как описывать ссылки (указатели) на двумерные массивы? Рассмотрим такую программу:

Указателем здесь является ptr. Отметим, что у него задана размерность по второму измерению: Second, именно для того, чтобы компилятор мог правильно вычислить двумерные индексы.

Попробуйте сами объявить

и увидеть, к каким невеселым эффектам это приведет (компилятор, кстати, будет ругаться; но есть вероятность, что он все же странслирует это для вас. Но работать оно будет плачевно). Попробуйте также использовать ptr[x][y].

Обратите также внимание на инициализацию строк в нашем примере. Строка «ABC.» равносильна объявлению

Указатели на функции

Например, используется в функции: Быстрая сортировка (англ. quicksort), часто называемая qsort. В функции qsort указатель на функцию применяется для указания способа сортировки.

Прежде чем вводить указатель на функцию, напомним, что каждая функция характеризуется типом возвращаемого значения, именем и сигнатурой. Напомним, что сигнатура определяется количеством, порядком следования и типами параметров. Иногда говорят, что сигнатурой функции называется список типов ее параметров.

А теперь путём последовательности утверждений придем к обсуждению темы данного раздела урока.

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

2. Это значение адреса может быть присвоено некоторому указателю, и затем уже этот новый указатель можно применять для вызова функции.

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

4. Указатель на функцию определяется следующим образом:

Иллюстрируем на практике. В определении указателя на функцию тип возвращаемого значения и сигнатура (типы, количество и последовательность параметров) должны совпадать с соответствующими типами и сигнатурами тех функций, адреса которых предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания. В качестве простейшей иллюстрации сказанного приведем программу с указателем на функцию:

Результат выполнения программы:

Здесь значением имени_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr();. Дело в том, что операция () имеет более высокий приоритет, нежели операция обращения по адресу *. Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.

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

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

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

Результаты выполнения программы:

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

Как обычно, индексация массива начинается с 0, и поэтому третий элемент массива имеет индекс 2.

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

Источник

Smart pointers для начинающих

Эта небольшая статья в первую очередь предназначена для начинающих C++ программистов, которые либо слышали об умных указателях, но боялись их применять, либо они устали следить за new-delete.

UPD: Статья писалась, когда C++11 еще не был так популярен.

Введение

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

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

В принципе, никакой разницы, но представим себе такой код:

Придется в каждой ветке выхода из функции писать delete [], либо вызывать какие-либо дополнительные функции деинициализации. А если выделений памяти много, либо они происходят в разных частях функции? Уследить за всем этим будет все сложнее и сложнее. Подобная ситуация возникает, если мы в середине функции бросаем исключение: гарантируется, что объекты на стеке будут уничтожены, но с кучей проблема остается открытой.
Ок, будем использовать RAII, в конструкторах инициализировать память, в деструкторе освобождать. И пусть поля нашего класса будут указателями на участки динамической памяти:

Простейший smart pointer

boost::scoped_ptr

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

Оно и понятно, если бы было разрешено присваивание, то и p1 и p2 будут указывать на одну и ту же область памяти. А по выходу из функции оба удалятся. Что будет? Никто не знает. Соответственно, этот пойнтер нельзя передавать и в функции.
Тогда зачем он нужен? Советую применять его как указатель-обертка для каких-либо данных, которые выделяются динамически в начале функции и удаляются в конце, чтобы избавить себя от головной боли по поводу корректной очистки ресурсов.
Подробное описание здесь.

std::auto_ptr

Чуть-чуть улучшенный вариант предыдущего, к тому же он есть в стандартной библиотеке (хотя в C++11 вроде как deprecated). У него есть оператор присваивания и конструктор-копировщик, но работают они несколько необычно.
Поясняю:

Теперь при присваивании в p2 будет лежать указатель на MyObject (который мы создавали для p1), а в p1 не будет ничего. То есть p1 теперь обнулен. Это так называемая семантика перемещения. Кстати, оператор копирования поступает таким же образом.
Зачем это нужно? Ну например у вас есть функция, которая должна создавать какой-то объект:

Это означает, что функция создает новый объект типа MyObject и отдает его вам в распоряжение. Понятней станет, если эта функция сама является членом класса (допустим Factory): вы уверены, что этот класс (Factory) не хранит в себе еще один указатель на новый объект. Объект ваш и указатель на него один.
В силу такой необычной семантики auto_ptr нельзя использовать в контейнерах STL. Но у нас есть shared_ptr.

std::shared_ptr (С++11)

Умный указатель с подсчетом ссылок. Что это значит. Это значит, что где-то есть некая переменная, которая хранит количество указателей, которые ссылаются на объект. Если эта переменная становится равной нулю, то объект уничтожается. Счетчик инкрементируется при каждом вызове либо оператора копирования либо оператора присваивания. Так же у shared_ptr есть оператор приведения к bool, что в итоге дает нам привычный синтаксис указателей, не заботясь об освобождении памяти.

Теперь и p2 и p1 указывают на один объект, а счетчик ссылок равен 2, По выходу из скоупа счетчик обнуляется, и объект уничтожается. Мы можем передавать этот указатель в функцию:

Заметьте, если вы передаете указатель по ссылке, то счетчик не будет увеличен. Вы должны быть уверены, что объект MyObject будет жив, пока будет выполняться функция test.

Итак, smart pointers это хорошо, но есть и минусы.
Во-первых это небольшой оверхед, но я думаю у вас найдется несколько тактов процессора ради такого удобства.
Во-вторых это boiler-plate, например

Это частично можно решить при помощи дефайнов, допустим:

Либо при помощи typedef.
В-третьих, существует проблема циклических ссылок. Рассматривать ее здесь не буду, чтобы не увеличивать статью. Так же остались нерассмотренными boost::weak_ptr, boost::intrusive_ptr и указатели для массивов.
Кстати, smart pointers достаточно хорошо описаны у Джеффа Элджера в книге «С++ for real programmers».

Источник

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Указатели

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Заключение

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

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

Вопрос 1

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

Вопрос 2

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

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

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

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

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

Источник

Понравилась статья? Поделиться с друзьями:

Не пропустите наши новые статьи:

  • Что такое provider в программировании
  • что такое program в автозагрузке windows 10
  • что такое program manager в windows 10
  • что такое production в программировании
  • что такое print 3d в windows 10

  • Операционные системы и программное обеспечение
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии