Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
УМК ПП1 (C++ БД).doc
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
5.01 Mб
Скачать

4.4. Использование указателей

Указатель – это переменная, содержащая адрес ячейки памяти, по которому записана другая переменная. Ссылка на значение посредством указателя называется косвенной адресацией.

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

type *ptr;

где type – один из предопределенных или определенных пользователем типов, a ptr – указатель (pointer – англ.). Читается это объявление так: «ptr является указателем на значение типа type». Например

int *ptrInt;

объявляет переменную ptrInt типа int * (т.е. указатель на целое число). Каждая переменная, объявляемая как указатель, должна иметь перед собой знак звездочки (*).

Может быть объявлен и указатель на void;

void *ptrV;

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

ptrV = ptrInt;

Для 32-разрядной операционной системы в оперативной памяти ПК для переменной типа «указатель» всегда отводится 4 байта – таков размер поля адреса в машинных командах. Однако размер поля памяти, на которое указывает такая переменная, является произвольным и зависит от объявления указателя. Ниже приведено описание трех указателей:

int *ptrA; byte *ptrB; double *ptrC;

Здесь переменная ptrA указывает на поле из 4-х байтов, переменная ptrB – подразумевает обращение к однобайтному полю, а ptrC – указывает на 8 байтов.

В C++Builder указатели используются очень широко. В частности, все компоненты, формы и т.д. объявляются именно как указатели на соответствующий объект. Посмотрев заголовочный файл любого приложения, вы увидите в нем объявления вида:

TForm1 *'Form1;

TButton *Buttonl;

Указатели должны инициализироваться либо при своем объявлении, либо с помощью оператора присваивания. Указатель может получить в качестве начального значения NULL или адрес. Указатель с начальным значением NULL ни на что не указывает. NULL это символическая константа, определенная специально для цели показать, что данный указатель ни на что не указывает. Пример объявления указателя с его инициализацией:

int * ptrInt = null;

Совет программисту: Присваивайте указателям при их объявлении значение NULL или адрес. Неинициализированный указатель может при работе с ним приводить к самым неожиданным результатам. Сохраняйте в наименовании переменной-указателе аббревиатуру ptr, отражающую природу этой переменной.

Для присваивания указателю адреса некоторой переменной используется операция адресации &, которая возвращает адрес своего операнда. Например, если имеются объявления

int у;

int *mPtr;

то оператор

mPtr = &у;

присваивает адрес переменной у указателю mPtr.

Для того чтобы получить значение, на которое указывает указатель, используется операция *, обычно называемая операцией косвенной адресации или операцией разыменования. Она возвращает значение объекта, на который указывает ее операнд (т.е. указатель). Например, если продолжить приведенный выше пример, то оператор

x = *mPtr присвоит переменной х значение 5, т.е. значение переменной у, на которую указывает mPtr.

Суть операции разыменования состоит в том, что система C++Builder обращается к байту оперативной памяти , адрес которого записан в переменной-указателе (в нашем примере – mPtr), считывает столько подряд идущих байтов, сколько подразумевает тип данного, на который настроен указатель (в нашем случае тип указателя –int, следовательно , будет считано 4 байта). Далее эти 4 байта интерпретируются как целое число, значение которого и передаётся (записывается) в переменную x.

Операцию косвенной адресации нельзя применять к указателю на void, поскольку для него неизвестно, какой размер памяти надо разыменовывать.

Массивы и указатели в Си++ тесно связаны и могут быть использованы почти эквивалентно. Имя массива можно понимать как константный указатель на первый элемент массива. Его отличие от обычного указателя только в том, что его нельзя модифицировать.

Указатели можно использовать для выполнения любой операции, включая индексирование массива. Пусть вы сделали следующее объявление:

int b[5] = {1,2,3,4,5}, *ptrI;

Тем самым вы объявили массив целых чисел b[5] и указатель на целое ptrI. Поскольку имя массива является указателем на первый элемент массива, вы можете задать указателю ptrI адрес первого элемента массива b с помощью оператора

ptrI =b;

Это эквивалентно присваиванию адреса первого элемента массива следующим образом

ptrI = &b[0] ;

Теперь можно сослаться на элемент массива b[3] с помощью выражения *( ptrI + 3).

Указатели можно индексировать точно так же, как и массивы. Например, выражение ptrI [3] ссылается на элемент массива b[3].

Таким образом, манипуляции, определенные для массивов, определены и для указателей на массивы. Но с точки зрения понятности программы лучше это без крайней необходимости не использовать. Указатели могут применяться как операнды в арифметических выражениях, выражениях присваивания и выражениях сравнения. Однако не все операции, обычно используемые в этих выражениях, разрешены применительно к переменным указателям.

С указателями может выполняться ограниченное количество арифметических операций. Указатель можно увеличивать (+), уменьшать (-), складывать с указателем целые числа, вычитать из него целые числа или вычитать один указатель из другого. Сложение указателей с целыми числами отличается от обычной арифметики. Прибавить к указателю 1 означает сдвинуть его на число байтов, содержащихся в переменной, на которую он указывал. Обычно подобные операции применяются к указателям на массивы. Если продолжить приведенный выше пример, в котором указателю ptrI было присвоено значение b указателя на первый элемент массива, то после выполнения оператора

ptrI= ptrI +2;

ptrI будет указывать на третий элемент массива b. Истинное же значение указателя Pt изменится на число байтов, занимаемых одним элементом массива, умноженное на 2. Например, если каждый элемент массива b занимает 2 байта, то значение ptrI (т.е. адрес в памяти, на который указывает ptrI) увеличится на 4.

Аналогичные правила действуют и при вычитании из указателя целого значения. Переменные указатели можно вычитать один из другого. Например, если ptrI указывает на первый элемент массива b, а указатель ptrI3 – на третий, то результат выражения ptrI3-ptrI будет равен 2 разности индексов элементов, на которые указывают эти указатели. И так будет, несмотря на то, что адреса, содержащиеся в этих указателях, различаются на 4 (если элемент массива занимает 2 байта).

Операции отношения == и != имеют смысл для любых указателей. При этом указатели равны, если они указывают на один и тот же адрес в памяти.

Указатель можно присваивать другому указателю, если оба указателя имеют одинаковый тип. В противном случае нужно использовать операцию приведения типа, чтобы преобразовать значение указателя в правой части присваивания к типу указателя в левой части присваивания. Исключением из этого правила является указатель на void (т.е. void*), который является общим указателем, способным* представлять указатели любого типа. Указателю на void можно присваивать все типы указателей без приведения типа. Однако указатель на void не может быть присвоен непосредственно указателю другого типа, указатель на void сначала должен быть приведен к типу соответствующего указателя.

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

Например, вы можете сделать следующее объявление:

char *stroki[2] = {"Первая строка", "Вторая строка"};

вы объявили массив размером 2, состоящий из элементов типа (char *). Каждый элемент такого массива строка. Но в C++ строка является, по существу, указателем на ее первый символ. Таким образом, каждый элемент в массиве строк в действительности является указателем на первый символ строки. Каждая строка хранится в памяти как последовательность символов, завершающаяся условным нулём. Число символов в каждой из строк может быть различным. Таким образом, массив указателей на строки позволяет обеспечить доступ к строкам символов любой длины.

Указатели широко используются при передаче параметров в функции.

Ссылки

Ссылки – это специальный тип указателя, который позволяет работать с указателем как с объектом. Объявление ссылки делается с помощью операции ссылки, обозначаемой амперсандом (&) – тем же символом, который используется для адресации. Если в вашей программе имеется указатель на объект какого-то типа MyObject:

MyObject *P = new MyObject;

то вы можете создать ссылку на этот объект оператором:

MyObject & Ref = *Р;

Объявленная таким образом переменная Ref является ссылкой на объект MyObject. Она может рассматриваться как псевдоним объекта. Эта переменная реально является указателем, а не самим объектом. Но работа с ней производится как с объектом. Например, если вы хотите получить доступ к некоторому свойству объекта х, то через указатель на объект вы обеспечиваете доступ выражением Р->х, т.е. через операцию стрелка. А через ссылку вы обеспечиваете доступ к свойству х выражением Ref.x, т.е. через операцию точка.

Аналогичным образом вы можете получить доступ по ссылке и к любым компонентам. Например, если в вашем приложении имеется метка Label2, то вы можете обращаться к его свойству Caption оператором

Label2->Caption = "Это обращение по указателю";

А можете ввести соответствующую ссылку и обращаться через нее:

TLabel & ref = *Label2;

ref.Caption =s "Это обращение по ссылке";

Вопросы для самопроверки

  1. Объясните конструкцию данных типа TDateTime.

  2. Каким образом можно определить интервал в днях между двумя датами, указанными переменными типа TDateTime.

  3. Какие компоненты интерфейса можно использовать для ввода даты ?

  4. Приведите пример двумерного числового массива и способы его представления на экране.

  5. В чем состоят особенности компонента «Сетка строк»?

  6. Приведите особенности обработки числового двумерного массива, если он содержится в компоненте StringGrid?

  7. Каким образом в приложении можно построить навигатор типа «Проводник»?

  8. В чём состоят отличие компонента OpenDialog от группы DriveComboBox, ВirectoryListBox и FileListBox?

  9. Что такое «косвенная адресация» и с какой целью она используется?

  10. В чём состоит отличие типизированного указателя от нетипизирован-ного?

  11. Поясните различие в понятиях «присваивание указателю значения переменной» и «присваивание указателю адреса переменной».

  12. В чем состоят особенности передачи массивов в вызываемую подпро-граммму (функцию) по значению и по наименованию?

  13. Поясните понятие «ссылки».