355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Стивен Кочан » Программирование на Objective-C 2.0 » Текст книги (страница 13)
Программирование на Objective-C 2.0
  • Текст добавлен: 19 сентября 2016, 13:02

Текст книги "Программирование на Objective-C 2.0"


Автор книги: Стивен Кочан



сообщить о нарушении

Текущая страница: 13 (всего у книги 32 страниц)

Если вы реализуете все необходимые методы для определенного протокола, вы подчиняетесь (conform) этому протоколу или принимаете (adopt) его.

Протокол определяется просто: укажите имя протокола после директивы @protocol. После этого нужно объявить методы так же, как в секции interface. Все объявления методов вплоть до директивы @end становятся частью данного протокола.

При работе с Foundation framework вы увидите, что несколько протоколов уже определены. Один из них, NSCopying, объявляет метод, который вам потребуется реализовать, если ваш класс должен поддерживать копирование объектов с помощью метода сору (или copyWrthZone:). (Подробно тема копирования объектов рассматривается в главе 18.)

Ниже показано, как определяется протокол NSCopying в стандартном файле Foundation NSObject.h. @protocol NSCopying – (id)copyWithZone: (NSZone *}zone; @end

Если вы приняли протокол NSCopying в своем классе, то должны реализовать метод copyWithZone:. Вы сообщаете компилятору, что принимаете протокол, заключая имя этого протокола в угловые скобки (<...>) в строке @interface. Имя про школа указывается после имени класса и его родительского класса, как в следующей строке: @interlace AddressBook: NSObject

В этой строке сообщается, что AddressBook является объектом с родительским классом NSObject и подчиняется протоколу NSCopying. Поскольку система уже знает о методах, определенных ранее для этого протокола (в данном случае – из файла NSObject.h), эти методы не нужно объявлять в секции interface. Однако их нужно определить в вашей секции implementation.

В данном примере в секции implementation для AddressBook компилятор предполагает обнаружить определение метода copyWithZone:.

Если ваш класс принимает более одного протокола, просто перечислите их в угловых скобках, разделяя запятыми: @interface AddressBook: NSObject

Здесь компилятору сообщается, что класс AddressBook принимает протоколы NSCopying и NSCoding. В данном случае компилятор предполагает обнаружить определение всех требуемых методов (перечисленных в этих протоколах) в секции implementation для AddressBook.

Определив свой собственный протокол, вы не обязаны реализовать его сам и. Вы уведомляете других программистов, что если они хотят принять этот протокол, то должны реализовать соответствующие методы. Эти методы могут наследоваться из суперкласса. Так, если класс подчиняется протоколу NSCopying, это действительно и для его подклассов (хотя и не означает, что эти методы правильно реализованы для данного подкласса).

Протокол позволяет определить методы, которые будут реализовать другие люди, использующие подкласс нашего класса. Например, вы можете определить протокол Drawing для своего класса GraphicObjcct; п нем можно определить методы paint (окраска), erase (стирание) и outline (контур). @protocol Drawing -(void) paint; -(void) erase; @optional -(void) outline; @end

Создав класс GraphicObject, вы не обязаны реализовать эти методы, однако вы должны указать методы, которые должен реализовать человек, создающий подкласс класса GraphicObject, чтобы соответствовать стандарту для создаваемых объектов рисования.

Примечание. Любые методы, которые указаны после директивы @optional директивы, не являются обязательными. Что человек, принявший протокол Drawing, не обязан реализовать метод outline, подчиняясь этому протоколу. (Вы можете вернуться к перечислению обязательных протоколов с помощью директивы @required в определении протокола.)

Таким образом, если вы создаете подкласс Rectangle класса GraphicObject и объявляете (то есть документируете), что ваш класс Rectangle подчиняется протоколу Drawing, пользователи данного класса будут знать, что они могут передавать экземплярам этого класса сообщения paint, erase и (возможно) outline.

Примечание. Это теория. Компилятор позволяет вам указать, что вы подчиняетесь протоколу, и выдает предупреждающие сообщения, только если вы не реализуете эти методы.

Отметим, что в протоколе нет ссылки ни на какие классы; это «бесклассовое» средство. Протоколу Drawing может подчиняться любой класс, не только подклассы GraphicObject.

Чтобы проверить, подчиняется ли объект какому-либо протоколу, можно использовать метод conformsToProtocol:. Например, вы хотите определить, подчиняется ли объект с именем currentObject протоколу Drawing, чтобы передавать этому объекту сообщения для рисования. Для этого можно написать следующее. id currentObject; ... if ([currentObject conformsToProtocol: @protocol (Drawing)] == YES) { // Передача сообщений currentObject paint, erase и/или outline ... }

Специальная директива @protocol, которая используется здесь, принимает имя протокола и создает объект типа Protocol, который используется как аргумент методом conformsToProtocol:.

Вы можете воспользоваться помощью компилятора, чтобы проверить согласование с вашими переменными, заключив имя протокола в угловые скобки после имени типа: id currentObject;

Это указывает компилятору, что cunentObject будет содержать объекты, подчиняющиеся протоколу Drawing. Если присвоить currentObject объект статического типа, который не согласуется с протоколом Drawing (например, у вас есть несогласуюш ийся класс Square), то компилятор выдаст предупреждающее сообщение: warning: class 'Square' does not implement the 'Drawing' protocol (предупреждение: класс 'Square' не реализует протокол 'Drawing')

Здесь проверку выполняет компилятор, поэтому присваивание currentObject переменной типа id не приведет к выводу этого сообщения. Ддля объекта, хранящегося в переменной типа id, компилятор не сможет определить, подчиняется ли он протоколу Drawing.

В списке можно указать более одного протокола, если переменная будет содержать объект, подчиняющийся нескольким протоколам: id myDocument;

Определение протокола можно расширить. В следующем объявлении протокола указывается, что протокол Drawing3D принимает также протокол Drawing. @protocol Drawing3D

Таким образом, класс, который принимает протокол Drawing3D, должен реализовать методы, перечисленные для этого протокола, а также методы из протокола Drawing.

И, наконец, категория тоже может принимать протокол, например: @interface Fraction (Stuff)

Здесь Fraction содержит категорию Stuff, которая принимает протоколы NSCopying и NSCoding.

Как и имена классов, имена протоколов должны быть уникальными. Неформальные протоколы

В литературе встречается понятие неформального, свободного (informal) протокола. На самом деле это категория, содержащая список группы методов, но не реализующая их. Все (или почти все) наследуется из одного корневого объекта, поэтому неформальные категории часто определяются для корневого класса. Иногда неформальные протоколы называют также абстрактными (abstract) протоколами.

В header-файле могут встретиться объявления методов, которые выглядят следующим образом: @interface NSObject (NSComparisonMethods) -(BOOL)isEqualTo:(id)object; -(BOOL)isLessThanOrEqualTo:(id)object; -{B00L}isLessThan:(id)object; -

Здесь определяется категория с именем NSComparisonMethods для класса NSObject. В этом неформальном протоколе содержится список группы методов (в данном случае – девять), которые могут быть реализованы как часть этого протокола. Неформальный протокол – это фактически просто группа методов под определенным именем. Это полезно с точки зрения документирования и модульной организации методов.

Класс, где объявляется неформальный протокол, не реализует методы в самом этом классе. В подклассе, выбранном для реализации этих методов, требуется переобъявить их в секции interface, а также реализовать один или несколько из этих методов. В отличие от формальных протоколов, компилятор не оказывает никакой помощи с неформальными протоколами: здесь нет никакой концепции подчинения или проверки компилятором.

Если объект принимает какой-либо формальный протокол, этот объект должен подчиняться всем требуемым сообщениям в этом протоколе. Это можно сделать как на этапе выполнения (runtime), так и во время компиляции. Если объект принимает неформальный протокол, то он не обязан принять все методы данного протокола (в зависимости от самого протокола). Подчинение неформальному протоколу можно сделать обязательным на этапе выполнения (с помощью respondsToSelector:), но не во время компиляции.

Примечание. Описанную выше директиву @optional (она была добавлена в Objective-C 2.0) можно использовать вместо неформальных протоколов. Она используется в нескольких классах UIKit (UIK.it – составная часть структур Cocoa Touch framework). 11.3. Составные объекты

Вы уже знаете несколько способов, позволяющих расширить определение класса с помощью таких средств, как подклассы и категории. Еще один способ – это определение класса, который содержит один или несколько объектов из других классов. Объект из этого класса называется составным (composite) объектом, поскольку он составлен из других объектов.

В качестве примера рассмотрим класс Square (квадрат), который мы определили в главе 8. Он был определен как подкласс класса Rectangle (прямоугольник), поскольку квадрат это прямоугольник с равными сторонами. Подкласс, который мы определяем наследует все переменные экземпляра и методы родительского класса. В некоторых случаях это нежелательно. Метод setWidth:andHeight: (задание ширины и высоты) класса Rectangle наследуется классом Square, но реально не относится к квадрату (хотя действует правильно). Кроме того, создавая подкласс, мы должны обеспечить правильность работы наследуемых методов, поскольку пользователи этого подкласса будут иметь к ним доступ.

Вместо подкласса можно определить новый класс, который содержит в качестве одной из своих переменных экземпляра объект из класса, который вы хотите расширить. Затем нужно определить в новом классе только те методы, которые для него подходят. В примере с классом Square можно определить Square следующим образом. @interface Square: NSObject { Rectangle *rect; } -(int) setSide: (int) s; -(int) side; (сторона) •(int) area; (площадь) -(int) perimeter; (периметр) @end

Здесь определен класс Square с четырьмя методами. В отличие от версии с подклассом, которая дает непосредственный доступ к методам класса Rectangle (setWidth:, setHeight:, setWidthiandHeight:, width и height), этих методов нет в определении для Square. Это имеет смысл, поскольку не все методы подходят для работы с квадратами.

Если мы определяем класс Square таким способом, то он становится ответственным за выделение памяти для прямоугольника (rectangle), который содержит. Например, без заметающих методов оператор Square *mySquare = [[Square alloc] init];

выделяет память для нового объекта типа Square, но не выделяет память для объекта типа Rectangle, хранящегося в его переменной экземпляра red.

Чтобы выполнить выделение памяти, требуется замещение метода init или добавление нового метода, например, initWithSide:. Этот метод может выделять память для переменной Rectangle red и задавать соответствующим образом сторону (side). Необходимо также заместить метод dealloc (как описано для класса Rectangle в главе 8), чтобы освободить память, используемую для Rectangle red, когда освобождается сам объект Square.

Определяя свои методы в классе Square, мы по-прежнему можем использовать методы класса Rectangle. Например, мы можем реализовать метод area следующим образом: -(int) area { return [rect area]; }

Реализацию остальных методов вы можете написать в качестве упражнения (см. ниже упражнение 5). Упражнения

Выполните расширение категории MathOps из программы 11.1, чтобы дополнительно включить метод invert, который возвращает дробь (Fraction), обратную получателю.

Добавьте в класс Fraction категорию с именем Comparison. В этой категории добавьте два метода в соответствии со следующими объявлениями. -(BOOL) isEqualTo: (Fraction *) 1; -(int) compare: (Fraction *) f; Первый метод должен возвращать значение YES, если две дроби равны, и значение N0 в противном случае. Правильно сравнивайте дроби (например, при сравнении дробей 3/4 и 6/8 следует возвращать значение YES). Второй метод должен возвращать значение -1, если получатель меньше дроби, представляемой аргументом; возвращать 0, если две дроби равны; возвращать 1, если получатель больше дроби, представляемой аргументом.

Выполните расширение класса Fraction, добавив методы, которые подчиняются неформальному протоколу NSComparisonMethods, описанному з этой главе. Напишите реализацию первых шести методов из этого протокола (isEqualTo:, isLessThanOrEqualTo:, isLessThan:, isGreaterThanOrEquaHo:, isGreaterThan:,isNotEqualTo:) и выполните их тестирование.

Функции sin (),cos() и tan {) включены в стандартную библиотеку Standard Library (как и scant ()). Эти функции объявлены в header-файле , который вы должны импортировать в программу с помощью строки #import Эти функции можно использовать для вычисления синуса, косинуса и тангенса аргумента типа double, выраженного в радианах. Возвращаемый результат является значением с плавающей точкой двойной точности. Например, для вычисления синуса аргумента d, где d – угол, выраженный в радианах, можно использовать следующую строку: result = sin (d); Добавьте категорию с именем Trig в класс Calculator, определенный в главе 6. Включите в эту категорию методы для вычисления синуса, косинуса и тангенса в соответствии со следующими объявлениями. -(double) sin; -(double) cos; -(double) tan;

Напишите секцию implementation для Square и выполните тестирование программы для проверки ее методов, используя описание составных объектов из этой главы и следующую секцию interface, @interface Square: NSObject { Rectangle *rect; } -(Square*) initWithSide: (int) s; -(void) setSide: (int) s; -(int) side; -(int) area; -(int) perimeter; -(void) dealloc; // Замещающий метод для освобождения памяти объекта типа Rectangle @end

Глава 12. Препроцессор

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

Препроцессор – это составная часть процесса компиляции Objective-C. Он распознает специальные операторы и обрабатывает их, прежде чем будет выполнен анализ самой программы. Операторы препроцессора идентифицируются знаком «решетка» (#), который должен быть первым символом строки, отличным от пробела. Синтаксис операторов препроцессора несколько отличаюется от обычных операторов Objective-C. Мы начнем с описания оператора #define. 12.1. Оператор # define

Оператор #define позволяет присваивать символические имена программным константам. Оператор препроцессора #define TRUE 1

определяет имя TRUE и делает его эквивалентным значению 1. Затем имя TRUE можно использовать в любом месте программы, где могла бы использоваться константа 1. Там, где появляется это имя, препроцессор автоматически подставляет вместо него значение 1. Например, следующий оператор Objective-C использует определенное имя TRUE. gameOver = TRUE;

Этот оператор присваивает значение TRUE переменной gameOver. Вам не обязательно помнить, какое конкретное значение вы определили для TRUE. Приведенный выше оператор будет присваивать 1 переменной gameOver. Оператор препроцессора #define FALSE О

определяет имя FALSE и делает его эквивалентным значению 0. Например, оператор gameOver = FALSE;

присваивает значение FALSE переменной gameOver, а оператор if ( gameOver == FALSE)

сравнивает значение gameOver с определенным значением FALSE.

Определенное таким образом имя не является переменной, поэтому вы не можете присвоить ему значение, если результатом подстановки значения не янляется переменная. Если в программе используется определенное имя, препроцессор автоматически подставляет то, что представлено в правой части оператора «define. Это аналогично операции поиска и замены втекстозом редакторе; в данном случае препроцессор заменяет все экземпляры определенного имени соответствующим текстом.

Отметим, что оператор «define имеет специальный синтаксис: для присваивания TRUE значения 1 не используется знак равенства. Кроме того, в конце оператора нет точки с запятой.

Операторы «define часто помещают ближе к началу программы, после операторов «import или «include, хотя они могут присутствовать в любом месте программы. Однако имя должно быть определено до того, как оно будет использовано. Определенные таким образом имена отличаются по своему поведению от переменных: не существует такого понятия, как локальный «define». После определения имени его можно использовать в любом месте программы. Большинство программистов помещают операторы define в header-файлы (*.h), чтобы использовать их в нескольких исходных файлах.

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

Поэтому мы можем включить в программу следующую строку: #define PI 3.141592654

Теперь эту константу можно использовать в обоих методах класса Circle (в предположении, что класс Circle содержит переменную экземпляра с именем radius). -(double) area { return PI * radius * radius; } -(double) circumference { return 2.0 * PI * radius; }

Назначение константы для символического имени освобождает вас от необходимости помнить значение константы. Кроме того, если потребуется изменить значение этой константы, то это нужно будет сделать только в одном месте программы: в операторе #define. В противном вам придется выполнить поиск по всей программе и явно изменить значение константы.

Во всех приведенных примерах операторов define использовались прописные буквы (TRUE, FALSE и PI). Это было сделано, чтобы визуально отличить определенное значение от переменной. Некоторые программисты записывают все определенные имена прописными буквами, чтобы сразу различать, что перед ними: переменную, объект, имя класса или определенное имя. Перед определенным именем принято ставить букву к. В этом случае прописными буквами обозначаются не все символы, например, kMaximumValues и kSignificantDigits.

Применение определенного имени для значения константы упрощает расширение программы. Для массивов вместо конкретного указания размера массива, который нужно выделить в памяти, можно определить нужное значение: #define МAXIМUM_DATA_VALUES 1000

Теперь на этом определенном значении можно основывать все ссылки на размер массива (например, чтобы выделить место для этого массива в памяти) и допустимые индексные значения.

Кроме того, если профамма использует MAXIMUM DATA VALUES во всех случаях, где нужно указать размер массива, то чтобы изменить размер массива, достаточно изменить только это определение. Более сложные типы определений

Определение имени может включать не только простое константное значение. Это может быть и выражение и почти все, что можно предположить!

Ниже имя TW0_PI определяется как произведение 2.0 на 3.141592654: #define 7W0_PI 2.0 * 3.141592654

После этого определенное имя можно использовать в любом месте программы, где требуется выражение 2.0 * 3.141592654. Например, оператор return в методе circumference из предыдущего примера можно заменить оператором return TWO_PI * radius;

Если в профамме на Objective-C встречается определенное имя, то все, что находится справа от этого определенного имени в операторе «define, подставляется вместо этого имени. Так, если препроцессор встречает имя TW0_PI в приведенном выше операторе return, он подставляет вместо этого имени то, что находится справа от этого имени в операторе «define, то есть 2.0 * 3.141592654.

Теперь вы понимаете, почему оператор #define нельзя закончить точкой с запятой. Если вы сделаете это, точка с запятой будет подставлена в том месте, где встретится соответствующее определенное имя. Если определить PI с помощью оператора #define PI 3.141592654;

и затем написать return 2.0 * PI * r;

препроцессор заменит этот экземпляр определенного имени PI на 3.141592654;.

Тогда после подстановки компилятору нужно будет обработать оператор return 2.0*3.141592654; * г;

что даст синтаксическую ошибку. Ставить точку с запятой после операторов define можно только в тех случаях, когда это действительно нужно.

Определение для препроцессора может не быть допустимым выражением Objective-C, но результирующее выражение в программе должно быть допусти мым. Например, вы можете задать определения: #define AND && #define OR ||

А затем писать такие выражения, как if ( х > 0 AND х < 10)

и if ( у == 0 OR у == value )

Можно даже использовать «define для оператора проверки равенства: #define EQUALS ==

И затем написать выражение if (у EQUALS 0 OR у EQUALS value )

Это позволяет избежать ошибки в тех случаях, когда для проверки равенства используют один знак равенства.

Возможности оператора «define велики, но практика надежного программирования не приветствует такие способы переопределения синтаксиса базового языка. Кроме того, это затрудняет другим людям понимание вашего кода.

Это еще не все. Поскольку определенное значение может ссылаться на другое определенное значение, следующие две строки «define допустимы. #define PI 3.141592654 #define TWO_PI 2.0 * PI

Имя TWO_PI определяется со ссылкой на предыдущее определенное ими PI, что избавляет от необходимости повторно писать значение 3.141592654.

Обратный порядок этих определений тоже допустим. #define TWO PI 2.0 * PI #define PI 3.141592654

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

Разумное использование операторов #define позволяет сократить количество комментариев в программе. Рассмотрим оператор if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)

В выражении проверяется, високосный ли год записан в переменной year.

Рассмотрим оператор #define и оператор if. #define IS_LEAP_YEAR year % 4 == 0 && year % 100 != 0 ||year % 400 == 0 if (IS_LEAP_YEAR)

Обычно препроцессор предполагает, что определение целиком содержится в одной строке. Символом переноса строки (т.е. продолжения оператора) для препроцессора является обратный слэш. Этот символ игнорируется при подстановке. Если строк продолжения несколько, то каждая строка должна заканчиваться обратным слэшем.

Последний оператор if намного понятнее, чем предыдущий. Конечно, данное определение позволяет проверить на високосный год только переменную year, но мы можем написать определение, с помощью которого можно проверить на високосный год любое выражение. Для этого нужно включить в определение один или несколько аргументов.

IS_LEAP_YEAR можно определить с аргументом у следующим образом. #define IS_LEAP_YEAR(y) у % 4 == 0 && у % 100 != 0 || у % 400 == 0

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

Используя это определение, напишем оператор if ( IS_LEAP_YEAR (year))

Оператор проверяет, является ли значение для year високосным годом. Можно написать аналогичное выражение для nextYear. if ( IS_LEAP YEAR (nextYear))

В этом операторе определение для IS_LEAP_YEAR непосредственно подставляется в оператор if с заменой аргумента у на nextYear. В результате компилятор будет обрабатывать следующий оператор. if ( nextYear % 4 == 0 && nextYear % 100 != О |j nextYear % 400 == 0)

Определения часто с одним или несколькими аргументами то называют макросами. В следующем макросе представлен квадрат его аргумента. #define SQUARE(x) х * х

В определении для SQUARE необходимо учитывать возможность следующей ошибки. Исходя из нашего описания, оператор у = SQUARE (v);

присваивает у значение V2. Но в операторе у = SQUARE (v+1);

переменной не присваивается значение (v + 1)2, как ожидалось. Поскольку в определении этого макроса препроцессор выполняет подстановку текста вместо аргумента, здесь получится следующее выражение: у = v + 1 * v + 1;

Чтобы решить проблему, нужно использовать в определении макроса SQUARE круглые скобки. #define SQUARE(x) ( (х) * (х))

Определение может показаться несколько странным, но помните, что х будет заменяться подставляемым выражением. Теперь оператор у = SQUARE (v+ 1);

будет правильно обработан как У = ((v+ 1) * (v+ 1)); Следующий макрос позволяет создавать новые дроби из нашего класса Fraction. #define MakeFract(x.y) ([[Fraction alloc] initWith: x over: y]])

Теперь для сложения дробей nl/dl и n2/d2 можно писать такие выражения, как myFract = MakeFract (1, 3); // Создание дроби 1/3

или sum = [MakeFract (nl, d1) add: MakeFract (n2, d2)];

Макросы удобны для работы с условными выражениями. В следующей строке определяется макрос с именем МАХ, определяющий максимальное из двух значений. #define MAX(a,b) { ((а) > (Ь)) ? (а): (Ь))

С помощью этого макроса можно писать такие операторы, как limit = МАХ (х + у, minValue);

Переменной limit присваивается максимальное из двух значений: х + у и minValue. Все определение МАХ заключено в круглые скобки, чтобы правильно обрабатывать такие выражения, как МАХ (х, у)* 100

Каждый аргумент тоже заключен в круглые скобки, чтобы правильно обрабатывать такие выражения, как МАХ (х & у, z)

Оператор & – это побитовый оператор AND, и он имеет меньший приоритет, чем оператор > в данном макросе. Без этих круглых скобок оператор > обрабатывался бы раньше побитового AND, что давало бы неверный результат.

В следующем макросе проверяется, является ли символ строчной буквой. #define IS_LOWER_CASE(x) ( ((х) >= 'а') && <(х) <= ’z') )

С помощью этого макроса можно писать такие выражения, как if ( IS_LOWER_CASE (с))

Этот макрос можно даже использовать в определении другого макроса, чтобы преобразовывать символ из нижнего регистра в верхний (делать строчную букву прописной), не изменяя другие символы. #define T0_UPPER(x) ( IS_LOWER_CASE (х) ? (х) -'a' + 'A' : (х))

Здесь мы снова работаем со стандартным набором символов ASCII. В части II, когда будут описываться объекты-строки, вы увидите, как выполнять преобразование символов из одного регистра в другой для международных наборов символов (Unicode). Оператор

Если поместить символ # перед параметром в определении макроса, препроцессор создаст строку-константу в стиле С из аргумента макроса при его вызове. Например, определение #define str(x) # х

при последующем вызове str (testing)

будет раскрыто препроцессором как "testing"

Например, вызов printf printf (str ("Программировать на Objective-C интересно""));

эквивалентен printf ("Программировать на Objective-C интересно");

Препроцессор заключает в кавычки фактический аргумент макроса. Препроцессор сохраняет в аргументе все кавычки и обратные слэши. Поэтому вызов str ("hello")

даст в результате ""hello"" Более близкий к практике пример оператора # представляет следующее определение макроса. define printint(var) printf (# var « = %in», var) Этот макрос используется для вывода значен ия целой переменной. Если count – переменная целого типа со значением 100, то оператор

printint (count); будет раскрыт как

printf ("count" " = %in", count); Компилятор выполнит конкатенацию двух смежных литеральных строк, чтобы создать одну строку. Поэтому после конкатенации оператор примет следующий вид.

printf ("count = %in", count); ### Оператор ## В определении макроса оператор ## сливает два маркера. Он ставится перед именем параметра макроса (или после него). Препроцессор берет фактический аргумент, указанный при вызове макроса, и создает один маркер из этого аргумента и из маркера, который следует за ## или предшествует ##. Предположим, что у нас имеется список переменных от х1 до хЮО. Мы можем написать макрос с вызовом printx, который принимает в качестве аргумента значение от 1 до 100 и выводит значение соответствующей переменной. define printx(n) printf («%in», x ## n) Часть x ## n указывает, что нужно взять маркеры, стоящие перед и после ## (соответственно букву х и аргумент п), и создать из них один маркер. Поэтому вызов

printx (20); будет раскрываться следующим образом,

printf ("%in", х20); В макросе printx можно использовать ранее определенный макрос printint, чтобы выводить имя переменной вместе с ее значением. define printx(n) printint(x ## n) Вызов

printx (10); сначала раскрывается как

printint (x10); затем как

printf ("x10" "= %in", х10); и, наконец, так:

printf ("х10 = %in", хЮ); ## 12.2. Оператор #import Программируя на Objective-C, вы постепенно разработаете для своих программ собственный набор макросов. Чтобы не вводить их в каждую новую программу, вы можете собрать все определения в один файл и включать свои макросы в программу с помощью оператора #import. Эти файлы обычно имеют расширение имени .h и называются заголовочными (header) или включаемыми (include) файлами. Предположим, что мы пишем набор программ для метрических преобразований. Нам нужно задать операторы «define для констант, которые требуются при выполнении этих преобразований. define INCHES PER CENTIMETER 0.394 (дюйм/см) define CENTIMETERS_PER_INCH (1 / INCHES_PER_CENTIMETER) (см/дюйм) define QUARTS_PER_LITER 1.057 (кварта/литр) define LITERS_PER_QUART (1 / QUARTS_PER_L!TER) (литр/кварта) define OUNCES PER GRAM 0.035 (унция/г) define GRAMS_PER_OUNCE (1 / OUNCES_PER_GRAM) (г/унция| Мы ввели эти определения в файл с именем metric.h. Чтобы использовать определения из файла metric.h, в программе достаточно ввести следующую директиву препроцессора: import «metric.h» Этот оператор должен появиться до ссылки на любые операторы «define, содержащиеся в файле metric.h. Обычно его помешают в начале исходного файла. Препроцессор ищет указанный файл в системе и копирует содержимое этого файла в то место программы, где находится оператор «import. Таким образом, любые операторы из этого файла обрабатываются так, как если бы они были непосредственно введены в программу в этом месге. Кавычки, в которые заключено имя файла, показывают препроцессору, что файл нужно искать водном или нескольких файловых каталогах (папках). Обычно поиск начинается с каталога, содержащего исходный файл, но в Xcode можно указать конкретные места для поиска, изменив настройки проекта (ProjectSettings). Если заключить имя файла в угловые скобки (< и >) import то препроцессор будет искать include-файл только в специальном «системном» каталоге (или каталогах) header-файлов, но в текущем каталоге поиск выполняться не будет. И в этом случае при работе в Xcode можно изменить каталоги, выбрав в меню Project, Edit Project Settings (Изменить настройки проекта). **Примечание.** При компиляции программ для этого раздела книги файл Foundation.h был импортирован из следующего каталога на моем компьютере: /Developer$/SDKs/MacOSX10.5.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers. Покажем на примере конкретной программы, как работают include-файлы. Введем шесть приведенных выше операторов #define в файл с именем metric.h. Затем введем и запустим программу 12.1.


    Ваша оценка произведения:

Популярные книги за неделю