Перейти к содержимому

Какой тип имеет разыменованный указатель

  • автор:

Какой тип имеет разыменованный указатель

Указатели поддерживают ряд операций: присваивание, получение адреса указателя, получение значения по указателю, некоторые арифметические операции и операции сравнения.

Присваивание адреса

Указателю можно присвоить адрес объекта того же типа, либо значение другого указателя. Для получения адреса объекта используется операция & :

int a ; int *pa ; // указатель pa хранит адрес переменной a

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

Разыменование указателя

Операция разыменования указателя представляет выражение в виде *имя_указателя . Эта операция позволяет получить объект по адресу, который хранится в указателе.

#include int main() < int a ; int *pa ; // хранит адрес переменной a std::cout #include int main() < int a ; int b ; int *pa ; // указатель на переменную a int *pb ; // указатель на переменную b std::cout pa: address=0x56347ffc5c value=10 pb: address=0x56347ffc58 value=2 pa: address=0x56347ffc58 value=2 b value=125

Нулевые указатели

Нулевой указатель (null pointer) - это указатель, который не указывает ни на какой объект. Если мы не хотим, чтобы указатель указывал на какой-то конкретный адрес, то можно присвоить ему условное нулевое значение. Для определения нулевого указателя можно инициализировать указатель нулем или константой nullptr :

int *p1; int *p2<>;

Ссылки на указатели

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

#include int main() < int a ; int b ; int *p<>; // указатель int *&pRef

; // ссылка на указатель pRef = &a; // через ссылку указателю p присваивается адрес переменной a std::cout &:

int a ; int *pa ; std::cout >, >=, , ,==, !=. Операции сравнения применяются только к указателям одного типа. Для сравнения используются номера адресов:

#include int main() < int a ; int b ; int *pa ; int *pb ; if(pa > pb) std::cout

Консольный вывод в моем случае:

pa (0xa9da5ffdac) is greater than pb (0xa9da5ffda8)

Приведение типов

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

#include int main() < char c ; char *pc ; // указатель на символ int *pd <(int *)pc>; // указатель на int void *pv ; // указатель на void std::cout std::cout

Указатели. Типизированные и нетипизированные.

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

Указатель является одной из наиболее важных концепций языка C.

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

· указатели являются средством, при помощи которого функции могут изменять значения передаваемых в нее аргументов;

· при помощи указателей выполняется динамическое распределение памяти;

· указатели позволяют повысить эффективность программирования;

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

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

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

Общая форма объявления указателя:

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

Фактически указатель любого типа может ссылаться на любое место в памяти, но выполняемые над указателем операции существенно зависят от его типа. Так, если объявлен указатель типа int *, компилятор предполагает, что любой адрес, на который он ссылается, содержит переменную типа int, хотя это может быть и не так. Следовательно, объявляя указатель, необходимо убедиться в том, что его тип совместим с типом объекта, на который он будет ссылаться.

char *c; // указатель на char

int *i, j; // указатель на int

i=&j; // i присвоить адрес j

*i=1; // разыменованный указатель j=1

* - операция косвенной адресации

& - операция получения адреса

void *p; // нетипизированный указатель

float *pf, f; // типизированный указатель

pf=&f; // pf присвоить адрес f

p=pf; // одному указателю присвоить значение другого

pf=(float *) p; // явное указание типа при разыменовании

Пример: вычисление суммы квадратов первых 100 натуральных чисел:

*sum = *sum + (*ptr)*(*ptr);

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

s: ^array[1..10] of byte;

Кроме того, существуют универсальные нетипизированные указатели, которые могут хранить адрес переменной любого типа:

Указатели в C++ для начинающих.

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

В C++ указатель объявляется с помощью звездочки

int * x ; //переменная x есть указатель на int;

так как указатель хранит адрес памяти, то попытка обращения к указателю вернет этот адрес
cout //На экран выводится какой-то адрес памяти

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

cout /звездочка – это оператор разыменования
* x = 100 ; / звездочка – это оператор разыменования
/В этом примере сначала на экран выведется какое-то любое значение, после чего по адресу памяти на который указывает x будет записано число 100. Так как в обоих случаях было применено разыменование, то будут выведены именно значения, а не адреса памяти.

Код C++ Указатель + Разыменованный указатель
==================

int x = 20 ; //Переменная x = 20
int * ptr ; // ptr есть указатель на int

void main ()

clrscr ();
//Выводим на экран различные значения
cout //Указатель ptr = Адрес памяти
cout <<“ *ptr” <* ptr //Разыменованный указатель ptr = Значение по адресу
getch ();
return;
>
==================
Будьте внимательны в приведенном коде, где была операция разыменования, стоит звездочка. В первый раз было обращение непосредственно к указателю, а так как указатель есть переменная, хранящая адрес памяти, то именно с этим адресом памяти и работает программа при прямом обращении к указателю, поэтому и был выведен некоторый адрес памяти вместо значения. Во второй раз при обращении к указателю была поставлена звездочка. Такое обращение к указателю называется разыменование и указатель обращается к объекту на который ссылается. На экран было выведено значение, а не адрес памяти

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

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

В C++ у любой переменной, в том числе и не указателя можно узнать адрес памяти, по которому она расположена. Делается это с помощью оператора &

Код C++ Взять адрес у переменной. Ссылка
=============================

Так вот, после объявления указатель не связан с конкретным значением и указывает пальцем в небо. Для того, чтоб его связать с каким-то объектом используется оператор &. Вы уже знаете, что оператор & обозначает взятие памяти у переменной. А так как указатель есть адрес памяти , то и работает он с адресами памяти .
Следовательно, чтобы связать указатель с каким-то значением, нужно обратиться к адресу нужного значения . Надеюсь логика проста и понятна.

void main()

clrscr ();
int * ptr ; // ptr есть указатель на int
int a = 100 ; // a является обычной переменной

cout //Чтобы получить значение ptr – разыменовали указатель ptr
//так как указатель не был связан ни с какими значениями, можно получить что угодно
ptr =& a ; // Взяли адрес у переменной a и присвоили этот адрес в указатель
cout “* ptr = “ //Указатель теперь указывает на a . Значит *ptr = a = 100

getch ();
return;
>
=============================

Очень важный момент
Если указатель указывает на некоторый один адрес памяти, то и работает он со значением из этого адреса.
а) Изменяя значение переменной по адресу на который указатель указывает – изменится и значение разыменовываемого указателя
б) При присвоении значений разыменованному указателю – изменится значение переменной по указываемому указателем адресу
Код C++ двойственность природы указателей
================================

void main ()

clrscr ();
int a , b ; //Разные переменные
int * ptr ; // ptr есть указатель на int

//Вариант а. Изменение значения переменной для указателя
ptr =& a ; //Указатель ptr = адрес переменной a
a = 100 ; //присвоили любое значение в переменную на адрес которой указатель указывает
cout <“a = “ //т.к. указатель опирается на адрес переменной a, любое изменение a влияет на то, что отображает разыменованный указатель. *ptr=a=100
a = 999 ; //поменяли значение переменной
cout << “a = “ //указатель опирается на адрес переменной a . *ptr=a=999

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



//Вариант б. Изменение разыменованного указателя влияет на переменную по адресу указателя
a = 100 ; //чтоб было понятнее. изменим a на новое значение
с = 55 ; //вторая переменная

ptr =& a ; // Указатель = адрес переменной a => *ptr=a=100;
* ptr = 999 ; //По адресу переменной а записалось новое значение. a=*ptr=999;

ptr =& c ; //Указатель теперь указывает на адрес с . Влияние указателя на а прекращено
* ptr = 88 ; / /с изменилось было 55 – стало 88

cout //a=100 a=999
cout //c=55 c=88
getch ();
return;
>
================================
Двойственность природы указателей причина многих недопониманий и ошибок. Понять для начинающих очень непросто, но понять можно. А вот, чтобы с ними дружить надо понимать природу указателей и как с ними работать на превосходное отлично.

Подводя итоги я повторюсь и напишу самое главное из этой статьи про указатели.

  • Указатель – это вид переменной, которая хранит адрес в памяти
  • Объявляется указатель с помощью звездочки
  • Чтобы обратиться к значению по адресу указателя используется разыменование
  • У любой переменной можно взять адрес памяти. Для этого используется символ &
  • Так как указатель есть адрес памяти, то ему можно присвоить адрес памяти от любой переменной
  • У указателя двойственная природа. Можно обрабатывать адрес памяти, а можно значение внутри этого адреса.

Понять сложно, но можно. самое главное старайтесь понять, а не заучить

Кратко об указателях в Си: присваивание, разыменование и перемещение по массивам

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

Введение

Указатель - переменная, которая хранит адрес сущностей (т.е. других переменных любого типа, будь то структура, или массив), и над которой возможно выполнять операцию разыменования (dereferencing). Адрес обычно выражен целым положительным числом. Диапазон адресов зависит от архитектуры компьютера. Указателю надо указать тип переменной, адрес которой он хранит, или же использовать ключевое слово void, для обозначения указателя, хранящего адрес чего-угодно (т.е. разрешён любой тип). Указатели объявляются как и обычные переменные, с той разницей, что имя типа переменной указателя имеет префикс, состоящий как минимум из одной звёздочки (*). Например:

int a = 12; /* usual variable */ int * ptr = &a; /* ptr-variable which contains address of variable a */ int **pptr = &ptr; /* ptr-variable which contains address of variable ptr */ int aval = **pptr; /* get value by adress which is contained in pptr. */ int aval2 = *ptr; /* get value of a by address (value of ptr) */

Количество звёздочек лишь указывает на длину цепочек хранимых адресов. Поскольку указатель также является переменной и имеет адрес, то его адрес также можно хранить в другом указателе. В выше приведённом примере адрес переменной a сохраняется в переменной-указателе ptr. Адрес же самой переменной ptr сохраняется в другом указателе pptr. Чтобы получить адрес переменной, перед её именем надо поставить знак амперсанда (&). Наконец, чтобы выполнить обратную операцию, т.е. получить значение (содержимое) по адресу, хранимому в указателе, имя указателя предваряется звёздочкой, почти как при объявлении. Почти, потому что одной звёздочки достаточно чтобы "распаковать" указатель. Поскольку pptr указывает по адресу на значение, хранимое в ptr, то необходимо два раза применить операцию разыменования.

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

int b = 0xff; void *pb = &b; void **ppb = &pb; int bval1 = *((int *) pb); int bval2 = *((int *) *ppb);

В данном примере адреса хранятся в указателе типа void. Перед получением значения по адресу, хранимым в pb, необходимо привести указатель pb к типу int*. Затем, воспользоваться стандартной операцией разыменования. Что касается указателя ppb, то он разыменовывается два раза. Первый раз до приведения к типу, для получения содержимого переменной pb, на которую он указывает. Второй раз - после приведения к типу int*.

Изменения значения переменной через указатель.

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

char a = 'x'; char *pa = &a; /* save address of a into pa */ *pa = 'y'; /* change content of variable a */ printf("%c\n", a); /* prints: y */

Как было сказано выше, указатели хранят адреса. Естественно, что адреса могут указывать не только на ячейки данных переменных в вашей программе, но и на другие вещи: адрес стека процедур, адрес начала сегмента кода, адрес какой-то процедуры ядра ОС, адрес в куче и т. д. Логично, что не все адреса можно использовать напрямую в программе, поскольку некоторые из них указывают на те участки памяти, которые нельзя изменять (доступ для чтения), или которые нельзя затирать. В случае, при обращении к участку, доступному только для чтения, при попытке изменить значение получим ошибку Segmentation Fault (SF).

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

И ещё, указатели могут указывать на один и тот же объект. Например:

int a = 123; int *p1 = &a; //Теперь p2 хранит тот же адрес, что и p1. int *p2 = &a; *p1 -= 3; // a = 123 - 3. printf("*p2 = %d\n", *p2); //Выведет 120

Этот простой пример показывает, что через адреса можно менять содержимое простых переменных, а также остальных указателей, ссылающихся на тоже самое. Таким образом, указатель p2 как бы является псевдонимом (alias) для p1.

Передача параметров через указатели.

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

int swap(int *a, int *b)

Здесь переменные а и b меняются своими значениями друг с другом (при условии, что параметры содержат не нулевой адрес). Отметим ещё раз, что мы можем изменить содержимое, указываемое по параметру-указателю методов. И, конечно, мы можем стереть данный адрес, присвоив параметру новое значение.

Проверка типов и массивы

Как было сказано, указатели хранят адреса переменных. Несмотря на указание типа для переменной указателя, это не мешает присвоить ему адрес переменной другого типа, если вы компилируете БЕЗ флагов. Например, следующий код не скомпилируется, если вы включили флаги -Werror -Wall .

#include #include int main(int argc, char **argv)

Конечно, компилятор gcc и без -Wall заметит недопустимую операцию в 7 строке кода. Флаг -Wall покажет все предупреждения компилятора. Главный флаг -Werror не позволит компилировать код, если есть предупреждения.

Что же касается массивов, то для массива не нужно предварять имя переменной амперсандом, поскольку компилятор автоматически при присваивании адреса массива присвоит адрес первого его элемента в указатель. Для многомерных массивов потребуются указатели на массивы, а не массивы указателей. Первые имеют форму объявления вида int (*arr)[] , а вторые вида int *arr[] . В квадратных скобках обязательно нужно указать размер массива. Для трёхмерных массивов потребуется уже две пары скобок, например int (*arr)[2][2] . Для четырёхмерных - три и так далее.

// В ПУСТОМ теле метода main. int A[2] = ; // A -> (int *) ptr to A[0] element, &A -> (int (*)[]) -> ptr to whole Array. int *ptr = A; printf("ptr -> A[1] = %d\n", *(ptr + 1)); // A[1] => 20. //Illegal usage of A. // int a_2 = ++A; //expected lvalue. //But with ptr you can do this. int b_2 = *++ptr; //Now ptr contains address of A[1]. (b_2 = A[1]); int (*ptr2)[2] = &A; //ptr to array, not to literal element. //*ptr2 => get array. //**ptr2 => get first element of array. //*ptr2 + 1 => get address of second element of array. printf("ptr2 -> A[1] = %d\n", *( *ptr2 + 1) ); int M[2][2] = < , >; // (*mp)[k] => (*mp)[k] => mp[0][k]. int (*mp)[2] = M; //again you must not add '&' to variable M. printf("M[0][0] = %d\n", **mp);//get array and extract it first element printf("M[1][0] = %d\n", **(mp + 1));//move to the address of second element printf("M[1][1] = %d\n", *( *(mp + 1) + 1));

В выше приведённом коде даны примеры для работы с массивами (одномерными и двумерными). В квадратных скобках указывается размер последнего измерения. Важно помнить, что первое разыменование приводит вас ко всему массиву (т. е. к типу int * ). А второе разыменование распаковывает элемент данного массива. В случае одномерного массива, у нас всего одна ячейка, и указатель ссылается на неё. В случае двумерного массива, у нас две ячейки - массивы, а указатель ссылается на первую. Для перемещения на второй массив, достаточно прибавить единицу к адресу, хранимому в переменной mp, например, так mp + 1 . Чтобы получить первый элемент второго массива, надо два раза распаковать указатель с соответствующим адресом массива, т.е. **(mp + 1) .

Постоянные (const) и указатели.

Напомним, чтобы сделать переменную с постоянным, фиксированным значением, надо добавить ключевое слово const перед её именем (до имени типа или после). Например:

const int i1 = 10; int const i2 = 222; // Warning: variable e3 is unitialized. With -Werror it won't be compiled. // (Внимание: переменной e3 не присвоено значение. С флагом gcc -Werror // данный код не скомпилируется). // const int e3;

Для объявления указателя на постоянное значение, ключевое слово const должно быть ПЕРЕД звёздочкой.

int A[2] = ; const int *a0 = A; printf("content of a0 = %d\n", *a0); //*a0 *= 10; //error: cannot change constant value. a0 = (A + 1); // A[1] printf("content of a0 = %d\n", *a0); //prints: A[1]

В примере выше была создана переменная-указатель, ссылающееся на постоянное значение. Слово const перед звёздочкой указывает, что нельзя менять содержимое напрямую (путём разыменования, обращения к ячейке). Но сама переменная указатель постоянной не является. А значит, ей можно присвоить новый адрес. Например, адрес следующей ячейки в массиве.

Чтобы запретить менять адрес (значение переменной) указателя, надо добавить слово const ПОСЛЕ звёздочки. Кроме того, можно добавить ключевые слова const перед и после '*' , чтобы сделать переменную фиксированной ещё сильнее, например так:

// Переменная с постоянным адресом и постоянным содержимым. const int *const ptr = A; // constant address with constant content // Переменная с постоянным адресом (содержимое можно менять) int *const ptr2 = A; // constant address only. // Переменная с постоянным содержимым, но с изменяемым адресом (значение справа) const int *ptr3 = A; // constant content only (can change address (rvalue))
  • Программирование
  • Системное программирование
  • C

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

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