Указатели в C++

Указатели широко используются в 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.

Результат работы программы:

Таким образом, при преобразовании указателей разного типа приведение типов разрешает не только синтаксическую проблему присваивания. Следует помнить, что операция * над указателями различного типа, ссылающимися на один и тот же адрес, возвращает различные значения.

Рейтинг
( Пока оценок нет )
Загрузка ...