белая методичка страница 3,лекция в тетради тоже есть!!!отметила если что на полях *
1.1.Указатели и адреса объектов
Познакомимся с новым для нас объектом – указателем. Указателем называется такой объект программы, который обладает двумя свойствами:
- Значением любого указателя может быть только какой - либо адрес участка оперативной памяти, выделенный под конкретный объект;
- С указателем всегда связан тип объекта, для которого выделен этот участок.
Таким образом, указатель, значение которого определено, указывает адрес объекта в оперативной памяти и его тип, который определяет количество выделенных под этот объект байтов. Другими словами, указатель дает возможность реализации доступа к определенному участку оперативной памяти. Этот факт демонстрируется на рис. 1
На этом рисунке значение указателя равно 0FFC, т.е. он указывает на байт с адресом 0FFC. Длина поля (в данном случае 4 байта, выделенные серым цветом) известна системе из объявления указателя в программе, в котором всегда обозначается тип объекта, на который может указывать указатель.
Напомним, что адресом любого участка оперативной памяти называется наименьший адрес среди всех адресов байтов участка (младший адрес).
Т. е. в нашем случае адресом поля, выделенного серым цветом, является 0FFC.
В простейшем случае объявление указателя на некоторый объект имеет вид:
type * имя указателя;
где type –обозначение типа, имя указателя – идентификатор.
Таким образом, признаком указателя при лексическом разборе объявления объекта служит символ ‘*’, помещенный перед именем. Ниже представлены примеры объявления указателей:
int *p1, *p2;
float *p3, *p4;
В этом примере p1, p2 – это указатели на поле типа int, а p3,p4 – указатели на поле типа float. А какова длина участка поля, на которое могут указывать эти указатели? Для p1, p2 – это 2 байта (поле типа int занимает 2 байта), а для p3,p4 – 4 байта (поле типа float занимает 4 байта). Для размещения самих указателей количество выделяемых байтов зависит от реализации компилятора и может быть от 2 до 4 байтов. В стандартизуемом варианте языка С++ предполагается, что все указатели одинаковы, т. е. внутреннее представление адресов всегда одно и то же. Однако в реализациях компиляторов для конкретных вычислительных машин это не всегда справедливо. Практически все компиляторы языка С++ обязательно учитывают архитектурные особенности аппаратных средств и включают дополнительные возможности для того, чтобы программист мог их эффективно использовать. Сейчас, однако, мы безболезненно пропустим эти тонкости, и будем считать, что в наших рассматриваемых случаях все указатели одинаковы и для размещения каждого выделяется два байта.
При объявлении указателя возможно выполнить его инициализацию (присвоить ему какое – либо значение). В этом случае возможны две формы объявления:
type * имя указателя = инициализирующее выражение;
type * имя указателя = (инициализирующее выражение);
В качестве инициализирующего выражения используется константное выражение, частными случаями которого могут быть:
- Явно заданный участок памяти;
- Указатель, уже имеющий значение;
- Выражение, позволяющее получить адрес объекта с помощью операции & - определения адреса.
Указатель может принимать особое значение, называемое пустым, или нулевым. Это значение отлично от значения указателя на любой конкретный объект. Кроме того, внутреннее (битовое) представление пустого указателя может отличаться от битового представления целого значения 0. Нулевое значение адреса, соответствующее значению пустого указателя, имеет специальное обозначение NULL.
Если значение константного выражения равно нулю, то это нулевое значение преобразуется к пустому (нулевому) указателю. Ниже приведены примеры объявлений указателей:
int x = 5; // целочисленная переменная
float y = 45.6; // действительная переменная
char c = ‘a’; // символьная переменная
char * p (NULL); //нулевой указатель на объект типа сhar
char *p1; //неинициализированный указатель на объект типа сhar
char *p2 = &c; // инициализированный указатель на объект типа сhar
float *p3 = &y; // инициализированный указатель на объект типа float
int * p4 = &x; // инициализированный указатель на объект типа int
int * p5; //неинициализированный указатель на объект типа int
char *p6; //неинициализированный указатель на объект типа сhar
int **p7; //неинициализированный указатель на объект типа int *
// р7 – указатель на указатель
Рассмотрим более сложные случаи описания указателей:
float * pm[5];// массив указателей на тип float из 5 элементов
float (*pz) [5];// указатель на массив из 5 элементов типа float
Запомните последний случай. В нем описан указатель pz на одномерный массив из 5 элементов. Описание таких указателей нам понадобится в следующем разделе.
Далее возникает вопрос. А как использовать указатели на объекты при обработке данных? Дело в том, что указатель можно использовать с одной стороны как обычную переменную (в пределах разрешенных для этой переменной операций), а с другой стороны, появляется дополнительная возможность выполнять операции над полем, на которое указывает хранимый в указателе адрес.
1.2. Типы указателей и операции над ними
Основные типы указателей определяются ключевыми словами char, int, float, long, double, short, unsigned, signed, void. Возможны также типы объектов, описываемые программистом, но с этой возможностью мы будем знакомиться позже. Напомним, что тип void предполагает отсутствие значения, а указатели типа void имеют некоторые особенности, которые должны учитываться при выполнении операций над такими указателями (с ними познакомимся в последующих главах).
Операции над указателями можно представить следующим списком:
- Операция разыменования, или доступа по адресу - * указатель;
- Преобразование типов (приведение типов);
- Присваивание;
- Получение (взятия) адреса- &;
- Сложение и вычитание (аддитивные операции);
- Инкремент, или автоувеличение – (++);
- Декремент, или автоуменьшение (--);
- Операции отношений (сравнения).
Операция разыменования
Операция разыменования указателя ‘*’имеет формат : *указатель. Операция разыменования обеспечивает доступ к полю, адрес которого содержится в указателе. Унарное выражение *указатель обладает в некотором смысле правами переменной. Рассмотрим следующий пример:
int z =20;
int * p = &z;
printf (“z = %d *p = %d\n”, z, *p);
*p = 30;
printf (“z = %d *p = %d\n”, z, *p);
Выражение *p в этом примере служит синонимом имени z. При выполнении операторов примера на мониторе получим следующее:
z = 20 *p = 20
z= 30 *p = 30
Таким образом, выражение *указатель может использоваться практически везде, где допустимо использование имен объектов того типа, к которому относится указатель. Это утверждение справедливо лишь в том случае, если указатель инициализирован при объявлении явным образом или ему присвоен адрес конкретного объекта либо значение уже инициализированного указателя.
Отметим важную особенность операции разыменования. Результат этой операции зависит не только от значения указателя, но и от его типа. Дело в том, что при доступе к памяти с помощью указателя требуется информация не только о размещении объекта, но и о размерах участка памяти, который будет использоваться. Эту дополнительную информацию компилятор получает из типа указателя. Указатель char * pc; при обращении к памяти будет использовать участок памяти в 1 байт. Указатель long double * pd; будет использовать участок в 10 смежных байт памяти.
Запомните также, что операцию разыменования нельзя непосредственно применить к указателю типа void *! Ниже приводится пример такой неправильного применения этой операции:
int x = 50;
void p = &x;
printf (“*p = %d \n”, *p);// здесь ошибка!
Ошибка заключается в том, что операция *p невыполнима, т. к. при использовании указателя типа void в операции разыменования тип поля, на которое он указывает, неизвестен (это тип void, т. е. он “никакой”). Если же все-таки необходимо посмотреть какое – либо поле, на которое указывает такой p, то нужно одновременно с операцией разыменования использовать операцию преобразования типа. Но об этом смотрите далее.