Указатели(Pointer) позволяют эффективно представлять сложные структуры данных, изменять значения, передаваемые в виде аргументов функциям и методам, а также проще и эффективнее работать с массивами.
В конце этой статьи мы расскажем, насколько они важны для реализации объектов в языке Objective-C.
Чтобы понять, как действуют указатели, вы должны сначала ознакомиться с понятием косвенного обращения(indirection). Мы часто встречаемся с этим понятием в повседневной жизни.
Предположим, мне нужно купить новый картридж с тонером для моего принтера. В компании, где я работаю, все приобретения выполняются через отдел снабжения, поэтому я звоню сотруднику этого отдела и прошу заказать для меня новый картридж.
Он звонит в местный магазин запчастей, чтобы заказать картридж. Для получения картриджа я применяю косвенный подход вместо прямого заказа картриджа в магазине.
Понятие косвенного подхода можно применять и к указателями в Objective-C. Указатель(Pointer) — это средство косвенного доступа к значению определенного элемента данных. Рассмотрим, как действуют указатели. Определим переменную с именем count:
1 |
int count = 10; |
С помощью следующего объявления мы можем определить другую переменную с именем intPtr для косвенного доступа к значению переменной count.
1 |
int * intPtr; |
Звездочка показывает, что переменная intPtr является указателем на тип int. Это означает, что программа будет использовать intPtr для косвенного доступа к значению одной или нескольких целых переменных.
Мы уже использовали в предыдущих программах оператор & при обращении к scanf. Этот унарный оператор, который называют адресным(address) оператором, создает указатель на переменную в Objective-C.
Например, если x — переменная определенного типа, то выражение &x является указателем(Pointer) на эту переменную. Если нужно, вы можете присвоить выражение &x любой переменной-указателю, объявленной как указатель на тип, которой имеет переменная x.
Оператор:
1 |
intPtr = &count; |
задает косвенную связь между intPtr и count. Адресный оператор & присваивает переменной intPtr не значение переменной count, а указатель на переменную count.
На рис.1 показана связь между intPtr и count. Линия со стрелкой показывает, что intPtr содержит не само значение count, а указатель на переменную count.
Рис.1. Указатель на переменную целого типа
Чтобы указать содержимое count с помощью переменной-указателя intPtr, используется оператор косвенного обращения <звездочкой>(*). Если переменная x была определена с типом int, то оператор:
1 |
x = *intPtr; |
присвоит переменной x значение, которое указывается путем косвенного обращения через intPtr. Поскольку выше мы задали, что intPtr указывает на переменную count, результатом этого оператора будет присваивание переменной x значения, содержащегося в переменной count(10).
В программе 1.5 показано использование двух основных операторов для указателей: адресного оператора (&) и оператора косвенного обращения (*).
Программа 1.5:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Программа, показывающая использование указателей #import <Foundation/Foundation.h> int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int count = 10, x; int *intPtr; intPtr = &count; x = *intPtr; NSLog(@"count = %i, x = %i", count, x); [pool drain]; return 0; } |
Вывод программы 1.5:
1 |
count = 10, x = 10 |
Переменные count и x объявляются обычным образом как целые переменные. В следующей строке переменная intPtr объявляется как <указатель на тип int>. Эти две строки можно было бы объединить в одну:
1 |
int count = 10, x, *intPtr; |
После этого к переменной count применяется адресный оператор (&), результатом которого является создание указателя на эту переменную, который затем присваивается переменной intPtr.
Выполнение строки:
1 |
x = *intPtr; |
происходит следующим образом. Оператор косвенного обращения (*) сообщает системе Objective-C, что переменная intPtr содержит указатель на другой элемент данных. Этот указатель используется для доступа к нужному элементу данных, тип которого задан при объявлении переменной-указателя.
При объявлении переменной мы сообщили компилятору, что переменная intPtr указывает на целые элементы данных, поэтому компилятор <понимает>, что значение, указываемое выражением *intPtr, является целым. Кроме того, в предыдущей строке мы задали, что intPtr указывает на целую переменную count, поэтому это выражение представляет косвенный доступ к значению count.
В программе 1.6 иллюстрируются некоторые интересные свойства переменных-указателей. В ней используется указатель на символ.
Программа 1.6:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#import <Foundation/Foundation.h> int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; char c = "Q"; char *charPtr = &c; NSLog(@"%c%c", c, *charPtr); c = '/'; NSLog(@"%c%c, c, *charPtr); *charPtr = '('; NSLog(@"%c%c", c, *charPtr); [pool drain]; return 0; } |
Вывод программы 1.6:
1 2 3 |
QQ // (( |
Символьная переменная «c» определяется и инициализируется со значением ‘Q‘. В следующей строке этой программы определяется переменная charPtr как <указатель на тип char>. Это означает, что любое значение, сохраняемое внутри этой переменной, следует интерпретировать как косвенное обращение(указатель) к символу.
Этой переменной можно присвоить начальное значение обычным образом. В этой программе переменной charPtr присваивается указатель на переменную «c«, и для этого к переменной «c» применяется адресный оператор &.
Эта инициализация приведет к появлению ошибки компилятора, если определить «c» после этого оператора, поскольку любая переменная должна быть объявлена раньше ссылки на эту переменную в любом выражении.
Объявление переменной charPtr и присваивание ее начального значения можно было бы представить в двух отдельных строках:
1 2 |
char *charPtr; charPtr = &c; |
Но не:
1 2 |
char *charPtr; *charPtr = &c; |
как можно было бы предположить из объявления в одной строке.
Значение указателя в Objective-C не существует, пока мы не зададим, на какой элемент данных он указывает.
В строке первого вызова NSLog выводится содержимое переменной «c» и содержимое этой переменной, указанное с помощью charPtr. Поскольку мы задали, что charPtr указывает на переменную «c«, выводится и содержимое «c«, что подтверждается первой строкой вывода программы.
В следующей строке программы символьной переменной «c» присваивается символ ‘/’. Поскольку charPtr по-прежнему указывает на «c», в следующем вызове NSLog для значения *charPtr на терминал выводится новое значение переменной «c».
Это важно. Если значение charPtr не изменяется, то выражение *charPtr всегда означает доступ к значению «c». Таким образом, если изменяется значение переменной «c», то изменяется и значение выражения *charPtr.
Мы говорили, что если значение charPtr не изменено, то выражение *charPtr всегда является ссылкой на значение «c». Поэтому в выражении:
1 |
*charPtr = '('; |
переменной «c» присваивается символ <левая круглая скобка>. Более формально символ ‘(‘ присваивается переменной, на которую указывает charPtr. Мы знаем, что это переменная «c», поскольку в начале программы мы присвоили charPtr указатель на «c».
Эти концепции являются ключом к пониманию действия указателей. Продумайте их снова, если что-то осталось неясным или вы что-то не поняли.