Указатели – это одно из тех средств, которые обеспечили гибкость и мощь C++. В то же время непродуманное использование указателей, особенно неопытными программистами, может стать источником серьезных проблем.
Указатель (pointer) – это объект, который хранит некоторый адрес в памяти. Обычно по этому адресу находится другой объект, например, переменная. Именно для доступа к этому другому объекту по его адресу в памяти и используются указатели. Объявляется указатель следующим образом:
тип *имя_указателя;
То есть аналогично обычной переменной, но с использованием операции разыменования «*». Здесь тип означает тип данных, на который будет ссылаться указатель. То есть по адресу, который будет хранить в себе указатель, будет находиться значение этого типа данных. Например:
char *pchar; // указатель на char
int *ptr; // указатель на int
MyType *p; // указатель на тип MyType
float *pointer, fl; // pointer - указатель на float, а
// fl – обычная переменная типа float
short* pshort; // указатель на short
При объявлении указателя есть следующие особенности: операция разыменования («*») может стоять как рядом с именем переменной, так и рядом с типом данных – это ни на что не влияет, допустимы и используются оба варианта. Но нужно помнить, что эта операция относится к имени ПЕРЕМЕННОЙ. Поэтому в четвертой строке примера pointer – это указатель, а fl – обычная переменная типа float.
Размер указателя зависит от компилятора и разрядности операционной системы. Так как указатель должен хранить адрес памяти, то и его размер должен соответствовать разрядности адреса памяти, используемого в ОС. Для 32битных систем размер указателя обычно равен 4 байтам (те же 32 бита), для 64битных систем он составляет 8 байт (64 бита).
С указателями тесно связана ещё одна операция – взятие адреса «&». Она возвращает адрес памяти, по которому располагается её операнд. Например:
short val = 10;
short *ptr;
ptr = &val;
Здесь объявляется переменная val типа short и переменная ptr типа указатель на short. Затем в указатель записывается адрес, по которому находится значение val. Говорят, что теперь ptr указывает на val. Это можно представить в виде следующей схемы:
Вверху указаны адреса в памяти. Обычно они представляются в шестнадцатеричном виде, т.е. с префиксом 0x. Пусть переменной val выделена память начиная с адреса 0x100. Поскольку она имеет тип short, то выделено ей 2 байта. В них хранится значение, которое было в нее записано – т.е. 10. Переменной ptr выделена память начиная с адреса 0x108. Так как она является указателем, то (считая, что используется 32битная система) под нее выделено 4 байта. В них записывается адрес переменной val, т.е. 0x100.
Чтобы обратиться к значению переменной val, на которую ссылается ptr, нужно использовать операцию разыменования «*» (она же называется косвенным обращением).
cout << ptr; // выведет 0x100
cout << *ptr; // выведет 10
Не нужно путать унарную операцию разыменования и бинарную операцию умножения. Хотя они и обозначаются одним знаком – астериском (звездочкой), но смысл у них разный. Кроме того, разыменование имеет более высокий приоритет. Используя указатель можно не только косвенно обращаться к некоторому значению, но и изменять его.
float fl = 0.5;
float *ptr = &fl;
*ptr = 15;
cout << fl << '\n'; // выведет 15
В данном примере значение переменной fl явно не менялось. Но на нее ссылался указатель ptr. И применяя к нему операцию разыменования было изменено значение самой переменной fl. Обратная ситуация также допустима. Т.е. изменив значение переменной fl мы, тем самым, поменяем значение, которое выдаст переменная ptr при разыменовании.
Другой пример:
short *px, x = 1;
px = &x;
*px += 1;
cout << "px = 0x" << px << " x = " << x <<'\n';
*px++;
cout << "px = 0x" << px << " x = " << x <<'\n';
Данный пример выведет на экран:
px = 0x0012FF34, x = 2
px = 0x0012FF36, x = 2
Здесь инструкция *px += 1; изменяет значение, на которое указывает px, т.е. значение переменной x. А инструкция *px++; работает по-другому, т.к. в ней сначала выполняется операция инкремента ++, а уже потом – извлекается значение с помощью операции разыменования. Т.е. эта операция изменяет сам указатель, а не значение, на которое он ссылается. Если внимательно посмотреть на вывод программы, можно увидеть что операция инкремента ++ увеличила значение px не на единицу, как это можно было ожидать, а на 2. Причина такого поведения описывается ниже.