Указатели в языке с понятие указателя описание указателя в программе

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

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

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

Язык Си для начинающих / #8 — Указатели в Си

В этом разделе

  • Необработанные указатели
  • Константные и переменные указатели
  • Операторы new и delete
  • Смарт-указатели
  • Практическое руководство. Создание и использование экземпляров unique_ptr
  • Практическое руководство. Создание и использование экземпляров shared_ptr
  • Практическое руководство. Создание и использование экземпляров weak_ptr
  • Практическое руководство. Создание и использование экземпляров CComPtr и CComQIPtr

Источник: learn.microsoft.com

Указатели

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

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

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

тип_данных* название_указателя;

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

Например, определим указатель на объект типа int:

int *p;

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

int main(void) < int x = 10; // определяем переменную int *p; // определяем указатель p = // указатель получает адрес переменной return 0; >

Получение адреса данных

Указатель хранит адрес объекта в памяти компьютера. И для получения адреса к переменной применяется операция int x = 10; int *p; p = printf(«%p n», p); // 0060FEA8 return 0; >

В моем случае машинный адрес переменной x — 0x0060FEA8. (Для адресов в памяти применяется шестнадцатеричная система.) Но в каждом отдельном случае адрес может быть иным. Фактически адрес представляет целочисленное значение, выраженное в шестнадцатеричном формате.

Язык Си с нуля — Урок 26 — Указатель на указатель

То есть в памяти компьютера есть адрес 0x0060FEA8, по которому располагается переменная x.

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

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

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

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

Стоит отметить, что при выводе адреса указателя функция printf() ожидает, что указатель будет представлять void* , то есть указатель на значение типа void . Поэтому некоторые компиляторы при некоторых настройках могут при компиляции отображать предупреждения. И чтобы было все канонически правильно, то переданный указатель нужно преобразовать в указатель типа void * :

Читайте также:
Программа для настройки Яндекс Браузера

printf(«%p n», (void *)p);

Получение значения по адресу

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

#include int main(void)

Address = 0060FEA8 x = 10

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

int x = 10; int *p = int y = *p; // присваиваем переменной y значение по адресу из указателя p printf(«x = %d n», y); // 10

Здесь присваиваем переменной y значение по адресу из указателя p , то есть значение переменной x .

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

int x = 10; int *p = *p = 45; printf(«x = %d n», x); // 45

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

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

#include int main(void) < char c = ‘N’; int d = 10; short s = 2; char *pc = // получаем адрес переменной с типа char int *pd = // получаем адрес переменной d типа int short *ps = // получаем адрес переменной s типа short printf(«Variable c: address=%p t value=%c n», (void*) pc, *pc); printf(«Variable d: address=%p t value=%d n», (void*) pd, *pd); printf(«Variable s: address=%p t value=%hd n», (void*) ps, *ps); return 0; >

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

Variable c: address=0060FEA3 value=N Variable d: address=0060FE9C value=10 Variable s: address=0060FE9A value=2

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

Источник: metanit.com

Указатели

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

Указатели

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

  • Определение
  • Арифметика указателей
  • Указатель на указатель
  • Приведение типов указателей
  • NULL pointer — нулевой указатель
  • Примеры

Определение

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

Например
float *a;
long long *b;
Два основных оператора для работы с указателями – это оператор int A = 100; int *p; //Получаем адрес переменной A p = //Выводим адрес переменной A printf(«%pn», p); //Выводим содержимое переменной A printf(«%dn», *p); //Меняем содержимое переменной A *p = 200; printf(«%dn», A); printf(«%d», *p); getch(); >

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

int A = 100;

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

int *p;

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

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

*p = 200;

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

#include #include void main()

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

Читайте также:
Как нарисовать иконки программ

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

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

#include #include void main() < int A[10] = ; int *p; p = A; printf(«%dn», *p); p++; printf(«%dn», *p); p = p + 4; printf(«%dn», *p); getch(); >

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

p = A;

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

#include #include void main() < int A[10] = ; int *a, *b; a = b = printf(«, a); printf(«, b); if (a < b) < printf(«a < b»); >else < printf(«b < a»); >getch(); >

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

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

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

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

#include #include #define SIZE 10 void main() < int A; int B; int *p; int **pp; A = 10; B = 111; p = pp = printf(«A = %dn», A); *p = 20; printf(«A = %dn», A); *(*pp) = 30; //здесь скобки можно не писать printf(«A = %dn», A); *pp = printf(«B = %dn», *p); **pp = 333; printf(«B = %d», B); getch(); >

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

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

#include #include #define SIZE 10 void main()

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

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

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

Для решения этой проблемы был введён макрос NULL библиотеки stdlib.
Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.

int *ptr = NULL;

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

Читайте также:
Куда устанавливаются программы на mac

int *ptr = NULL; if (ptr == 0)

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

int a = 0; if (a == NULL)

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

#include #include #include void main() < int *a = NULL; unsigned length, i; printf(«Enter length of array: «); scanf(«%d», if (length >0) < //При выделении памяти возвращается указатель. //Если память не была выделена, то возвращается NULL if ((a = (int*) malloc(length * sizeof(int))) != NULL) < for (i = 0; i < length; i++) < a[i] = i * i; >> else < printf(«Error: can’t allocate memory»); >> //Если переменая была инициализирована, то очищаем её if (a != NULL) < free(a); >getch(); >

Примеры

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

#include #include void main() < int A[10] = ; int even[10]; int evenCounter = 0; int *iter, *end; //iter хранит адрес первого элемента массива //end хранит адрес следующего за последним «элемента» массива for (iter = A, end = iter < end; iter++) < if (*iter % 2 == 0) < even[evenCounter++] = *iter; >> //Выводим задом наперёд чётные числа for (—evenCounter; evenCounter >= 0; evenCounter—) < printf(«%d «, even[evenCounter]); >getch(); >

2. Когда мы сортируем элементы часто приходится их перемещать.

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

#include #include #define SIZE 10 void main() < double unsorted[SIZE] = ; double *p[SIZE]; double *tmp; char flag = 1; unsigned i; printf(«unsorted arrayn»); for (i = 0; i < SIZE; i++) < printf(«%.2f «, unsorted[i]); >printf(«n»); //Сохраняем в массив p адреса элементов for (i = 0; i < SIZE; i++) < p[i] = >do < flag = 0; for (i = 1; i> > while(flag); printf(«sorted array of pointersn»); for (i = 0; i < SIZE; i++) < printf(«%.2f «, *p[i]); >printf(«n»); printf(«make sure that unsorted array wasn’t modifiedn»); for (i = 0; i < SIZE; i++) < printf(«%.2f «, unsorted[i]); >getch(); >

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

#include #include #include void main() < int length; char *p1, *p2; char tmp; float a = 5.0f; float b = 3.0f; printf(«a = %.3fn», a); printf(«b = %.3fn», b); p1 = (char*) p2 = (char*) //Узнаём сколько байт перемещать length = sizeof(float); while (length—) < //Обмениваем местами содержимое переменных побайтно tmp = *p1; *p1 = *p2; *p2 = tmp; //не забываем перемещаться вперёд p1++; p2++; >printf(«a = %.3fn», a); printf(«b = %.3fn», b); getch(); >

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

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

#include #include void main() < char buffer[128]; char *p; unsigned length = 0; scanf(«%127s», buffer); p = buffer; while (*p != ‘’) < p++; length++; >printf(«length = %d», length); getch(); >

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

Источник: learnc.info

Рейтинг
( Пока оценок нет )
Загрузка ...
EFT-Soft.ru