Указатели

Указателем называют переменную, содержащую адрес другой переменной.Таким образом, именно указатели дают возможность косвенного доступа программы к объектам в памяти. Предположим, что х — переменная, например, типа int, а рх — указатель. Они описываются следующим образом:int x; int *px;Из описания следует, что указатель может указывать только на определенный тип объектов.Унарная операция * ("взятие значения") рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y также имеет тип int, то операцияy = *рх;присваивает y содержимое того объекта, на который указывает рх. Так последовательность операцийрх = &х;y = *рх;присваивает y то же самое значение, что и операторy = x;Указателю можно присваивать адрес объекта и непосредственно при описании:int x;int *px=&x;Унарная операция & ("взятие адреса") уже упоминалась нами в п. 5.5. Здесь указатель px содержит адрес переменной x, ему присвоен ее адрес.Над указателями определены операции сложения и вычитания с числом:px++;В этом примере унарная операция ++ увеличивает px так, что он указывает на следующий элемент набора объектов того типа, что задан при определении указателя. Именно подобное уменьшение или увеличение указателя дает возможность сканировать такие объекты, как строки и массивы.px-=2;Здесь операция px+=i увеличивает px так, чтобы он указывал на элемент, отстоящий на i элементов от текущего.Сравнение указателей в общем случае некорректно! Это связано с тем, что одним и тем же физическим адресам памяти могут соответствовать различные пары значений "сегмент‑смещение".Указатели являются переменными, соответственно, их можно присваивать:int *py=px;илиint *py; py=px;Теперь py указывает на то же, что px.Указатели px и py адресуют одну и ту же переменную x, но сравнение px==py может быть некорректным в отличие от сравнения значений *px==*py.Унарная операция & выдает адрес объекта, так что операторрх = &х;присваивает адрес х переменной рх; говорят, что теперь рхуказываетна х. Операция & применима только к переменным и элементам массива, конструкции вида &(х-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной.Указатели могут входить в выражения. Например, если px указывает на целое x, то *pxможет появляться в любом контексте, где может встретиться x. Так, операторy = *px + 1;присваивает y значение, на 1 большее значения x (получаем значение из указателя, затем прибавляем 1). Оператор printf ("\n%d", *px); печатает текущее значение x, а оператор d = sqrt((double)*px); получает в d квадратный корень из x, причем до передачи функции sqrt значение x преобразуется к типу double. В выражениях видаy = *px + 1;унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции (см. табл. 3.3), так что это выражение берет значение, на которое указывает px, прибавляет 1 и присваивает результат переменной y:y = (*px) + 1;Выражение y = *(px + 1);имеет совершенно иной смысл: записать в y значение, взятое из ячейки памяти, следующей за той, на которую указывает px. Адрес, на который указывает px, при этом не изменится.Ссылки на указатели могут появляться в левой части операторов присваивания. Если px указывает на x, то*px = 0;записывает в x значение 0, а*px += 1;увеличивает значение x на единицу, как и выражение(*px)++;Круглые скобки в последнем примере необходимы; если их опустить, то поскольку унарные операции, подобные * и ++,выполняются справа налево, это выражение увеличит px, а не ту переменную, на которую указывает px.Наконец, операция*px++;получает значение из указателя, затем сдвигает указатель на следующую ячейку памяти (поскольку использована постфиксная форма инкремента).Рассмотрим подробнее различные аспекты использования указателей. 7.1 Указатели и аргументы функций.Так как в Си передача аргументов функциям осуществляется по значению, вызванная подпрограмма не имеет непосредственной возможности изменить переменную из вызывающей подпрограммы.Указатели в качестве аргументов используются в функциях, которые должны возвращать более одного значения.Пример ниже иллюстрирует применение указателей в качестве аргументов функции — иными словами, передачу параметров по адресу и прием по значению.void swap (int *a, int *b) { int c=*a; *a=*b; *b=c;}int a,b,c; swap (&a,&b);int *p=&c; swap (&a, p);Сравните этот подход с ранее применявшимися передачей по значению и приемом по адресу, использующими операцию &:void swap (int &a, int &b) { int c=a; a=b; b=c;}int a,b; swap (a,b); 7.2. Указатели и массивы.Как правило, указатель используется для последовательного доступа к элементам статического или динамического массива. Так, конструкцияint a[]={1,2,3};int *p=a;for (int i=0;i<3;i++) printf ("\t%d",*p++);последовательно распечатает элементы массива a, доступ к которым осуществлялся через указатель p.Присваивание указателю адреса нулевого элемента массива можно было записать и в видеint *p=&a[0];илиint *p=&(*a+0);илиint *p=&*a;Следующий пример иллюстрирует сканирование строки с помощью указателя.int strlen(char *s){ int n; for (n = 0; *s != '\0'; s++) n++; return(n);}char *s="Test";int len=strlen (s);Здесь с указателем на тип char сопоставляется статическая строка символов, подробнее этот прием будет раскрыт в п. 7.3.Тело функции strlen можно было записать и короче:int n=0;while (*s++) n++;return n;

Существуют важные различия между массивами и указателями.

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

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

Для одномерного массива следующие 2 выражения эквивалентны, если а — массив или указатель, а b — целое:

а[b] *(а + b)

Аналогично, для матрицы a с целочисленными индексами i и j эквивалентны выраженияa[i][j] *(*(a+i)+j)Так, следующий оператор выводит элемент матрицы a[1][2]:int i=1,j=2;printf ("%d",*(*(a+i)+j));

Специальное применениеимеют указатели на тип void. Указатель на void может указывать на значения любого типа. Однако для выполнения операций над указателем на void либо над указуемым объектом необходимо явно привести тип указателя к типу, отличному от void. Например, если объявлена переменная i типа int и указатель р на тип void

int i; void *p;

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

p= &i;

но изменить значение указателя без приведения типа нельзя:

р++; /* недопустимо */

(int *)р++; /* допустимо */

В стандартном включаемом файле stdio.h определена константа с именем NULL. Она предназначена специально для инициализации указателей. Гарантируется, что никакой программный объект никогда не будет иметь адрес NULL.