Понятие указателя
Указатель — это переменная. В отличие от простых переменных, непосредственно хранящих значение заданного типа, указатель – это переменная, хранящая адрес памяти, где размещено значение заданного типа. То есть, указатель хранит не само значение, а адрес, обратившись по которому, можно получить это значение.
Переменная-указатель (далее будем говорить просто — указатель) объявляется также, как и любые другие переменные, но после имени типа ставится звездочка.
int* ptrInt;
Здесь объявляется переменная ptrInt. Ее тип — указатель на переменную типа int.
Синтаксис описания указателя
Следующие формы записи для компилятора эквивалентны:
1. int* ptr;
2. int *ptr;
Тем не менее, тип переменной ptr – «указатель на int», поэтому чаще используется первая форма записи.
В программах, как правило, используется раздельное объявление указателей и переменных, однако вы можете встретить и смешанное объявление:
int* p, b;
В ней p – указатель на int, b – переменная типа int.
Адрес переменной и значение переменной по адресу
|
|
Рассмотрим две переменные: целочисленную переменную x и указатель на целочисленную переменную.
int x = 10;
int* p;
Чтобы получить адрес переменной, нужно перед ее именем написать & (амперсанд).
p = &x;
Данная конструкция будет выполняться справа налево. Сначала с помощью операции &, примененной к переменной x, будет получен адрес x. Затем этот адрес будет присвоен указателю p.
Есть и обратная операция. Чтобы получить значение переменной по ее адресу, следует написать * (звездочку) перед именем указателя, т.е. выполнить операцию косвенной адресации (получение значения переменной по ее адресу):
int y = *p;
Такая операция в русском языке называется «разыменование» (анг. dereference).
В данном примере с помощью операции * мы получим то значение, которое находится в памяти по адресу p и сохранили его в переменную y:
// example1. cpp
#include <iostream>
using std::cout;
using std::endl;
Int main()
{
int x = 10, y = 20;
int* p;
p = &x;
y = *p;
cout<<"value x = "<< x<<endl<<"address x = "<<&x<<endl<<endl;
cout<<"value p = "<< p<<endl<<"address p = "<<&p<<endl<<endl;
cout<<"value y = "<< y<<endl<<"address y = "<<&y<<endl<<endl;
return 0;
}
В указанном примере значение x и y будут одинаковы. А также адрес x и значение p.
Результаты работы программы:
=*x, =&p?
1.4. Адресная арифметика
К указателям можно применять операции ++ и --. На основе этого сделана адресация в массиве. Рассмотрим пример:
// Объявление и инициализация массива
int arr[5] = {1, 2, 3, 4, 5};
//Объявление указателя на int и инициализация его адресом нулевого элемента массива.
|
|
int* p = &arr[0]; // лучше int* p = arr;
// Увеличение значения p не просто на 1, а на 1 * sizeof(int)
p++;
Комментарии:
1. Т.к. имя массива является указателем на его первый элемент, то вторая строка может быть записана иначе:
int* p = arr;
Таким образом, в p лежит адрес начала массива. А обращение *p даст 1.
2. Оператор sizeof(int) возвращает количество байт для типа int в данной системе. После увеличения p на 1, p указывает не на следующий байт, а на первый байт из следующей четверки байтов. Программисту не нужно думать в данном случае о размере типа.
С указателем можно складывать (вычитать) число, представленное целочисленной переменной или целочисленной константой. Вычесть можно не только число, но и указатель из указателя (полезно, например, при обработке строк). Сложение двух указателей, умножение или деление указателя на число или на другой указатель не имеет смысла.
Обращение к элементам массива
Последний важный момент рассмотренного кода в том, как преобразуется обращение к элементу массива. Имя массива — это указатель на его начало. Индекс, переданный в квадратных скобках, — смещение относительно начала массива (именно поэтому первый элемент массива имеет номер 0).
Конструкция arr[i] будет преобразована компилятором к *(arr + i). К начальному адресу массива будет прибавлено число с учетом размерности типа данных. А затем будет взято значение по вычисленному адресу.