Указатели и ссылки
Понятие указателей
Указатель – это переменная, содержащая адрес другой переменной. Типичная компьютерная система содержит массив последовательно пронумерованных (адресуемых) ячеек памяти, с которыми можно работать по отдельности либо целыми непрерывными группами. Указатель представляет собой группу ячеек памяти, которые содержат адрес какой-либо переменной. Синтаксис объявления указателя на переменную:
тип *имя_указателя
Например,
int *px;
double *yx;
Указатели принято инициализировать при объявлении либо нулем (т.е. указатель не содержит адреса и не указывает ни на какой объект), либо адресом другой переменной. Например,
int *px = 0; // указатель инициализируется нулем
int x;
int *px = &x; // указатель инициализируется адресом переменной х
Одноместная унарная операция & дает адрес объекта (переменной). В этом случае говорят, что px указывает на x. Одноместная операция * называется операцией ссылки по указателю или разыменовывания. Применяя ее к указателю, получаем объект (переменную), на который он указывает.
Если указатель px указывает на целую переменную x, выражение *px может фигурировать в любом контексте, в котором допускается x.
Пример. Присвоение указателям адресов переменных, косвенное обращение к переменным через указатели.
#include<iostream>
int main()
{
double a,b;
double *pa = &a;
double *pb = &b;
std::cout << "pa= " << pa << std::endl;
std::cout << "pb= " << pb << std::endl;
*pa = 5.55;
*pb = *pa + 6.57;
std::cout << "a= " << a << std::endl;
std::cout << "b= " << b << std::endl;
return 0;
}
Поскольку указатели сами являются переменными, их можно употреблять без разыменовывания.
Пример. Присваивание указателю значения другого указателя.
|
#include<iostream>
int main()
{
double a,b;
double *pa = &a;
double *pb = &b;
*pa = 5.55;
*pb = *pa + 6.57;
pb=pa;
return 0;
}
Теперь указатель pb указывает на такую же ячейку, что и pa, но значения переменных a и b не изменились!! Заметьте кардинальное отличие от операции *pb = *pa, когда меняется значение переменной b, но значение самого указателя остается неизменным. Проверить это можно, заменив строку pb = pa строкой *pb = *pa.
Следует заметить, что любой указатель может указывать только на объекты одного конкретного типа данных, заданного при его объявлении. Т.е. указатель типа double * не может указывать на тип int. Исключением является только указатель на тип void, в котором может содержаться произвольный адрес без указателя на тип данных. Однако по указателям этого типа нельзя ссылаться и получать значения.
Арифметические действия с указателями
С указателями можно использовать только четыре арифметических оператора: ++, --, +и-.
Чтобы лучше понять, что происходит при выполнении арифметических действий с указателями, начнем с примера. Пусть р1 — указатель на int-переменную с текущим значением 2000 (т.е. р1 содержит адрес 2000). После выполнения (в 32-разрядной среде) выражения p1++ содержимое переменной-указателя р1 станет равным 2004, а не 2001. Дело в том, что при каждом инкрементировании указатель р1 будет указывать на следующее int-значение. Для операции декрементирования справедливо обратное утверждение, т.е. при каждом декрементировании значение р1 будет уменьшаться на 4. Например, после выполнения инструкции p1--; указатель р1 будет иметь значение 1996, если до этого оно было равно 2000.
|
Итак, каждый раз, когда указатель инкрементируется, он будет указывать на область памяти, содержащую следующий элемент базовоготипа этого указателя. А при каждом декрементировании он будет указывать па область памяти, содержащую предыдущий элемент базового типа этого указателя.
Для указателей на символьные значения результат операций инкрементирования и декрементирования будет таким же, как при "нормальной" арифметике, поскольку символы занимают только один байт. Но при использовании любого другого типа указателя при инкрементировании или декрементировании значение переменной-указателя будет увеличиваться или уменьшаться на величину, равную размеру его базового типа.
Выражение p1=p1+9 заставляет р1 ссылаться на девятый элемент базового типа указателя р1 относительно элемента, на который р1 ссылался до выполнения этой инструкции.
Несмотря на то, что складывать указатели нельзя, один указатель можно вычесть из другого (если они оба имеют один и тот же базовыйтип). Разность покажет количество элементов базового типа, которые разделяют эти два указателя.
Помимо сложения указателя с целочисленным значением и вычитания его из указателя, а также вычисления разности двух указателей, над указателями никакие другие арифметические операции не выполняются. Например, с указателями нельзя складывать float- или double-значения.
Пример.
#include <iostream>
int main ()
{
int A;
int *pA = 0;
A = 5;
pA = &A;
std::cout << "pA = " << pA << std::endl;
int B = – 32;
int *pB = &B;
std::cout << "pB = " << pB << std::endl;
int C = 101;
|
int *pC = &C;
std::cout << "pC = " << pC << std::endl;
std::cout << "pB-pA = " << pB - pA << std::endl;
std::cout << "pC-pB = " << pC - pB << std::endl;
std::cout << "pC-pA = " << pC - pA << std::endl;
getch ();
std::cout << "pA-pB = " << pA - pB << std::endl;
std::cout << "pB-pC = " << pB - pC << std::endl;
std::cout << "pA-pC = " << pA - pC << std::endl;
*(pB + 6) = 10;
std::cout << " A = " << A << std::endl;
return 0;
}
Результаты выполнения програмы:
pA = 0014F7D0
pB = 0014F7B8
pC = 0014F7A0
pB – pA = – 6
pC – pB = – 6
pC – pA = – 12
pA – pB = 6
pB – pC = 6
pA – pC = 12
A = 10
Таблица 2. Расположение переменных А и В в ячейках памяти.
B | |||||||||||
1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт |
0014F7B8 | 0014F7BC | 0014F7C0 |
1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт |
0014F7C4 | 0014F7C8 | 0014F7CC |
A | |||||||||||
1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт | 1 байт |
0014F7D0 |
Пример. Демонстрация работы операции увеличения указателя на целое число
#include <iostream>
int main ()
{int A;
int *pA = 0;
A = 5;
pA = &A;
std::cout << "pA = " << pA << std::endl;
int B = 35;
int *pB = &B;
std::cout << "pB = " << pB << std::endl;
std::cout << "pA-pB = " << pA - pB << std::endl;
*(pB + 6) = 12;
std::cout << " A = " << A << std::endl;
short int X = 6;
short int * pX = &X;
std::cout << "pX = " << pX << std::endl;
*(pX+12) = 23;
std::cout << " B = " << B << std::endl;
*(pX+13) = 23;
std::cout << " B = " << B << std::endl;
return 0; }
Результаты выполнения програмы:
pA = 0028FAB0
pB = 0028FA98
pA – pB = 6
A = 12
pX = 0028FA80
B =23!!!!!
B = 1507363!!!
Из результатов выполнения программы видно, что операции *(pX+12)=23 и *(pX+13)=23 приводят к косвенному обращению к ячейке памяти, содержащей переменную В. Результатом этого является потеря информации, содержащейся в переменной B.
Таблица 3. Расположение переменных Х и В в ячейках памяти.
X | 24байта =12 | B |
0028FA80 | 0028FA98 |
Переменная int занимает 4 байта, а переменная short int 2 байта.