Взаимосвязь между массивами и указателями

Массивы и указатели в Си тесно связаны и могут быть использованы почти эквивалентно. Имя массива можно понимать как константный указатель на первый элемент массива. Его отличие от обычного указателя только в том, что его нельзя модифицировать. Например, задан целочисленный массив A на 5 элементов:

int A[5]={-2,5,4,-17,3};

Посмотрим, как этот массив разместится в оперативной памяти (рисунок 7.1).

  . . .  
A[4]  
  0x0012FF90
 
 
   
A[3]   0x0012FF8C
 
 
   
A[2]   0x0012FF88
 
 
   
A[1]   0x0012FF84
 
 
   
A[0]   0x0012FF80
 
 
  . . .  
const int *A   0х0012FF70
 
 
 
  . . .

Рисунок 7.1 – Представление массива элементов в памяти ЭВМ

 

Обратите внимание, что под хранение значения каждого элемента массива выделяется четыре ячейки памяти, что видно из чередования адресов ячеек памяти через четыре байта. Это связано с тем, что тип этого массива – int. Причем, под хранение адреса начала массива выделяется тоже четыре байта, так как все указатели для 32-разрядных процессоров 4-х байтные.

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

int b[5] = {1,2,3,4,5}, *Pt;

Тем самым вы объявили массив целых чисел b[5] и указатель на целое Pt. Поскольку имя массива является указателем на первый элемент массива, вы можете задать указателю Pt адрес первого элемента массива b с помощью оператора Pt = b;

Это эквивалентно присваиванию адреса первого элемента массива следующим образом: Pt = &b[0];

Теперь можно сослаться на элемент массива b[3] с помощью выражения *(Pt + 3).

Указатели можно индексировать точно так же, как и массивы. Например, выражение Pt[3] ссылается на элемент массива b[3].

Или, например, определение int a[5];задает массив из пяти элементов а[0], a[1], a[2], a[3], a[4]. Если объект *у определен как

int *у;

то оператор

у = &a[0];

присваивает переменной у адрес элемента а[0]. Если переменная у указывает на очередной элемент массива а, то y+1 указывает на следующий элемент, причем здесь выполняется соответствующее масштабирование для приращения адреса с учетом длины объекта (для типа int – 4 байта,long - 4 байта, (double - 8 байт и т.д.).

Так как само имя массива есть адрес его нулевого элемента, то оператор у = &a[0]; можно записать и в другом виде: у = а. Тогда элемент а[1] можно представить как *(а+1). С другой стороны, если у - указатель на массив a, то следующие две записи: a[i] и *(у+i) эквивалентны. Рассмотрим пример:

int main(int argc, char* argv[])

{

int a[5]={-5,0,34,12,-17};

cout<<endl<<(a+2); // оператор 1

cout<<endl<<&a[2]; // оператор 2

cout<<endl<<*(a+2); // оператор 3

cout<<endl<<a[2]; // оператор 4

cout<<endl;

return 0;

}

Так, операторы 1 и 2 выведут одно и то же значение – адрес элемента массива с индексом 2. А операторы 3 и 4 – одно и то же значение 34.

Между именем массива и соответствующим указателем есть одно важное различие. Указатель - это переменная и у = а или y++ - допустимые операции. Имя же массива - константа, поэтому конструкции вида a = y, a++ использовать нельзя, так как значение константы постоянно и не может быть изменено.

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

С указателями может выполняться ограниченное количество арифметических операций. Указатель можно увеличивать (++), уменьшать (--), складывать с указателем целые числа (+ или +=), вычитать из него целые числа (- или -=) или вычитать один указатель из другого.

Сложение указателей с целыми числами отличается от обычной арифметики. Прибавить к указателю 1 означает сдвинуть его на число байтов, содержащихся в переменной, на которую он указывал. Обычно подобные операции применяются к указателям на массивы. Если продолжить приведенный выше пример, в котором указателю Pt было присвоено значение b - указателя на первый элемент массива, то после выполнения оператора

Pt += 2;

Pt будет указывать на третий элемент массива b. Истинное же значение указателя Pt изменится на число байтов, занимаемых одним элементом массива, умноженное на 2. Например, если каждый элемент массива b занимает 2 байта, то значение Pt (т.е. адрес в памяти, на который указывает Pt) увеличится на 4.

Аналогичные правила действуют и при вычитании из указателя целого значения.

Переменные указатели можно вычитать один из другого. Например, если Pt указывает на первый элемент массива b, а указатель Pt1 - на третий, то результат выражения Pt1 - Pt будет равен 2 - разности индексов элементов, на которые указывают эти указатели. И так будет, несмотря на то, что адреса, содержащиеся в этих указателях, различаются на 4 (если элемент массива занимает 2 байта).

Арифметика указателей теряет всякий смысл, если она выполняется не над указателями на массив. Сравнение указателей операциями >, <, >=, <= также имеют смысл только для указателей на один и тот же массив. Однако, операции отношения == и != имеют смысл для любых указателей. При этом указатели равны, если они указывают на один и тот же адрес в памяти.