Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
A.doc
Скачиваний:
36
Добавлен:
09.04.2015
Размер:
5.6 Mб
Скачать

227 Глава 5

При компиляции сразу возникает подсказка, что все не так, как хотелось бы — в виде предупреждающего сообщения компилятора:

Вывод, который я получил при запуске этой программы, был таким:

Описание полученных результатов (или почему это не работает)

Функция main () вызывает treble () и сохраняет возвращенный адрес в указателе ptr, который по идее должен указывать на утроенное значение аргумента num. Затем отображается результат простого умножения num на три, за которым следует значе- ние адреса, возвращенного функцией.

Ясно, что вторая строка вывода не показывает корректного значения — 15, но в чем же ошибка? Вообще-то это не секрет, потому что компилятор ясно предупредил о проблеме. Ошибка возникает потому, что переменная result в функции treble () создается, когда функция начинает выполнение, а уничтожается при выходе из функ- ции. Поэтому память, на которую указывает указатель, уже не содержит исходного корректного значения. Память, ранее выделенная result, становится доступной для других целей, и здесь она как раз использована для чего-то другого.

Железное правило возврата адресов

Существует абсолютное железное правило относительно возвращаемых адресов:

Никогда не возвращать из функции адрес локальной автоматической переменной.

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

Структурная организация программ 228

С помощью операции new вы создаете новую переменную в свободном хранили- ще, которая продолжает существовать до тех пор, пока не будет уничтожена операци- ей delete или пока не завершится программа. С таким подходом функция выглядит так:

Вместо объявления result типа double теперь эта переменная объявлена с ти- пом double* и ей присваивается адрес, возвращенный операцией new. Поскольку результат — указатель, остальная часть функции изменена соответствующим образом, и адрес, записанный в result, в конечном итоге возвращается вызывающей програм- ме. Можете проверить эту версию, заменив ею функцию из предыдущего примера.

Необходимо помнить, что при динамическом распределении памяти в функции "родного" С++, вроде этой, при каждом вызове выделяется новый фрагмент памяти. Ответственность за освобождение выделенной памяти, когда она более не нужна, ло- жится на программу, которая вызывает эту функцию. На практике очень легко забыть об этом, в результате чего функция будет последовательно "отгрызать" куски памяти от свободного хранилища до тех пор, пока в определенный момент не израсходует ее всю, и программа потерпит крах. Как уже упоминалось, проблема подобного рода известна как утечка памяти.

Ниже приведен пример использования новой версии функции. Единственное не- обходимое отличие от исходного кода — применение delete для освобождения памя- ти, возвращенной функцией treble ().

Возврат ссылки

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

Ссылки, как возвращаемые типы, особенно важны в контексте объектно-ори- ентированного программирования. Как вы увидите позднее в этой книге, они по- зволяют делать такие вещи, которые невозможно осуществить без их применения (в частности, это касается "перегрузки операций", о которой речь пойдет в главе 9). Принципиальная характеристика возвращаемого значения типа ссылки в том, что оно является lvalue. Это значит, что вы можете использовать результат функции, ко- торая возвращает ссылку, в левой части оператор присваивания.

Теперь рассмотрим пример, иллюстрирующий использование возвращаемых ти- пов ссылок, а также демонстрирующий, как функция может применяться в левой ча- сти операции присваивания, когда она возвращает lvalue. Этот пример предполагает, что у вас есть массив, который содержит смешанный набор значений. Всякий раз, когда вы хотите вставить новое значение в массив, вы заменяете элемент с наимень- шим значением.

;

Структурная организация программ 231

Ниже показан вывод этого примера.

Описание полученных результатов

Посмотрим сначала, как реализована функция. Прототип функции lowest () ис- пользует doubles в качестве спецификации возвращаемого типа, который, таким образом, является "ссылкой на double". Возвращаемое значение ссылочного типа пишется точно так же, как это вы видели в случае объявления ссылочных перемен- ных — с добавлением & к имени типа. Функция принимает два параметра — одномер- ный массив типа double и параметр типа int, указывающий длину массива.

Тело функции содержит цикл for, в котором определяется, какой элемент пере- данного массива содержит минимальное значение. Индекс j найденного элемента с минимальным значением изначально равен 0, а затем модифицируется внутри цикла, если текущий элемент a [i] меньше а [ j ]. Таким образом, по завершении цикла j рав- но индексу элемента массива с минимальным значением. Оператор return выглядит следующим образом:

Несмотря на тот факт, что это выглядит точно так же, как оператор, возвращаю- щий значение, поскольку тип возврата объявлен как ссылка, здесь возвращается не значение элемента а [ j ], а ссылка на него. Адрес а [ j ] используется для инициализа- ции возвращаемой ссылки. Эта ссылка создается компилятором, потому что возвра- щаемый тип объявлен как ссылка.

Не путайте возврат &а [ j ] с возвратом ссылки. Если вы укажете в качестве возвра- щаемого значения &а [ j ], это будет означать адрес а [ j ], то есть указатель. Если вы сделаете это после спецификации типа возврата как ссылки, то получите от компиля- тора сообщение об ошибке. Если конкретно, вы получите:

Функция main (), которая вызывает lowest (), очень проста. Здесь объявляется массив типа double и инициализируется 12-ю произвольными значениями, а пере- менная 1еп инициализируется длиной массива. Начальные значения массива выво- дятся на экран для сравнения.

Опять-таки, использование в программе манипулятора потока setw() для выравнивания выводимых значений по ширине требует директивы ^include <iomanip>.

Функция main () использует функцию lowest () в левой части операции присваи- вания, чтобы изменить элемент, содержащий минимальное значение в массиве. Это делается дважды, чтобы продемонстрировать, что все это действительно работает, и написано не случайно. Затем содержимое массива снова выводится на дисплей, с той