Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кетков.doc
Скачиваний:
17
Добавлен:
27.09.2019
Размер:
2.22 Mб
Скачать

Раздел 7. Указатели и ссылки

Указатели – это переменные специального типа, значениями которых является адреса различных объектов программы. Если мы используем имя того или иного объекта для извлечения его значения или для изменения его значения, то принято говорить о непосредственном (прямом) доступе к объекту. В том случае, когда адрес объекта помещен в указатель, то речь идет о косвенном доступе к объекту, на который "смотрит" указатель.

Идеи такой косвенной адресации зародились еще в архитектуре ЭВМ первого поколения, когда адрес нужной ячейки памяти помещался в специальный регистр (например, РА – регистр адреса в ЭВМ типа М-20). Доступ по содержимому регистра предоставлял программистам более широкие возможности за счет наличия машинных команд изменения содержимого такого регистра (автоматическое увеличение-инкрементирование или уменьшение-декрементирование на 1) и использование РА в командах организации циклов. Наиболее полное воплощение идеи косвенной адресации нашли в проекте адресного языка, разработанного профессором Е.Л. Ющенко (Институт кибернетики АН УССР, Киев).

7.1. Объявление указателей

В языках C, C++ различают три категории указателей. Первая категория указателей предназначена для хранения адресов данных определенного типа (по терминологии языка Паскаль – типизированные указатели). При их объявлении указывается тип данных, на которые эти указатели могут "смотреть". Ко второй категории относятся указатели, которые могут "смотреть" на данные любого типа (по терминологии языка Паскаль – не типизированные указатели). При их объявлении используется служебное слово void. Наконец, третью группу составляют указатели, значениями которых могут быть только адреса точек входа в функции (по терминологии языка Паскаль – данные процедурного типа). Объявление и использование указателей разных категорий имеет свою специфику.

Для объявления одного указателя с именем p1 или нескольких указателей с именами p1, p2, …, которые должны будут "смотреть" на объекты типа type1, используется одна из следующих синтаксических конструкций:

type1 *p1;

type1 *p1,*p2,...;

Объявленные указатели еще никуда конкретно не смотрят – в выделенных им участках памяти находится "грязь". И одна из типичных ошибок начинающих программистов – попытка записать что-либо по указателям, которым еще не присвоены значения. Инициализацию указателя можно совместить с его объявлением:

int x=2,y;

int *p1=&x; //инициализация адресом переменной x

int *p2(&x); //инициализация адресом переменной x

int *p3=p1; //инициализация значением другого указателя

Указатель является переменной и его значение можно задать или изменить с помощью оператора присваивания:

p1=&y; //теперь значением p1 является адрес переменной y

Если целочисленному указателю p1 присваивается имя массива a или его адрес, то это эквивалентно засылке в p1 адреса первого элемента массива a[0]:

int a[10];

int *p1=a; //p1 смотрит на начало массива a

int *p2=&a[0]; //p2 тоже смотрит на начало массивa a

int *p3=&a; //p3 тоже смотрит на начало массивa a

Когда указатель p1 "смотрит" на переменную x, то по значению указателя можно извлечь значение переменной x или изменить его:

int x=5,y;

int *p1=&x; //значением p1 является адрес x

..........

y=*p1; //теперь значение переменной y равно 5

*p1=2; //теперь значение переменной x равно 2

Когда указатель p2 "смотрит" на начало массива q, то доступ к элементам этого массива можно организовать одним из следующих способов:

int q[20];

int p2=q;

...........

y=*(p2+5); //теперь y=q[5]

x=p2[3]; //теперь x=q[3]

*(p+1)=7; //теперь q[1]=7

В программах на языке C можно встретить нагромождение символов "*". Пугаться не надо – это просто многоступенчатая адресация:

int x,y;

int *p1=&y;

int **p2=&p1;

x=**p2; //то же, что x=*(*p2)=*(p1)=y

Объявление не типизированного указателя выглядит следующим образом:

void *pu;

Не типизированному указателю может быть присвоено значение указателя любого типа. Однако непосредственно извлечь или изменить значение по не типизированному указателю нельзя. Приходится прибегать к приведению типов:

#include <iostream.h>

#include <conio.h>

void main()

{ int x=5;

void *p=&x;

int *p1;

p1=(int*)p; //приведение указателя p к типу int*

cout<<"x="<<*p1<<endl;

getch();

}

Для объявления указателя pf на функцию типа double f(double x) имя указателя заключается в круглые скобки:

double (*pf)(double x);

Обратите внимание, что вместо имени функции здесь указано имя указателя в круглых скобках. А в качестве его конкретного значения можно задать адрес любой функции с аргументом типа double, которая возвращает значение типа double:

#include <iostream.h>

#include <conio.h>

#include <math.h>

void main()

{ double (*pf)(double x); //объявление указателя на функцию

double x=0.2;

pf=sin; //присвоение значения указателю

cout<<"sin(0.2)="<< pf(x) <<endl; //обращение по указателю

cout<<"sin(0.2)="<<(*pf)(x)<<endl;//обращение по указателю

getch();

}

Из двух возможных вариантов обращения к функции по указателю, наверное, надо отдать предпочтение первому, как более естественному. Хотя второй более выдержан в плане философии использования указателей.