Программирование на Objective-C 2.0
Шрифт:
Если поместить символ # перед параметром в определении макроса, препроцессор создаст строку-константу в стиле С из аргумента макроса при его вызове. Например, определение #define str(x) # х
при последующем вызове str (testing)
будет раскрыто препроцессором как "testing"
Например, вызов printf printf (str ("Программировать на Objective-C интересно""));
эквивалентен printf ("Программировать на Objective-C интересно");
Препроцессор заключает в кавычки фактический аргумент макроса. Препроцессор сохраняет в аргументе все кавычки и обратные слэши. Поэтому вызов str ("hello")
даст в результате "\"hello\"" Более близкий к практике пример оператора # представляет следующее определение макроса. define printint(var) printf (# var " = %i\n", var) Этот макрос используется для вывода значен ия целой переменной. Если count — переменная целого типа со значением 100, то оператор
printint (count); будет раскрыт как
printf ("count" " = %i\n", count); Компилятор выполнит конкатенацию двух смежных литеральных строк, чтобы создать одну строку. Поэтому после конкатенации оператор примет следующий вид.
printf ("count = %i\n", count); ### Оператор ## В определении макроса оператор ## сливает два маркера. Он ставится перед именем параметра макроса (или после него). Препроцессор берет фактический аргумент, указанный при вызове макроса, и создает один маркер из этого аргумента и из маркера, который следует за ## или предшествует ##. Предположим, что у нас имеется список переменных от х1 до хЮО. Мы можем написать макрос с вызовом printx, который принимает в качестве аргумента значение от 1 до 100 и выводит значение соответствующей
printx (20); будет раскрываться следующим образом,
printf ("%i\n", х20); В макросе printx можно использовать ранее определенный макрос printint, чтобы выводить имя переменной вместе с ее значением. define printx(n) printint(x ## n) Вызов
printx (10); сначала раскрывается как
printint (x10); затем как
printf ("x10" "= %i\n", х10); и, наконец, так:
printf ("х10 = %i\n", хЮ); ## 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.
/ Пример использования оператора #import Примечание. В этой программе предполагается, что определения заданы в файле с именем metric.h (галлон = 4 кварты) / import import
int main (int argc, char *argv[])
{ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; float liters, gallons; NSLog (@"*** Liters to Gallons ***"); NSLog (@*Enter the number of liters:"); scanf ("%f", Sliters); gallons = liters * QUARTS_PER_UTER / 4.0; NSLog (@"%g liters = %g gallons", liters, gallons); [pool drain]; return 0;
} Вывод программы 12.1
Liters to Gallons (Литры в галлоны) Enter the number of liters: (Введите количество литров) 55.75 55.75 liters = 14.7319 gallons. (55.75 литров = 14.7319 галлонов) В программе 12.1 используется только одно определенное значение (QUARTS_PER_LITER) из include-файла metric.h. Тем не менее, это вполне показательный пример: после ввода в файл metric.h определения доступны в любой программе, где применяется соответствующий оператор «import. Одним из наиболее важных преимуществ использования файла импорта является возможность централизовать все определения, что гарантирует использование в программах одного и того же значения. Кроме того, ошибки в include-файле достаточно исправить только в одном месте, не изменяя все программы, использующие это значение. Любую программу, в которой используется неверное значение, достаточно перекомпилировать, не редактируя. Другие системные include-файлы содержат объявления для различных функций, хранящихся в базовой системной библиотеке С. Например, файл limits.h содержит системно-зависимые значения, которые задают размеры символьных и целых типов данных. Максимальный размер типа int определяется вну три этою файла именем INT_MAX, максимальный размер unsigned long int определяется с помощью ULONG^MAX, и т.д. Файл float.h задает информацию о типах данных с плавающей точкой: FLTMAX указывает максимальное число с плавающей точкой, a FLT_DIG — число десятичных цифр для точности типа float. Файл string.h содержит объявления для библиотечных процедур копирования, сравнения и конкатенации, которые выполняют операции с символьными строками. Если вы работаете исключительно со строчными классами Foundation (см. главу 15), то эти процедуры вам не потребуются. ## 12.3. Условная компиляция Препроцессор Objective-C позволяет работать с условной компиляцией (conditional compilation). Условная компиляция обычно применяется для создания одной программы, которую можно компилировать для выполнения в различных компьютерных системах. Она часто используется для включения или отключения в программе различных операторов, например, операторов отладки, которые выводят значения переменных или отслеживают последовательность выполнения программы. ### Операторы #ifdef, #endif, #else и #ifndef К сожалению, программа может основываться на системно-зависимых параметрах, которые различны для разных процессоров (например, Power PC или Intel) в разных версиях операционной системы (например, Tiger или Leopard). Для большой программы, содержащей много таких зависимостей от оборудования или программного обеспечения вам пришлось бы изменять много операторов типа define. Чтобы свести к минимуму эту проблему, вы можете включить в программу значения определений операторов типа define для разных машин, используя средства условной компиляции препроцессора. Например, в следующих операторах для DATADIR определяется значение "/uxnl/data", если ранее был определен символ MAC_0S_X, в противном случае присваивается значение "\usr\data". ifdef MAC_0S_X define DATADIR "/uxnl/data" else define DATADIR "\usr\data" endif После символа #, начинающего оператор препроцессора, можно помещать один или несколько пробелов. Операторы #ifdef, #else и #endif действуют в соответствии с их именами. Если символ, указанный в строке #ifdef, уже определен (с помощью оператора #define или из командной строки при компиляции программы), компилятор обрабатывает последующие строки до #else, #elrf или ftendif; в противном случае они игнорируются. Чтобы определить символ POWER_PC для препроцессора, достаточно оператора define P0WER_PC 1 или define POWER.PC. Как видно из примера, после определенного имени не требуется текста, чтобы выполнить проверку #ifdef. Компилятор также позволяет определить имя для препроцессора, если программа компилируется с использованием специальной опции в команде компилятора. В командной строке
gcc -framework Foundation -D P0WER_PC program.m - для препроцессора определяется имя P0WER_PC, чтобы все операторы #ifdef POWER PC внутри
программы program.m давали значение TRUE (отметим, что -D POWER PC следует ввести в командной строке до указания имя программы). Это средство позволяет определять имена без редактирования исходной программы. В Xcode для добавления новых определенных имен и указания их значений нужно выбрать Add User-Defined Setting (Добавить пользовательское значение) в Project Settings (Настройки проекта). После оператора #ifndef следуют такие же строки, как после оператора #ifdef. В нем последующие строки обрабатываются в том случае, если указанный символ не определен. Как уже говорилось, условная компиляция полезна для отладки программ. Можно включить в программу много вызовов printf для вывода промежуточных результатов и отслеживания последовательности выполнения. Для запуска этих операторов можно выполнить их условную компиляцию в программе, если определено некоторое имя, например DEBUG. Следующую последовательность операторов можно использовать для вывода значений некоторых переменных, только если программа была откомпилирована с определением имени DEBUG. ifdef DEBUG NSLog (@"User name = %s, id = %i", userName, userid); endif В вашей программе может быть много таких отладочных операторов. При отладке программу можно компилировать с определением имени DEBUG, чтобы выполнялась компиляция отладочных операторов. Если программа работает правильно, ее можно перекомпилировать без определения DEBUG. Это позволяет сократить размер программы, поскольку все отладочные операторов не будут компилироваться. ### Операторы препроцессора #if и #elif Оператор препроцессора #if представляет более общий подход к управлению условной компиляцией. Этот оператор позволяет проверить, равно ли нулю константное выражение. Если результат выражения не равен нулю, последующие строки до Seise, telif или tendif обрабатываются; в противном случае они пропускаются. Рассмотрим следующие строки из файла Foundation NSString.h. if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_0S_X_VERSI0N _10_5 define NSMaximumStringLength (INT MAX-1) endif Здесь сравниваю тся значения определенных переменных MAC_OS_X_VERSION_MIN_REQUIRED и MAC_OS_X_VERSIONJO_5. Если первое значение меньше второго, то обрабатывается следующий оператор tdefine; в противном случае он пропускается. Это позволяет задать максимальную длину строки, равную максимальному размеру целого типа минус 1, если программа компилируется в системе MAC OS X 10.5 или последующих версиях. В строках с #if применяется также специальный операторdefined (имя) Следующие наборы операторов дают одинаковый результат: if defined (DEBUG) ... endif и ifdef DEBUG ... endif Следующие операторы включены в файл NSObjcRuntime.h, чтобы определить имя NSJNI INF (если оно не было определено раньше) в зависимости от конкретного компилятора. if defined(NSJNUNE) #if defined(_GNUC_) #define NSJNUNE static _inline_attribute_((always_inline)) #elif defined(_MWERKS_J || defined(_cplusplus) #define NSJNUNE static inline #elif defined(MSC_VER) #define NS_INUNE static_inline #elif defined(_"wiN32_) #define NS_INUNE static_inline_ #endif endif Ниже приводится еще один типичный случай применения #if. if defined (DEBUG) && DEBUG ... endif Операторы между #if и #cndif будут обрабатываться в том случае, если имя DEBUG определено и имеет ненулевое значение. ### Оператор #undef Сделать определенное имя неопределенным позволяет оператор #undef. Чтобы удалить определение имени, нужно написать строку undef имя Следующий оператор удаляет определение POWER_PC. undef POWER_PC Последующие операторы #ifdef P0WER_PC или #if defined (POWER_PC) будут давать значение FALSE. На этом заканчивается наше описание возможностей препроцессора. В приложении В приводятся дополнительные операторы препроцессора, которые не были описаны здесь. Упражнения 1. Найдите на своей машине файлы limits.h и float.h. Посмотрите, что находится в этих файлах. Если в эти файлы включены другие файлы, просмотрите и их. 2. Определите макрос с именем MIN, который дает минимальное из двух значений. Напишите программу, которая проверяет определение этого макроса. 3. Определите макрос с именем МАХЗ, который дает максимальное из трех значений. Напишите программу, которая проверяет это определение. 4. Напишите макрос с именем IS_UPPER_CASE, который выдает ненулевое значение, если символ является прописной буквой. 5. Напишите макрос с именем IS_ALPHABETIC, который выдает ненулевое значение, если символ является буквой. Сделайте так, чтобы в этом макросе использовались макрос IS_LOWER CASE, определенный в данной главе, и макрос IS_UPPER_CASE, определенный в упражнении 4. 6. Напишите макрос с именем IS_DIGfT, который выдает ненулевое значение, если символ является цифрой от 0 до 9. Используйте этот макрос в определении другого макроса с именем IS_SPECIAL, который выдает ненулевое значение, если символ является специальным символом (то есть не буквой и не цифрой). Обязательно используйте макрос IS_ALPHABET1C из упражнения 5. 7. Напишите макрос с именем ABSOLUTE VALUE, который вычисляет абсолютное значение своего аргумента. Этот макрос должен правильно вычислять такие выражения, как ABSOLUTE_VALUE (х + delta) ```
Рассмотрим определение макроса printint из этой главы#define printx(n) printf ("%i\n", x ## n) Можно ли использовать следующие строки для вывода значений 100 переменных отх1 до хЮО? Почему?for (i = 1; i <= 100; ++i) printx (i);
Глава 13. Базовые средства из языка С
В этой главе описываются средства языка Objective-C, взятые из базового языка программирования С. Вам необязательно знать их. Такие средства, как функции, структуры, указатели, объединения и массивы лучше изучать по мере необходимости. Поскольку С является процедурным языком, некоторые из этих средств противоречат основам объектно-ориентированного программирования и могут также не согласовываться с некоторыми стратегиями Foundation framework, например, с методологией выделения памяти или работой с символьными строками, содержащими символы из нескольких байтов. Примечание. На уровне Objective-C существуют способы работы с многобайтными символами, но в Foundation имеется нам нога более удобное решение с помощью собственного класса NSString. С другой стороны, в некоторых приложениях может потребоваться низкоуровневый подход. Например, при работе с большими массивами данных мо- iyr применяться встроенные структуры данных в виде массивов из Objective-C вместо объектов-массивов из Foundation (см. главу 15). Функции могут оказаться удобным средством для группировки повторяющихся операций и разбиения программы на модули. Ознакомьтесь с данной главой, чтобы получить общее представление, и вернитесь к ней, когда закончите изучение части II (Foundation Framework), или пропустите ее и перейдите к части II, где описывается Foundation framework. Если вам придется поддерживать чей-то код или вы начнете изучать header- формы Foundation framework, вам могут встретиться некоторые конструкции, описанные в этой главе. Некоторые из типов данных Foundation, такие как NSRange, NSPoinrt и NSRect, требуют элементарного понимания описываемых здесь структур. В таких случаях вы всегда сможно вернуться к этой главе. 13.1. Массивы
Язык Objective-C позволяет определить набор упорядоченных элементов данных, который называется массивом (array). В этом разделе описывается определение и управление массивами. В последующих разделах описывается использование массивов совместно с функциями, структурами, символьными строками и указателями. Предположим, что вам нужно считать набор оценок (grades) и затем выполнить с ними некоторые операции, например, расположить их в порядке возрастания, вычислить среднее значение и найти медиану. Вы не можете выполнить эти операции, пока не введете все оценки. В Objective-C вы можете определить переменную с именем grades, которая представляет не одно значение оценки, а весь набор оценок. Для ссылки на элементы этого набора используется число, которое называется порядковым номером, или индексом (index или subscript). В математике /-й элемент набора х обозначается как х; в Objective-C он обозначается как x[i]
Тем самым, выражение grades[5]
соответствует элементу с номером 5 в массиве с именем grades. В Objective-C элементы массива начинаются с номера 0, поэтому grades[0]
на самом деле обозначает первый элемент массива.
Отдельный элемент массива можно использовать в любом месте как обычную переменную. Например, элемент массива можно присвоить другой переменной с помощью оператора g = grades[50];
Здесь значение, содержащееся в gradesfSO], присваивается переменной д. В общем виде, если i объявлена как целая переменная, оператор g = grades[i];