
- •6 Глава 1
- •12 Глава 1
- •14 Глава 1
- •16 Глава 1
- •18 Глава 1
- •20 Глава 1
- •22 Глава 1
- •24 Глава 1
- •26 Глава 1
- •31 Глава 1
- •34 Глава 2
- •36 Глава 2
- •Puc. 2.4. Дополнительные опции консольного приложения Win32
- •38 Глава 2
- •40 Глава 2
- •42 Глава 2
- •44 Глава 2
- •48 Глава 2
- •50 Глава 2
- •52 Глава 2
- •54 Глава 2
- •56 Глава 2
- •58 Глава 2
- •60 Глава 2
- •62 Глава 2
- •64 Глава 2
- •66 Глава 2
- •68 Глава 2
- •70 Глава 2
- •74 Глава 2
- •76 Глава 2
- •79 Глава 2
- •82 Глава 2
- •84 Глава 2
- •86 Глава 2
- •88 Глава 2
- •92 Глава 2
- •94 Глава 2
- •96 Глава 2
- •98 Глава 2
- •103 Глава 2
- •105 Глава 2
- •107 Глава 2
- •110 Глава 2
- •115 Глава 3
- •119 Глава 3
- •121 Глава 3
- •123 Глава 3
- •125 Глава 3
- •129 Глава 3
- •131 Глава 3
- •133 Глава 3
- •139 Глава 3
- •141 Глава 3
- •143 Глава 3
- •145 Глава 3
- •148 Глава 3
- •150 Глава 3
- •155 Глава 3
- •165 Глава 4
- •168 Глава 4
- •170 Глава 4
- •173 Глава 4
- •175 Глава 4
- •178 Глава 4
- •184 Глава 4
- •186 Глава 4
- •188 Глава 4
- •190 Глава 4
- •192 Глава 4
- •194 Глава 4
- •198 Глава 4
- •201 Глава 5
- •203 Глава 5
- •205 Глава 5
- •207 Глава 5
- •213 Глава 5
- •217 Глава 5
- •219 Глава 5
- •221 Глава 5
- •223 Глава 5
- •225 Глава 5
- •227 Глава 5
- •232 Глава 5
- •234 Глава 5
- •236 Глава 5
227 Глава 5
При компиляции сразу возникает подсказка, что все не так, как хотелось бы — в виде предупреждающего сообщения компилятора:
Вывод, который я получил при запуске этой программы, был таким:
Описание полученных результатов (или почему это не работает)
Функция main () вызывает treble () и сохраняет возвращенный адрес в указателе ptr, который по идее должен указывать на утроенное значение аргумента num. Затем отображается результат простого умножения num на три, за которым следует значе- ние адреса, возвращенного функцией.
Ясно, что вторая строка вывода не показывает корректного значения — 15, но в чем же ошибка? Вообще-то это не секрет, потому что компилятор ясно предупредил о проблеме. Ошибка возникает потому, что переменная result в функции treble () создается, когда функция начинает выполнение, а уничтожается при выходе из функ- ции. Поэтому память, на которую указывает указатель, уже не содержит исходного корректного значения. Память, ранее выделенная result, становится доступной для других целей, и здесь она как раз использована для чего-то другого.
Железное правило возврата адресов
Существует абсолютное железное правило относительно возвращаемых адресов:
Никогда не возвращать из функции адрес локальной автоматической переменной.
Очевидно, что нельзя применять функцию, которая не работает, но как же это ис- править? Можно использовать ссылочный параметр и модифицировать в функции его исходное значение, но это не совсем то, что нужно. Вы пытаетесь вернуть указа- тель на нечто полезное, потому что в конечном итоге, вам может понадобиться воз- вращать нечто более сложное, чем отдельный элемент данных. Ответ заключается в динамическом выделении памяти (вы видели это в действии в предыдущей главе).
Структурная
организация программ
С помощью операции new вы создаете новую переменную в свободном хранили- ще, которая продолжает существовать до тех пор, пока не будет уничтожена операци- ей delete или пока не завершится программа. С таким подходом функция выглядит так:
Вместо объявления result типа double теперь эта переменная объявлена с ти- пом double* и ей присваивается адрес, возвращенный операцией new. Поскольку результат — указатель, остальная часть функции изменена соответствующим образом, и адрес, записанный в result, в конечном итоге возвращается вызывающей програм- ме. Можете проверить эту версию, заменив ею функцию из предыдущего примера.
Необходимо помнить, что при динамическом распределении памяти в функции "родного" С++, вроде этой, при каждом вызове выделяется новый фрагмент памяти. Ответственность за освобождение выделенной памяти, когда она более не нужна, ло- жится на программу, которая вызывает эту функцию. На практике очень легко забыть об этом, в результате чего функция будет последовательно "отгрызать" куски памяти от свободного хранилища до тех пор, пока в определенный момент не израсходует ее всю, и программа потерпит крах. Как уже упоминалось, проблема подобного рода известна как утечка памяти.
Ниже приведен пример использования новой версии функции. Единственное не- обходимое отличие от исходного кода — применение delete для освобождения памя- ти, возвращенной функцией treble ().
Возврат ссылки
Функция может возвращать ссылку. Это также чревато потенциальными ошиб- ками, как и в случае возврата указателя, поэтому здесь вы также должны быть осто- рожны. Поскольку ссылка не является какой-то отдельной сущностью (это всегда псевдоним чего-то другого), вы должны быть уверены, что объект, на который она ссылается, все еще существует после завершения выполнения функции. Очень легко забыть об этом, используя в функции ссылки, поскольку они выглядят как обычные переменные.
Ссылки, как возвращаемые типы, особенно важны в контексте объектно-ори- ентированного программирования. Как вы увидите позднее в этой книге, они по- зволяют делать такие вещи, которые невозможно осуществить без их применения (в частности, это касается "перегрузки операций", о которой речь пойдет в главе 9). Принципиальная характеристика возвращаемого значения типа ссылки в том, что оно является lvalue. Это значит, что вы можете использовать результат функции, ко- торая возвращает ссылку, в левой части оператор присваивания.
;
Структурная
организация программ
Ниже показан вывод этого примера.
Описание полученных результатов
Посмотрим сначала, как реализована функция. Прототип функции 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 () в левой части операции присваи- вания, чтобы изменить элемент, содержащий минимальное значение в массиве. Это делается дважды, чтобы продемонстрировать, что все это действительно работает, и написано не случайно. Затем содержимое массива снова выводится на дисплей, с той