Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdfЧЛС/УМЬ II
бъектноориентированное программирование
на C++
]^]^_ этой части книги описываются базовые инструментальные средства
ЖвЩ^объектно-ориентированного программирования на С4- + . Объектно-
^<^[^и^ ориентированное программирование — это в первую очередь исполь зование функций, ведь каждая операция с объектом должна реализовываться через
вызов функции. Функции С+Н достаточно сложная тема, и в главе 7 рассказы вается об их синтаксисе. Передача параметров C + + может представлять труд ности. Остается надеяться, что данная глава поможет читателям овладеть этими важными навыками.
В главе 8 продолжается обсуждение функций и поясняется, как их использо вать. Она знакомит с критериями сцепления, связности, инкапсуляции и сокрытия информации. В ней рассказано, как писать легко читаемые и независимые функ ции, продемонстрировано, что основные преимуш,ества объектно-ориентирован ного программирования могут достигаться и без использования объектов С+Н за счет проектирования функций, вызываемых клиентскими программами (вместо
прямого доступа к полям структуры). Показываются и ограничения объектно-ори ентированного программирования с применением функций, перечисляются цели использования классов C++ . Эта глава очень важна для выработки верной интуи ции при объектно-ориентированном программировании.
В главе 9 читатель познакомится с вершиной программирования на С+Н классами. Здесь описывается синтаксис определения классов C + + , обсуждаются функции-члены и элементы данных, управление доступом к компонентам класса, инициализация, уничтожение объектов и другие технические детали работы с объ ектами. Читатели найдут немало сложных подробностей, но без этого не обойтись. Классы С+Н тема не простая. Пусть эти детали не скрывают от вас основной цели применения классов — доведение до сопровождаюидего приложение про граммиста общего смысла обработки данных и передачи информации между функциями.
Глава 10 посвящена операторным функциям — примечательной части син таксиса C + + . Операторные функции введены в язык для поддержки принципа, согласно которому программа должна иметь возможность делать с объектами класса все, что она может делать с обычными числовыми значениями,-включая сложение, вычитание и т. д. Такая концепция не очень важна с точки зрения раз работки ПО, но позволяет дополнить код C + + превосходными синтаксическими конструкциями.
В главе 11 обсуждаются опасности непродуманного использования конструк торов и деструкторов C+ + , поясняется, как можно вовремя увидеть данную опасность. В ней приводятся некоторые методы, позволяющие избежать порчи содержимого и "у'^^ечек" памяти. Это очень важно, так как неопытный програм мист может нанести немало вреда, некорректно работая с инициализацией объек тов C+ + .
^ / ^ г
^
п,"Программирование с использованием функций C+ +
Темы данной главы
^ Функции C++ как инструмент разбиения программы на модули »^ Передача и преобразование аргументов
•^ Передача параметров в C++
^Встраиваемые функции
^Параметры со значениями по умолчанию •^ Перегрузка имен функций
^Итоги
J^\^ предыдущих главах мы рассмотрели основные концепции языка C+ + , ш ~5^лозволяющие реализовать любые сложные требования, стоящие перед
^ ^^"^Z^ компьютерной системой.
Встроенные типы данных C + + дают программисту возможность привести объекты в соответствие с решаемой задачей. Они предоставляют богатый выбор для числовых диапазонов и точности. С помощью операций C + + вводимые зна чения комбинируются в гибкие и мощные выражения для вычисления необходи мого результата, а управляющие структуры помогают организовать требуемую последовательность вычислений и изменить ход вычислений в зависимости от условий, либо итеративно повторять их.
Мы рассмотрели также средства C+ + , обеспечивающие агрегирование ком понентов, и типы данных, определяемые программистом. С их помощью програм мист может комбинировать отдельные логически связанные значения данных для работы с ними как с целыми блоками. Разработчику они позволяют выражать свои идеи в понятном сопровождающему приложение программисту виде, показывая, что эти компоненты должны быть связаны. Массивы дают программисту возмож ность комбинировать элементы, подлежащие одинаковой обработке в программе. Наконец, мы обсудили управление динамической памятью и файлами. Они расши ряют мощность и гибкость обычных массивов и позволяют преодолеть свойствен ные массивам ограничения.
254 |
Часть II • Объектно-ориентированное программирование но С^-^ |
|
|
Далее нам предстоит углубиться в еще один инструмент модульности и агреги |
|
|
рования C+ + . Комбинируя отдельные элементы в функции, программист может |
|
|
интерпретировать их как единый логический блок. Разбиение программы на |
|
|
отдельные функции — мощный инструмент разделения труда. Разные программи |
|
|
сты могут создавать отдельные функции параллельно. |
|
|
В данной главе мы изучим методы написания функций C + + . Основное внима |
|
|
ние будет уделено коммуникациям с функциями — тому, как они обмениваются |
|
|
данными, методам передачи параметров и возврата значений из функций. Эти |
|
|
методы зависят от того, модифицирует ли функция свои аргументы или сохраняет |
|
|
те значения, которые они имели при вызове. Кроме того, они зависят от типа |
|
|
параметров: это может быть встроенный тип C+ + , массив, определенная про |
|
|
граммистом структура или класс. |
|
|
Часть главы посвящена методам проектирования функций, позволяющим |
|
|
ослабить ограничения, связанные с именами функций, включая значения пара |
|
|
метров по умолчанию и перегрузку имен функций (overloading). Эти методы суще |
|
|
ственно расширяют возможности выбора программистом различных вариантов |
|
|
реализации. Кроме того, читатели узнают, как преодолеть отрицательное влияние |
|
|
на производительность при использовании встраиваемых функций (inline) и что |
|
|
происходит, когда подставляемые в вызове функции аргументы не соответствуют |
|
|
по типу формальным параметрам, определенным в заголовке функции. |
|
|
Весьма амбициозная программа. Функции С+Н |
гибкий и мощный меха |
|
низм. Они предоставляют программисту изумительные возможности выбора раз |
|
|
ных вариантов их реализации. Попробуем извлечь из этого некоторую выгоду. |
|
|
Весь материал данной главы крайне важен для освоения классов C + + . Прочи |
|
|
тайте его, экспериментируйте с примерами, и их использование окажется не таким |
|
|
уж сложным. |
|
Функции С+ + как средства разбиения программы на модули
Как и в других языках, в С + + программист скрывает сложность компьютерных алгоритмов в относительно небольших модульных единицах — функциях. Каждая функция представляет собой набор операторов языка, предназначенных для дос тижения определенной цели. Эти операторы могут представлять собой простое присваивание, сложные управляющие конструкции или вызовы других функций — стандартных библиотечных функций, созданных в предыдущих проектах или спе циально разработанных для конкретного проекта. С точки зрения программиста, разница между реализациями различных функций в том, что код специально раз работанных для проекта функций можно проверить. Программист, использующий библиотечные функции как серверы для собственной функции, не знает способы их реализации. Ему известно лишь описание интерфейса серверной функции: какие параметры нужно ей передавать, что именно вычисляет функция, какие результаты получаются из значений на входе, какие ограничения и исключения применяются к данной функции.
Это не означает, что исходный код библиотечных функций является торговым секретом. Иногда (но не часто) он свободно распространяется. На самом деле ограничение знаний программиста интерфейсом функции имеет свои преимуще ства: это уменьшает сложность программы. Изучение исходного кода функций оправдано только в том случае, если функция содержит ошибки, которые можно исправить. Это имеет место в функциях, определяемых программистом и созда ваемых для конкретного проекта. Но даже в этих функциях задача анализа взаи модействия между функциями должна ограничиваться изучением интерфейсов функций, а не их реализации.
256 |
Часть II « Обьектно-ориентированное програттыроваиив на С4-+ |
Если функция не возвращает значения, то возвращаемый ею тип не просто опускается, а описывается как void, однако, если возвращаемый тип опущен, это не синтаксическая ошибка. Компилятор предполагает, что программист хочет ис пользовать вместо void тип int, поэтому пропуск типа вполне допустим. Пропуск возвращаемого функцией типа — популярный метод программирования на C+ + . Однако он затрудняет понимание программы, и сопровождающему ее програм мисту приходится тратить время на уяснение сути вещей. Если функция возвра щает тип int, нужно так и сказать об этом. Пропуск типа — порочная практика. Кроме того, некоторые компиляторы могут выводить предупре>вдение, что данный стиль определения функции устарел.
add(int X, int у); |
/ / |
возвращает i n t : плохой |
стиль |
void PutValues(int val, int cnt); |
/ / |
не возвращает значения: |
тип void |
Функция может возвращать только одно значение. Если клиентской программе нужно получать от нее несколько значений, то функция может возвращать струк турную переменную, хотя это замедляет выполнение программы. Кроме того, функ ция может модифицировать любое число глобальных переменных, определенных в файле вне этой функции. Как будет показано далее, ни один из этих методов нельзя считать хорошей практикой программирования — они велут к ошибкам. Функция может также модифицировать значения своих аргументов. Все эти мето ды достаточно сложны, но не отчаивайтесь. Вы их освоите.
Определения функций
в определении функции реализуется ее алгоритм на C+ + . Определение начи нается со строки заголовка, где указывается тип возвращаемого значения, имя функции (определенное программистом идентификатора). Оно содержит также список разделенных запятыми параметров (имена и типы). Разница между заго ловком функции и прототипом функции в том, что прототип заканчивается точкой с запятой, а заголовок — нет. Еще одно отличие в параметрах: в прототипах они обязательны, а в заголовках функции — нет. На самом деле, если параметр в теле функции не используется, его имя в заголовке также необязательно, но вряд ли кому в голову придет писать такую функцию.
Тело функции — это блок со своей областью действия. Как и в любом коде C++, если не используется управляющая конструкция или вызов другой функции, операторы в теле функции выполняются последовательно.
void Put\/alues(int |
val, |
Int |
cnt) |
|
|
|||
{ |
cout |
« |
"Значение " « |
val |
« " встречается "; |
|
||
|
cout |
« |
cnt « " |
раз" |
« endl; |
|
|
|
|
return; |
} |
|
|
// необязательно, функция void |
|||
|
|
|
|
|
|
// не возвращает значения |
||
int add |
(int x, int y) |
|
|
|
|
|||
{ |
count++; |
|
|
|
/ / |
глобальная переменная модифицирована |
||
|
return x+y; } |
|
|
/ / |
оператор |
return и возвращаемое |
||
|
|
|
|
|
|
/ / |
значение |
необязательны |
Для функции void оператор return необязателен. Такие операторы можно использовать в любом месте функции void, но они не возвращают значения. Выполнение оператора return завершает выполнение функции и возвращает управление в вызывающую программу. Отличная от void функция должна содер жать по крайней мере один оператор return, а может использоваться несколько операторов return. Каждый return в этом случае должен возвращать значение, тип которого указан в заголовке функции (или преобразовываться к данному типу в операторе return).
Глава 7 • Программирование с исоользование^л функций €4-+ |
Г 257 I |
Вызовы функций
в языках Паскаль, Ада и др. различаются процедуры и функции. Процедуры там не возвращают значений, но могут изменять свои аргументы и глобальные переменные. В клиентском коде они вызываются только в отдельных операторах, но не в выражениях. Функции в этих языках возвращают значения, но не могут влиять на аргументы. В клиентском коде их нельзя использовать как отдельные операторы, а нужно включать в выражения (или как г-значение в операторе при сваивания):
а = add(b,c) * 2; |
/ / |
использование возвращаемого значения в выражении |
PutValues(a,5); |
/ / |
вызов функции в операторе |
b = PutValues(a,5)*2; |
/ / |
нонсенс: нет возвращаемого значения |
В С+Н- процедуры и функции не различаются. Функции С+Ч- могут как воз вращать значения, так и иметь побочные эффекты. Тип возвращаемого значе ния — это любой встроенный или определяемый программистом тип, но массивы быть возвращаемым значением не могут.
Если функция имеет тип void, она работает как процедура и не возвращает значения в вызывающую программу. Ее нельзя использовать в выражении. Такая функция должна вызываться в отдельном операторе.
В отличие от языков Паскаль и Ада, C++ позволяет клиенту игнорировать возвращаемое функцией значение и использовать вызов функции как вызов процедуры. Это означает, что отличная от void функция может быть частью выражения и вызываться в отдельном операторе. Когда вызывающая программа использует вызов функции как оператор, единственная цель такого вызова — побочные эффекты (изменение глобальных переменных):
add(b,c); / / корректный синтаксис, даже если в нем нет смысла
Это неправильная практика программирования. Если функция возвращает значение, его следует употребить в клиентском коде. Между тем, в C++ сущест вует немало библиотечных функций, возвращающих отличные от void значения, которые используются редко, например sctcpyO и strcat().
Тело функции в фигурных скобках определяет действие, выполняемое при вызове функции. При этом говорят, что операция вызова () применяется к имени функции и разделенным запятыми аргументам вызова:
PutValues(17,14); |
/ / применяется оператор вызова функции |
Большинство не рассматривают вызов функции в терминах применения опе рации вызова. Достаточно думать о списке аргументов в скобках. Между тем в "продвинутом" программировании на C++ важно не забывать о том, что вызов функции C++ представляет собой применение операции вызова. Более того, эту операцию можно использовать в других контекстах, придавая ей иной смысл.
Если определение функции-сервера лексически предшествует определению функции-клиента, то компилятор перед обработкой вызова функции видит опре деление сервера. В этих случаях определение сервера может служить также его объявлением, но многие программисты не полагаются на лексический порядок функций и применяют прототипы — это становится привычкой.
В программе функция может определяться только один раз, а ее прототипы — повторяться столько раз, сколько необходимо (и даже более). Часто прототипы функции размещают в отдельных заголовочных файлах в каталоге проекта. Такие файлы включаются в программы, вызывающие эти функции. Часто программисты включают в исходный файл не только вызываемые функции, но и другие, пока не используемые в программе. Это проще, чем изучать, кто и что вызывает и в каком файле. Компилятор воспринимает такие действия нормально — лишние прото типы он просто игнорирует. В то же время заголовочные файлы не должны
