Указателем называется компонент заданного типа, являющийся ссылкой на некоторую область памяти.
Определение указателя имеет следующий вид.
Тип-данных *p;
Тип переменной р определяется как указатель на тип-данных. Эти переменные служат ссылками на объекты типа тип-данных.
int *p; /*указатель на целый объект */
Для понимания, что такое указатели рассмотрим упрощенно как им образом переменные хранятся в памяти компьютера.
Память упрощенно можно рассматривать как массив байтов(рисунок). Доступ к ячейкам осуществляется по индексу (адресу в памяти).
При компиляции компилятор выделяет часть памяти под заведенные переменные и заменяет в теле программы обращение к переменным на обращение к участкам памяти.
Любому указателю можно присвоить значение 0 или идентификатор NULL описанный в stdio.h как константа значение которой 0. это присвоение будет значить что указательни на что не ссылается.
int *p=NULL;
Указатели и адреса
Так как указатель содержит адрес объекта, это дает возможность "косвенного" доступа к этому объекту через указатель. Пусть, х - переменная, например, типа INT, а рх – указатель на целый тип.
int x, *px;
Унарная операция & выдает адрес объекта, так что оператор
рх = &х;
присваивает адрес х переменной рх; говорят, что рх "указывает" на х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными.
scanf(“%d”,&x);/*берет адрес переменной х и записывает в него введенное значение*/
Унарная операция * рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. следовательно, если y тоже имеет тип int, то
inty;
y = *рх;
присваивает y содержимое того, на что указывает рх. Так последовательность
рх = &х;
y = *рх;
присваивает y то же самое значение, что и оператор
y = x;
Описание указателя
int *px;
говорит, что комбинация *px имеет тип int. Это означает, что если px появляется в контексте *px, то это эквивалентно переменной типа int. Фактически синтаксис описания переменной имитирует синтаксис выражений, в которых эта переменная может появляться. Это замечание полезно во всех случаях, связанных со сложными описаниями.
Вы должны также заметить, что из этого описания следует, что указатель может указывать только на определенный вид объектов.
Указатели могут входить в выражения. например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так, оператор
y = *px + 1
присваивает y значение, на 1 большее значения x;
printf("%d\n", *px)
печатает текущее значение x;
Ссылки на указатели могут появляться и в левой части присваиваний. если px указывает на x, то
*px = 0
полагает x равным нулю, а
*px += 1
увеличивает его на единицу, как и выражение
(*px)++
Круглые скобки в последнем примере необходимы; если их опустить, то поскольку унарные операции, подобные * и ++, выполняются справа налево, это выражение увеличит px, а не ту переменную, на которую он указывает.
И наконец, так как указатели являются переменными, то с ними можно обращаться, как и с остальными переменными. если py - другой указатель на переменную типа int, то
py = px
копирует содержимое px в py, в результате чего py указывает на то же, что и px.
Указатели и массивы
В языке "C" существует сильная взаимосвязь между указателями и массивами. Любую операцию, которую можно выполнить с помощью индексов массива, можно сделать и с помощью указателей. Вариант с указателями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания, по крайней мере для начинающего.
Пусть есть описание
int a[10]:
определяет массив размера 10, т.е. Набор из 10 последовательных объектов, называемых a[0], a[1], ..., a[9]. Запись a[i] соответствует элементу массива через i позиций от начала. если pa - указатель целого, описанный как
int *pa
то присваивание
pa = &a[0]
приводит к тому, что pa указывает на нулевой элемент массива a; это означает, что pa содержит адрес элемента a[0]. Теперь присваивание
int x;
x = *pa
будет копировать содержимое a[0] в x.
Если pa указывает на некоторый определенный элемент массива a, то по определению pa+1 указывает на следующий элемент, и вообще pa-i указывает на элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i позиций после. Таким образом, если pa указывает на a[0], то
*(pa+1)
ссылается на содержимое a[1], pa+i - адрес a[i], а *(pa+i) - содержимое a[i].
Эти замечания справедливы независимо от типа переменных в массиве a. Суть определения "добавления 1 к указателю", а также его распространения на всю арифметику указателей, состоит в том, что приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель. таким образом, i в pa+i перед прибавлением умножается на размер объектов, на которые указывает pa.
В действительности компилятор преобразует ссылку на массив в указатель на начало массива. В результате этого имя массива является указательным выражением. Отсюда вытекает несколько весьма полезных следствий. Так как имя массива является синонимом местоположения его нулевого элемента, то присваивание pa=&a[0] можно записать как
pa = a;
Еще более удивительным, по крайней мере на первый взгляд, кажется тот факт, что ссылку на a[i] можно записать в виде *(a+i). При анализировании выражения a[i] в языке "c" оно немедленно преобразуется к виду *(a+i); эти две формы +эквивалентны. &a[i] и a+i тоже идентичны: a+i - адрес i-го элемента от начала a. С другой стороны, если pa является указателем, то в выражениях его можно использовать с индексом: pa[i] идентично *(pa+i). Короче, любое выражение, включающее массивы и индексы, может быть записано через указатели и смещения и наоборот, причем даже в одном и том же утверждении.
Имеется одно различие между именем массива и указателем, которое необходимо иметь в виду. Указатель является переменной, так что операции pa=a и pa++ имеют смысл. Но имя массива является константой, а не переменной: конструкции типа a=pa или a++, или p=&a будут незаконными.