Программирование на Objective-C 2.0
Шрифт:
Вывод программы 7.4 1/4 + 1/2 3/4
Этот результат, несомненно, лучше! 7.6. Ключевое слово self
В программе 7.4 мы решили сокращать дробь вне метода add:. Мы могли бы делать это и внутри метода add:. Но как мы сможем указать методу reduce дробь, для которой нужно выполнить сокращение? Мы знаем, как непосредственно идентифицировать по имени переменные экземпляра внутри метода, но не знаем, как непосредственно идентифицировать получателя сообщения.
Вы можете использовать ключевое слово self для ссылки на объект, который является получателем текущего метода. Если внутри метода add: добавить [self reduce]; `
то метод reduce будет применен к объекту Fraction, который был получателем метода add:, а это нам как раз
После сложения будет выполнено сокращение дроби. 7.7. Выделение памяти и возврат объектов из методов
Мы уже говорили, что метод add: изменяет значение объекта, который получает сообщение. Мы создадим новую версию метода add:, который будет вместо этого создавать новый объект типа fraction для сохранения результата сложения. В этом случае мы должны возвращать новый объект типа Fraction отправителю сообщения. Ниже приводится определение этого нового метода add:. -(Fraction *) add: (Fraction *) f { // Для сложения двух дробей: // a/b + c/d = ((a*d) + (b*c)) / (b * d) // В result будет сохраняться результат сложения Fraction *result = [[Fraction alloc] init]; iint resultNum, resultDenom; resultNum = numerator * f.denominator + denominator * f.numerator; resultDenom = denominator * f.denominator; [result setTo: resultNum over: resultDenom]; [result reduce]; return result; }
Первая строка определения этого метода: -(Fraction *) add: (Fraction *) f;
Она указывает, что метод add: будет возвращать объект класса Fraction и принимать такой объект в качестве аргумента. Этот аргумент будет складываться с получателем сообщения, которым тоже является объект класса Fraction.
Метод add: выделяет память и инициализирует новый объект типа Fraction с именем result и затем определяет две локальные переменные с именами resultNum и resultDenom. Они будут использоваться для сохранения числителей и знаменателей, получаемых в результате сложения.
После выполнения сложения (как и раньше) и присваивания значений числителей и знаменателей локальным переменным можно задать значение result с помощью следующего выражения. [result setTo: resultNum over: resultDenom];
После сокращения result (на наибольший общий делитель) его значение возвращается отправителю сообщения с помощью оператора return.
Отметим, что занимаемая объектом result память, которая выделена внутри метода add:, возвращается этим методом и не освобождается. Вы не можете освободить ее из метода add:, поскольку она требуется отправителю сообщения.
Поэтому пользователь этого метода обязан знать, что возвращаемый объект является новым экземпляром и должен быть в дальнейшем освобожден. Это можно указать в соответствующей документации, которая предоставляется пользователям данного класса.
В программе 7.5 выполняется проверка нового метода add:. #import «Fraction.h» int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *aFraction = [[Fraction alloc] init]; Fraction *bFraction = [[Fraction alloc] init]; Fraction ResultFraction; [aFraction setTo: 1 over: 4]; // присваивание значения 1/4 первой дроби [bFraction setTo: 1 over: 2]; // присваивание значения 1/2 второй дроби [aFraction print]; NSLog (@"+"); [bFraction print]; NSLog resultFraction = [aFraction add: bFraction]; [resultFraction print]; // Непосредственный вывод результата, см. ниже, // вызовет "утечку" памяти! [[aFraction add: bFraction] print]; [aFraction release]; [bFraction release]; [resultFraction release]; [pool drain]; return 0; }
Вывод программы 7.5 1/4 + 1/2 3/4 3/4
Сначала определяются два объекта типа Fraction: aFraction и bFraction, которым присваиваются
значения 1/4 и 1/2 соответственно. Здесь также определяется объект Fraction с именем resultFraction (для которого не нужно выполнять выделение памяти и инициализацию). В этой переменной будет сохраняться результат последующих операций сложения.В следующих строках кода сначала выполняется отправка сообщения add: получателю aFraction с передачей bFraction в качестве аргумента метода. resultFraction = [aFraction add: bFraction]; [resultFraction print];
Результирующий объект типа Fraction, возвращаемый методом, сохраняется в resultFraction и затем выводится путем передачи сообщения print. Отметим, что вы должны аккуратно завершить эту программу, освободив (release) resultFraction, хотя вы не выделяли для нее память (allocate) в main. Код выполнил метод add:, но освободить память должны вы сами. Следующее сообщение кажется очень удобным, но на самом деле здесь возникает проблема. [[aFraction add: bFraction] print];
Поскольку здесь берется объект типа Fraction, возвращаемый методом add:, и ему передается сообщение print, у вас нет никакого способа освободить этот. Мы видим здесь пример утечки памяти (memory leakage). При многократном повторении вложенной передачи сообщения такого рода у вас будет понемногу накапливаться память, которую вы не сможете непосредственным образом освободить.
Чтобы решить эту проблему, можно сделать так, чтобы метод print возвращал своего получателя, и вы могли бы его освободить. Но это обходной способ. Проще разбить эти вложенные сообщения на два отдельных сообщения, как сделано выше.
Мы могли бы избежать использования временных переменных resultNum и resultDenom в методе add: с помощью следующего сообщения. [result setTo: numerator * f.denominator + denominator * f.numerator over: denominator * f.denominator;
Мы не предлагаем писать такой сокращенный код, но вы можете встретить его у других программистов, поэтому нужно уметь читать и понимать подобные сложные выражения.
Рассмотрим еще один (последний в этой главе) пример, связанный с дробями. Это суммирование последовательности. ± 1/2 i= I
Оно означает, что нужно сложить значения 1/2i, где i изменяется от 1 до п, то есть сложить 1/2 + 1/4 + 1/8 .... Если сделать значение п достаточно большим, сумма будет приближаться к 1. Мы опробуем несколько значений п, чтобы посмотреть, насколько мы приблизимся к 1.
Программа 7.6 запрашивает ввод значения п и выполняет указанные вычисления. #import Traction.h" int main (int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Fraction *aFraction = [[Fraction alloc] init]; Fraction *sum = [[Fraction alloc] init], *sum2; int i, n, pow2; [sum setTo: 0 over: 1]; // присваивание значения 0 первой дроби NSLog (@"Enter your value for n:"); scant ("%i", &n); pow2 = 2; for (i = 1; i <= n; ++i) { [aFraction setTo: 1 over: pow2]; sum2 = [sum add: aFraction]; [sum release]; // освобождение памяти предыдущей суммы sum = sum2; pow2 *= 2; NSLog (@"After %i iterations, the sum is %g", n, [sum convertToNum]); [aFraction release]; [sum release]; [pool drain]; return 0; }
Вывод программы 7.6 Enter your value for n: (Введите значение n) 5 After 5 iterations, the sum is 0.96875 (По 5 итерациям сумма равна ...) Вывод программы 7.6 (Повторный запуск) Enter your value for n: 10 After 10 iterations, the sum is 0.999023 Вывод программы 7.6 (Повторный запуск) Enter your value for n: 15 After 15 iterations, the sum is 0.999969
Переменной sum типа Fraction присваивается значение 0: числитель (numerator), равный 0, и знаменатель (denominator), равный 1. (А что произойдет, если задать числитель и знаменатель, равные 0?). Затем программа запрашивает у пользователя ввод значения п и считывает его с помощью scant. Затем начинается цикл for для вычисления суммы последовательности. Перед циклом переменной pow2 присваивается значение 2. Эта переменная используется для сохранения значения 2i. На каждом шаге цикла это значение умножается на 2.