Шрифт:
Результирующий объект типа 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.
Цикл for начинается с 1 и продолжается до значения п. На каждом шаге цикла aFraction присваивается значение 1/pow2, то есть 1/2i. Это значение затем добавляется к сумме (sum) с помощью определенного ранее метода add:. Значение result, возвращаемое из add:, присваивается sum2, а не sum, чтобы избежать проблем утечки памяти. (А что произойдет, если присвоить result непосредственно sum?) Старое значение sum затем освобождается, и для следующего шага цикла sum присваивается новое значение суммы — sum2. Изучите, каким образом здесь происходит освобождение памяти для дробей. Для цикла for, который выполняется сотни или тысячи раз, при неверном освобождении памяти для дробей у вас быстро накопится большой объем неиспользуемой памяти.
По окончании цикла for конечный результат выводится в виде десятичного значения с помощью метода convertToNum. Остается только освободить два объекта: aFraction и конечный объект типа Fraction, сохраненный в sum. После этого завершается выполнение программы.
Результаты вывода показывают, что происходит при запуске программы. При значении 5 сумма последовательности равна 0.96875. При 15 результат очень близок к 1. Расширение определений класса и файл секции interface
К настоящему моменту мы разработали небольшую библиотеку методов для работы с дробями. Ниже приводится файл секции interface, в котором применяется все, что мы умеем делать с использованием этого класса. #import <Foundation/Foundation.h> // Определение класса Fraction @interface Fraction : NSObject { int numerator; int denominator; } @property int numerator, denominator; -(void) print; -(double) convertToNum; -(Fraction *) add: (Fraction *) f; -(void) reduce; @end
Мы постепенно уточняли и расширяли класс путем добавления новых методов. Этот файл секции interface можно передать другому человеку, чтобы он мог писать программы, в которых операции с дробями. Если ему потребуется добавить новый метод, он сможет сделать это непосредственно, расширив определение класса, или косвенно, определив собственный подкласс и добавив свои собственные новые методы. Этому посвящена следующая глава. Упражнения
Добавьте следующие методы к классу Fraction для завершения списка арифметических операций над дробями. Выполните сокращение (reduce) результата в каждом случае. // Вычитание аргумента из получателя -(Fraction *) subtract: (Fraction *) f; // Умножение получателя на аргумент -(Fraction *) multiply: (Fraction *) f; // Деление получателя на аргумент -(Fraction *) divide: (Fraction *) f;
Модифицируйте метод print из класса Fraction, чтобы он мог принимать необязательный аргумент типа B00L, который указывает, нужно ли выполнить сокращение дроби для ее вывода. Если дробь должна быть сокращена, сделайте так, чтобы сама дробь не изменялась необратимо.