Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Коктейль из ссылок и указателей
Не будет ошибкой в списке параметров одной функции объявить как указатели, так и ссылки, а также объекты, передаваемые как значения, например: CAT * SomeFunction (Person & theOwner, House *theHouse, int age); Это объявление означает, что функция SomeFunction принимает три параметра. Первый является ссылкой на объект типа Person, второй — указателем на объект типа House, а третий — целочисленным значением. Сама же функция возвращает указатель на объект класса CAT. Следует также отметить, что при объявлении соответствующих переменных можно использовать разные стили размещения операторов ссылки (& ) и косвенного обращения (*). Вполне законной будет любая из следующих записей: 1: CAT& rFrisky; 2: CAT & rFrisky; 3: CAT & rFrisky;
Примечание: Символы пробелов в программах на языке C++ полностью игнорируются, поэтому везде, де вы видите пробел, можно ставить несколько пробелов, символов табуляции или символов разрывов строк. Оставив в покое вопросы свободного волеизъявления, попробуем разобраться в том, какой вариант все же лучше других. Как ни странно, можно найти аргументы в защиту каждого из трех вариантов. Аргумент в защиту первого варианта состоит в следующем. rFrisky — это переменная с именем rFrisky, тип которой можно определить как ссылку на объект класса CAT. Поэтому вполне логично, чтобы оператор & стоял рядом с типом. Однако есть и контраргумент. CAT — это тип. Оператор & является частью объявления, которое включает имя переменной и амперсант. Но следует отметить, что слияние вместе символа & и имени типа CAT может привести к возникновению следующей ошибки: CAT& rFrisky, rBoots; Поверхностный анализ этой строки может натолкнуть на мысль, что как переменная rFrisky, так и переменная rBoots являются ссылками на объекты класса CAT. Однако это не так. На самом деле это объявление означает, что rFrisky является ссылкой на объект класса CAT, а rBoots (несмотря на свое имя с характерным префиксом) — не ссылка, а обыкновенная переменная типа CAT. Поэтому последнее объявление следует переписать по-другому: CAT & rFrisky, rBoots; В ответ на это возражение стоит порекомендовать, чтобы объявления ссылок и обычных переменных никогда не смешивались в одной строке. Вот правильный вариант той же записи: CAT& rFrisky; CAT Boots;
Примечание: Наконец, многие программисты не обращают внимания на приведенные аргументы и, считая, что истина находится посередине, выбирают средний вариант (средний, кстати, в двух смыслах), который иллюстрируется случаем 2: 2: CAT & rFrisky; Безусловно, все сказанное до сих пор об операторе ссылки (& ) относится в равной степени и к оператору косвенного обращения (< < ). Выберите стиль, который вам подходит, и придерживайтесь его на протяжении всей программы, ведь ясность текста программы — одна из основных составляющих успеха. Многие программисты при объявлении ссыпок и указателей предпочитают придерживаться двух соглашений. 1. Размещать амперсант или звездочку посередине, окаймляя этот символ пробелами с двух сторон. 2. Никогда не объявлять ссылки, указатели и переменные в одной и той же строке программы.
Не возвращайте ссылку на объект, который находиться вне области видимости!
Научившись передавать аргументы как ссылки на объекты, программисты порой теряют чувство реальности. Не стоит забывать, что все хорошо в меру. Помните, что ссылка всегда служит псевдонимом некоторого объекта. При передаче ссылки в функцию или из нее не забудьте задать себе вопрос: " Что представляет собой объект, псевдонимом которого я манипулирую, и будет ли он существовать в момент его использования? " В листинге 9.13 показан пример возможной ошибки, когда функция возвращает ссылку на объект, которого уже не существует. Листинг 9.13. Возвращение ссылки на несуществующий объект 1: // Листинг 9.13. 2: // Возвращение ссылки на объект, 3: // которого больше не существует 4: 5: #include < iostream.h> 6: 7: class SimpleCat 8: { 9: public: 10: SimpleCat (int age, int weight); 11: ~SimpleCat() { } 12: int GetAge() < return itsAge; } 13: int GetWeight() { return itsWeight; } 14: private: 15: int itsAge; 16: int itsWeight; 17: }; 18: 19: SimpleCat:: SimpleCat(int age, int weight) 20: { 21: itsAge = age; 22: itsWeight = weight; 23: } 24: 25: SimpleCat & TheFunction(); 26: 27: int main() 28: { 29: SimpleCat & rCat = TheFunction(); 30: int age = rCat.GetAge(); 31: cout < < " rCat " < < age < < " years old! \n" 32: return 0; 33: } 34: 35: SimpleCat & TheFunction() 36: { 37: SimpleCat Frisky(5, 9); 38: return Frisky; 39: }
Результат: Compile error: Attempting to return a reference to a local object! (Ошибка компиляции: попытка возвратить ссылку на локальный объект! )
Предупреждение: Эта программа не компилируется на компиляторе фирмы Borland, но для нее подходят компиляторы компании Microsoft. Однако профессиональный программист никогда не станет полагаться на уступки компилятора.
Анализ: В строках 7—17 объявляется класс SimpleCat. В строке 29 инициализируется ссылка на объект класса SimpleCat с использованием результатов вызова функции TheFunction(), объявленной в строке25. Согласно объявлению эта функция возвращает ссылку на объект класса SimpleCat. В теле функции TheFunction() объявляется локальный объект типа SimpleCat и инициализируется его возраст и вес. Затем этот объект возвращается по ссылке. Некоторые компиляторы обладают достаточным интеллектом, чтобы распознать эту ошибку, и не позволят вам запустить данную программу на выполнение. Другие же (сразу видно, кто настоящий друг) спокойно разрешат вам выполнить эту программу с непредсказуемыми последствиями. По возвращении функции TheFunction() локальный объект Frisky будет разрушен (надеюсь, безболезненно для самого объекта). Ссылка же, возвращаемая этой функцией, останется псевдонимом для несуществующего объекта, а это явная ошибка.
Возвращение ссылки на в области динамического обмена
Можно было бы попытаться решить проблему, представленную в листинге9.13, сориентировав функцию TheFunction() на размещение объекта Frisky в области динамического обмена. В этом случае после возврата из функции TheFunction() объект Frisky будет все еще жив. Новый подход порождает новую проблему: что делать с памятью, выделенной для объекта Frisky, после окончания обработки этого объекта? Эта проблема показана в листинге 9.14. Листинг 9.14. Утечка памяти 1: // Листинг 9.14. 2: // Разрешение проблемы утечки памяти 3: #include < iostream.h> 4: 5: class SimpleCat 6: { 7: public: 8: SimpleCat (int age, int weight); 9: ~SimpleCat() { } 10: int GetAge() { return itsAge; } 11: int GetWeight() { return itsWeight; } 12: 13: private: 14: int itsAge; 15: int itsWeight; 16: }; 17: 18: SimpleCat:: SimpleCat(int age, int weight) 19: { 20: itsAge = age; 21: itsWeight = weight; 22: } 23: 24: SimpleCat & TheFunction(); 25: 26: int main() 27: { 28: SimpleCat & rCat = TheFunction(); 29: int age = rCat.GetAge(); 30: cout < < " rCat " < < age < < " years old! \n"; 31: cout < < " & rCat: " < < & rCat < < endl; 32: // Как освободить эту память? 33: SimpleCat * pCat = & rCat; 34: delete pCat; 35: // Боже, на что же теперь ссылается rCat?? 36: return 0; 37: } 38: 39: SimpleCat & TheFunction() 40: { 41: SimpleCat * pFrisky = new SimpleCat(5, 9); 42: cout < < " pFrisky: " < < pFrisky < < endl; 43: return *pFrisky; 44: }
Результат: pFrisky: 0x00431C60 rCat 5 years old! & rCat: 0x00431C60
Предупреждение: Эта программа компилируется, компонуется и, кажется, работает. Но мина замедленного действия уже ожидает своего часа.
Анализ: Функция TheFunction() была изменена таким образом, чтобы больше не возвращать ссыпку на локальную переменную. В строке 41 вычисляется некоторая область динамически распределяемой памяти и ее адрес присваивается указателю. Этот адрес выводится на экран, после чего указатель разыменовывается и объект класса SimpleCat возвращается по ссылке. В строке 28 значение возврата функции TheFunction() присваивается ссылке на объект класса SimpleCat, а затем этот объект используется для получения возраста кота, и полученное значение возраста выводится на экран в строке 30. Чтобы доказать, что ссылка, объявленная в функции main(), ссылается на объект, размещенный в области динамической памяти, выделенной для него в теле функции TheFunction(), к ссылке rCat применяется оператор адреса (& ). Вполне убедителен тот факт, что адрес объекта, на который ссылается rCat, совпадает с адресом объекта, расположенного в свободной области памяти. До сих пор все было гладко. Но как же теперь освободить эту область памяти, которая больше не нужна? Ведь нельзя же выполнять операции удаления на ссылках. На ум приходит одно решение: создать указатель и инициализировать его адресом, полученным из ссылки rCat. При этом и память будет освобождена, и условия для утечки памяти будут ликвидированы. Все же одна маленькая проблема остается: на что теперь ссылается переменная rCat после выполнения строки 34? Как указывалось выше, ссылка всегда должна оставаться псевдонимом реального объекта; если же она ссылается на нулевой объект (как в данном случае), о корректности программы говорить нельзя.
Примечание: Не будет преувеличением определение программы как некорректной, если она содержит ссылку на нулевой объект (несмотря на то что она успешно компилируется), поскольку результаты ее выполнения непредсказуемы.
Для решения этой проблемы есть три пути. Первый состоит в объявлении объекта класса SimpleCat в строке 28 и возвращении этого объекта из функции TheFunction как значения. Второй — в объявлении класса SimpleCat в свободной области (в теле функции TheFunction()), но сделать это нужно так, чтобы функция TheFunction() возвращала указатель на данный объект. Затем, когда объект больше не нужен, его можно удалить в вызывающей функции с помощью оператора delete. Третье решение (возможно, самое правильное) — объявить объект в вызывающей функции, а затем передать в функцию TheFunction() ссылку на него.
А где же уазатель?
При выделении в программе памяти в области динамического обмена возвращается указатель. Важно сохранить указатель на эту область памяти, поскольку при его утрате эту память нельзя удалить, что приводит к ее утечке. При передаче данных, хранящихся в этом блоке памяти, между функциями, необходимо следить, кому принадлежит этот указатель. Обычно ответственность за освобождение ячеек памяти в области динамического обмена ложится на ту функцию, которая их зарезервировала. Но это не догма, а лишь рекомендация для программистов. Весьма небезопасно, если одна функция создает объект с выделением для него некоторой памяти, а другая занимается освобождением этой памяти. Неопределенность относительно владельцев указателя может привести к одной из двух проблем: можно забыть освободить память или применить оператор delete дважды к одному и тому же указателю. Любая из этих проблем может стать причиной больших неприятностей в вашей программе. Именно поэтому целесообразно придерживаться принципа, что память освобождает та функция, которая ее зарезервировала. Если вы пишете функцию, которая требует выделения памяти в области динамического обмена, а затем возвращаете этот объект в вызывающую функцию, пересмотрите свой интерфейс. Пусть лучше вызывающая функция выделяет память, а затем передает в другую функцию этот объект как ссылку. Затем, после возвращения объекта из функции, его можно будет удалить в вызывающей функции, где он и был создан.
Рекомендуется: Передавайте параметры функции как значениятолько тогда, когда в этом есть необходимость. Возвращайте результат работы функции как значение только тогда, когда в этом есть необходимость.
Не рекомендуется: Не используйте ссылки на объекты, которые могут выйти в программе за пределы области видимости. Не создавайте ссылки на нулевые объекты.
Резюме
Сегодня вы узнали, что представляют собой ссылки и чем они отличаются от указателей. Важно уяснить для себя, что ссылки всегда инициализируют существующие объекты и их нельзя переназначить до окончания программы. Ссылка выступает псевдонимом объекта, и любое действие, выполненное над ссылкой, выполняется над ее адресатом. Доказательством этого может служить тот факт, что при взятии адреса ссылки возвращается адрес связанного с ней объекта. Вы убедились, что передача объектов в функции как ссылок может быть более эффективной, чем передача их как значений. Передача объектов как ссылок позволяет вызываемой функции изменять значения переменных вызывающей функции. Вы также узнали, что аргументы, передаваемые функции, и значения, возвращаемые из функций, могут передаваться как ссылки и этот процесс можно реализовать как с помощью указателей, так и с помощью ссылок. Теперь вы научились для безопасной передачи значений между функциями использовать константные указатели на константные объекты или константные ссылки, благодаря чему достигается как эффективность, так и безопасность работы программы.
Вопросы и ответы
Зачем использовать ссыпки, если указатели могут делать ту же работу? Ссылки легче использовать, и они проще для понимания. Косвенность обращений при этом скрывается, и отсутствует необходимость в многократном разыменовании переменных. Зачем нужны указатели, если со ссыпками легче работать? Ссылки не могут быть нулевыми, и их нельзя переназначать. Указатели предлагают большую гибкость, но их сложнее использовать. Зачем вообще результат функции возвращать как значение? Если возвращается объект, который является локальным в данной функции, необходимо организовать возврат его именно как значения, в противном случае возможно появление ссылки на несуществующий объект. Если существует опасность от возвращения объекта как ссылки, почему бы тогда не сделать обязательным возврат по значению? При возвращении объекта как ссылки достигается гораздо большая эффективность, которая заключается в экономии памяти и увеличении скорости работы программы.
Коллоквиум
В этом разделе предлагаются вопросы для самоконтроля и укрепления полученных знаний, а также ряд упражнений, которые помогут закрепить ваши практические навыки. Попытайтесь самостоятельно ответить на вопросы теста и выполнить задания, а потом сверьте полученные результаты с ответами в приложении Г. Не приступайте к изучению материала следующей главы, если для вас остались неясными хотя бы некоторые из предложенных ниже вопросов. Контрольные вопросы
1. В чем разница между ссылкой и указателем? 2. Когда нужно использовать именно указатель, а не ссылку? 3. Что возвращает оператор new, если для создания нового объекта недостаточно памяги? 4. Что представляет собой константная ссылка? 5. В чем разница между передачей объекта как ссылки и передачей ссылки в функцию?
Упражнения
1. Напишите программу, которая объявляет переменную типа int, ссылку на значение типа int и указатель на значение типа int. Используйте указатель и ссылку для управления значением переменной типа int. 2. Напишите программу, которая объявляет константный указатель на постоянное целое значение. Инициализируйте этот указатель, чтобы он указывал на целочисленную переменную varOne. Присвойте переменной varOne значение 6. Используйте указатель, чтобы присвоить переменной varOne значение 7. Создайте вторую целочисленную переменную varTwo. Переназначьте указатель, чтобы он указывал на переменную varTwo. Пока не компилируйте это упражнение. 3. Скомпилируйте программу, написанную в упражнении 2. Какие действия компилятор считает ошибочными? Какие строки генерируют предупреждения? 4. Напишите программу, которая создает блуждающий указатель. 5. Исправьте программу из упражнения 4, чтобы блуждающий указатель стал нулевым. 6. Напишите программу, которая приводит к утечке памяти. 7. Исправьте программу из упражнения 6. 8. Жучки: что неправильно в этой программе? 1: #include < iostream.h> 2: 3: class CAT 4: { 5: public: 6: CAT(int age) { itsAge = age; } 7: ~CAT(){ } 8: int GetAge() const { return itsAge; } 9: private: 10: int itsAge; 11: }; 12: 13: CAT & MakeCat(int age); 14: int main() 15: { 16: int age = 7; 17: CAT Boots = MakeCat(age); 18: cout < < " Boots is " < < Boots.GetAge() < < " years old! \n"; 19: return 0; 20: } 21: 22: CAT & MakeCat(int age) 23: { 24: CAT * pCat = new CAT(age); 25: return *pCat; 26: } 9. Исправьте программу из упражнения 8.
Популярное:
|
Последнее изменение этой страницы: 2017-03-08; Просмотров: 539; Нарушение авторского права страницы