1.Описание языка СИ.

1.7.Указатели и адресная арифметика.

Операции с указателями

Над указателями можно выполнять унарные операции: инкремент и декремент. При выполнении операций ++ и -- значение указателя увеличивается или уменьшается на длину типа, на который ссылается используемый указатель.

Пример:

 
        int *ptr, a[10];
           ptr=&a[5];
           ptr++;      /* равно адресу элемента a[6] */
           ptr--;      /* равно адресу элемента a[5] */
 

В бинарных операциях сложения и вычитания могут участвовать указатель и величина типа int. При этом результатом операции будет указатель на исходный тип, а его значение будет на указанное число элементов больше или меньше исходного.

Пример:

 
        int *ptr1, *ptr2, a[10];
        int i=2;
           ptr1=a+(i+4);   /*  равно адресу элемента a[6] */
           ptr2=ptr1-i;    /*  равно адресу элемента a[4] */
 

В операции вычитания могут участвовать два указателя на один и тот же тип. Результат такой операции имеет тип int и равен числу элементов исходного типа между уменьшаемым и вычитаемым, причем если первый адрес младше, то результат имеет отрицательное значение.

Пример:

 
        int *ptr1, *ptr2, a[10];
        int i;
           ptr1=a+4;
           ptr2=a+9;
           i=ptr1-ptr2;  /*   равно 5   */
           i=ptr2-ptr1;  /*   равно -5  */
 

Значения двух указателей на одинаковые типы можно сравнивать в операциях ==, !=, <=, >, >= при этом значения указателей рассматриваются просто как целые числа, а результат сравнения равен 0 (ложь) или 1 (истина).

Пример:

 
      int *ptr1, *ptr2, a[10];
         ptr1=a+5;
         ptr2=a+7;
         if (prt1>ptr2) a[3]=4;
 

В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3]=4 не будет выполнен.

Массивы указателей

В языке СИ элементы массивов могут иметь любой тип, и, в частности, могут быть указателями на любой тип. Рассмотрим несколько примеров с использованием указателей.

Следующие объявления переменных

 
     int a[]={10,11,12,13,14,};
     int *p[]={a, a+1, a+2, a+2, a+3, a+4};
     int **pp=p;
 

порождают программные объекты, представленные на схеме на рис.4.

 

pp
 

в

 

  p

а

.

.

.

.

.

 

в

в

в

в

в

 

  a

а

11
12
13
14
15

Рис.4. Схема размещения переменных при объявлении.

При выполнении операции pp-p получим нулевое значение, так как ссылки pp и p равны и указывают на начальный элемент массива указателей, связанного с указателем p ( на элемент p[0]).

После выполнения операции pp+=2 схема изменится и примет вид, изображенный на рис.5.

 

  pp
 

в

  p

а

.

.

.

.

.

 

 

 

в

в

в

в

в

 

 

  a

а

10
11
12
13
14
 
 

Рис.5. Схема размещения переменных после выполнения операции pp+=2.

 

 

Результатом выполнения вычитания pp-p будет 2, так как значение pp есть адрес третьего элемента массива p. Ссылка *pp-a тоже дает значение 2, так как обращение *pp есть адрес третьего элемента массива a, а обращение a есть адрес начального элемента массива a. При обращении с помощью ссылки **pp получим 12 - это значение третьего элемента массива a. Ссылка *pp++ даст значение четвертого элемента массива p т.е. адрес четвертого элемента массива a.

Если считать, что pp=p, то обращение *++pp это значение первого элемента массива a (т.е. значение 11), операция ++*pp изменит содержимое указателя p[0], таким образом, что он станет равным значению адреса элемента a[1].

Сложные обращения раскрываются изнутри. Например обращение *(++(*pp)) можно разбить на следующие действия: *pp дает значение начального элемента массива p[0], далее это значение инкременируется ++(*p) в результате чего указатель p[0] станет равен значению адреса элемента a[1], и последнее действие это выборка значения по полученному адресу, т.е. значение 11.

В предыдущих примерах был использован одномерный массив, рассмотрим теперь пример с многомерным массивом и указателями. Следующие объявления переменных

 
     int a[3][3]={  { 11,12,13 },
                    { 21,22,23 },
                    { 31,32,33 }   };
     int *pa[3]={ a,a[1],a[2] };
     int *p=a[0];
 

порождают в программе объекты представленные на схеме на рис.6.

 


Рис.6. Схема размещения указателей на двумерный массив.

 

Согласно этой схеме доступ к элементу a[0][0] получить по указателям a, p, pa при помощи следующих ссылок: a[0][0], *a, **a[0], *p, **pa, *p[0].

Рассмотрим теперь пример с использованием строк символов. Объявления переменных

 
     char *c[]={ "abs", "dx", "yes", "no" };
     char **cp[]={ c+3, c+2 , c+1 , c };
     char ***cpp=cp;
 

можно изобразить схемой представленной на рис.7.

 


Рис.7. Схема размещения указателей на строки.