Указатели широко используются в C++. В чем то, именно их наличие сделало этот язык более удобным для системного программирования. Но стоит отметить, что это одна из наиболее сложных для освоения возможностей C++. Идея работы с указателями состоит в том, что пользователь работает с адресом ячейки памяти и имеет возможность динамически создавать и уничтожать переменные.
Как правило, при обработке оператора объявления переменной тип имя_переменной; компилятор автоматически выделяет память под переменную имя_переменной в соответствии с указаннымтипом:
char C='$'; //Будет выделена под символьную переменную С, //и ей присвоено стартовое значение
Доступ к объявленной переменной осуществляется по ее имени. При этом все обращения к переменной меняются на адрес ячейки памяти, в которой хранится ее значение:
cout<<C; //из ячейки памяти с именем C будет извлечено значение //и выведено на экран
При завершении программы или функции, в которой была описана переменная, память автоматически освобождается.
Доступ к значению переменной можно получить иным способом — определить собственные переменные для хранения адресов памяти. Такие переменные называют указателями. С помощью указателей можно обрабатывать массивы, строки и структуры, создавать новые переменные в процессе выполнения программы, передавать адреса фактических параметров.
Указатель — это переменная, значением которой является адрес памяти, по которому хранится объект определенного типа (другая переменная). Например, если C — это переменная типа char, а P — указатель на C, значит в P находится адрес, по которому в памяти компьютера хранится значение переменной C.
Как и любая переменная, указатель должен быть объявлен. При объявлении указателей всегда указывается тип объекта, который будет хранится по данному адресу:
тип имя_переменной;
Например:
int *p //по адресу, записанному в переменной p, //будет хранится переменная типа int //или, другими словами, p указывает на тип данных int
Звездочка в описании указателя относится непосредственно к имени, поэтому, чтобы объявить несколько указателей, ее ставят перед именем каждого из них:
float *x, y, *z; //описаны указатели на вещественное число - x и z //а также вещественная переменная y
Операции * и & при работе с указателями
Операция получения адреса обозначается знаком &, а операция разадресации *. Первая возвращает адрес своего операнда. Например:
float a; //объявлена вещественная переменная a float *adr_a; //объявлен указатель на тип float adr_a = &a; //оператор записывает в переменную adr_a //адрес переменной a
Операция разадресации * возвращает значение переменной, хранящееся в по заданному адресу, то есть выполняет действие, обратное операции &:
float a; //объявлена вещественная переменная a float *adr_a; //объявлен указатель на тип float a = *adr_a; //оператор записывает в переменную a //вещественное значение, хранящиеся по адресу adr_a
Присваивание указателей
Значение одного указателя можно присвоить другому. Если указатели одного типа, то для этого применяют обычную операцию присваивания . Рассмотрим ее на примере:
#include "stdafx.h" #include <iostream> using namespace std; int main() {setlocale (LC_ALL, "Rus"); float PI=3.14159, *p1, *p2; p1=p2=&PI; cout<<"По адресу p1="<<p1<<" хранится *p1="<<*p1<<"\n"; cout<<"По адресу p2="<<p2<<" хранится *p2="<<*p2<<"\n"; system ("pause"); return 0; }
В данной программе определены: вещественная переменная PI=3.14159 и два указателя на тип float — p1 и p2. В указатели записывается адрес переменной PI. В результате работы программы в переменных p1 и p2 будет хранится значение одного и того же адреса, по которому хранится вещественная переменная PI=3.14159.
Результат работы программы:
Если указатели ссылаются на различные типы, то при присваивании значения одного указателя другому, необходимо использовать преобразование типов
Без преобразования можно присваивать любому указателю указатель void*. Рассмотрим пример работы с указателями различных типов:
#include "stdafx.h" #include <iostream> using namespace std; int main() { setlocale (LC_ALL, "Rus"); float PI=3.14159; //объявлена вещественная переменная PI float *p1; //объявлен указатель на float - p1 double *p2; //объявлен указатель на double - p2 p1=&PI; //переменной p1 присваивается значние адреса PI p2=(double *)p1; //указателю на double присваивается значение, //которое ссылается на тип float cout<<"По адресу p1="<<p1<<" хранится *p1="<<*p1<<"\n"; cout<<"По адресу p2="<<p2<<" хранится *p2="<<*p2<<"\n"; system ("pause"); return 0; }
В указателях p1 и p2 хранится один и тот же адрес, но значения, на которые они ссылаются, оказываются разными. Это связано с тем, что указатель типа *float адресует 4 байта, а указатель *double — 8 байт. После присваивания p2=(double *)p1; при обращении к *p2 происходит следующее: к переменной, хранящийся по адресу p1, дописывается еще 4 следующих байт из памяти. В результате значение *p2 не совпадает со значением *p1. Результат работы программы будет примерно такой:
А вот что произойдет в результате работы следующего кода:
#include "stdafx.h" #include <iostream> using namespace std; int main() { setlocale (LC_ALL, "Rus"); double PI=3.14159, *p1; float *p2; p1=&PI; p2=(float *)p1; cout<<"По адресу p1="<<p1<<" хранится *p1="<<*p1<<"\n"; cout<<"По адресу p2="<<p2<<" хранится *p2="<<*p2<<"\n"; system ("pause"); return 0; }
После присваивания p2=(double*)p1; при обращении к *p2 происходит следующее: из переменной, хранящийся по адресу p1, выделяется только 4 байта. В результате значение *p2 не совпадает со значением *p1.
Результат работы программы:
Таким образом, при преобразовании указателей разного типа приведение типов разрешает не только синтаксическую проблему присваивания. Следует помнить, что операция * над указателями различного типа, ссылающимися на один и тот же адрес, возвращает различные значения.