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

Обновлено: 18.05.2024

Теги: Си указатели. Указатель на указатель. Тип указателя. Арифметика указателей. Сравнение указателей.

Указатели

Э то, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – очень простая концепция, очень логичная, но требующая внимания к деталям.

Определение

У казатель – это переменная, которая хранит адрес области памяти. Указатель, как и переменная, имеет тип. Синтаксис объявления указателей

Например
float *a;
long long *b;
Два основных оператора для работы с указателями – это оператор & взятия адреса, и оператор * разыменования. Рассмотрим простой пример.

Рассмотрим код внимательно, ещё раз

Была объявлена переменная с именем A. Она располагается по какому-то адресу в памяти. По этому адресу хранится значение 100.

Создали указатель типа int.

Теперь переменная p хранит адрес переменной A. Используя оператор * мы получаем доступ до содержимого переменной A.
Чтобы изменить содержимое, пишем

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

Будет выведено
4
4
8
4
Несмотря на то, что переменные имеют разный тип и размер, указатели на них имеют один размер. Действительно, если указатели хранят адреса, то они должны быть целочисленного типа. Так и есть, указатель сам по себе хранится в переменной типа size_t (а также ptrdiff_t), это тип, который ведёт себя как целочисленный, однако его размер зависит от разрядности системы. В большинстве случаев разницы между ними нет. Зачем тогда указателю нужен тип?

Арифметика указателей

В о-первых, указателю нужен тип для того, чтобы корректно работала операция разыменования (получения содержимого по адресу). Если указатель хранит адрес переменной, необходимо знать, сколько байт нужно взять, начиная от этого адреса, чтобы получить всю переменную.
Во-вторых, указатели поддерживают арифметические операции. Для их выполнения необходимо знать размер.
операция + N сдвигает указатель вперёд на N*sizeof(тип) байт.
Например, если указатель int *p; хранит адрес CC02, то после p += 10; он будет хранить адрес СС02 + sizeof(int)*10 = CC02 + 28 = CC2A (Все операции выполняются в шестнадцатиричном формате). Пусть мы создали указатель на начало массива. После этого мы можем "двигаться" по этому массиву, получая доступ до отдельных элементов.

Заметьте, каким образом мы получили адрес первого элемента массива

Массив, по сути, сам является указателем, поэтому не нужно использовать оператор &. Мы можем переписать пример по-другому

Получить адрес первого элемента и относительно него двигаться по массиву.
Кроме операторов + и - указатели поддерживают операции сравнения. Если у нас есть два указателя a и b, то a > b, если адрес, который хранит a, больше адреса, который хранит b.

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

Указатель на указатель

У казатель хранит адрес области памяти. Можно создать указатель на указатель, тогда он будет хранить адрес указателя и сможет обращаться к его содержимому. Указатель на указатель определяется как

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

Указатели и приведение типов

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

В этом примере мы пользуемся тем, что размер типа int равен 4 байта, а char 1 байт. За счёт этого, получив адрес первого байта, можно пройти по остальным байтам числа и вывести их содержимое.

NULL pointer - нулевой указатель

У казатель до инициализации хранит мусор, как и любая другая переменная. Но в то же время, этот "мусор" вполне может оказаться валидным адресом. Пусть, к примеру, у нас есть указатель. Каким образом узнать, инициализирован он или нет? В общем случае никак. Для решения этой проблемы был введён макрос NULL библиотеки stdlib.
Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.

По стандарту гарантировано, что в этом случае указатель равен NULL, и равен нулю, и может быть использован как булево значение false. Хотя в зависимости от реализации NULL может и не быть равным 0 (в смысле, не равен нулю в побитовом представлении, как например, int или float).
Это значит, что в данном случае

вполне корректная операция, а в случае

поведение не определено. То есть указатель можно сравнивать с нулём, или с NULL, но нельзя NULL сравнивать с переменной целого типа или типа с плавающей точкой.

Примеры

Теперь несколько примеров работы с указателями
1. Пройдём по массиву и найдём все чётные элементы.

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

3. Более интересный пример. Так как размер типа char всегда равен 1 байт, то с его помощью можно реализовать операцию swap – обмена местами содержимого двух переменных.

В этом примере можно поменять тип переменных a и b на double или любой другой (с соответствующим изменением вывода и вызова sizeof), всё равно мы будет обменивать местами байты двух переменных.

4. Найдём длину строки, введённой пользователем, используя указатель

Обратите внимание на участок кода

его можно переписать

или, убрав инкремент в условие

email

Всё ещё не понятно? – пиши вопросы на ящик

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

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

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

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

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

Какой именно адрес имеет переменная x? Для вывода значения указателя можно использовать специальный спецификатор %p :

В моем случае машинный адрес переменной x - 0060FEA8. Но в каждом отдельном случае адрес может быть иным. Фактически адрес представляет целочисленное значение, выраженное в шестнадцатеричном формате.

То есть в памяти компьютера есть адрес 0x0060FEA8, по которому располагается переменная x. Так как переменная x представляет тип int , то на большинстве архитектур она будет занимать следующие 4 байта (на конкретных архитектурах размер памяти для типа int может отличаться). Таким образом, переменная типа int последовательно займет ячейки памяти с адресами 0x0060FEA8, 0x0060FEA9, 0x0060FEAA, 0x0060FEAB.

Указатели в Си

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

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

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

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

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

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

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

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

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

  • чтобы выделить новые объекты в куче,
  • передача функций в другие функции
  • для итерации элементов в массивах или других структурах данных.

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


Всем привет! В этом уроке мы разберём то, что очень тесно связано с памятью в компьютере. С помощью этого можно улучшать работу своей программы. Как вы догадались с названия урока, это — указатели.

Адрес переменной в C++

Поголовно у каждой переменной имеется свой индивидуальный адрес. Адрес переменной — это путь, по которому находится значение самой переменной. Он записывается в шестнадцатеричном виде. Так, компьютер может записать переменную, как в такой адрес 0x155, так и в такой 0x212 .

Давайте приведем аналогию с круизным лайнером. В нем, как и в отеле, имеются номера. Вот, например, при покупке номера вам могут дать номер — 0x155 (да, мы понимаем, что не в одном лайнере или отеле не станут записывать номера в шестнадцатеричном виде, но давайте все таки немного отвлечемся). А друг может оказаться в номере 0x212 — так и с переменными, они могут получить разный путь. И только сам компьютер при создании переменной знает, где она находится.

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

Пример удаления переменных

В играх присутствует хорошая графика, различные спецэффекты. Например, тот же дым. Все это — переменная (может не одна!), которой в будущем придётся уничтожиться навсегда. А вот, если бы она не удалилась, то она бы своей фоновой работой понемножку нагружала бы наш компьютер.

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

Что такое указатели в C++

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

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

  • * — показывает значение переменной по заданному адресу (показывает, кто живет в этом номере). Если вы используете оператор * , то вы занимаетесь операцией разыменование указателя.
  • & — показывает адрес переменной (говорит, по какому адресу проживает этот человек).

Как создать указатели в C++

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

Читайте также: