Другим способом расширения набора типов, данных в языке программирования С являются указатели (K&R 5.1). Указатель представляет собой целое число (того или иного размера), обозначающее адрес в памяти, где находятся реальные данные.

Знание структуры реальных данных и принципы работы с ними, а также выделение блока памяти требуемого размера заранее и его освобождение, когда он больше не нужен, - это достаточно сложное дело. К счастью, язык Objective-C берет практически всю работу на себя и заботится о программисте настолько, что все, что вам действительно нужно знать, чтобы использовать указатели, - это то, что они существуют и какие обозначения нужны для обращения к ним.

Начнем с простого объявления. Если мы хотим объявить в языке программирования С целое число, мы пишем

int i;

Эта строка гласит: "i является целым числом': Теперь объявим указатель на целое число:

int* intPtr;

Смысл этой строки - "intPtr является указателем на целое число': Не имеет значения, откуда мы знаем, что по адресу, указываемому данным указателем, находится целое число, - здесь я только показываю обозначения, используемые при работе с указателями. Звездочку в объявлении можно ставить и перед именем, а не после типа данных:

int *intPtr;

Можно даже поставить с обеих сторон от звездочки по пробелу (хотя в реальных исходных текстах это делают редко):

int * intPtr;

Я предпочитаю первую запись, но временами прибегаю и ко второй, и компания Apple также достаточно часто ее использует, так что вы должны четко понимать, что это разные способы записи одной и той же сущности. Не имеет значения, сколько пробелов вы добавите, - имя типа будет in t *. Если вы спросите, какому типу данных принадлежит переменная intPtr, ответом будет int* (указатель на int); звездочка в данном случае является частью имени типа этой переменной3• Если вам надо выполнить приведение переменной р к этому типу, следует записать выражение наподобие ( in t *) р. В этом выражении вполне допускаются пробелы перед звездочкой, так что-то же выражение может быть записано как ( int *) р.

Наиболее общим типом указателя является обобщенный указатель, или "указатель на void" (void*). Обобщенный указатель может использоваться везде, где используется указатель на конкретный тип данных. По сути, этот указатель опускает все проверки того, что именно находится в указываемом месте. Таким образом, следующий код вполне корректен:

int* pl; // Считаем, что pl имеет конкретное значение
void* р2
р2 = pl;
pl = р2;

Указатели очень важны в языке Objective-C, поскольку в нем почти все является объектами (см. главу 2), а каждая переменная, ссылающаяся на объект, сама по себе является указателем. По сути, язык Objective-C использует преимущества того факта, что указатель в языке С может ссылаться на реальные данные произвольной природы. В данном случае реальные данные представляют собой объект языка Objective-C. Язык Objective-C знает, что это значит, но вас это не должно беспокоить - вы просто работаете с указателем языка С и позволяете Objective-C позаботиться о деталях. Например, я уже упоминал о том, что строковый тип в языке Objective-C называется NSString. Таким образом, способ объявления переменной NSString выглядит как объявление указателя на NSString:

NSString* s;

Литерал NSString представляет собой значение NSString, так что можно объявить и инициализировать этот объект NSString, записав действительно полезную строку кода Objective-C:

NSString* s = @"Hello, world!";

В языке С, объявив указатель на целое значение под именем intPtr, вы позже обращаетесь к этому значению в коде, как в *intPtr. Это обозначение вне объявления означает "то, на что указывает указатель intPtr". Вы используете запись * intPtr, так как хотите получить доступ к целому числу "на дальнем конце" указателя, в том месте, куда он указывает. Такой процесс называется разыменованием указателя.

Но в языке Objective-C в общем случае все обстоит не так. В вашем коде вы будете рассматривать указатель на объект как объект; вам никогда не придется его разыменовывать. Так, например, объявив s как указатель на NSString, вы не будете использовать запись *s; вы будете просто использовать s так, как будто это просто строка. В языке Objective-C для выполнения операций над объектом используется указатель, который на него ссылается, а не сам объект. Язык Objective-C "за кулисами" сам позаботится обо всех необходимых действиях по обращению к блоку памяти, на который указывает переданный указатель, и сделает все, что нужно, в этом блоке памяти. Это чрезвычайно удобно для программиста, но приводит к определенным терминологическим неточностям: как правило, говорят, что "s является объектом класса NSString~ хотя, конечно же, на самом деле это указатель на объект класса NSString.

Логика работы указателей как в языке С, так и в языке Objective-C отличается от логики работы простых типов данных. Разница становится особенно очевидной в случае присваивания. Присваивание простых типов, данных изменяет значение данных. Присваивание указателя перенаправляет его на другие данные. Предположим, что ptrl и ptr2 - указатели, и мы имеем код

ptrl = ptr2;

Теперь ptrl и ptr2 указывают на одно и то же место в памяти. Любые изменения данных, на которые указывает ptrl, будут изменять данные, на которые указывает ptr2, потому что их значения одинаковы (рис. 1.1). При этом на то, на что раньше, до присваивания, указывал указатель ptrl, теперь может не указывать ни один указатель, и это может быть очень плохо. Твердое понимание этих фактов имеет решающее значение при программировании на языке Objective-C, и мы еще вернемся к этой теме в главе 3.


Похожие статьи

 

 

 

Добавить комментарий