Указатель – это ячейка памяти, предназначенная для хранения адреса объекта некоторого типа (указывающая на данный объект). Адрес объекта – это адрес в памяти компьютера. Форма объявления указателя:
модификатор тип список_указателей;
Поле модификатор определяет класс памяти или модель памяти указателя или особенности преобразования указателя компилятором и является необязательным. Поле тип является обязательным и определяет тип объекта, на который указывает указатель. Возможны следующие типы объектов: базовые (в том числе и void), перечислимые, структуры, объединения, другие указатели. Указатель может хранить и адрес входа в функцию, тогда это указатель на функцию. Указатель на тип void имеет особое значение и может указывать на объект любого типа. Но ни с самим указателем на тип void, ни с объектом, на который он указывает, нельзя выполнять никакие операции, кроме присвоения адресного значения, если тип указателя явно не преобразовать к типу объекта.
Поле список_указателей является обязательным и содержит один или несколько идентификаторов указателей, разделённых запятыми. При объявлении указателя компилятор выделяет место в памяти компьютера, необходимое для размещения адреса объекта данного типа (размер адреса не соответствует размеру объекта).
Форма объявления указателя в поле список_указателей:
*модификатор имя=инициализатор
Поле модификатор является необязательным и содержит модификатор, определяющий класс памяти или модель памяти объекта, на который указывает указатель. Для модели памяти модификатор может принимать значения near, far, huge. Эти ключевые слова используются только в среде программирования Borland C++ 3.1. Подробнее об этом – при рассмотрении моделей памяти и модификаторов языка C.
Символ ‘*’ – признак указателя. При объявлении нескольких указателей символ ‘*’ ставится перед каждым указателем.
Поле инициализатор является необязательным и содержит адрес, которым инициализируется указатель. Рекомендуется использовать в качестве инициализаторов адреса переменных, константы или инициализированные указатели.
Пример 9
int *p,a,*f;
В данном примере объявляются указатели p и f и переменная a.
С указателями связаны две специальные унарные операции: «взятие адреса» и «обращение по адресу». Форма записи операции «обращение по адресу»: *операнд. Операнд – указатель. Операция производит обращение к объекту, адрес которого хранится в указателе. Форма записи операции «взятие адреса»: &операнд. Операнд – объект. Операция возвращает адрес объекта. Знаки данных операций имеют свои аналоги среди знаков бинарных операций, и в контексте программы они различаются по количеству операндов, участвующих в операции.
Пример 10
short int i=10,j=3,k;
short int *p=&i,*s;
s=&j;
*p+=1;
k=i**p+*s;
printf("%d ",k);
s=&k;
*s+=10;
printf("%d ",k);
На экране будет напечатано: 124 134
Как и обычные переменные, указатели инициализируются нулевым значением (константа NULL определена в файле stdio.h) при компиляции, только если они объявлены на внешнем уровне или с классом памяти static. Для остальных указателей инициализация не проводится, они указывают на произвольную область памяти, поэтому, прежде, чем использовать указатель, его значение необходимо явно определить. Ни в коем случае нельзя присваивать значение указателю непосредственно.
Пример 11
int *p, *j;
p=(int*)0xDE35;
j=NULL;
Кроме того, инициализация объекта, на который указывает указатель, неявно не производится никогда, за исключением массивов, объявленных на внешнем уровне с классом памяти static. Значение объекта, на который указывает указатель, также необходимо определять до его использования.
Связь указателей и массивов.
В языке C имя массива – это адрес памяти, начиная с которого расположен массив, то есть адрес первого элемента массива со смещением 0.
Операции над указателями.
Над указателями можно производить арифметические операции сложения и вычитания указателей с целыми числами, присвоения и сравнения указателей, инкремента и декремента. Кроме того, возможна операция вычитания указателей одного типа. Складывать указатели нельзя.
Важное значение имеет тип указателя. Так как указатель указывает на объект какого-то типа, то и значения указателя могут изменяться исходя из значений размера объекта данного типа. При сложении и вычитании указателей с целым числом значение указателя изменяется на произведение складываемого числа и размера в байтах типа данных указателя.
Пример 12
short int m[4]={0,1,2,3};
short int *p=&m[0];
short int *s=&m[2];
printf("%p %p %d %d ",p,s,s-p,*(p+1));
p++;
printf("%p %d",p,*p);
if(p>s)
printf(" p>s");
else
printf(" p<=s");
На экране будет напечатано: FFEE FFF2 2 1 FFF0 1 p<=s
В примере 12 при увеличении указателя p на единицу значение указателя увеличивается на 2, так как это указатель типа short int, а данный тип имеет размер 2 байта. Если бы тип указателя был double, значение указателя изменилось бы на 8. При вычитании указателей одного типа результатом также является не сама численная разница значений указателей (адресов), а её отношение к размеру типа указателей в байтах. При выполнении сравнения указателей производится сравнение их численных значений (адресов).
Массивы указателей и указатель на указатель.
Указатели могут объединяться в массивы, каждый элемент которого является указателем. Каждому элементу такого массива можно присвоить адрес. Форма объявления:
класс_памяти тип *имя [размер1][размер2]…[размерN] ={список_инициализаторов};
Поля имеют тот же смысл, что и при объявлении массивов переменных.
Пример 13
short int i=1,j=2;
short int *m[2]={&i,&j};
printf("%d %d ",*m[0],*m[1]);
m[0]=m[1];
*m[1]+=2;
printf("%d %d ",*m[0],*m[1]);
char *s[]={"Hello, ","World!"};
printf("%s%s",s[0],s[1]);
На экране будет напечатано: 1 2 4 4 Hello, World!
Указатель на указатель – это указатель на объект, который в свою очередь также является указателем на объект. Форма объявления:
тип модификатор * модификатор * модификатор имя=инициализатор;
Поля имеют тот же смысл, что и при объявлении указателей.
Пример 14
short int i=1,*p=&i,**f=&p;
*p+=1;
**f+=2;
printf("%d %d %d",i,*p,**f);
На экране будет напечатано: 4 4 4
Приведенный индекс.
Приведённый индекс – это возможность адресоваться к элементам N-мерного массива с использованием N-P координат, P – произвольно, P<N.
При обращении к элементу массива a[индекс1][индекс2]…[индексN] компилятор вычисляет смещение этого элемента от начала массива по формуле:
cмещение=индекс1*размер2*…*размерN+индекс2*размер3*…размерN+…+индексN-1*размерN+индексN
Зная это, можно при адресации к элементам массива использовать меньшее число координат. Обычно приведённый индекс используется при адресации к элементам многомерного массива через одномерный массив с использованием указателей.
Пример 15
Для двумерного массива a[X][Y] к элементу a[i][j] можно адресоваться следующим образом через указатель *b: *(b+i*Y+j), 0<=i<=X-1, 0<=j<=Y-1, b=&a[0][0].
Пример 16
Для трёхмерного массива a[X][Y][Z] к элементу a[i][j][k] можно адресоваться следующим образом через указатель *b: *(b+i*Y*Z+j*Z+k), 0<=i<=X-1, 0<=j<=Y-1, 0<=k<=Z-1, b=&a[0][0][0].
В примерах 15 и 16 используется указатель b вместо a, так как при попытке обращения a[i*Y*Z+j*Z+k] компилятор выдаст ошибку. Многомерный массив мерности N является массивом массивов мерности N-1 (например, трехмерный массив – это массив двумерных массивов, каждый из которых является массивом одномерных массивов). При этом массив мерности N>1 представляется программой как массив указателей, под него также выделяется память. Поэтому если обратиться к N-мерному массиву, используя меньше, чем N, координат, компилятор выдаст ошибку. Для использования приведённого индекса необходимо использовать указатель b, тип которого совпадает с типом элементов массива.
Пример 17
Объявление массива m[2][3] приводит к появлению в памяти трёх объектов: указатель на указатель m, который указывает на безымянный массив указателей длиной в два элемента, и безымянный массив из 6 чисел. Каждый указатель безымянного массива указателей указывает на позицию, соответствующую началу второй координаты массива при изменении значения первой координаты.
m[2][3]
<noname>[0]
|
| m[0][0]
| m[0][1]
| m[0][2]
|
<noname>[1]
| m[1][0]
| m[1][1]
| m[1][2]
|
Пример 18
int m[2][1][3],*p=**m;
for(int i=0;i<2;i++)
for(int j=0;j<1;j++)
for(int k=0;k<3;k++)
{
m[i][j][k]=i*1*3+j*3+k;
printf("%d ",m[i][j][k]);
(*(p+i*1*3+j*3+k))++;
}
for(int t=0;t<6;t++)
printf("%d ",*(p+t));
На экране будет напечатано: 0 1 2 3 4 5 1 2 3 4 5 6