Текст
                    Учимся программировать на языке C++


RESCUED BY с ++ SECOND EDITION KRISJAMSA, Ph.D.
К.ДЖАМСА УЧИМСЯ ПРОГРАММИРОВАТЬ НА ЯЗЫКЕ с Перевод с английского С. П. Кошеля Москва МИР 1997
ББК 32.973 Дж40 УДК681.3.06 Джамса К. Дж40 Учимся программировать на языке C+ + : Пер. с англ. — М.: Мир, 1997.-320 с, ил. ISBN 5-03-003265-7 Книга американского автора представляет собой прекрасный учебник по языку программирования C++. Она построена в виде уроков, содержит ясные инструкции и иллюстрации. Предложенные в книге типовые программы читатель сможет создать в течение нескольких минут, приступая к изучению каждого урока. Книга начинается с самых основ и шаг за шагом ведет через все аспекты C++ и объектно-ориентированного программирования. Для начинающих программистов, а также программистов, переходящих с языка С на язык C++. ББК 32.973 Редакция литературы по информатике и новой технике Федеральная целевая программа книгоиздания России Научное издание Крис Джамса УЧИМСЯ ПРОГРАММИРОВАТЬ НА ЯЗЫКЕ C++ Заведующий редакцией Т. Г. Хохлова. Ведущий редактор Н. М. Савина. Художник В. И. Кейдан. Художественный редактор Л. М. Аленичева. Технический редактор Е. В. Денюкова. Корректор Г. И. Герман Оригинал-макет подготовлен В. Н. Цлаф ИБ№ 8811 Лицензия Л. Р. № 010174 от 22.01.92 г. Подписано к печати 15.11.96. Формат 70 х 100/16. Бумага офсетная. Печать офсетная. Объем 10,00 бум. л. Усл. печ. л. 26,00. Усл. кр.-отт. 26,65. Уч.-изд. л. 18,56. Изд. № 6/9612. Тираж 5000 экз. Заказ 637. С156 Издательство «Мир» Государственного комитета Российской Федерации по печати 129820, ГСП, Москва, И-110, 1-й Рижский пер., 2. Типография АО «Внешторгиздат» 127576, Москва, ул. Илимская, 7. ISBN 5-03-003265-7 (русск.) © 1996 by Jamsa Press ISBN 1-884133-08-8 (англ.) © перевод на русский язык, «Мир» 1997
Изучение основ В этой части вы изучите основные понятия, необходимые для создания собственных программ на C++. Если вы никогда раньше не создавали программ, не беспокойтесь, здесь описано все шаг за шагом с самого начала. К тому времени, когда вы освоите несложные уроки, представленные в этой части, вы будете готовы выбрать свой путь в программировании на C++! Уроки этой части: Урок 1. Создание вашей первой программы. Урок 2. Более внимательный взгляд на C++. Урок 3. Вывод сообщений на экран. Урок 4. Программы хранят информацию в переменных. Урок 5. Выполнение простых операций. Урок 6. Чтение ввода с клавиатуры* Урок 7. Программа принимает решение. Урок 8. Повторение одного или нескольких операторов.
Урок 1 Создание вашей первой программы Все вы использовали компьютерные программы, такие как текстовый процессор, электронные таблицы и даже Microsoft Windows 95. Компьютерные программы, или программное обеспечение, представляют собой файлы, содержащие инструкции, которые указывают компьютеру, что следует делать. Если вы работаете в среде MS-DOS или Windows, то, например, файлы с расширениями ЕХЕ и СОМ содержат команды, которые компьютер может выполнять. Другими словами, файлы содержат специальные инструкции, выполняемые компьютером, обычно одну за другой, для решения определенной задачи. При создании программы вы указываете инструкции, которые компьютер должен выполнить. Из этого урока вы узнаете, как указать такие инструкции с помощью операторов C++. К концу данного урока вы освоите следующие основные концепции: • При создании программы используйте текстовый редактор, чтобы ввести операторы C++ в исходный файл программы. • Для преобразования операторов программы C++ в выполнимую программу, в единицы и нули, которые понимает компьютер, используйте специальную программу — компилятор C++. • Для изменения или исправления программы используйте текстовый редактор. • При нарушении одного (или более) правил программирования на C++ компилятор выдаст на экран сообщения о синтаксических ошибках. Вам следует отредактировать программу, чтобы исправить ошибки, а затем запустить компилятор снова. Программирование представляет собой процесс определения последовательности инструкций, которые должен выполнить компьютер для решения определенной задачи. Для указания этих инструкций вы используете язык программирования, например C++. С помощью текстового редактора вы вносите программные операторы в исходный файл. Далее вы используете специальную программу — компилятор — для преобразования операторов из формата, который вы можете читать и понимать, в единицы и нули, которые понимает компьютер. Лучший способ понять процесс создания и компиляции программы — построить простую программу на C++. Давайте этим займемся!
8 Урок 1 СОЗДАНИЕ ПРОСТОЙ ПРОГРАММЫ Как и следовало ожидать, ваша первая программа на C++ называется FIRST.CPP. При создании программ на C++ используйте расширение СРР, чтобы другие могли понять, что этот файл содержит программу на C++. Когда вы позже запустите эту программу, она выведет на экран дисплея сообщение Учимся программировать на языке C++/Следующий пример вывода показывает подсказку командной строки (в данном примере С:\>), вводимую вами командную строку (имя программы FIRST, за которым следует ENTER) и вывод программы на экран: С:\> FIRST <ENTER> Учимся программировать на языке C++! Как известно, при программировании вы можете работать в среде, основанной на командной строке, например MS-DOS или UNIX, или в среде типа Windows. Для упрощения вывода в данной книге подразумевается, что вы работаете из командной строки. В этом случае для выполнения программы FIRST.EXE вы должны ввести имя программы FIRST в ответ на системную подсказку и затем нажать ENTER. Для начала вам следует использовать текстовый редактор, например EDIT (поставляется с MS-DOS), для создания файла, который содержит операторы программы и называется исходным файлом. Не используйте текстовый процессор, такой как Word или WordPerfect, для создания исходного файла программы. Как известно, текстовые процессоры позволяют вам создавать форматированные документы, которые могут содержать полужирный текст, выровненные поля, а также другие особенности. Чтобы отформатировать документы таким образом, текстовый процессор вставляет специальные (скрытые) символы внутрь документа. Такие символы могут включать или выключать курсив или выбирать определенную ширину полей. Несмотря на то что такие специальные символы имеют смысл для текстового процессора, C++ их не поймет и эти символы приведут к ошибкам. С помощью текстового редактора введите следующие операторы программы C++ (точно так, как они изображены, используя верхний и нижний регистры), как показано ниже: #include <iostream.h> void main(void) { cout << "Учимся программировать на языке C++!"; } Не беспокойтесь, если операторы C++ не имеют для вас смысла. Вы узнаете назначение каждого из них из урока 2. А пока обратите особое внимание на ваш ввод. Удостоверьтесь, например, что вы ввели верное количество кавычек, точек с запятой и скобок. Еще раз более внимательно проверьте операторы своей программы. Если они верны, сохраните операторы в файле FIRST.CPP.
Создание вашей первой программы 9 Что означает имя? При создании программы на C++ вы вносите операторы программы в исходный файл. Применяйте расширение СРР, чтобы другие программисты могли понять, что данный файл содержит программу на C++. Далее используйте имя файла, которое указывает назначение программы. Например, если вы создаете финансовую программу, можно использовать имя BUDGET.СРР. Аналогичным образом программу, которая вычисляет оклады в фирме, вы можете назвать SALARY.СРР. Чтобы избежать путаницы, никогда не используйте для названия программы имя существующей команды MS-DOS, например COPY или DEL. КОМПИЛЯЦИЯ ВАШЕЙ ПРОГРАММЫ Компьютер работает с комбинациями единиц и нулей (называемых машин- ным языком), которые представляют наличие или отсутствие электрических сигналов. Если сигнал равен единице (наличие), компьютер может выполнить одну операцию, а если сигнал равен нулю (отсутствие), компьютер может выполнить другую операцию. Однако к счастью, нет необходимости писать программы в нулях и единицах (как это делали программисты в 1940 и 50 гг.). Вместо этого специальная программа — компилятор С+н— преобразует операторы программы (ваш исходный код) в машинный язык. Другими словами, компилятор просматривает исходный файл, содержащий операторы программы на C++. Если ваши операторы не нарушают ни одно правило языка C++, компилятор преобразует их в машинный язык (единицы и нули), который компьютер может выполнить. Компилятор хранит машинный язык в выполняемом файле, имеющем, как правило, расширение ЕХЕ. Если файл ЕХЕ существует, вы можете запустить программу, вводя ее имя в ответ на командную подсказку. В зависимости от используемого вами компилятора, команды, которые вы применяете для его вызова, будут различны. Например, при использовании Borland C++ вам следует компилировать программу FIRST.CPP с помощью команды ВСС: С:\> ВСС FIRST.CPP <ENTER> Если вы применяете не Borland C++, обратитесь к документации, поставляемой с вашим компилятором, чтобы определить правильную команду для его запуска. По окончании работы компилятор создаст выполнимую программу и сохранит ее в файле на диске. В среде MS-DOS файл выполнимой программы будет иметь расширение ЕХЕ, например FIRST.EXE. Если при компиляции программы компилятор выдает сообщения об ошибках, отредактируйте свой исходный файл и сравните каждый символ исходного файла с символами, которые приведены в этой книге. Исправьте все ошибки, сохраните сделанные изменения, а затем откомпилируйте про-
10 Урок 1 грамму второй раз. После того как вы успешно откомпилировали свою программу, запустите ее, вводя имя программы в ответ на командную подсказку, как это было показано выше. Представление о компиляторе При создании программы вы используете язык программирования (такой как С++), чтобы указать инструкции, выполняемые компьютером. Применяя текстовый редактор, вы вносите операторы программы в исходный файл. Далее используется специальная программа — компилятор, которая преобразует ваш исходный файл в машинный язык (единицы и нули, понимаемые компьютером). Если компиляция прошла успешно, результатом будет файл выполнимой программы. Однако если вы допустили одну или несколько ошибок или нарушили какое-либо правило C++, компилятор выдаст на экран сообщения об ошибках, и для их исправления вам следует заново отредактировать исходный файл. Если вы работаете на большой машине или мини-ЭВМ, у вас должен быть компилятор, доступный для вас и других пользователей вашей системы. Если же вы используете ПК, вам следует приобрести и установить компилятор, такой как Borland C++ или Microsoft Visual C++. СОЗДАНИЕ ВТОРОЙ ПРОГРАММЫ Хочется надеяться, что вы смогли успешно откомпилировать и выполнить программу FIRST.CPP. Если это так, используйте ваш текстовый редактор, чтобы создать второй программный файл с именем EASY.CPP, который содержит следующие операторы программы: #include <iostream.h> void main(void) { cout << "Программировать на C++ просто!"; } Как и ранее, сохраните свои операторы программы на C++ в исходном файле и вызовите компилятор, указав имя файла программы в командной строке компилятора. В случае Borland C++ используйте следующую команду для компиляции программы: С:\> ВСС EASY.CPP <ENTER> Если компиляция программы прошла успешно, компилятор создаст выполнимую программу с именем EASY.EXE. Когда вы запустите эту программу, на вашем экране появится следующее сообщение:
Создание вашей первой программы 11 С:\> EASY <ENTER> Программировать на C++ просто! Далее, используя редактор, отредактируйте исходный файл EASY.CPP и измените выводимое на экран сообщение таким образом, чтобы включить слово очень, как показано ниже: cout << "Программировать на C++ очень просто!"; Сохраните ваше изменение в исходном файле и откомпилируйте программу. После успешной компиляции запустите программу, как показано ниже: С:\> EASY <ENTER> Программировать на C++ очень просто! Каждый раз, когда вы изменяете исходный файл, вам следует откомпилировать программу заново, чтобы изменения вступили в силу. Например, используйте текстовый редактор, чтобы снова изменить исходный файл. В этот раз добавьте новую строку в свою программу: #include <iost?eam.h> void main(void) { cout << "Программировать на C++ очень просто!"; cout << endl << "Можно расслабиться!"; } Сохраните ваши изменения в исходном файле. Затем запустите программу, как показано ниже: С:\> EASY <ENTER> Программировать на C++ очень просто! Как видите, программа не выводит новую строку на экран. Чтобы изменения в исходном файле вступили в силу, вы должны откомпилировать программу. В данном случае необходимо откомпилировать программу так, как было описано выше, а затем запустить ее. Так как компилятор использовал изменения вашего исходного кода, то, как показано ниже, на экран будет выведена новая строка: С:\> EASY <ENTER> Программировать на C++ очень просто! Можно расслабиться! ИЗУЧЕНИЕ СИНТАКСИЧЕСКИХ ОШИБОК Каждый язык — английский, французский, немецкий и даже С+н— имеет набор правил, называемых синтаксисом, которым вы должны следовать, когда используете данный язык. В английском языке, например, предложения
12 Урок 1 обычно заканчиваются точкой, восклицательным или вопросительным знаком. Вы также используете заглавные буквы в начале предложения. В синтаксисе C++ используется точка с запятой, круглые скобки, фигурные скобки и многие другие символы. Когда вы забываете или неправильно употребляете эти символы, компилятор C++ выводит на экран сообщение об ошибке, которое описывает ошибку и соответствующий ей номер строки в исходном файле. Компилятор C++ не может создать выполнимую программу, пока не будут исправлены все синтаксические ошибки. Чтобы понять процесс обнаружения и исправления синтаксических ошибок, создайте следующую программу с именем SYNTAX. CPP: #include <iostream.h> void main(void) { cout << Заключайте сообщение в кавычки; } Если посмотреть внимательно, можно заметить, что сообщения, выведенные двумя предыдущими программами, в вашем исходном файле взяты в кавычки. Синтаксис (правила) C++ требует кавычек. При компиляции программы компилятор выведет сообщения о синтаксических ошибках. В случае Borland C++ компилятор выведет следующие сообщения: С:\> ВСС SYNTAX.CPP <ENTER> Borland C++ Version 4.00 Copyright (c) 1993 Borland International syntax.epp: Error syntax.epp 5: Undefined symbol 'Заключайте' in function main() Error syntax.epp 5: Statement missing ; in function main() *** 2 errors in Compile *** В этом случае компилятор вывел две синтаксические ошибки. Обе ошибки относятся к 5 строке исходного файла. Отредактируйте файл и возьмите сообщение в кавычки, как показано ниже: cout << "Заключайте сообщение в кавычки"; Теперь можете успешно откомпилировать программу и получить выполнимый файл. Когда вы впервые начинаете использовать какой-либо язык программирования, можете рассчитывать на несколько синтаксических ошибок каждый раз при компиляции программы. После того как вы самостоятельно создадите несколько программ, вы будете быстро определять и исправлять подобные ошибки. Изучение синтаксических ошибок При создании программы на C++ вам следует придерживаться определенных правил, называемых правилами синтаксиса. Например, надо
Создание вашей первой программы 13 брать текстовые сообщения в кавычки и ставить точку с запятой после большинства операторов вашей программы (позже вы узнаете, для каких операторов требуется точка с запятой, а для каких нет). Если в программе нарушаются правила синтаксиса, компилятор C++ выводит сообщение об ошибке на экран. Вам следует исправить все синтаксические ошибки до того, как компилятор сможет создать выполнимую программу. РАБОТА В СРЕДЕ ТИПА WINDOWS Для упрощения в каждом из предыдущих примеров предполагается, что вы работаете в среде, основанной на командной строке, например MS-DOS или UNIX. Однако сегодня большинство программистов на C++ программируют в среде типа Windows, такой как Visual C++, или интегрированной среде разработки фирмы Borland. При программировании в среде типа Windows операторы программы не отличаются от тех, которые показаны здесь. Другими словами, операторы C++ в программе FIRST.CPP, написанной в Windows, идентичны тем, которые вы будете использовать в среде, основанной на командной строке. Что изменяется в Windows, так это процесс компиляции и запуска программы. Рисунок 1, например, иллюстрирует среду программирования Windows. Внутри такой среды программирования вы можете создавать исходные файлы, используя встроенный редактор, и затем компилировать программу с помощью выбора пункта меню или щелчка мыши по кнопке инструментальной линейки. Если программа содержит синтаксические ошибки, среда програм- Рис. 1. Среда программирования Windows. мирования выводит сообщения об ошибках в специальное окно. После того как вы успешно откомпилируете вашу программу, можно использовать
14 Урок 1 пункт меню (или кнопку инструментальной линейки) для запуска программы. Программная среда может открыть отдельное окно, в котором будет отображаться вывод программы. Среда программирования называется так, потому что обеспечивает все инструментальные средства, необходимые для создания, компиляции и запуска программ. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как создавать и компилировать программы на C++! В уроке 2 вы получите более подробный обзор операторов, которые использовались в программах, созданных в данном уроке. Вы изучите использование фигурных скобок {}, ключевых слов, таких как void, а также как научить программы направлять вывод на экран. До изучения урока 2 убедитесь, что вы освоили следующие основные концепции: 0 Программы представляют собой файлы, содержащие последовательность инструкций, которые компьютер будет выполнять. 0 Вы создаете программы на C++, используя текстовый редактор. 0 Вы сохраняете свои программы на C++ в исходных файлах, для которых используется расширение СРР. 0 Компилятор преобразует операторы программы на C++ в единицы и нули — машинный язык, который понимает компьютер. 0 Подобно всем языкам, в C++ существует набор правил, называемых синтаксисом. 0 Если вы нарушаете правила синтаксиса, компилятор выводит сообщение, описывающее ошибку. 0 Вы должны исправить все синтаксические ошибки, прежде чем компилятор создаст выполняемую программу. 0 После внесения изменений в исходный файл следует заново откомпилировать программу, чтобы изменения вступили в силу.
Урок 2 Более внимательный взгляд на C++ В уроке 1 вы создали несколько программ на C++. В то время ваша цель заключалась в том, чтобы понять процесс создания и компиляции программ на C++, а не в том, чтобы понять операторы C++. В данном уроке вы впервые более внимательно рассмотрите операторы, из которых состоит программа на C++. Вы увидите, что большинство программ на C++ придерживаются одного и того же формата: начинаются с одного или нескольких операторов ^include, содержат строку void main(void), а затем набор операторов, сгруппированных между левой и правой фигурными скобками. Из этого урока вы поймете, что эти несколько запугивающие операторы реально очень просто освоить. К концу данного урока вы изучите следующие основные концепции: • Оператор ^include обеспечивает преимущества использования заголовочных файлов, которые содержат операторы C++ или программные определения. • Основная часть программы на C++ начинается с оператора void main(void). • Программы состоят из одной или нескольких функций, которые, в свою очередь, состоят из операторов, предназначенных для решения определенной задачи. • При выводе на экран ваши программы будут широко использовать выходной поток cout. Когда вы создаете программы на C++, вы реально работаете в терминах операторов, но не инструкций. Позже вы изучите оператор присваивания, который присваивает значения переменным, оператор if, который позволяет программе принимать решения и т. д. А'пока мы просто будем ссылаться на содержимое вашей программы, как на операторы программы. ВЗГЛЯД НА ОПЕРАТОРЫ ПРОГРАММЫ В уроке 1 вы создали на C++ программу FIRST.CPP, которая содержала следующие операторы: #include <iostream.h>
16 Урок 2 void main(void) { cout << "Учимся программировать на языке C++!"; } В данном случае программа содержит три оператора. Фигурные скобки (называемые группирующими символами) группируют связанные операторы: #include <iostream.h> Операторы программы void main (void) { cout << "Учимся программировать " « "на языке C++!"; ' } В следующем разделе каждый из операторов программы описывается более подробно. ПРЕДСТАВЛЕНИЕ ОБ ОПЕРАТОРЕ «include Каждая программа, представленная в уроке 1, начинается со следующего оператора ^include: #include <iostream.h> При создании программ на C++ вы получаете преимущества от использования операторов и определений, которые обеспечивает вам компилятор. При компиляции программы оператор ^include заставляет компилятор включить содержимое заданного файла в начало вашей программы. В данном случае компилятор включит содержимое файла iostream.h. Файлы с расширением h, которые вы включаете в начало (или заголовок) вашей программы, называются заголовочными файлами. Если вы посмотрите на каталог, содержащий файлы вашего компилятора, то найдете подкаталог с именем INCLUDE, в котором находятся разные заголовочные файлы. Каждый заголовочный файл содержит определения, предоставляемые компилятором для различных операций. Например, существует заголовочный файл, который содержит определения для математических операций, другой заголовочный файл описывает файловые операции и т. д. Заголовочные файлы представляют собой файлы в формате ASCII, следо- вательно* вы можете вывести их содержимое на экран или принтер. В данный момент не беспокойтесь о содержимом заголовочных файлов. Просто поймите, что оператор ^include позволяет вам использовать эти файлы. Все программы на C++, созданные вами в процессе изучения этой книги, содержат операторы ^include, которые вы должны применять в ваших программах.
Более внимательный взгляд на C++ 17 Заголовочные файлы C++ ЧТО ТАКОЕ void main(void) При создании программы на C++ ваш исходный файл будет содержать множество операторов. Как вы поймете в процессе изучения, порядок, в котором операторы появляются в программе, не обязательно должен совпадать с порядком, в котором операторы будут выполняться при запуске программы. Каждая программа на C++ имеет один вход, с которого начинается выполнение программы, — главную программу. В программах на C++ оператор void main(void) указывает стартовую точку вашей программы. По мере того как ваши программы становятся больше и сложнее, вы будете делить их на несколько небольших легко управляемых частей. При этом оператор void main(void) указывает начальные (или главные) операторы программы — часть программы, которая выполняется первой. Представление о главной программе Исходные файлы C++ могут содержать очень много операторов. При запуске программы оператор void main(void) определяет главную программу, содержащую первый выполняемый оператор. Ваши програм- Каждая создаваемая вами программа на C++ начинается с одного или нескольких операторов ^include. Э^и операторы указывают компилятору включить содержимое заданного файла (заголовочного файла) в вашу программу, как если бы программа содержала операторы, которые находятся во включаемом файле. Заголовочные файлы содержат определения, используемые компилятором для операций различных типов. Существуют заголовочные файлы, которые определяют операции В/В (ввода/вывода) C++, системные функции (например, функции, возвращающие текущие дату и время) и многое другое. Заголовочные файлы, подобно программам на C++, представляют собой файлы в формате ASCII, содержимое которых вы можете просмотреть или напечатать. Чтобы лучше понять содержимое заголовочных файлов, найдите время для того, чтобы напечатать заголовочный файл IOSTREAM.H, содержимое которого вы будете использовать в каждой создаваемой вами программе на C++. Обычно заголовочный файл IOSTREAM.H расположен в подкаталоге с именем INCLUDE, который находится в каталоге, содержащем файлы компилятора C++. Используйте текстовый редактор, чтобы просмотреть и напечатать содержимое заголовочных файлов. Замечание: Никогда не изменяйте содержимое заголовочных файлов. Это может привести к ошибкам компиляции в каждой создаваемой вами программе.
18 Урок 2 мы на C++ должны всегда включать один и только один оператор с именем main. При рассмотрении больших программ на C++ ищите main, чтобы определить операторы, с которых начинается выполнение программы. Внутри командного файла MS-DOS вы можете проверить результат работы программы, используя команду IF ERRORLEVEL: PAYROLL IF ERRORLEVEL 0 IF NOT ERRORLEVEL 1 GOTO SUCCESSFUL IF ERRORLEVEL 1 IF NOT ERRORLEVEL 2 GOTO NO_FILE IF ERRORLEVEL 2 IF NOT ERRORLEVEL 3 GOTO NO_PAPER REM Далее идут другие команды Использование void Как только ваша программа становится более сложной, вы должны разделить ее на небольшие более легко управляемые части, называемые функциями. Функция представляет собой простой набор операторов внутри программы, выполняющих определенную задачу. Например, при создании программы платежных документов, вы могли бы создать функцию с именем salary, вычисляющую оклад служащих. Аналогичным образом, если вы пишете математическую программу, вы могли бы создать функции с именами square_root или cube, которые возвращают результат определенных математических операций. Если ваша программа использует функцию, функция выполняет свою задачу и затем возвращает свой результат программе. Каждая функция в вашей программе имеет уникальное имя. А каждая программа имеет по крайней мере одну функцию. Каждая программа из урока 1 имела только одну функцию с именем main. Урок 9 предоставляет более подробный обзор функций. В данный момент просто имейте в виду, что функция состоит из нескольких связанных по смыслу операторов, выполняющих определенную задачу. При исследовании различных программ на C++ вы будете постоянно сталкиваться со словом void. Программы используют слово void для указания того, что функция не возвращает значения или не имеет значений, передаваемых в нее. Например, если вы используете среду MS-DOS или UNIX, программа может завершить свое выполнение с возвратом операционной системе значения статуса, которое может быть проверено командным файлом. Командные файлы MS-DOS проверяют выходной статус программы, используя команду IF ERRORLEVEL. Например, предположим, что программа с именем PAYROLL.EXE завершается с одним из следующих выходных значений статуса в зависимости от результата обработки: Значение статуса Смысл 0 Успех 1 Файл не найден 2 В принтере нет бумаги
Более внимательный взгляд на C++ 19 Большинство простых программ на C++, которые будут созданы вами в процессе изучения этой книги, не возвращают выходное значение статуса операционной системе. Поэтому вы должны размещать слово void перед main, как показано ниже: В следующих уроках вы узнаете, что ваши программы могут использовать информацию (например, имя файла), которую пользователь указывает в командной строке при запуске программы. Если программа не использует информацию командной строки, вы должны разместить слово void внутри круглых скобок после main, как показано ниже: По мере усложнения ваши программы могут возвращать значения в операционную систему или использовать параметры командной строки. Однако в настоящий момент просто используйте void в операторе с main, как показано в этой программе. ПРЕДСТАВЛЕНИЕ О ГРУППИРУЮЩИХ ОПЕРАТОРАХ { } По мере усложнения в ваших программах будет один набор операторов, которые компьютер должен выполнить определенное число раз, и другой набор операторов, которые компьютер должен выполнить, если выполняется определенное условие. В первом случае компьютер может выполнить один и тот же набор операторов 100 раз, чтобы добавить для 100 студентов тестовые очки. Во втором случае компьютер может вывести на экран одно сообщение, если все студенты прошли тест, и другое сообщение, если один или несколько студентов потерпели неудачу. Внутри своих программ на C++ вы будете использовать правую и левую фигурные скобки {}, чтобы сгруппировать связанные операторы. В простых программах, представленных в нескольких первых уроках книги, эти символы группируют операторы, которые соответствуют операторам вашей главной программы. ИСПОЛЬЗОВАНИЕ cout ДЛЯ ОТОБРАЖЕНИЯ ВЫВОДА НА ЭКРАН Все программы на C++, созданные вами в уроке 1, выводили сообщения на экран. Чтобы вывести сообщение, программы использовали cout и двойной знак "меньше" («), как показано ниже: cout << "Привет, C++!''; void main(void) Программа не возвращает значение void main(void) Программа не использует аргументы командной строки
20 Урок 2 Слово cout представляет собой выходной поток, который C++ назначает на стандартное устройство вывода операционной системы. По умолчанию операционная система назначает стандартное устройство вывода на экран дисплея. Чтобы вывести сообщение на экран, вы просто используете двойной символ "меньше" (называемый оператором вставки) с выходным потоком cout. Из урока 3 вы узнаете, что можно использовать оператор вставки для передачи символов, чисел и других знаков на экран. Представление о выходном потоке cout Вы уже знаете, что программы на C++ используют выходной поток cout для вывода сообщений на экран. При использовании cout для вывода сообщений представляйте cout в виде потока символов, которые операционная система отображает на экране. Другими словами, порядок, в котором ваша программа посылает символы в cout, определяет порядок символов, которые будут появляться на экране. Например, для следующих операторов программы: cout << "Это сообщение появляется первым,"; cout << " а за ним следует настоящее сообщение."; операционная система выводит поток символов следующим образом: Это сообщение появляется первым, а за ним следует настоящее сообщение. Оператор вставки («) называется так, потому что позволяет вашей программе вставлять символы в выходной поток. Вы уже знаете, что выходной поток cout по умолчанию соответствует вашему экрану. Другими словами, когда ваши программы посылают вывод в cout, вывод появляется на экране. Однако, используя операторы переназначения вывода операционной системы, вы можете послать вывод программы на принтер или в файл. Например, следующая команда предписывает MS- DOS направить вывод программы FIRST.EXE на принтер, а не на экран: С:\> FIRST > PRN <ENTER> Как вы узнаете из Урока 3, с помощью cout в C++ можно выводить символы, целые числа, например 1001, и числа с плавающей точкой, например 3.12345. Из Урока 8 вы узнаете, что в C++ существует также входной поток с именем tin, который ваши программы могут использовать для чтения информации, вводимой с клавиатуры. ЧТО ВЫ ДОЛЖНЫ ЗНАТЬ В этом уроке обсуждались некоторые общие вопросы, с которыми вы столкнетесь в программах на C++. Из Урока 3 вы узнаете, как использовать cout для вывода символов, целых чисел и значений с плавающей точкой. Вы так-
Более внимательный взгляд на C++ 21 же узнаете, как форматировать вывод. До изучения урока 3 убедитесь, что вы освоили следующие основные концепции: 0 Большинство программ на C++ начинаются с оператора ^include, который предписывает компилятору включить содержимое заданного заголовочного файла в программу. 0 Заголовочные файлы содержат определения, предоставляемые компилятором, которые ваши программы могут использовать. 0 Исходный файл может состоять из множества операторов; оператор void main(void) указывает начало главной программы, которая содержит первый выполняемый оператор программы. 0 По мере того как ваша программа становится более сложной, вы будете группировать связанные операторы в небольшие легко управляемые части, называемые функциями. Группируйте операторы программы с помощью правой и левой фигурных скобок {}. 0 Большинство программ на C++ используют выходной поток соШцдя вывода информации на экран; однако, используя операторы переназначения В/В операционной системы, вы можете перенаправить вывод cout в файл, устройство (например, принтер) или даже сделать его входом другой программы.
УрокЗ Вывод сообщений на экран Все программы на C++, созданные вами в уроках 1 и 2, использовали выходной поток cout для вывода сообщений на экран. В этом уроке вы будете использовать соШддя вывода символов, целых чисел, например 1001, и чисел с плавающей точкой, например 0.12345. К концу данного урока вы освоите следующие основные концепции: • Для вывода символов и чисел на экран вы можете использовать выходной поток cout. • В C++ можно использовать с cout специальные символы для вывода табуляции или новой строки и даже для воспроизведения звука на вашем компьютере. • В C++ можно легко отображать числа в десятичном, восьмеричном (по основанию 8) или шестнадцатиричном (по основанию 16) формате. • Используя в командной строке операционной системы операторы переназначения, вы можете перенаправить выходные сообщения своей программы, посылаемые в cout, с экрана в файл или на принтер. • Используя выходной поток сегт, ваши программы могут посылать сообщения на стандартное устройство ошибок, избавляя пользователей от необходимости переназначения сообщений. • Вы можете форматировать вывод вашей программы, используя модификатор setw внутри выходного потока. Почти все создаваемые вами программы на C++ используют cout для вывода сообщений на экран. Из этого урока вы узнаете, как лучше использовать cout. ИСПОЛЬЗОВАНИЕ cout ДЛЯ ВЫВОДА ЧИСЕЛ До сих пор созданные вами программы использовали cout для вывода символьных строк (букв и чисел, взятых в кавычки). Теперь вы узнаете, что cout можно также использовать для вывода чисел. Следующая программа 1001.СРР выводит число 1001 на ваш экран: #include <iostream.h> void main(void) { cout << 1001; }
Вывод сообщений на экран 23 Откомпилируйте и запустите эту программу. На вашем экране будет отображено число 1001, как показано ниже: С:\> 1001 <ENTER> 1001 Далее отредактируйте программу и измените оператор cout, чтобы вывести число 2002, как показано ниже: cout << 2002; Кроме отображения целых чисел (чисел без десятичной точки), cout также позволяет вашим программам отображать числа с плавающей точкой, например 1.2345. Следующая программа FLOATING.CPP использует cout для вывода числа 0.12345 на экран: #include <iostream.h> void main(void) { cout << 0.12345; } Как и ранее, откомпилируйте и запустите эту программу. На вашем экране появится следующий вывод: С:\> FLOATING <ENTER> 0.12345 ВЫВОД НЕСКОЛЬКИХ ЗНАЧЕНИЙ ОДНОВРЕМЕННО Как вы уже знаете, двойной знак "меньше" является операцией вставки (эта операция вставляет символы в выходной поток для отображения). С помощью cout вы можете использовать несколько операций вставки в пределах одного оператора. Например, следующая программа 1001ТОО.СРР использует эту операцию четыре раза для отображения числа 1001 на вашем экране: #include <iostream.h> void main(void) { COUt << 1 << 0 << 0 << 1; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующее: С:\> 1001TOO <ENTER> 1001
24 Урок 3 Каждый раз, когда в C++ встречается операция вставки, число или символы просто добавляются к тем, что находятся в настоящее время в выходном потоке. Следующая программа SHOW1001.CPP с помощью сои/выводит символьную строку и число: #include <lostream.h> void main(void) { cout << "Мое любимое число равно " « 1001; > Обратите внимание, что пробел, следующий за словом равно (внутри кавычек), служит для отделения числа 1001 от этого слова. Без пробела число сливается со следующим словом (равно 1001). Подобным образом следующая программа 1001MID.CPP отображает число 1001 в середине символьной строки: #include <iostream.h> void main(void) { cout << "Число " << 1001 << " мне очень нравится"; } Как и ранее, обратите внимание на расстановку пробелов до и после числа 1001. Наконец, следующая программа MIXMATCH.CPP комбинирует строки, символы, целые числа и числа с плавающей точкой внутри одного и того же выходного потока: #include <iostream.h> void main(void) { cout << "B " << 20 << " лет мой оклад был " << 493.34 << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> MIXMATCH <ENTER> В 20 лет мой оклад был 493.34 ИСПОЛЬЗОВАНИЕ СПЕЦИАЛЬНЫХ СИМВОЛОВ ВЫВОДА Все программы, созданные вами до сих пор, отображали свой вывод в виде одной строки. Однако большинство программ, которые вы создадите в дальнейшем, будут отображать несколько строк вывода. Например, предполо-
Вывод сообщений на экран 25 жим, что вы пишете программу, которая будет выводить адреса на экран. Вероятно, вы захотите, чтобы адреса появлялись в виде нескольких строк. Если необходимо переместить курсор в начало следующей строки, можно поместить символ новой строки (\п) в выходной поток. В C++ вам предоставляется два разных способа генерации новой строки. Во-первых, вы можете поместить символы \п внутри символьной строки. Например, следующая программа TWOLINES.CPP отображает свой вывод в виде двух строк, используя символ новой строки: #include <iostream.h> void main(void) { cout << "Это строка один\пЭто строка два"; } Когда вы откомпилируете и запустите эту программу, символ новой строки обеспечит вывод двух строк, как показано ниже: С:\> TWOLINES <ENTER> Это строка один Это строка два Если вы не выводите символьную строку, можете поместить символ новой строки внутри одинарных кавычек. Например, следующая программа NEWLINES.CPP выводит числа 1, 0, 0 и 1, каждое на своей собственной строке: #include <iostream.h> void main(void) { cout << 1 << #\n' << 0 << '\n' << 0 << '\n' << 1; } В дополнение к использованию символа новой строки для продвижения курсора в начало следующей строки ваши программы могут использовать символ endl(конец строки). Следующая программа ENDL.CPP иллюстрирует использование endl для продвижения курсора в начало новой строки: #include <iostream.h> void main(void) { cout << "А теперь..." << endl << "Учимся программировать на языке C++"; } Как и ранее, когда вы откомпилируете и запустите эту программу, на экране будет отображен вывод программы в виде двух строк:
26 Урок 3 С:\> ENDL <ENTER> А теперь Учимся программироватьна языке С++ Наконец, следующая программа ADDRESS.CPP выводит адрес издательства "Jamsa Press" в несколько строк: #include <iostream.h> void main(void) { cout << "Jamsa Press" << endl; cout << 975 South Rainbow, Suite I" << endl; cout << "Las Vegas, NV 89102" << endl; } Другие специальные символы В дополнение к символу новой строки, позволяющему вашим программам продвигать курсор в начало новой строки, вы можете использовать специальные символы, перечисленные в табл. 3.1. Таблица ЗА. Специальные символы для использования с cout. Символ \а \ь V \п V V \v \\ \? V \" \о \ооо \xhhhh Назначение Сигнальный (или звонок) символ Символ возврата Символ перевода страницы Символ новой строки Возврат каретки (не перевод строки) Символ горизонтальной табуляции Символ вертикальной табуляции Символ обратный слеш Знак вопроса Одинарные кавычки Двойные кавычки Нулевой символ Восьмеричное значение, например \007 Шестнадцатеричное значение, например \xFFFF Замечание: При использовании специальных символов, перечисленных в табл. 3.1, вам следует располагать их внутри одинарных кавычек, если вы используете данные символы сами по себе, например '\п', или внутри двойных кавычек, если вы используете их внутри строки, например "Привет\пМир!". Следующая программа SPECIAL.CPP использует специальные символы сигнала (\а) и табуляции (\t) для выдачи звука на встроенный динамик ком-
Вывод сообщений на экран 27 пьютера и затем выводит слова Звонок Звонок Звонок, разделенные табуляцией: #include <iostream.h> void main(void) { cout << BOHOK\a\t3BOHOK\a\t3BOHOK\a"; } ВЫВОД ВОСЬМЕРИЧНЫХ И ШЕСТНАДЦАТЕРИЧНЫХ ЗНАЧЕНИЙ Программы, представленные в этом уроке до сих пор, выводили числа в десятичном виде. В зависимости от назначения ваших программкам, возможно, потребуется выводить числа в восьмеричном или шестнадцатеричном виде. Для этого можно разместить модификаторы dec, octn hex внутри выходного потока. Следующая программа ОСТНЕХ.СРР использует эти модификаторы для вывода значений в десятичном, восьмеричном и шестнадцатеричном виде: #include <iostream.h> void main(void) { cout << "Восьмеричный: " << oct << 10 << ' ' << 20 << endl; cout << "Шестнадцатеричный: " << hex << 10 << ' ' << 20 << endl; cout << "Десятичный: " << dec << 10 << ' ' << 20 << endl; } Когда вы откомпилируете и запустите эту программу, на экране появится следующий результат: С:\> OCTHEX <ENTER> Восьмеричный: 12 24 Шестнадцатеричный: а 14 Десятичный: 10 20 Замечание: Когда вы используете один из модификаторов для выбора восьмеричного, шестнадцатеричного или десятичного вывода, ваш выбор будет оставаться в силе до тех пор, пока программа не закончится или пока вы не используете другой модификатор. ВЫВОД НА СТАНДАРТНОЕ УСТРОЙСТВО ОШИБОК Как вы уже знаете, используя cout, вы можете перенаправить вывод программы на устройство или файл с помощью операторов переназначения вывода операционной системы. Однако, если ваши программы сталкиваются с
28 УрокЗ ошибкой, вы, вероятно, не захотите, чтобы сообщение об ошибке было перенаправлено с экрана. Перенаправление сообщений об ошибках в файл может скрыть от пользователя факт появления ошибки. Если вашей программе нужно вывести сообщение об ошибке, вы должны использовать выходной поток сетт. C++ связывает сегг со стандартным устройством ошибок операционной системы. Следующая программа CERR.CPP использует выходной поток сегг для вывода на экран сообщения "Это сообщение появляется всегда "\ #include <lostream.h> void main(void) { cerr << "Это сообщение появляется всегда"; > Откомпилируйте и запустите эту программу. Далее попытайтесь перенаправить вывод программы в файл, используя оператор переназначения вывода: С:\> CERR > FILENAME.EXT <ENTER> Так как операционная система не позволит вашим программам перенаправить вывод, записываемый на стандартное устройство ошибок, сообщение появится на вашем экране. УПРАВЛЕНИЕ ШИРИНОЙ ВЫВОДА Несколько предыдущих программ выводили числа на экран. Чтобы гарантировать правильное отображение этих чисел (с правильной расстановкой пробелов), программы включали пробелы до и после чисел. При выводе на соШ или сегг ваши программы могут указать ширину вывода каждого числа, используя модификатор^^ (установка ширины). С помощью setw программы указывают минимальное количество символов, занимаемое числом. Например, следующая программа SETW.CPP использует модификатор setw для выбора ширины 3, 4, 5 и 6 для числа 1001. Чтобы использовать модификатор setw, ваша программа должна включать заголовочный файл iomanip.h: #include <iostream.h> #include < iomanip.h> void main(void) { cout << "Мое любимое число равно" << setwC) << 1001 << endl; cout << "Мое любимое число равно" << setwD) << 1001 << endl; cout << "Мое любимое число равно" << setwE) << 1001 << endl; cout << "Мое любимое число равно" << setwF) << 1001 << endl; } Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод:
Вывод сообщений на экран 29 С:\> SETW <ENTER> Мое любимое число равною01 Мое любимое число равно1001 Мое любимое число равно 1001 Мое любимое число равно 1001 Если вы указываете ширину с помощью setw, вы указываете минимальное количество символьных позиций, занимаемых числом. В предыдущей программе модификатор setwC) указывал минимум три символа. Однако, так как число 1001 потребовало больше трех символов, cout использовал реально требуемое количество, которое в данном случае равнялось четырем. Следует отметить, что при использовании setw для выбора ширины, указанная ширина действительна для вывода только одного числа. Если вам необходимо указать ширину для нескольких чисел, вы должны использовать setw несколько раз. Замечание: Предыдущая программа использует заголовочный файл IOMANIP.H. Вам, возможно, понадобится сейчас напечатать и исследовать содержимое этого файла. Как и в случае с заголовочным файлом IOSTREAM.H вы найдете данный файл внутри подкаталога INCLUDE, который находится в каталоге с файлами вашего компилятора. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали несколько способов использования cout для отображения вывода на экран. Все программы, которые вы создадите в процессе изучения оставшейся части книги, будут использовать cout для отображения вывода. Из урока 4 вы узнаете, как использовать переменные внутри своих программ для хранения значений, которые могут изменяться в процессе выполнения программы. Однако до изучения урока 4 убедитесь, что вы освоили следующие основные концепции: 0 Выходной поток cout позволяет вам выводить символы и числа. 0 Используя специальные символы внутри выходного потока, ваша программа может указать новую строку, табуляцию и другие специальные возможности. 0 Для продвижения курсора в начало следующей строки программы могут создать новую строку, используя символ \и или модификатор endl 0 Модификаторы dec, oct и hex позволяют программам выводить значения в десятичном, восьмеричном и шестнадцатиричном виде. 0 Используя выходной поток сегг, программы могут записать сообщения в стандартное устройство ошибок операционной системы. 0 С помощью модификатора setw ваши программы могут управлять шириной вывода чисел.
Урок 4 Программы хранят информацию в переменных Все программы, представленные в уроках 1—3, были очень простыми. Однако по мере того, как ваши программы начинают выполнять более многоплановые задачи, они должны хранить информацию во время выполнения. Например, программе, печатающей файл, нужно знать имя файла и, возможно, число копий, которые вы хотите напечатать. В процессе выполнения программы хранят такую информацию в памяти компьютера. Чтобы использовать определенные ячейки памяти, программы применяют переменные. Проще говоря, переменная представляет собой имя ячейки памяти, которая может хранить конкретное значение. В этом уроке описано, как создавать и использовать переменные в программах на C++. К концу данного урока вы освоите следующие основные концепции: • Вы должны объявлять переменные, которые будете использовать в программе, сообщая компилятору имя и тип переменной. • Тип переменной определяет тип значения (например, целое число или число с плавающей точкой), которое может хранить переменная, а также операции, которые ваши программы могут выполнять над переменной. • Чтобы присвоить значения переменной, используйте оператор присваивания C++ (знак равно). • Для вывода значения переменной на экран программы используют выходной поток cout. • При объявлении переменных используйте имена, выражающие смысл переменных, чтобы ваши программы было легче читать и понимать. • Используйте комментарии, описывающие работу программы. В этом случае, если другим программистам нужно изменить вашу программу, комментарии подробно опишут работу программы. Когда вы присваиваете значение переменной, представьте себе переменную в виде ящика, в который можно поместить значение. Если вам позже потребуется использовать значение переменной, компьютер просто посмотрит значение, содержащееся в ящике. ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ В ПРОГРАММАХ Ваши программы используют переменные для хранения информации. В зависимости от типа хранимого значения, например, целое число, буква алфавита или число с плавающей точкой, тип вашей переменной будет разным.
Программы хранят информацию в переменных 31 Тип переменной указывает тип значения, хранимого в переменной, а также набор операций (таких как сложение, умножение и другие), которые программа может выполнять над значением переменной. Большинство программ на C++ будут использовать типы переменных, перечисленные в табл. 4.1. Таблица 4.1. Типы переменных C++. Тип Хранимые значения char Значения в диапазоне от -128 до 127. Обычно используется для хранения букв алфавита int Значения в диапазоне от —32768 до 32767 unsigned Значения в диапазоне от 0 до 65535 long Значения в диапазоне от -2147483648 до 2147483647 float Значения в диапазоне от —3.4 х 10—J5 до 3.4 х 1038 double Значения в диапазоне от 1.7 х 10~~308 до 1.7 х 10^08 Прежде чем вы сможете использовать переменную, ваша программа должна ее объявить. Другими словами, вам следует представить переменную компилятору C++. Чтобы объявить переменную в программе, вам следует указать тип переменной и ее имя, по которому программа будет обращаться к данной переменной. Указывайте тип и имя переменной после открывающей фигурной скобки главной программы, как показано ниже: тип_переменной имя_переменной; Как правило, тип переменной будет одним из типов, перечисленных в табл. 4.1. Выбираемое вами имя переменной должно нести смысловую нагрузку, которая описывает (для всех, кто читает вашу программу) использование переменной. Например, ваша программа могла бы использовать переменные, такие как employee_name, employee_age и т. д. Обратите внимание на точку с запятой, которая следует за именем переменной. В C++ объявление переменной считается оператором. Поэтому вы должны поставить после объявления точку с запятой. Фрагмент следующей программы объявляет три переменные, используя типы int, float и long'. #include <iostream.h> void main(void) { int test_score; float salary; long distance_to_mars; } Важно обратить внимание, что данная программа ничего не выполняет, а только объявляет переменные. Как видите, объявление каждой переменной заканчивается точкой с запятой. Если вы объявляете несколько переменных
32 Урок 4 одного и того же типа, можно разделять их имена запятой. Следующий оператор, например, объявляет три переменных с плавающей точкой: float salary, income_tax, retirement_fund; Изучение переменных Переменная представляет собой имя ячейки в памяти компьютера. Во время выполнения ваши программы хранят информацию в переменных. При создании программ вы должны объявлять переменные, сообщая компилятору C++ имя и тип переменной. Например, следующий оператор объявляет переменную с именем age типа int: int age; Смысловые имена переменных Каждая создаваемая вами переменная должна иметь уникальное имя. Чтобы сделать свои программы более легкими для чтения и понимания, следует использовать смысловые имена переменных. Например, следующий оператор объявляет три переменных с именами х,ук% int х, у, z; Предположим, что эти переменные хранят возраст, тестовые очки и оценку студента, тогда следующие имена переменных более понятны по смыслу для других программистов, читающих ваш исходный код: int student_age, test_score, grade; При выборе имен переменных можно использовать комбинацию букв, цифр и подчеркивания (_). Первый символ в имени переменной должен быть буквой или подчеркиванием. Нельзя начинать имя переменной с цифры. Кроме того, в C++ буквы нижнего и верхнего регистров считаются разными. Сначала для имен своих переменных используйте только буквы нижнего регистра. Как только вы освоитесь в C++, можете комбинировать буквы верхнего и нижнего регистров для получения смысловых имен, как показано ниже: float MonthlySalary, IncomeTax; Слова, которые нельзя использовать для имен переменных При создании имен переменных необходимо знать, что в C++ слова, перечисленные в табл. 4.2, резервируются в качестве ключевых слов, имеющих специальное значение для компилятора. Вы не имеете права использовать ключевые слова C++ в качестве имен переменных.
Программы хранят информацию в переменных 33 Таблица 4.2. Ключевые слова C++. asm auto break case catch char class const continue default delete do double else enum extern float for friend goto if inline int long new operator private protected public register return short signed sizeof static struct switch template this throw try typedef union unsigned virtual void volatile while Почему ваши программы используют переменные По мере усложнения ваши программы могут выполнять самые разнообразные операции. Например, программа платежей обрабатывает информацию о каждом служащем. В такой программе вы могли бы использовать переменные с именами employ ее_name, employeejtd, employee_salary и т. д. После запуска программа занесет информацию о первом служащем в эти переменные. После расчета оклада первого служащего программа повторит весь процесс для следующего служащего. Чтобы рассчитать оклад второго служащего, программа занесет информацию о втором служащем (его или ее имя, номер и оклад) в перечисленные выше переменные, а затем выполнит свою обработку. Другими словами, в процессе выполнения программа присваивает переменной разные значения, которые, в свою очередь, изменяют или варьируют значение переменной. ПРИСВАИВАНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ Как вы уже знаете, переменные хранят значения во время выполнения программы. После объявления переменной вы используете оператор присваивания C++ (знак равно), чтобы присвоить значение переменной. Следующие операторы присваивают значения нескольким разным переменным. Обратите внимание на использование точки с запятой в конце каждого оператора: аде = 32; salary = 25000.75; distance_to_the_moon = 238857; Замечание: Значения, присваиваемые переменным, не должны содержать запятые (например, 25,000.75 и 238,857) . Если вы включаете запятые, компилятор C++ будет генерировать и выводить сообщения о синтаксических ошибках. Фрагмент следующей программы сначала объявляет переменные, а затем использует оператор присваивания, чтобы присвоить переменным значения: Американская запись чисел предполагает отделение каждых трех разрядов целой части числа запятой. — Прим. ред.
34 Урок 4 #include <iostream.h> void main(void) { int age; float salary; long distance_to_the_moon; age = 32; salary = 25000.75; distance_to_the_moon = 238857; > Присваивание значения при объявлении При объявлении переменной часто удобно присваивать ей начальное значение. Чтобы упростить такую процедуру, C++ позволяет присваивать значение во время объявления переменной, как показано ниже: int age = 32; float salary = 25000.75; long distance_to_the_moon = 238857; Многие программы, представленные в этой книге, присваивают значения переменным при объявлении. Присваивание значений переменным При выполнении программы переменные хранят информацию. Для записи значения в переменную программы должны использовать оператор присваивания C++ (знак равно). Следующая строка использует оператор присваивания для записи значения 4 в переменную lesson: lesson = 4; Для упрощения этого процесса в C++ можно также присвоить значение переменной при ее объявлении: int lesson = 4; ИСПОЛЬЗОВАНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ После присвоения значения переменной ваши программы могут использовать это значение, просто обращаясь к ее имени. Следующая программа SHOWVARS.CPP присваивает значения трем переменным и затем выводит значение каждой переменной, используя cout: #include <iostream.h>
Программы хранят информацию в переменных 35 void main(void) { int age = 32; float salary = 25000.75; long distance_to_the_moon = 238857; cout << "Служащему " << age << " года (лет)" << endl; cout << "Оклад служащего составляет $" << salary << endl; cout << "От земли до луны " << distance_to_the_moon << " миль" << endl; } Замечание: Последний оператор cout не помещается на одной строке. В этом случае программа просто переносит слова на следующую строку. Вы можете сделать такой перенос, поскольку C++ использует точку с запятой для указания конца оператора. Если вам необходимо перенести строку, постарайтесь не делать этого в середине символьной строки (внутри двойных кавычек), используйте дополнительный отступ для перенесенной части строки, как показано выше. Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод: С:\> SHOWVARS «ENTER> Служащему 32 года (лет) Охлад служащего составляет $25000.75 От земли до луны 238857 миль Как видите, для использования значения переменной вы просто обращаетесь к имени переменной в вашей программе. ПРЕВЫШЕНИЕ ДИАПАЗОНА ЗНАЧЕНИЙ ПЕРЕМЕННОЙ Как вы уже знаете, тип переменной определяет набор значений, которые переменная может хранить. Например, переменная типа int может хранить значения в диапазоне от -32768 до 32767. Если вы присваиваете переменной значение, которое находится вне этого диапазона, возникает ошибка переполнения. Например, следующая программа OVERFLOW.CPP иллюстрирует, как превышение диапазона значений переменной приводит к ошибке. Как видите, программа присваивает значения, которые находятся вне диапазона для переменной каждого типа: #include <iostream.h> void main(void) { int positive = 40000; long big_positive = 4000000000; char little_positive = 210; cout << "сейчас positive содержит " << positive << endl;
36 Урок 4 cout << "сейчас big_positive содержит " « big_positive << endl; cout << "сейчас little_positive содержит " << little_positive << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: сейчас positive содержит -25536 сейчас big positive содержит -294967296 сейчас little_positive содержит Т Как видите, программа присваивает переменным типа int, long и char значения, которые находятся вне диапазона хранения каждого типа, поэтому возникает ошибка переполнения. При работе с переменными вам необходимо помнить диапазон значений, которые может хранить переменная каждого типа. Ошибки переполнения плохо уловимы, и поэтому их трудно определить и исправить. Обратите внимание на значение, которое программа выводит для переменной little_positive. Поскольку это переменная типа char, выходной поток cout пытается вывести ее значение в символьном виде. В этом случае выведенное значение соответствует второй половине таблицы ASCII со значением 210. ПРЕДСТАВЛЕНИЕ О ТОЧНОСТИ В предыдущем разделе вы узнали, что ошибки переполнения возникают при присваивании переменной значения, которое находится вне диапазона значений для переменной данного типа. Подобно этому вам также необходимо знать, что компьютеры не могут обеспечить неограниченную точность при хранении чисел. Например, при работе с числами с плавающей точкой (числа с десятичной точкой) компьютер не всегда может представить число с требуемой точностью. Поэтому возможны ошибки округления, которые тяжело обнаружить. Следующая программа PRECISE.CPP присваивает значение чуть меньше 0.5 переменным типа float и double. К сожалению, поскольку компьютер обладает ограниченной способностью в представлении чисел, переменные реально содержат не присваиваемые им значения, а число 0.5: #include <iostream.h> void main(void) { float f_not_half = 0.49999990; double d_not_half * 0.49999990; cout << "Значение типа float 0.49999990 равно " << f_not_half << endl; cout << "Значение типа double 0.49999990 равно "
Программы хранят информацию в переменных 37 « d_not_half << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: Значение типа float 0.49999990 равно 0.5 Значение типа double 0.49999990 равно 0.5 Как видите, значения, присваиваемые программой переменным, и значения, которые переменные содержат в действительности, не идентичны. Такие ошибки округления происходят потому, что компьютер должен представлять числа, используя фиксированное количество единиц и нулей. В большинстве случаев компьютер может точно представлять числа. Однако иногда, как показано в этой программе, компьютер представляет числа приближенно, а не точно. При программировании вам необходимо всегда помнить о точности. В зависимости от значений, с которыми работают ваши программы, могут возникать трудно обнаруживаемые ошибки округления. ИСПОЛЬЗОВАНИЕ КОММЕНТАРИЕВ ДЛЯ УЛУЧШЕНИЯ ЧТЕНИЯ ВАШИХ ПРОГРАММ По мере усложнения ваших программ количество содержащихся в них операторов может сделать программы слишком трудными для понимания. Поскольку другим программистам может потребоваться понять и, возможно, изменить ваши программы, вам следует делать программы более удобочитаемыми. Способы улучшения удобочитаемости программ включают: • Использование понятных по смыслу имен переменных, описывающих их применение. • Использование подходящих отступов и выравнивания (см. урок 7). • Использование пустых строк для разделения несвязанных операторов. • Использование комментариев, которые объясняют работу программы. При создании программ вы можете поместить в исходном файле замечания, которые объясняют работу программы. Такие замечания (называемые комментариями) не только помогают другим программистам понять вашу программу, но могут напомнить, почему программа содержит определенные операторы, если вы ее посмотрите через несколько месяцев. Для размещения комментария в своих программах на C++ просто поставьте два знака прямого слеша (//), как показано ниже: // Это комментарий Когда компилятор C++ встречает двойной слеш, он игнорирует весь оставшийся на этой строке текст. По крайней мере размещайте в начале каж-
38 Урок 4 дой программы комментарии, которые указывают, кто написал программу, когда и почему: // Программа: BUDGET.СРР // Программист: Kris Jams a // Дата создания: 1-10-96 // // Цель: Ежемесячная информация о бюджете. При выполнении вашей программой различных процессов вам следует поместить до или после определенных операторов комментарии, которые объясняют их назначение. Например, рассмотрим следующий оператор присваивания: distance_to_the_moon = 238857; // Расстояние в милях Комментарий справа от оператора присваивания обеспечивает дополнительную информацию всем, кто читает программу. У начинающих программистов часто возникают проблемы, связанные с тем, что и когда комментировать. Как правило, вы включаете немного комментариев в ваши программы. Поэтому убедитесь, что они полезны. Следующие комментарии не дают дополнительной информации программистам, читающим код: age =32; // Присвоить 32 переменной age salary = 25000.75; // Присвоить 25000.75 переменной salary Цель использования комментариев заключается в том, чтобы объяснить работу программы. Добавление комментариев в ваши программы При создании программ включайте комментарии, которые объясняют работу программы. Если другим программистам понадобится изменить вашу программу, они смогут воспользоваться комментариями, чтобы понять поведение программы. Обычно в программах на C++ комментарий начинается с двойного слеша: // Это комментарий C++ Когда компилятор C++ встречает двойной слеш, он игнорирует весь текст (оставшийся в текущей строке), который следует за слешем. Хорошие программы должны быть легки для чтения и понимания. Комментарии улучшают удобочитаемость вашей программы. Замечание: В дополнение к использованию комментариев для улучшения удобочитаемости своей программы вам следует использовать пустые строки для разделения несвязанных операторов. Когда компилятор C++ встречает пустую строку, он просто пропускает ее.
Программы хранят информацию в переменных 39 ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что программы во время выполнения хранят информацию в переменных. Говоря кратко, переменная представляет собой имя, которое ваша программа назначает ячейке памяти, в которой программа хранит информацию. Прежде чем ваша программа сможет использовать переменную, вы должны объявить ее тип и имя. Из урока 5 вы узнаете, как выполнять простые операции, например, сложение и вычитание переменных. Однако до изучения урока 5 убедитесь, что вы освоили следующие основные концепции: 0 Для использования переменной в вашей программе вы должны объявить ее тип и имя. 0 Имена переменных должны быть уникальны и понятны по смыслу другим программистам, читающим ваш исходный текст. Имя переменной должно соответствовать ее назначению. 0 Имена переменных должны начинаться с буквы или символа подчеркивания. 0 В C++ буквы верхнего и нижнего регистров считаются разными. 0 Тип переменной определяет тип значения, которое переменная может содержать. Общими типами переменных являются char, int, float и long. 0 Комментарии повышают удобочитаемость вашей программы, поясняя ее работу. В программах на C++ комментарии начинаются с двойного слеша (//).
Урок 5 Выполнение простых операций Из урока 4 вы узнали, как объявлять и использовать переменные в своих программах. По мере усложнения программ вы будете выполнять арифметические операции, такие как сложение, вычитание, умножение и деление, над значениями, содержащимися в переменных. Данный урок описывает, как использовать арифметические операторы C++ для выполнения этих операций. К тому времени, когда вы закончите изучение данного урока, вы освоите следующие основные концепции: • Для выполнения математических операций используйте в своих программах арифметические операторы C++. • Чтобы гарантировать последовательность операций, C++ назначает приоритет каждому оператору. • Используя круглые скобки в арифметических выражениях, вы можете управлять порядком, в котором C++ выполняет операции. • Многие программы на C++ прибавляют или вычитают единицу, используя операции увеличения (++) или уменьшения (—). После того как вы научитесь распознавать разные арифметические операторы C++, вы поймете, что выполнять математические операции очень легко! ОСНОВНЫЕ МАТЕМАТИЧЕСКИЕ ОПЕРАЦИИ Независимо от назначения большинство ваших программ на C++ будут складывать, вычитать, умножать или делить. Вы узнаете, что ваши программы могут выполнять арифметические операции с константами (например, 3*5) или с переменными (например, payment — total). Таблица 5.1 перечисляет основные математические операции C++: Таблица 5./. Основные математические операции C++. Операция Назначение Пример + Сложение total = cost + tax; - Вычитание change = payment - total; • Умножение tax = cost * tax_rate; / Деление average = total / count;
Выполнение простых операций 41 Следующая программа SHOWMATH.CPP использует cout для вывода результата нескольких простых арифметических операций: #include <iostream.h> void main(void) { cout <<  + 7 = " « 5 + 7 << endl; cout << 2 - 7 = " << 12 - 7 << endl; cout << .2345 * 2 = " << 1.2345 * 2 << endl; cout << 5 / 3 = " << 15 / 3 << endl; } Посмотрите внимательно на операторы программы. Обратите внимание, что каждое выражение сначала появляется в кавычках, которые обеспечивают вывод символов (например, 5 + 7 =) на экран. Затем программа выводит результат операции и символ новой строки. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> SHOWMATH <ENTER> 5 + 7 = 12 12 - 7 = 5 1.2345 * 2 = 2.469 15 / 3 = 5 В данном случае программа выполняла арифметические операции, используя только постоянные значения. Следующая программа МАТН- VARS.CPP выполняет арифметические операции, используя переменные: #include <iostream.h> void main(void) { float cost =15.50; // Стоимость покупки float sales_tax = 0.06; // Налог на продажу 6% float amount_paid = 20.00; // Деньги покупателя float tax, change, total; // Налог на продажу, // сдача покупателю и общий счет tax = cost * sales_tax; total = cost + tax; change = amount_paid - total; cout << "Стоимость покупки: $" << cost << "\tHanor: $" << tax << "\t06iprii счет: $" << total << endl; cout << "Сдача покупателю: $" << change << endl; } В данном случае программа использует только переменные с плавающей точкой. Как видите, программа присваивает значения переменным при объ-
42 Урок 5 явлении. Далее программа выполняет арифметические операции над переменными для определения налога на продажу, общего счета и сдачи покупателю. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> MATHVARS <ENTER> Стоимость покупки: $15.5 Налог: $0.93 Общий счет: $16.43 Сдача покупателю: $3.57 УВЕЛИЧЕНИЕ ЗНАЧЕНИЯ ПЕРЕМЕННОЙ НА 1 Обычной операцией, которую вы будете выполнять при программировании, является прибавление 1 к значению целой переменной. Например, предположим, что ваша программа использует переменную с именем count, чтобы сохранить данные о количестве напечатанных файлов. Каждый раз, когда программа печатает файл, 1 будет добавляться к текущему значению count. Используя оператор присваивания C++, ваша программа может увеличивать значение count, как показано ниже: count = count + 1; В данном случае программа сначала выбирает значение count, а затем добавляет к нему единицу. Далее программа записывает результат сложения обратно в переменную count. Следующая программа INTCOUNT.CPP использует оператор присваивания для увеличения переменной count (которая первоначально содержит значение 1000) на единицу (присваивая переменной результат 1001): #include <iostream.h> void main(void) { int count = 1000; cout << "начальное значение count равно" << count << endl; count = count + 1; cout << "конечное значение count равно" << count << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующей вывод: С:\> INCCOUNT <ENTER> начальное значение count равно 1000 конечное значение count равно 1001 Так как увеличение значения переменной представляет собой обычную операцию в программах, в С4-4- есть операция увеличения — двойной знак плюс (++). Операция увеличения обеспечивает быстрый способ прибавления
Выполнение простых операций 43 единицы к значению переменной. Следующие операторы, например, увели* чивают значение переменной count на 1: count = count + 1; count++; Следующая программа INC_OP.CPP использует операцию увеличения для наращивания значения переменной count на 1: #include <iostream.h> void main(void) { int count = 1000; cout << "начальное значение count равно " « count << endl; count++; cout << "конечное значение count равно " << count << endl; } Эта программа работает так же, как INCCOUNT.CPP, которая использовала оператор присваивания для увеличения значения переменной. Когда C++ встречает операцию увеличения, он сначала выбирает значение переменной, добавляет к этому значению единицу, а затем записывает результат обратно в переменную. Представление о префиксной (до) и постфиксной (после) операциях увеличения При использовании операций увеличения ваши программы могут размещать оператор увеличения до или после переменной, как показано ниже: ++variable; variable++; Так как первый оператор появляется до переменной, он называется префиксным оператором увеличения. Аналогично этому, второй оператор появляется после переменной и называется постфиксным оператором увеличения. Вам необходимо знать, что C++ трактует эти два оператора по-разному. Например, рассмотрим следующий оператор присваивания: current_count = count++; Этот оператор присваивания указывает C++ присвоить текущее значение count переменной current count. В дополнение к этому постфиксный оператор увеличения заставляет C++ увеличить текущее значение count. Использование постфиксного оператора в этом случае делает показанный выше оператор эквивалентным следующим двум операторам: current_count = count; count = count + 1; Теперь рассмотрим следующий оператор присваивания, который исполь-
44 Урок 5 зует префиксный оператор увеличения: current_count = ++count; В этом случае оператор присваивания указывает C++ сначала увеличить значение count, а затем присвоить результат переменной current_count. Использование префиксного оператора увеличения делает показанный выше оператор эквивалентным следующим двум операторам: count = count + 1; current_count = count; Важно освоить префиксную и постфиксную операции увеличения, так как они будут встречаться вам в большинстве программ на C++. Следующая программа PRE_POST.CPP иллюстрирует использование префиксной и постфиксной операций увеличения: #include <iostream.h> void main(void) { int smalll_count = 0; int big_count = 1000; cout << "small_count равно " << small_count << endl; cout << "small_count++ производит " « small_count++ << endl; cout << "конечное значение small_count равно " << small_count << endl; cout << "big_count равно " << big_count << endl; cout << //++big_count производит " « ++big_count << endl; cout << "конечное значение big_count равно " « big_count << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> PRE_POST <ENTER> small_count равно 0 small_count++ производит 0 конечное значение small_count равно 1 big_count равно 1000 ++big_count производит 1001 конечное значение big_count равно 1001 С переменной small_count программа использует постфиксную операцию увеличения. В результате программа выводит текущее значение переменной @), а затем увеличивает его на 1. С переменной bigjcount программа использует префиксную операцию увеличения. В результате программа сначала увеличивает значение переменной A000 + 1), а затем выводит результат
Выполнение простых операций 45 A001). Найдите время, чтобы отредактировать эту программу, и сначала измените постфиксную операцию на префиксную, а затем префиксную на постфиксную. Откомпилируйте и запустите программу, обращая внимание на то, как изменение операции изменяет вывод. C++ обеспечивает также операции уменьшения Как вы уже знаете, двойной знак плюс (++) представляет собой оператор увеличения C++. Подобным образом двойной знак минус (—) соответствует оператору уменьшения C++, который уменьшает значение переменной на 1. Как и в случае с операцией увеличения, C++ поддерживает префиксный и постфиксный операторы уменьшения. Следующая программа DECCOUNT.CPP иллюстрирует использование оператора уменьшения C++: #include <iostream.h> void main(void) { int small_count = 0; int big_count = 1000; cout << "small_count равно " « small_count << endl; cout << "small_count— производит " << small_count— << endl; cout << "конечное значение small_count равно " « small_count << endl; cout << "big_count равно " << big_count << endl; cout << "—big_count производит " << —big_count << endl; cout << "конечное значение big_count равно " « big_count << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> DECCOUNT <ENTER> small_count равно 0 small_count— производит 0 конечное значение small_count равно -1 big_count равно 1000 —big_count производит 999 конечное значение big_count равно 999 Как видите, префиксный и постфиксный операторы уменьшения C++ работают так же, как и соответствующие операторы увеличения, с той лишь разницей, что они уменьшают значение переменной на 1.
46 Урок 5 ДРУГИЕ ОПЕРАТОРЫ C++ В этом уроке описаны обычные арифметические операции C++, а также операции увеличения и уменьшения. В программах на C++ вы можете встретить одну или несколько операций, перечисленных в табл. 5.2: Таблица 5.2. Операции C++, которые вы можете встретить в программах. Операция Функция % Взятие по модулю или остаток; возвращает остаток целочисленного деления ~ Дополнение; инвертирует биты значений & Побитовое И | Побитовое включающее ИЛИ Побитовое исключающее ИЛИ « Сдвиг влево; сдвигает биты значения влево на указанное количество разрядов » Сдвиг вправо; сдвигает биты значения вправо на указанное количество разрядов СТАРШИНСТВО ОПЕРАЦИЙ При выполнении арифметических операций в C++ необходимо знать, что C++ выполняет операции в определенном порядке, основанном на старшинстве операций. Например, операция умножения выполняется до сложения. Чтобы лучше понять старшинство операций, рассмотрите следующие выражения: result = 5 + 2*3; В зависимости от порядка, в котором C++ выполняет умножение и сложение, результат будет разным: result =5+2*3; result = 5 + 2*3; = 7*3; =5 + 6; = 21; = 11; Чтобы избежать путаницы, C++ присваивает каждой операции приоритет, который определяет порядок выполнения операций. Так как C++ выполняет операции в определенном порядке, то и ваши программы будут проводить арифметические вычисления соответствующим образом. Таблица 5.3 перечисляет старшинство операций C++. Операции, находящиеся в верхней части, имеют более высокий приоритет. Операции внутри каждой части имеют одинаковый приоритет. Если вы рассмотрите таблицу, то увидите, что в C++ умножение имеет более высокий приоритет, чем сложение. Вы не знакомы со многими операциями, представленными в таблице. В настоящее время не думайте об этих операциях. К концу изучения этой книги вы сможете использовать (и понять) каждую из них!
Выполнение простых операций 47 Таблица 5.3. Старшинство операций в C++. Операция -> [] 0 0 sizeof sizeof ++ ++ — — & * new delete delete [] ~ 1 + - 0 * -> * 1 % + - Имя Разрешение области видимости Глобальное разрешение Выбор элемента Выбор элемента Индексация Вызов функции Построение значения Размер объекта Размер типа Приращение после Приращение до Уменьшение после Уменьшение до Адрес объекта Разыменование Создание (размещение) Уничтожение (освобождение) Уничтожение массива Дополнение Логическое НЕ Унарный плюс Унарный минус Приведение Выбор элемента Выбор элемента Умножение Деление Взятие по модулю Сложение (плюс) Вычитание (минус) Пример class_name::class_member_name ::variable_name object. member_name pointer—>member_name pointer[element] expression(parameters) type(parameters) sizeof expression sizeof(type) variable++ ++variable variable— — variable &variable ¦pointer new type delete pointer delete pointer -expression ! expression + 1 -1 (type) expression object. *pointer object->*pointer expression * expression expression / expression expression % expression expression + expression expression expression
48 Урок 5 Управление порядком, в котором C++ выполняет операции Как вы уже знаете, C++ назначает операциям различный приоритет, который и управляет порядком выполнения операций. К сожалению, иногда порядок, в котором C++ выполняет арифметические операции, не соответствует порядку, в котором вам необходимо их выполнить. Например, предположим, что вашей программе необходимо сложить две стоимости и затем умножить результат на налоговую ставку: cost = price_a + price_b * 1.06; К сожалению, в этом случае C++ сначала выполнит умножение (price_b * 1.06), а затем прибавит значение priceja. Если ваши программы должны выполнять арифметические операции в определенном порядке, вы можете заключить выражение в круглые скобки. Когда C++ оценивает выражение, он сначала всегда выполняет операции, сгруппированные в круглых скобках. Например, рассмотрим следующее выражение: result = B + 3) * C + 4); C++ вычисляет данное выражение в следующем порядке: result = B + 3) * C + 4); = E) * C + 4); = 5 * G); = 5*7; = 35; Подобным образом группируя выражения внутри круглых скобок, вы можете управлять порядком, в котором C++ выполняет арифметические операции. Аналогично предыдущему примеру, ваша программа может сложить две стоимости внутри круглых скобок, как показано ниже: cost = (price_a + price_b) * 1.06; СЛЕДИТЕ ЗА ОШИБКАМИ ПЕРЕПОЛНЕНИЯ ПРИ АРИФМЕТИЧЕСКИХ ОПЕРАЦИЯХ Из урока 4 вы узнали, что, если вы присваиваете переменной значение, которое не входит в диапазон значений для данного типа переменной, возникает ошибка переполнения. При выполнении арифметических операций необходимо помнить о возможности возникновения ошибок переполнения. Например, следующая программа MATHOVER.CPP умножает 200 на 300 и присваивает результат переменной типа int. Однако, поскольку результат умножения F0000) превышает наибольшее возможное значение для типа int C2767), возникает ошибк;! переполнения:
Выполнение простых операций 49 #include <iostream.h> void main(void) { int result; result = 200 * 300; cout << 00 * 300 = " << result << endl; } Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод: С:\> MATHOVER <ENTER> 200 * 300 = -5536 ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ В данном уроке вы изучали обычные арифметические операции и операции приращения C++. Чтобы программы корректно выполняли арифметические вычисления, C++ назначает каждой операции приоритет, который управляет порядком выполнения операций. Из урока 6 вы узнаете, как использовать входной поток с именем cin для выполнения операций ввода с клавиатуры. До изучения урока 6 убедитесь, что вы освоили следующее: 0 C++ использует операторы +,— , * и / для сложения, вычитания, умножения и деления. 0 C++ обеспечивает префиксные (до) и постфиксные (после) операции увеличения, которые прибавляют единицу к значению переменной. 0 C++ обеспечивает префиксную (до) и постфиксную (после) операции уменьшения, которые вычитают единицу из значения переменной. 0 Префиксные (до) операции указывают C++ сначала увеличить (или уменьшить) значение переменной, а затем использовать это значение. 0 Постфиксные (после) операции указывают C++ сначала использовать значение переменной, а затем увеличить (или уменьшить) его. 0 Чтобы гарантировать, что выражения выполняются корректно, C++ назначает каждой операции приоритет, управляющий порядком выполнения операций. 0 Если вам нужно управлять порядком выполнения арифметических операций, используйте круглые скобки. C++ всегда вычисляет сначала выражение в скобках.
Урок 6 Чтение ввода с клавиатуры В этой книге ваши программы использовали выходной поток cout для отображения вывода на экран. Из данного урока вы узнаете, что C++ обеспечивает входной поток с именем cin, из которого программы могут читать информацию, введенную пользователем с клавиатуры. При использовании cin для чтения ввода с клавиатуры вы указываете одну или несколько переменных, которым cin будет присваивать входные значения. К тому времени, когда вы закончите этот урок, вы освоите следующие основные концепции: • Вы можете использовать входной поток cin для присваивания определенным переменным символов и чисел, введенных с клавиатуры. • При применении cin для чтения и присваивания переменной значения, введенного с клавиатуры, можно использовать содержимое переменной так, как если бы ваша программа использовала оператор присваивания для сохранения значения в переменной. • Когда ваша программа использует cin для выполнения ввода с клавиатуры, остерегайтесь ошибок переполнения и ошибок, возникающих при вводе пользователем значения неверного типа. Как вы уже знаете, если ваши программы используют выходной поток cout, они помещают данные в поток с помощью оператора вставки (<<). Подобным образом, если ваши программы применяют cin для чтения ввода с клавиатуры, они будут использовать оператор извлечения (>>). ПЕРВОЕ ЗНАКОМСТВО С cin Точно так же как выходной поток cout позволяет вашим программам записать вывод на экран, входной поток cin позволяет программам читать ввод с клавиатуры. Когда программы используют cin для чтения ввода с клавиатуры, они должны указать переменную, в которую cin поместит данные. Следующая программа FIRSTCIN.CPP использует cin для чтения числа, введенного с клавиатуры. Программа присваивает введенное число переменной с именем number, а затем выводит значение переменной, используя выходной поток cout. #include <iostream.h> void main(void)
Чтение ввода с клавиатуры 51 { int number; // Число, читаемое с клавиатуры cout << "Введите ваше любимое число и нажмите Enter: "; cin >> number; cout << "Ваше любимое число равно " « number << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появится сообщение, предлагающее вам ввести ваше любимое число. Если вы введете число и нажмете ENTER, программа присвоит ввод переменной number. Затем, используя cout, программа выведет сообщение, отображающее ваше любимое число. Следующая программа TWONBRS.CPP запрашивает у вас два числа. Программа присваивает числа переменным first и second. Затем программа выводит числа, используя cout: #include <iostream.h> void main (void) { int first, second; // Числа, введенные с клавиатуры cout << "Введите два числа и нажмите Enter: "; cin >> first >> second; cout << "Были введены числа " << first << " и " « second << endl; } Обратите внимание на использование с cin двух операторов извлечения: cin >> first >> second; В этом случае cin присвоит первое введенное значение переменной first, a второе переменной second. Если для вашей программы требуется третье значение, вы можете использовать третий оператор извлечения, как показано ниже: cin >> first >> second >> third; Если вы применяете cin для чтения чисел с клавиатуры, cin использует первый пустой символ (пробел, табуляцию, возврат каретки), чтобы определить, где начинается одно значение, а где второе. Экспериментируйте с программой TWONBRS, разделяя числа табуляцией, пробелом и возвратом каретки. Чтение ввода с клавиатуры с помощью cin Для чтения ввода с клавиатуры программы могут использовать входной поток cin. При использовании cin вы должны указать переменную, в которую cin помещает данные. Затем используйте оператор извлече-
52 Урок 6 ния (») для направления данных, как показано ниже: cin >> some_variable; Оператор извлечения называется так, потому что он извлекает (удаляет) данные из входного потока, присваивая значение указанной переменной. Следите за ошибками переполнения Если ваши программы выполняют ввод с использованием cin, остерегайтесь возможных ошибок, возникающих при вводе пользователем неверного числа. Например, запустите программу FIRSTCIN, которую вы только что создали. Когда программа запросит вас ввести ваше любимое число, введите число 1000000 и нажмите ENTER. При этом программа не сможет отобразить число 1000000 в качестве введенного значения. Вместо этого возникнет ошибка переполнения, так как 1000000 превышает наибольшее значение, которое может хранить тип int. Если вы внимательно рассмотрите программу FIRSTCIN.CPP, то обратите внимание, что cin присваивает введенное число переменной типа int. Как вы узнали из урока 4, переменные типа int могут хранить значения только в диапазоне от -32768 до 32767. Поскольку переменная типа intne может вместить значение 1000000, возникает ошибка. Запустите программу еще несколько раз, вводя отрицательные и положительные числа. Обратите внимание на ошибки, которые возникают, если вы выходите за допустимые пределы значений для той переменной, в которую cin помещает ввод. Следите за ошибками несовпадения типов Как уже обсуждалось, программа FIRSTCIN.CPP предполагает, что пользователь вводит значение в диапазоне от -32768 до 32767. Если вместо ввода числа вне этого диапазона, пользователь вводит буквы или другие символы, то возникает другая ошибка — ошибка несовпадения типов. Другими словами, программа ожидала значение одного типа {int), а пользователь ввел значение другого типа {char). Для примера запустите программу второй раз. Когда программа запросит число, введите буквы ABC. Как и раньше, возникнет ошибка, поскольку программа ожидает целое число, а не буквы. Выполните подобные эксперименты с программой TWONBRS, вводя бессмысленные значения или числа с плавающей точкой. Вы обнаружите, что программа наталкивается на те же ошибки. В последующих уроках вы научитесь выполнять операции ввода таким образом, чтобы уменьшить возможность подобных ошибок. А сейчас просто помните, что такие ошибки могут возникать.
Чтение ввода с клавиатуры 53 Чтение символьных данных Обе предыдущие программы использовали tin для чтения целых чисел в переменные типа int. Следующая программа CIN_CHAR.CPP использует входной поток tin для чтения символов с клавиатуры. Как видите, программа читает символ в переменную типа char. #include <iostream.h> void main(void) { char letter; cout << "Введите любой символ и нажмите Enter: "; cin >> letter; cout << "Выл введен символ " << letter << endl; } Откомпилируйте и поэкспериментируйте с данной программой, вводя более одного символа и наблюдая за реакцией программы. Вы обнаружите, что программа каждый раз работает только с одним символом. Чтение слов с клавиатуры Во второй части данной книги вы научитесь сохранять слова или даже строки текста в одной переменной. Там же вы узнаете, как использовать входной поток tin для чтения слов и целых строк. А сейчас можете создать свою собственную простую программу, которая читает значения типа float или long. Например, следующая программа CIN_LONG.CPP использует tin для чтения значения типа long: #include <iostream.h> void main(void) { long value; cout << "Введите большое число и нажмите Enter: "; cin >> value; cout << "Выло введено число " << value << endl; } Как и раньше, поэкспериментируйте с этой программой, вводя очень большие числа (и отрицательные тоже). Перенаправление В/В и входной поток cin Как вы уже знаете из урока 3, если ваши программы используют выходной поток cout, пользователь может перенаправить вывод програм-
54 Урок 6 мы с экрана дисплея в файл или на принтер. Как уже обсуждалось, выходной поток cout соответствует стандартному выводу операционной системы. Подобным образом входной поток tin соответствует стандартному вводу операционной системы. В результате, если ваша программа использует tin для выполнения операций ввода, пользователь может перенаправить ввод программы с клавиатуры на файл. В последующих уроках вы научитесь писать программы, которые читают и обрабатывают перенаправленный ввод. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ В этом уроке вы научились использовать входной поток tin для выполнения ввода с клавиатуры. Как вы уже знаете, если ваши программы используют tin для чтения ввода с клавиатуры, вам следует указать переменные, которым tin присваивает вводимые значения. В уроке 7 вы научитесь использовать оператор C++ if, чтобы позволить программам принимать собственные решения. Однако перед тем, как приступить к уроку 7, убедитесь, что вы освоили следующие основные концепции: 0 C++ предоставляет входной поток tin, который ваши программы могут использовать для чтения ввода с клавиатуры. 0 Если программы для чтения ввода используют tin, они должны указать одну или несколько переменных, в которые tin будет помещать данные. 0 Чтобы направить ввод в переменную, вам следует использовать tin с оператором извлечения (»). 0 При применении tin для чтения нескольких значений, tin использует пустые символы (пробел, табуляция или возврат каретки), чтобы определить, где заканчивается одно значение и начинается другое. 0 Если пользователь вводит неверные данные, могут возникать ошибки переполнения или несоответствия типов, а значения, присвоенные входным потоком tin переменным вашей программы, будут неверны.
Урок 7 Программа принимает решение Как вы уже знаете, программа представляет собой последовательность инструкций, выполняемых компьютером для реализации определенных задач. Все созданные вами до сих пор простые программы на C++ выполняли операторы по порядку, начиная с первого до конца программы. По мере усложнения программ вам потребуется, чтобы выполнялся один набор операторов, если определенное условие соблюдается, и другой набор, если условие не соблюдается. Другими словами, вам потребуется, чтобы ваши программы приняли решение и соответственно отреагировали. В этом уроке описывается оператор C++ //, который будет использоваться вашей программой для принятия подобных решений. К концу данного урока вы освоите следующие основные концепции: • Программы на C++ используют операции сравнения, чтобы определить, равны ли два значения или одно значение больше или меньше другого. • Для принятия решений используется оператор C++ if. • Операторы C++ могут быть простыми (одна операция) или составными (несколько операций, сгруппированных внутри правой и левой фигурных скобок {}). • Ваши программы используют оператор C++ if-else для выполнения одного набора операторов, если определенное условие соблюдается, и другого набора операторов, если условие не соблюдается. • Комбинируя несколько операторов if-else, программы могут проверять несколько условий. • Используя логические операторы C++ И и ИЛИ, ваши программы могут проверить несколько условий, например: Есть ли у пользователя собака И долматин ли это ? Программы, которые принимают решения, выполняют условную обработку. Другими словами, на основании результата одного или нескольких условий программа будет выполнять определенные операторы. Экспериментируйте с программами данного урока; ваш набор инструментальных средств C++ уже достаточно обширен для создания полезных программ.
56 Урок 7 СРАВНЕНИЕ ДВУХ ЗНАЧЕНИЙ Когда ваши программы принимают решение, они обычно выполняют некоторый вид проверки. Например, одна программа может проверять, равны ли тестовые очки студента 100, а вторая программа — составляет ли стоимость покупки больше $50.00. Для выполнения подобных проверок ваши программы будут использовать операции сравнения C++, перечисленные в табл. 7.1. Операции сравнения позволяют проверять, каким образом одно значение соотносится с другим. Другими словами, используя операции сравнения, программы могут проверить, больше одно значение, меньше или равно другому. Таблица 7.1. Операции сравнения C++. Операция Проверка Пример == Если два значения равны (score == 100) != Если два значения не равны (old != new) > Если первое значение больше (cost > 50.00) второго < Если первое значение меньше (salary < 20000.00) второго >= Если первое значение больше (stock_price >= 30.0) или равно второму <= Если первое значение меньше (age <= 21) или равно второму Если ваша программа использует операции сравнения для сравнения двух значений, результат сравнения может быть истиной или ложью. Другими словами, два значения или равны (истина), или нет (ложь). Каждый из операторов //, представленных в этой книге, использует операции сравнения, перечисленные в табл. 7.1. ЗНАКОМСТВО С ОПЕРАТОРОМ if Оператор C++ г/позволяет вашим программам осуществлять проверку и затем на основании этой проверки выполнять операторы. Формат оператора // следующий: if (условие_выполняется) оператор; Обычно оператор //выполняет проверку, используя операцию сравнения C++. Если результат проверки является истиной, //выполняет оператор, который следует за ним. Следующая программа FIRST_IF.CPP использует оператор //для сравнения значения переменной testscore со значением 90. Если набранные тестовые очки больше или равны 90, программа выведет со-
Программа принимает решение 57 общение пользователю, что он получил А. В противном случае, если значение меньше 90, программа просто завершается: #include <iostream.h> void main(void) { int test_score = 95; if (test_score >= 90) cout << "Поздравляем, вы получили А!" << endl; } Как видите, для выполнения проверки программа использует операцию сравнения C++ "больше или равно" (>=). Если результат сравнения значений является истиной, программа выполняет оператор, который следует за //, в данном случае вывод сообщения с использованием cout. Если результат сравнения не является истинным, программа не выводит сообщение. Экспериментируйте с этой программой, изменяя проверочные очки (в том числе и меньше 90), и обратите внимание на работу оператора if Представление о простых и составных операторах При использовании оператора //для условной обработки в некоторых случаях, если условие истинно, программе потребуется выполнить один оператор, а в других случаях несколько операторов. Когда программа выполняет только один оператор, следующий за if такой оператор называется простым оператором: if (test_score >= 90) Простой cout << "Поздравляем, вы получили А!" << endl;I оператор Если программе необходимо выполнить несколько инструкций, когда результат сравнения — истина, операторы должны быть сгруппированы внутри левой и правой фигурных скобок {}. Операторы, которые находятся внутри фигурных скобок, образуют составной оператор, как показано ниже: if (test_score >= 90) < . cout << "Поздравляем, вы получили А!" << endl; Составной cout << "Ваши тестовые очки были " « test_score оператор « endl; I } Вам необязательно запоминать термины "простой" и "составной" операторы, но вы должны знать, что следует группировать связанные операторы внутри левой и правой фигурных скобок. Следующая программа СОМ- POUND.CPP представляет собой измененный вариант предыдущей и выводит два сообщения, если тестовые очки больше или равны 90:
58 Урок 7 #include <iostream.h> void main(void) { int test_score = 95; if (test_score >= 90) { cout << "Поздравляем, вы получили А!" << endl; cout << "Ваши тестовые очки были " << test_score << endl; } } Использование простых и составных операторов При выполнении условной обработки в некоторых случаях программе необходимо выполнить только один оператор (простой оператор), если условие истинно. Однако в других случаях программа должна будет выполнить несколько операторов (составной оператор). Если в зависимости от результата определенного условия вашей программе нужно выполнить два или несколько связанных операторов, вы должны сгруппировать операторы внутри левой и правой фигурных скобок, как показано ниже: if (age >= 21) { cout << "Все на выборы!" << endl; cout << "Это то, что вам надо!" << endl; } ОПЕРАТОР else Две предыдущие программы использовали оператор if чтобы определить, больше тестовые очки или равны 90. Если условие было истинным, программы выводили сообщение на экран. В противном случае, т. е. если тестовые очки были меньше 90, программы не выводили сообщение, они просто завершались. В большинстве случаев вашим программам потребуется указать один набор операторов, выполняющийся, если условие истинно, и второй набор, выполняющийся, если условие ложно. Для указания операторов, которые должны выполняться, когда условие ложно, ваши программы должны использовать оператор else. Ниже приведен формат оператора else: if (условие_истинно) оператор; else оператор;
Программа принимает решение 59 Следующая программа IF_ELSE.CPP использует оператор //, чтобы проверить, больше тестовые очки или равны 90. Если условие истинно, программа выводит поздравление. Если условие ложно, программа сообщает, что студент должен работать усерднее: #include <iostream.h> void main(void) { int test_score = 95; if (test_score >= 90) cout << "Поздравляю, вы получили А!" << endl; else cout << "В следующий раз вы должны" << " работать усерднее!" << endl; } Применение составных операторов для else Как вы уже знаете, составной оператор представляет собой группу связанных операторов, заключенных между левой и правой фигурными скобками. Когда программа использует else для указания операторов, выполняющихся, если условие ложно, то для указания нескольких операторов можно использовать составной оператор. Следующая программа CMP_ELSE.CPP использует составные операторы как для if, так и для else: #include <iostream.h> void main(void) { int test_score = 65; if (test_score >= 90) { cout << "Поздравляю, вы получили А!" << endl; cout << "Ваши тестовые очки были " << test_score << endl; } else { cout << "Вы должны работать усерднее!" << endl; cout << "Вы потеряли " << 100 - test_score << " очков " << endl; } } Как и ранее, найдите время поэкспериментировать с этой программой, изменяя значения переменной testscore так, чтобы оно было меньше или
60 Урок 7 больше 90. Следующая программа GETSCORE.CPP использует входной поток cin для ввода тестовых очков пользователем. Затем программа сравнивает тестовые очки со значением 90, выводя соответствующее сообщение: #include <iostream.h> void main(void) { int test_score; cout << "Введите тестовые очки и нажмите Enter: "; cin >> test_score; if (test_score >= 90) { cout << "Поздравляем, вы получили А!" << endl; cout << "Ваши тестовые очки были " << test_score << endl; } else { cout << "Вы должны работать усерднее!" << endl; cout << "Вы потеряли " « 100 - test_score << " очков " << endl; } } Откомпилируйте и запустите эту программу. Вы увидите, что при комбинации операций ввода с условной обработкой ваши программы станут более совершенными. Представление об if-else По мере усложнения ваши программы будут проверять разные условия и выполнять один набор операторов, если условие истинно, и другой набор, если условие ложно. Для выполнения такой условной обработки программы используют операторы if-else, как показано ниже: if (условие_истинно) оператор; else оператор; Когда программе требуется выполнить несколько операторов, если условие ложно или истинно, вы должны сгруппировать связанные операторы внутри левой и правой фигурных скобок {}: if (условие_истинно) { первый_оператор_для_истины;
Программа принимает решение 61 второй_оператор_для_истины; } else { первый_оператор_для_лжи; второй_оператор_для_лжи; } ИСПОЛЬЗОВАНИЕ ОТСТУПОВ ДЛЯ УЛУЧШЕНИЯ УДОБОЧИТАЕМОСТИ ВАШЕЙ ПРОГРАММЫ Рассматривая программы, представленные в этой главе, вы увидите, что в них применяются отступы перед операторами, которые следуют за if, else или левой скобкой. Сдвигая подобным образом свои операторы на одну или две позиции, вы упрощаете процесс чтения ваших программ, выделяя связанные группы операторов, как показано ниже: if (test_score >= 90) { cout << "Поздравляем, вы получили А!" << endl; cout << "Ваши тестовые очки были " « test_score << endl; } else { cout << "Вы должны работать усерднее!" << endl; cout << "Вы потеряли " << 100 - test_score << " очков " << endl; } При создании программ используйте подобные отступы, чтобы программы были более удобочитаемыми. Отступы нужны не C++, а программистам, которые будут читать и пытаться понять ваш код. ПРОВЕРКА ДВУХ ИЛИ БОЛЕЕ УСЛОВИЙ Как вы уже знаете, оператор //позволяет программам проверять определенные условия. По мере усложнения ваших программ возникает необходимость в проверке сразу нескольких условий. Например, программа могла бы проверить, будут ли тестовые очки больше или равны 90, и получит ли студент оценку А. Подобно этому, вы могли бы проверить, есть ли у пользователя собака и долматин ли это. Для выполнения таких проверок можно использовать логическую операцию C++ И (&&). Кроме того, если вы хотите проверить, есть ли у пользователя собака или кошка, вам следует использовать логическую операцию ИЛИ (||). Если программы для проверки нескольких условий используют логические операции И или ИЛИ, поместите каждое условие внутри круглых скобок, как показано ниже:
62 Урок 7 if ((user_owns_a_dog) && (dog == da lmati an) )^^ Полное условие Как видите, программа группирует каждое условие внутри своих собственных круглых скобок, которые затем заключаются во внешние круглые скобки. if ((user_owns_a_dog) && (dog == dalmatian)) Когда ваша программа использует логическую операцию И (&&), то результат полного условия является истинным, только если все проверяемые условия истинны. Если какое-либо условие ложно, то полное условие становится ложным. Например, если у пользователя нет собаки, то предыдущее условие является ложным. Подобно этому, если собака пользователя не до- лматин, условие является ложным. Чтобы условие было истинным, у пользователя должна быть собака и она должна быть породы долматин. Следующий оператор использует логическую операцию ИЛИ (||), чтобы определить, есть ли у пользователя собака или кошка: if ((user_owns_a_dog) II (user_owns_a_cat)) При использовании логической операции ИЛИ полное условие будет истинным, если хотя бы одно условие является истинным. Например, если у пользователя есть собака, условие истинно. Если у пользователя есть кошка, условие истинно. Подобно этому, если у пользователя есть и собака, и кошка, условие истинно. Условие будет ложным только в том случае, если у пользователя нет ни собаки, ни кошки. В C++ истина представляется как ненулевое значение, а ложь как О Достоинством C++ является то, что истина представляется как любое ненулевое значение, а ложь как 0. Предположим, ваша программа использует переменную с именем user_owns_a_dog, чтобы определить, есть ли у пользователя собака или нет. Если у пользователя нет собаки, вы можете присвоить этой переменной значение 0 (ложь), как показано ниже: user_owns_a_dog = 0; Если у пользователя есть собака, вы можете присвоить этой переменной любое ненулевое значение, например 1: user_owns_a_dog = 1; Затем ваши программы могут проверить эту переменную, используя оператор //, как показано ниже: if (user_owns_a_dog) Если переменная содержит ненулевое значение, условие оценивается как истина; в противном случае, если переменная содержит 0, условие ложно. Исходя из того, как C++ представляет истину и ложь, предыдущий оператор идентичен следующему:
Программа принимает решение 63 if (user_owns_a_dog — 1) Следующая программа DOG_CAT.CPP использует переменные user_owns_a_dogn user owns_a_cat внутри оператора if, чтобы определить, какие животные есть у пользователя. #include <iostream.h> void main(void) { int user_owns_a_dog = 1; int user_owns_a_cat = 0; if (usei:_owns_a_dog) cout << "Собаки великолепны" << endl; if (user_owns_a_cat) cout << "Кошки великолепны" << endl; if ((user_owns_a_dog) && (user_owns_a_cat)) cout << "Собаки и кошки могут ужиться" << endl; if ((user_owns_a_dog) II (user_owns_a_cat)) cout << "Домашние животные великолепны!" << endl; } Экспериментируйте с этой программой, присваивая обоим переменным значение 1 или 0, а затем одной 1, а другой 0 и наоборот. Как видите, проверить два условия очень легко, используя логические операции И и ИЛИ. Использование операции НЕ в C++ Вы уже знаете, что, когда программа проверяет определенное условие, в ряде случаев она должна выполнить некоторые операторы, если это условие истинно. С другой стороны, вам может потребоваться, чтобы программа выполнила операторы, если условие является не истинным. Операция C++ НЕ — восклицательный знак (!) — позволяет вашим программам проверить, является ли условие не истинным. Например, следующий оператор проверяет, нет ли у пользователя собаки: if (! user_owns_a_dog) cout << "Вы должны купить собаку" << endl; Операция НЕ превращает ложь в истину, а истину в ложь. Например, предположим, что у пользователя нет собаки. Следовательно, переменная user_owns_a_dog должна содержать значение 0. Если C++ оценивает условие с помощью операции НЕ, он использует текущее значение переменной @) и применяет операцию НЕ. Операция НЕ превращает значение 0 в 1 (истину). Таким образом, полное условие превращается в истину и выполняется соответствующий оператор.
64 Урок 7 Следующая программа USE_NOT.CPP иллюстрирует использование операции НЕ: #include <iostream.h> void main(void) { int user_owns_a__dog = 0; int user_owns_a_cat = 1; if (! user_owns_a_dog) cout << "Вы должны купить собаку" << endl; if (! user_owns_a_cat) cout << "Вы должны купить кошку" << endl; > Как и раньше, экспериментируйте со значениями, присваиваемыми переменным user_owns_a_dog и user_owns_a_cat, и обратите внимание на поведение программы. По мере усложнения ваших программ вы будете постоянно использовать операцию НЕ. Например, ваша программа может продолжать повторять обработку, пока не встретит конец файла. Использование логических операций C++ Если вы указываете условия в программах, то иногда эти условия будут состоять из нескольких частей. Например, ваша программа может проверять, имеет ли служащий почасовую оплату и работал ли он более 40 часов на этой неделе. Если для истинности условия каждая из двух его частей должна быть истиной, вам следует использовать операцию C++ И (&&). При использовании операции И группируйте каждое условие внутри круглых скобок, а затем оба условия заключите в еще одну пару круглых скобок, как показано ниже: if ((employee_pay == hourly) && (employee_hours > 40)) оператор; Когда необходимо, чтобы условие было истинным, если только одна из его частей истинна, вам следует использовать операцию C++ ИЛИ (||). Например, следующее условие проверяет, есть ли у пользователя машина или мотоцикл: if ((vehicle == car) || (vehicle == motorcycle)) оператор; Как и ранее, программа группирует каждое условие внутри скобок. В ряде случаев вам потребуется, чтобы ваши программы выполняли оператор, если условие является не истинным. В таких случаях следует использовать операцию C++ НЕ (!). Операция НЕ превращает истину в ложь, а ложь в истину.
Программа принимает решение 65 Операции C++ И, ИЛИ и НЕ представляют собой логические операции. ОБРАБОТКА НЕСКОЛЬКИХ УСЛОВИЙ Программы, представленные в этом уроке, использовали //и else, чтобы указать один набор операторов, который программе следует выполнить, если условие истинно, и другой набор операторов, выполняемых, если условие ложно. Однако в некоторых случаях программам потребуется проверить несколько разных условий. Предположим, например, что вашей программе необходимо определить тестовые очки студента. Для этого программа должна проверить, больше тестовые очки или равны 90, 80, 70, 60 и т. д. Следующая программа SHOWGRAD.CPP использует для этого серию операторов if-else: #include <iostream.h> void main(void) { int test_score; cout << "Введите тестовые очки и нажмите Enter: "; cin >> test_score; if (test_score >= 90) cout << "Вы получили А!" << endl; else if (test_score >= 80) cout << "Вы получили ВI'7 << endl; else if (test_score >= 70) cout << "Вы получили С" << endl; else if (test_score >= 60) cout << "Ваша оценка была D" << endl; else cout << "Вы провалили тест" << endl; } При выполнении первого оператора if программа проверяет, больше тестовые очки или равны 90. Если это так, программа выводит сообщение пользователю, что он получил А. В противном случае, если тестовые очки не больше или равны 90, программа выполняет следующие else if, чтобы проверить, больше ли тестовые очки или равны 80. Программа повторяет данный процесс до тех пор, пока не определит правильную оценку. Как и ранее, экспериментируйте с этой программой, вводя разные тестовые очки. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА switch Как вы уже знаете, комбинируя серии операторов if-else, программы могут проверять несколько условий. В предыдущей программе использовались операторы if-else, чтобы определить, находятся ли тестовые очки в данном
66 Урок 7 диапазоне значений. В тех случаях, когда вашим программам необходимо осуществить проверку определенных значений, они могут использовать оператор C++ switch. Если вы используете оператор switch, вы должны указать условие и затем один или несколько вариантов (case), которые программа попытается сопоставить с условием. Например, следующая программа SWITCH.CPP использует оператор switch для вывода сообщения, основываясь на текущей оценке студента: #include <lostrearn.h> void main(void) { char grade = 'В'; switch (grade) { case 'A': cout << "Поздравляем, вы получили А" << endl; break; case 'B' : cout << "Хорошо, у вас В" << endl; break; case 'С: cout << "У вас всего лишь С" << endl; break; case 'D': cout << "Плохо, у вас D" << endl; break; default: cout << "Ужасно! Учите лучше!" << endl; break; } } Оператор switch состоит из двух частей. Первая часть оператора switch представляет собой условие, которое появляется после ключевого слова switch. Вторая часть представляет собой возможные варианты соответствия. Когда программа встречает оператор switch, она сначала исследует условие, а затем пытается найти среди возможных вариантов тот, который соответствует условию. Если программа находит соответствие, выполняются указанные операторы. Например, в предыдущей программе выбор варианта 'В' соответствует условию. Таким образом, программа выводит сообщение, что пользователь получил В. Найдите время для эксперимента с этой программой, изменяя оценку и наблюдая поведение программы. Если же ни один из указанных вариантов не соответствует условию, то выполняется вариант default. Обратите внимание на использование оператора break в каждом варианте предыдущей программы. Оказывается, если C++ встречает вариант, соответствующий условию оператора switch, то он подразумевает, что все последующие варианты тоже соответствуют условию. Оператор break указывает C++ завершить текущий оператор switch и продолжить выполнение программы с первого оператора, следующего за оператором switch. Если вы удалите операторы break из предыдущей программы, то программа выве-
Программа принимает решение 67 дет не только требуемое сообщение, но и сообщение для всех последующих вариантов (потому что если один вариант является истинным, то и все последующие варианты в C++ рассматриваются как истинные). ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как использовать оператор C++ {/для выполнения условной обработки, которая позволяет вашим программам принимать собственные решения. Как вы знаете, программы могут использовать оператор //, чтобы выполнить один набор операторов, когда условие истинно, и else для указания операторов, выполняющихся программой, если условие ложно. Из урока 8 вы узнаете, как использовать итеративные операторы C++ для повторения операторов указанное число раз или до тех пор, пока выполняется определенное условие. Например, вы можете повторять один и тот же оператор 100 раз, чтобы прибавить 100 студентам тестовые очки. Однако до изучения урока 8 убедитесь, что вы освоили следующие основные концепции: 0 Операции сравнения C++ позволяют вашим программам проверять, равны ли два значения или нет, или одно значение больше или меньше другого. 0 Оператор C++ //позволяет программе проверять условия и выполнять один или несколько операторов, если условие истинно. 0 Оператор C++ else позволяет указать один или несколько операторов, которые выполняются, если условие, проверяемое с помощью оператора if, является ложным. 0 C++ представляет истину, используя любое ненулевое значение, а ложь как 0. 0 Логические операции C++ И (&&) и ИЛИ (||) позволяют вашим программам проверять несколько условий. 0 Логическая операция НЕ (!) позволяет программам проверять условие на неистинность. 0 Если в операторе //или else нужно выполнить несколько операторов, то такие операторы следует расположить внутри левой и правой фигурных скобок {}. 0 Используйте отступы при записи операторов программы, чтобы помочь программистам, читающим ваш код, легко находить связанные операторы. 0 Если вашей программе необходимо проверить, соответствует ли условие указанным значениям, следует использовать оператор switch.
68 Урок 7 0 Когда программа встречает в операторе switch вариант {case), соответствующий условию, то все последующие варианты рассматриваются как удовлетворяющие условию. Используя оператор break, вы можете указать C++ прервать оператор switch и продолжить выполнение программы с первого оператора, который следует за switch.
Урок 8 Повторение одного или нескольких операторов Из урока 7 вы узнали, как использовать в ваших программах оператор C++ if для принятия решений. С подобным принятием решений тесно связана способность повторять одну или несколько инструкций определенное число раз или до достижения некоторого условия. В этом уроке вы будете использовать итеративные конструкции C++ для повторения одного или нескольких операторов. К концу данного урока вы освоите следующие основные концепции: • Для повторения операторов определенное число раз ваши программы используют оператор C++for. • С помощью оператора C++ while программы повторяют операторы до тех пор, пока указанное условие истинно. • Оператор C++ do while позволяет программам выполнять операторы по крайней мере один раз, а затем, возможно, повторять операторы, основываясь на определенном условии. Возможность повторять операторы очень важна в программировании. Экспериментируйте с программами, представленными в этом уроке. К концу урока вы сможете значительно усовершенствовать свои возможности программирования на C++. ПОВТОРЕНИЕ ОПЕРАТОРОВ УКАЗАННОЕ ЧИСЛО РАЗ Одной из наиболее широко используемых операций в ваших программах является повторение одного или нескольких операторов определенное число раз. Например, одна программа могла бы повторять один и тот же оператор, чтобы напечатать пять копий файла, а другая могла бы повторять некоторый набор операторов 30 раз, чтобы определить, поднялась или упала цена ваших 30 акций. Оператор C++ for предоставляет чрезвычайно простую возможность вашим программам повторять один или несколько операторов указанное число раз. Если ваша программа использует операторуЬг (часто называемый циклом for), она должна указать переменную, которая называется управляющей пере- менной, хранящей количество выполнений цикла. Например, следующий
70 Урок 8 цикл for использует переменную count для хранения количества выполнений цикла. В данном случае цикл будет выполнен десять раз. for (count = 1; count <= 10; count++) оператор; Цикл for состоит из четырех частей. Первые три части управляют количеством выполнений цикла. Сначала оператор count = 7; присваивает переменной управления начальное значение. Цикл for выполняет эту инициализацию один раз при запуске цикла. Далее цшсл проверяет условие count <= 10. Если условие истинно, цикл for выполняет следующий оператор. Если условие ложно, цикл завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом. Если условие истинно и цикл for выполняет свой оператор, то после этого цикл увеличивает переменную count, используя оператор count++. Далее программа проверяет условие count <= 10. Если это условие все еще истинно, то опять повторяется выполнение оператора внутри цикла, увеличение и проверка переменной count for (count = 1; count <= 10; count++) Инициализация Проверка Увеличение Следующая программа FIRSTFOR.CPP использует цикл for для вывода на экран дисплея значений от 1 до 100: #include <lostream.h> void main(void) { int count; for (count = 1; count <= 100; count++) cout << count << ' '; ) Как видите, оператор for инициализирует переменную count значением 1. Затем цикл проверяет, меньше ли значение переменной count или равно 100. Если это так, цикл for выполняет соответствующий оператор и затем увеличивает count, повторяя проверку. Экспериментируйте с этой программой, изменяя значение 100 на 10, 20 и даже 5000. Следующая программа ASKCOUNT.CPP выводит сообщение, запрашивающее пользователя ввести число, при котором цикл должен завершиться. Затем программа выводит числа от одного до указанного значения: #include <iostream.h> void main (void) < int count; int ending__value;
Повторение одного или нескольких операторов 71 cout << "Введите конечное значение и нажмите Enter: "; сin >> ending_value; for (count = 0; count <= ending_value; count++) cout << count << ' '; } Экспериментируйте с этой программой, вводя разные числа, например 10, 1 и даже 0. Если вы вводите значение 0 или 1, цикл for никогда не выполняется, потому что условие count <= endingjvalue сразу же ложно. Помните, если вы введете значение вне диапазона значений, которые может хранить переменная типа int, возникнет ошибка переполнения. Например, запустите программу и введите значение 50000. Поскольку это значение превышает наибольшее возможное для переменной типа int, то переполнение приводит к отрицательному значению, которое предотвращает выполнение цикла. Циклы for C++ поддерживают составные операторы Из урока 7 вы узнали, что если программы выполняют несколько операторов внутри if или else, то такие операторы следует сгруппировать внутри левой и правой фигурных скобок. Это же относится и к нескольким операторам в цикле for. Следующая программа ADD1_100.CPP зацикливает числа от 1 до 100, выводя и добавляя каждое число в общий итог: #include <iostream.h> void main(void) { int count; int total = 0; for (count = 1; count <= 100; count++) { cout << "Прибавляю " << count << " к " « total; total = total + count; cout << " получаю " << total << endl; } } Группируя операторы внутри фигурных скобок, цикл for тем самым может выполнить несколько операторов за один проход (называемый итерацией цикла). Изменение и увеличение цикла for Все представленные до настоящего момента циклы for увеличивали управляющую переменную цикла на 1 на каждой итерации цикла. Однако цикл for не обязывает ваши программы увеличивать эту переменную на единицу. Следующая программа BYJFIVES.CPP выводит каждое пятое число в диапазоне от 0 до 100:
72^ Урок 8 #include <iostream.h> void main(void) { int count; for (count = 0; count <= 100; count += 5) cout << count << ' '; } Если вы откомпилируете эту программу, на вашем экране будут отображаться числа 0, 5,10 и т. д. до 100. Обратите внимание, что оператор циклау&г использует для увеличения переменную count. count += 5; Если вы хотите добавить некоторое значение к текущему значению переменной, а затем присвоить результат той же переменной, C++ позволяет вам сделать это двумя способами. Первый: предположим, вашей программе необходимо добавить значение 5 к переменной count, это можно сделать, как показано ниже: count = count + 5; Второй: C++ позволяет вам использовать краткую запись, представленную ниже, для добавления значения 5 к переменной count count += 5; Поскольку это легче записать, то данная краткая форма является общепринятой внутри циклов. При использовании цикла/or вы не обязаны продвигать счетчик в сторону увеличения. Следующая программа CNT_DOWN.CPP использует цикл for для вывода чисел в порядке уменьшения от 100 до 1: #include <iostream.h> void main(void) { int count; for (count = 100; count >= 1; count—) cout << count « ' '} > Как видите, цикл for инициализирует переменную count значением 100. На каждой итерации цикл уменьшает значение этой переменной на 1. Цикл завершается, когда переменная count содержит значение 0.
Повторение одного или нескольких операторов 73 Остерегайтесь бесконечных циклов Как вы уже знаете, цикл for предоставляет вашим программам способ повторять связанные операторы определенное количество раз. Используя переменную управления, цикл for по существу считает количество выполненных итераций. Когда цикл достигает своего конечного условия, ваша программа прекращает повторение операторов и продолжает свое выполнение с первого оператора, следующего за циклом for. К сожалению, из-за ошибок в программах в некоторых случаях цикл никогда не достигает своего завершающего условия и, таким образом, зацикливается навсегда (или до тех пор, пока вы не прервете программу). Такие не завершающиеся циклы называются бесконечными циклами. Другими словами, это циклы, не имеющие способа для завершения. Например, следующий оператор for создает бесконечный цикл: for (count = 0; count < 100; wrong_variable++) // Операторы Как видите, цикл for использует переменную count в качестве своей управляющей переменной. Однако в секции цикла увеличения программа увеличивает не ту переменную. В результате цикл никогда не увеличивает переменную count, и она никогда не будет иметь значение больше или равно 100. Таким образом, этот цикл превращается в никогда не завершающийся бесконечный цикл. Важно обратить внимание, что циклы for не ограничиваются использованием переменных типа int в качестве их управляющих переменных. Например, следующая программа LOOPVAR.CPP использует переменную типа char для вывода букв алфавита внутри одного цикла и переменную типа float для вывода чисел с плавающей точкой внутри другого цикла: #include <iostream.h> void main(void) { char letter; float value; for (letter = 'A'; letter <= 'Я'; letter++) cout << letter; с out << endl; for (value = 0.0; value <= 1.0; value += 0.1) cout << value << ' '; cout << endl; >
74 Урок 8 Если вы откомпилируете и запустите эту программу, на экране появится следующий вывод: АВВГДЕЖЗИЙКЛМНОПРСТУФХЦЧЩЪЫЬЭЮЯ О 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 Повторение цикла определенное число раз Одна из наиболее общих операций, которую будут выполнять ваши программы, состоит в повторении одного или нескольких операторов определенное количество раз. Оператор C++for позволяет вашим программам сделать именно это. Такой оператор./^ использует управляющую переменную, хранящую количество выполнений цикла. Общий формат оператора for выглядит так: for (инициализация; проверка; увеличение) оператор; При запуске этот цикл for присваивает начальное значение управляющей переменной цикла. Далее программа проверяет условие цикла. Если условие истинно, она выполняет операторы внутри цикла, затем увеличивает управляющую переменную цикла и повторяет проверку условия. Если условие истинно, процесс повторяется. Если же условие ложно, цикл ./or завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом for. ВЗГЛЯД НА ЦИКЛ while Как вы только что узнали, цикл C++for позволяет вашим программам повторять один или несколько операторов определенное количество раз. Однако в некоторых случаях программе необходимо повторять операторы, пока удовлетворяется (истинно) некоторое условие. Например, в следующих уроках вы узнаете, как читать содержимое файла. Такие программы могли бы повторять цикл, пока не встретится конец файла. В ситуациях, когда программам необходимо выполнять цикл, пока удовлетворяется некоторое условие (но не обязательно определенное количество раз), ваши программы могут использовать оператор C++ while. Общий формат оператора while выглядит так: while (условие_верно) оператор; Если ваша программа встречает оператор while, она проверяет заданное условие. Если условие истинно, программа выполняет операторы цикла while. После выполнения последнего оператора в цикле, цикл while опять проверяет условие. Если условие все еще истинно, повторяются операторы
Повторение одного или нескольких операторов 75 цикла и повторяется данный процесс. Когда условие, наконец, становится ложным, цикл завершается и программа продолжает свое выполнение с первого оператора, следующего за циклом. Следующая программа GET_YN.CPP просит вас ввести Ддля да или Я для нет. Затем программа использует цикл while для чтения символов с клавиатуры, пока пользователь не введет Д или Н. Если пользователь вводит значение, отличное от Д или Н, программа сигналит встроенным динамиком, записывая символ сигнала '\а' в выходной поток cout #include <iostream.h> void main(void) { int done =0; // Устанавливается в состояние „истина", // если введены Д или Н char letter; while (! done) { cout << "\пВведите Д или Н" << " и нажмите Enter для продолжения: "; cin >> letter; if ((letter == 'Д') II (letter == 'д')) done = 1; else if ((letter == 'H') I I (letter == 'н')) done = 1; else cout << '\a#; // Играть сигнал динамика для // неверного символа } cout << "Вы ввели букву " << letter << endl; } Как видите, цикл while тоже поддерживает несколько операторов, сгруппированных внутри левой и правой фигурных скобок. В данном случае программа использует переменную done для управления циклом. Пока программа не завершится (т. е. пока пользователь не введет Д или Н), цикл продолжает выполняться. Когда пользователь вводит Д или Н, программа устанавливает переменную done в значение истина и цикл завершается. Как только ваши программы начнут работать с файлами, вы регулярно будете использовать цикл while. Повторение цикла до выполнения заданного условия По мере усложнения ваших программ им, возможно, понадобится выполнять группы связанных операторов, пока не реализуется заданное условие. Например, программа может вычислять суммы пла-
75 Урок 8 тежей для служащих компании. В этом случае цикл будет выполняться до тех пор, пока не обработаны данные для последнего служащего. Для повторения операторов до выполнения заданного условия программы, как правило, будут использовать оператор while: while (условие) оператор; Сталкиваясь с оператором while, программа будет оценивать условие цикла. Если условие истинно, ваша программа выполняет операторы цикла while. После выполнения последнего оператора цикла программа снова проводит проверку условия. Если условие истинно, программа повторит этот процесс, выполнит операторы, а затем повторит проверку условия. Если условие оценивается как ложь, программа продолжит свое выполнение с первого оператора, который следует за оператором while. ВЫПОЛНЕНИЕ ОПЕРАТОРОВ ПО КРАЙНЕЙ МЕРЕ ОДИН РАЗ Как вы уже знаете, цикл C++ while позволяет вашим программам повторять набор операторов, пока данное условие удовлетворяется. Когда программа встречает оператор while, она сначала оценивает заданное условие. Если условие истинно, программа входит в цикл. Если условие ложно, операторы цикла while никогда не выполняются. В зависимости от назначения ваших программ, возможны ситуации, когда некоторый набор операторов должен выполняться по крайней мере один раз, а затем выполнение, основываясь на некотором условии, может повторяться. В подобном случае ваши программы могут использовать цикл do while: do { операторы; > while (условие__истинно); Если программа встречает цикл do while, она входит в цикл и запускает выполнение операторов, содержащихся в цикле. Затем программа оценивает заданное условие. Если условие истинно, программа возвращается к началу цикла: do { < 1 операторы; } while (условие_истинно); 1 Если условие ложно, программа не повторяет инструкции цикла, продолжая вместо этого выполнение с первого оператора, следующего за циклом. Обычно цикл do while используется для отображения пунктов меню и затем обработки выбора пользователя. Вам требуется, чтобы программа отобразила меню по крайней мере один раз. Если пользователь выбирает какой-либо
Повторение одного или нескольких операторов 77 пункт меню, кроме Quit, программа выполнит пункт, а затем отобразит меню снова (повторяя оператор цикла). Если пользователь выбирает Quit, цикл завершится и программа продолжит свое выполнение с первого оператора после цикла. Повторение операторов, если условие истинно В зависимости от назначения программы, возможно, потребуется выполнить набор операторов, по крайней мере, один раз, и повторить операторы, если заданное условие истинно. В таких случаях ваши программы используют оператор C++ do while: do { оператор; } while (условие); Когда программа встречает оператор do while, она сразу же выполняет операторы, содержащиеся в цикле. Затем программа исследует условие цикла. Если условие истинно, программа повторяет операторы цикла и процесс продолжается. Если условие цикла становится ложным, программа продолжает свое выполнение с первого оператора, следующего за оператором do while. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Итеративная обработка представляет собой способность программы повторять один или несколько операторов. В этом уроке были описаны итеративные (или циклические) операторы C++. Как вы уже знаете, оператор for позволяет вашим программам повторять один или несколько операторов заданное число раз. Используя оператор while, программы повторяют операторы до тех пор, пока указанное условие истинно. Наконец, с помощью оператора do while программы выполняют операторы, по крайней мере один раз, повторяя их, если заданное условие истинно. Из урока 9 вы узнаете, как разделить программы на небольшие легко управляемые части, называемые функциями. Однако до изучения урока 9 убедитесь, что освоили следующее: 0 Оператор C++for позволяет вашим программам повторять один или более операторов заданное число раз. 0 Оператор for состоит из четырех частей: инициализации, проверяемого условия, операторов, которые повторяются, и приращения. 0 Оператор for не обязывает вас увеличивать управляющую переменную цикла именно на 1 или использовать именно приращение. 0 Цикл C++ while позволяет вашим программам повторять операторы, пока указанное условие истинно.
78 Урок 8 0 Программы часто используют цикл while для чтения содержимого файла, пока не встретится конец файла. 0 Оператор C++ do while позволяет вашим программам выполнять один или несколько операторов, по крайней мере один раз, и возможно, повторять их на основании заданного условия. 0 Программы часто используют операторы do while для работы с меню. 0 Если проверяемые условия в циклах for, while или do while становятся ложью, программа продолжает свое выполнение с первого оператора, следующего за циклом.
Создание программ с помощью функций По мере усложнения ваших программ вы будете делить их на небольшие легко управляемые части, называемые функциями. Каждая функция в вашей программе будет выполнять определенную задачу. Предположим, что вы создаете бухгалтерскую программу, в которой вы могли бы создать одну функцию для обработки дебиторской задолженности, одну для расчета платежей и т. д. Фокусируясь на одной функции в каждый момент времени, ваши программы становятся легче для создания и понимания. Более того, вы обнаружите, что многие функции, созданные для одной программы, могут быть использованы для другой, что сохраняет вам время, затрачиваемое на программирование. Уроки, представленные в этом разделе: Урок 9. Знакомство с функциями. Урок 10. Изменение значений параметров. Урок 11. Преимущества использования библиотеки этапа выполнения. Урок12. Локальные переменные и область видимости. Урок 13. Перегрузка функций. Урок 14. Использование ссылок в C++. Урок 15. Значения параметров по умолчанию.
Урок 9 Знакомство с функциями По мере увеличения размера и сложности ваших программ вам следует разделить их на небольшие легко управляемые части, называемые функциями. Каждая функция в вашей программе должна выполнять определенную задачу. Например, если вы пишете программу платежей, можете создать одну функцию, определяющую количество часов, отработанных служащим, вторую функцию, определяющую сверхурочную оплату, третью функцию, выводящую на печать и т. д. Если программе необходимо выполнить определенную задачу, то она вызывает соответствующую функцию, обеспечивая эту функцию информацией, которая ей понадобится в процессе обработки. Из этого урока вы узнаете, как создавать и использовать функции в ваших программах на C++. К концу данного урока вы освоите следующие основные концепции: • Функции группируют связанные операторы для выполнения определенной задачи. • Ваша программа вызывает функцию, обращаясь к ее имени, за которым следуют круглые скобки, например beepQ. • После завершения обработки большинство функций возвращают значение определенного типа, например int или float, которое программа может проверить или присвоить переменной. • Ваши программы передают параметры (информацию) функциям, например имя, возраст или оклад служащего, заключая параметры в круглые скобки, которые следуют за именем функции. • C++ использует прототипы функций для определения типа возвращаемого функцией значения, а также количества и типов параметров, передаваемых функции. По мере увеличения ваших программ использование функций становится их неотъемлемой необходимостью. Однако, как вы увидите, создавать и использовать функции в C++ очень легко. СОЗДАНИЕ И ИСПОЛЬЗОВАНИЕ ВАШИХ ПЕРВЫХ ФУНКЦИЙ При создании программ вам следует спроектировать каждую функцию для выполнения определенной задачи. Если вы обнаружите, что функция выполняет несколько задач, вам следует разделить ее на две или более функций. Каждая создаваемая в программах функция должна иметь уникальное имя.
82 Урок 9 Как в случае с именами переменных, имена функций, которые вы выбираете, должны соответствовать операции, выполняемой функцией. Например, глядя на имена функций, перечисленные в табл. 9, вы получите хорошее представление о назначении каждой функции. Таблица 9. Примеры смысловых имен функций. Имя функции Назначение функции print_test_scores Печатать тестовые очки класса accounts_payable Обработать счета компании get_user_name Запрос имени пользователя print_document Напечатать указанный документ calculate_income_tax Определить подоходный налог пользователя Функция C++ по структуре подобна программе main, которую вы использовали во всех предыдущих программах. Другими словами, имя функции предваряется ее типом, а за ним следует список параметров, описание которых появляется в скобках. Вы группируете операторы функций внутри левой и правой фигурных скобок, как показано ниже: тип_возврата имя_фунхции(списох_параметров) { объявления_переменных; операторы; } Рассмотрите, например, как структура этой функции соответствует следующей программе main: JTT тип имя(списох_параметров) void main(void) { { объявления_переменных; | 1 int count; операторы; |- for (count = 0; count < 10; count++) cout << count << ' '; } } Следующие операторы определяют функцию с именем showmessage, которая выводит сообщение на экран, используя cout. void show_message(void) { cout << "Привет, учусь программировать на C++" << endl; } Как вы, возможно, помните из урока 2, слово void, предшествующее имени функции, указывает функции не возвращать значение. Подобно этому, слово void, содержащееся внутри круглых скобок, указывает (компилятору C++ и программистам, читающим ваш код), что функция не использует параметры (инфор-
Знакомство с функциями 83 мацию, которую программа передает функции). Следующая программа SHOWMSG.CPP использует функцию showjnessage для вывода сообщения на экран: #include <iostream.h> void show_message(void) { cout << "Привет, учусь программировать на C++" << endl; } void main(void) { cout << "Перед вызовом функции" << endl; show_message(); cout << "Вернулись из функции" << endl; } Вы уже знаете, что выполнение программы всегда начинается внутри main. Внутри main следующий оператор (вызов функции) вызывает функцию showmessage: show_message(); Круглые скобки после имени функции сообщают компилятору C++, что ваша программа использует функцию. Позже вы узнаете, что внутри этих скобок программа может передавать в функции информацию (параметры). Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> SHOW_MSG <ENTER> Перед вызовом функции Привет, учусь программировать на C++ Вернулись из функции Если программа встречает вызов функции, она начинает выполнять операторы, находящиеся внутри функции. После того как программа выполнит все операторы, которые содержит функция (другими словами, функция завершится), выполнение программы продолжается с оператора, следующего непосредственно за вызовом функции: #include <iostream.h> Г* void show_message (void) { cout << "Привет, учусь программировать на C++" << endl; } г void main(void) { cout << "Перед вызовом функции" << endl; I show_message(); cout << "Вернулись из функции" << endl;x }
84 Урок 9 В данном случае программа выполняет первый оператор в main, который выводит сообщение для пользователя, что программа собирается вызвать функцию. Далее программа встречает вызов функции и запускает выполнение операторов в showjmesssage. После того как программа выполнит единственный оператор функции, она возвращается обратно в main и продолжает свое выполнение с оператора, непосредственно следующего за вызовом функции. В этом случае программа выводит сообщение, извещая пользователя о том, что она возвратилась из функции, и после этого заканчивает свою работу. Следующая программа TWO_MSGS.CPP использует две функции — showjitle и showjessons для вывода информации об этой книге: #include <iostream.h> void show_titie(void) { cout << "Книга: Учимся программировать на C++" << endl; ) void show_lesson(void) { cout << "Урок: Знакомство с функциями" << endl; } void main(void) { show_title() ; show_lesson(); } Когда программа начинает выполнение, она сначала вызывает функцию showjtitle, которая выводит сообщение с помощью cout. После завершения showjtitle программа вызывает функцию show_lesson, также выводящую сообщение. После завершения show_lesson программа завершается, поскольку в main больше нет операторов. Функции, представленные в этом уроке, выполняли очень простые задачи. В каждом случае ваша программа могла бы легко выполнить ту же самую обработку без использования функции просто включением тех же операторов в main. Однако назначение функций заключалось в том, чтобы показать вам, как программа определяет и затем вызывает функцию. По мере усложнения ваших программ вы будете использовать функции, чтобы упростить большие задачи, разбивая программу на небольшие легко управляемые части. При создании функций вы обнаружите, что, поскольку они содержат меньше строк кода, чем одна большая программа, их легче понять и изменить. В дополнение к этому в большинстве случаев функцию, созданную для одной программы, вы можете использовать без изменений в другой программе. Создавая библиотеку функций, вы тем самым снижаете количество времени, израсходованного на кодирование и тестирование подобных функций в будущем.
Знакомство с функциями 85 Вызов функции Функция представляет собой набор связанных операторов, которые выполняют определенную задачу. Создавая функции внутри программы, вы можете делить большие задачи на небольшие легко управляемые части. Ваши программы выполняют операторы функций посредством вызова функции. Для вызова функции программы просто обращаются к имени функции, за которым следуют круглые скобки, как показано ниже: function_name(); Если программа передает информацию (параметры) в функцию, она размещает эту информацию внутри круглых скобок, разделяя ее запятыми: payroll(employee_name, employee_id/ salary); После того как последний оператор функции завершен, выполнение программы продолжается с первого оператора, следующего за вызовом функции. ПРОГРАММА МОЖЕТ ПЕРЕДАВАТЬ ИНФОРМАЦИЮ В ФУНКЦИИ Для увеличения потенциальных возможностей ваших функций C++ позволяет программам передавать информацию (параметры) в функции. Если функция использует параметры, вы должны сообщить C++ тип каждого параметра, например int, float, char и т.д. Следующая функция show_number использует параметр типа int void show_number(int value) { cout << "Значение параметра равно " << value << endl; } Если ваша программа вызывает функцию show_number, она должна передать ей значение, как показано ниже: show_number A001); ^^~ Значение, передаваемое в функцию C++ будет подставлять переданное число вместо каждого имени параметра value внутри функции: show_numberA001) void show_number(int value) { cout << "Значение параметра равно " << value << endl; }
86 Урок 9 void show_numberA001) { cout << "Значение параметра равно " << 1001 << endl; > Как видите, поскольку C++ замещает значение параметра, функция show_number выводит число 1001, переданное ей главной программой. Следующая программа USEPARAM.CPP использует функцию showjtum- ber несколько раз, каждый раз передавая разные числа: #include <iostream.h> void show_number(int value) { cout << "Значение параметра равно " << value << endl; > void main (void) { show__number A); show_numberA001); show_number(-532); } Если вы откомпилируете и запустите эту программу, на вашем экране будет отображено следующее: С:\> USEPARAM <ENTER> Значение параметра равно 1 Значение параметра равно 1001 Значение параметра равно -532 Как видите, каждый раз, когда программа вызывает функцию, C++ присваивает передаваемое число переменной value. Найдите время для эксперимента с этой программой, изменяя значения, которые main передает в функцию, и обращая внимание на результат. Каждый параметр функции имеет определенный тип. В случае функции show_number параметр value должен быть типа int. Если вы попытаетесь передать в функцию значение другого типа, например с плавающей точкой, компилятор будет сообщать об ошибке. В большинстве случаев ваши программы будут передавать несколько значений в функцию. Для каждого передаваемого параметра функция должна указать имя и тип. Например, следующая программа BIGSMALL.CPP использует функцию show_big_and_little для вывода самого большого и самого маленького из трех полученных целочисленных значений: #include <lostream.h> void show_big_and_little(int a, int b, int c) {
Знакомство с функциями 87 int small = a; int big = а; if (b > big) big = b; if (b < small) small = b; if (c > big) big = c; if (c < small) small = c; cout << "Самое большое значение равно " << big << endl; cout << "Самое маленькое значение равно " << small << endl; } void main(void) { show_big_and_little(l, 2, 3); show_big_and_littleE00/ 0, -500); show_big_and_littleA001, 1001, 1001); } Когда программа вызывает функцию, C++ назначает параметры, как по казано ниже: show_big_and_li 111 е A, 2, 3); void show_big_and_little(int a, int b, int c); Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> BIGSMALL <ENTER> Самое большое значение равно 3 Самое маленькое значение равно 1 Самое большое значение равно 500 Самое маленькое значение равно -500 Самое большое значение равно 1001 Самое маленькое значение равно 1001 Наконец, следующая программа SHOW_EMP.CPP использует функцию show_employee для вывода возраста (тип int) и оклада (тип float) служащего: #include <iostream.h> void show_employee(int age, float salary) { cout << "Возраст служащего " << age << " года (лет)" << endl;
88 Урок 9 cout << "Служащий получает $" << salary << endl; } void main (void) { show_employeeC2, 25000.00); } Как видите, функция showemployee определяет параметры типа int yl float. Передача параметров в функцию Если ваша функция использует параметры, она должна указать уникальное имя и тип для каждого параметра. Когда программа вызывает функцию, C++ присваивает значения параметров именам параметров функции слева направо. Каждый параметр функции имеет определенный тип, например int, float или char. Значения, которые ваша программа передает в функцию, используя параметры, должны соответствовать типу параметров. ФУНКЦИИ МОГУТ ВОЗВРАЩАТЬ РЕЗУЛЬТАТ ВЫЗВАВШЕЙ ФУНКЦИИ Функция должна выполнять определенную задачу для вашей программы. В большинстве случаев функции будут выполнять некоторые типы вычислений. Затем функция возвратит свой результат вызвавшей функции. Когда функция возвращает значение, вы должны сообщить C++ тип значения, например int, float, char и т. д. Чтобы информировать C++ о типе возвращаемого функцией значения, просто поставьте перед именем функции соответствующий тип. Например, следующая функция add_values складывает два своих целочисленных параметра и возвращает результат типа int вызвавшей программе: int add_values(int a, int b) { int result; result = a + b; return(result); } В данном случае слово int, появляющееся перед именем функции, указывает тип возвращаемого значения функции. Функции используют оператор return для возврата значения вызвавшей функции. Когда ваша программа встречает оператор return, она возвращает заданное значение и завершает выполнение функции, возвращая управление вызвавшей программе. В про-
Знакомство с функциями 89 грамме вы можете использовать возвращаемое значение, как показано ниже: result = add_values(l , 2); В данном случае программа присваивает возвращаемое функцией значение переменной result. Ваша программа может также сразу же напечатать возвращаемое функцией значение с помощью cout, как показано ниже: cout << "Сумма значений равна " << add_valuesE00, 501) << endl; Предыдущая реализация функции add_values использовала три оператора, чтобы было легче понять смысл функции. Однако вы можете сократить функцию до единственного оператора return, как показано ниже: int add_values(int a, int b) { return(a+b); } Следующая программа ADDVALUE.CPP использует функцию add_values для сложения нескольких значений: #include <iostream.h> int add_values(int a, int b) { return(a+b); } void main(void) { cout << 00 + 200 = " << add_valuesA00, 200) << endl; cout << 00 + 501 = " << add_valuesE00, 501) << endl; cout << "-1 + 1 = " << add_values(-l/ 1) << endl; } Выберите время для эксперимента с этой программой, изменяя значения, которые программа передает в функцию. Вы могли бы попытаться передать в функцию большие значения, например 20000 и 30000. Как и можно предположить, выполнение функции, возвращающей значение типа int, приведет к ошибке переполнения. Не все функции возвращают значение типа int. Следующая функция average_value возвращает среднее двух целочисленных значений, которое может быть дробным, например 3.5: float average_value(int a, int b) { return((a + b) / 2.0); } В этом случае слово float, которое предшествует имени функции, указывает тип возвращаемого функцией значения.
90 Урок 9 ФУНКЦИИ, КОТОРЫЕ НЕ ВОЗВРАЩАЮТ ЗНАЧЕНИЕ Если функция не возвращает значение, вам необходимо предварить имя функции типом void. В противном случае вы должны предварять имя функции типом возвращаемого функцией значения, например, int, float, char и т. д. Чтобы возвратить значение вызвавшей функции, функция использует оператор return. Когда ваша программа встречает оператор return, выполнение функции завершается и указанное значение возвращается вызвавшей функции. Возможны ситуации, когда вы встретите оператор return в функции, которая не возвращает значение: return ; В этом случае функция имеет тип void (не возвращает значение) и оператор return просто завершает выполнение функции. Замечание: Если операторы появляются в функции после оператора return они не будут выполняться. Как уже обсуждалось выше, если ваша программа встречает оператор return в функции, то возвращается соответствующее значение, функция заканчивается и выполнение программы продолжается с первого оператора, следующего за вызовом функции. ИСПОЛЬЗОВАНИЕ ВОЗВРАЩАЕМОГО ФУНКЦИЕЙ ЗНАЧЕНИЯ Когда функция возвращает значение, вызвавшая программа может присвоить возвращенное значение переменной, используя оператор присваивания, как показано ниже: payroll_amount = payroll(employee, hours, salary); В дополнение к этому вызвавшая программа просто может обращаться к функции. Например, следующий оператор выводит возвращаемое функцией значение, используя cout. cout << "Служащий получил" << payroll(employee, hours, salary) << endl; Вызвавшая функция может также использовать возвращаемое значение в условии, как показано ниже: if (payroll(employee, hours, salary) < 500.00) cout << "Этот служащий нуждается в повышении" << endl; Как видите, программа может использовать возвращаемое функцией значение различными способами.
Знакомство с функциями 91 ПРЕДСТАВЛЕНИЕ О ПРОТОТИПАХ ФУНКЦИЙ Прежде чем ваша программа сможет вызвать функцию, C++ должен знать тип возвращаемого значения, а также количество и тип параметров, используемых функцией. В каждой из программ, представленных в этом уроке, определение функции, вызываемой программой, всегда предшествует вызову функции в исходном файле. Однако в большинстве случаев функции появляются в вашем исходном файле и, как правило, одна функция вызывает другую. Чтобы гарантировать, что C++ знает особенности каждой функции, используемой в программе, вы можете поместить прототипы функций в начало исходного файла. В общем случае прототип функции обеспечивает информацию о типе возвращаемого функцией значения и ее параметрах. Следующий оператор иллюстрирует прототипы функций для нескольких функций, используемых в данном уроке: void show_message(void); void show_number(int); void show_employee(int, float); int add_values(int, int); float average_value(int, int); Как видите, прототип функции указывает тип возвращаемого значения, а также количество и тип каждого параметра. Обратите внимание на точку с запятой в конце каждого прототипа. I Тип возвращаемого значения float average_value (int, int); ^ Типы параметров Если ваша программа вызывает функцию, для которой компилятор C++ не нашел определения или прототипа, компилятор сообщает о синтаксической ошибке. При исследовании заголовочных файлов C++ или других программ вы будете постоянно сталкиваться с прототипами функций. Следующая программа PROTO.CPP иллюстрирует использование прототипа функции: #include <iostream.h> float average_value(int, int); // Прототип функции void main(void) { cout << "Среднее значение 2000 и 2 равно " << average_valueB000/ 2) << endl; }
92 Урок 9 float average_value(int a, int b) { return ((a + b) / 2.0); } В этом случае программа вызывает функцию average_value до того, как функция определена. Таким образом, программа использует прототип функции, который предваряет определение main. Если вы удалите прототип функции и откомпилируете эту программу, компилятор C++ будет сообщать о синтаксических ошибках. Использование прототипов функций Прототип функции сообщает компилятору C++ тип возвращаемого значения, а также количество и тип параметров функции. Когда вы компилируете вашу программу, компилятор C++ использует прототип каждой функции, чтобы убедиться, что вы не перепутали тип возвращаемого функцией значения (например, не присваиваете возвращаемое значение типа float переменной типа int) и что вы не передаете в качестве параметра значение неверного типа. Раньше многие компиляторы C++ не выполняли подобную проверку. В результате программисты часто тратили часы, пытаясь найти ошибки, возникающие из-за того, что вместо ожидаемого значения типа float в функцию передавалось значение типа int. Если вы встречаете синтаксическую ошибку, которая возникает из-за противоречия с прототипом функции, будьте благодарны. В прошлом компилятор не определял подобную ошибку, и ваша программа просто не работала. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как использовать функции в программах на C++. Данный урок охватил целый ряд основных понятий, таких как параметры, типы возвращаемых значений и прототипы функций. Сейчас вы можете потратить несколько больше времени на эксперименты с простыми программами. Из урока 10 вы узнаете, как изменять значения параметров внутри функций. Однако до изучения урока 10 убедитесь, что вы освоили следующие основные концепции: 13 По мере усложнения ваших программ вам следует делить их на небольшие легко управляемые части, называемые функциями. Каждая функция должна иметь уникальное имя. Присваивайте вашим функциям имена, выражающие смысл задач, которые выполняют функции. 0 Функции могут возвращать значение вызвавшей функции. При этом вы должны указать тип возвращаемого функцией значения (int, char и т. д.) до имени функции, в противном случае вы должны предварять имя функции словом void.
Знакомство с функциями 93 0 Программы передают информацию в функции с помощью параметров. Если функция получает параметры, вы должны указать уникальное имя и тип каждого параметра. Если функция не получает параметры, вы должны поместить ключевое слово void внутри круглых скобок, следующих за именем функции. 13 C++ должен знать тип возвращаемого значения и количество и тип параметров, которые получает функция. Если определение функции следует за использованием функции, вы должны поместить прототип функции в начале исходного файла.
Урок 10 Изменение значений параметров Из урока 9 вы узнали, как разделить ваши программы на небольшие легко управляемые части, называемые функциями. Как вы уже знаете, программы могут передавать информацию (параметры) функциям. Представленные в уроке 9 программы использовали или выводили значения параметров, но не меняли их. Из этого урока вы узнаете, как изменить значение параметра в функции. Вы обнаружите, что для изменения параметров в функции фактически требуется больше шагов, чем можно предположить. Однако этот урок обучит вас всем шагам, которые необходимо знать. К концу данного урока вы освоите следующие основные концепции: • Если функция не использует указатели или ссылки, она не может изменить значение параметра. • Для изменения значения параметра функция должна знать адрес параметра в памяти. • Оператор адреса C++ (&) позволяет вашей программе определить адрес переменной в памяти. • Когда ваша программа узнает адрес памяти, она сможет использовать операцию разыменования C++ (*) для определения значения, хранимого по данному адресу. • Если программе нужно изменить значение параметров функции, программа передает в функцию адрес параметра. Изменение значения параметра функции представляет собой обычную операцию. Экспериментируйте с программами, представленными в этом уроке, чтобы убедиться, что вы полностью освоили этот процесс. ПОЧЕМУ ФУНКЦИИ ОБЫЧНО НЕ МОГУТ ИЗМЕНИТЬ ЗНАЧЕНИЯ ПАРАМЕТРОВ Следующая программа NOCHANGE.CPP передает два параметра с именами big и small в функцию display values. Функция display_values, в свою очередь, присваивает обоим параметрам число 1001 и затем выводит значение каждого параметра. Когда функция завершается, программа возобновляется и выводит значения этих же параметров:
Изменение значений параметров 95 #include <iostream.h> void display_values(int a, int b) { a = 1001; b = 1001; cout << "Значения в функции display_values равны " << a << " и " << b << endl; } void main(void) { int big = 2002, small = 0; cout << "Значения до функции " << big << " и " << small << endl; display_values(big/ small); cout << "Значения после функции " << big << " и " << small << endl; } Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод: С:\> NOCHANGE <ENTER> Значения до функции 2002 и 0 Значения в функции display_values равны 1001 и 1001 Значения после функции 2002 и 0 Как видите, значения параметров в функции display^values были изменены A001). Однако после завершения функции значения переменных big и small в main остались прежними. Чтобы понять, почему изменение параметров не повлияло на переменные big и small в main, вам необходимо понять, как C++ передает параметры в функции. Когда ваши программы передают параметр в функцию, то по умолчанию C++ делает копию значения параметра и помещает эту копию во временный участок памяти, называемый стеком. Затем функция использует копию значения для выполнения своих операций. Когда функция завершается, C++ сбрасывает содержимое стека и все изменения, сделанные функцией в копиях значений параметра. Как вы знаете, переменная представляет собой имя, присваиваемое вашей программой ячейке памяти, которая хранит значение определенного типа. Предположим, например, что переменные big и small находятся в ячейках памяти 10 и 12. Если вы передадите переменные в функцию display_values, C++ поместит копии значений этих переменных в стек. На рис. 10.1 показано, что далее функция displayjy alms будет использовать копии значений переменных.
96 Урок 10 Рис. 10.1. C++ размещает копии значений параметров во временном участке памяти, называемом стеком. Как видите, функция display_values может обращаться к содержимому стека, в котором находятся копии значений 2002 и 0. Так как функция display ^values ничего не знает о ячейках памяти big и small (адреса 10 и 12), функция не может изменить реальные значения переменных. Почему функции C++ обычно не могут изменить значения параметров Когда вы передаете параметры в функцию, C++ размещает копии значений параметров во временном участке памяти, называемом стеком. Любые изменения, выполняемые функцией над параметрами, проявляются только внутри стека. Когда функция завершается, C++ сбрасывает содержимое стека вместе с любыми изменениями, которые функция произвела в параметрах. Поскольку функция не знает адрес памяти параметра, она не может изменить его значение. ИЗМЕНЕНИЕ ЗНАЧЕНИЯ ПАРАМЕТРА Для изменения значения параметра функция должна знать адрес памяти параметра. Чтобы сообщить функции адрес параметра, ваши программы должны использовать оператор адреса C++ (&). Следующий вызов функции иллюстрирует, как программа будет использовать оператор адреса для передачи адресов переменных big и small в функцию change_values: change_values (&big, &small); Передача параметров по адресу Внутри функции вы должны сообщить C++ , что программа будет пере-
Изменение значений параметров 97 давать параметры с помощью адреса. Для этого вы объявляете переменные-указатели, предваряя имя каждой переменной звездочкой, как показано ниже: void change_values (int *big, int * small) ^^ Указатель на тип Int Переменная-указатель представляет собой переменную, которая содержит адрес памяти. Внутри функции вы должны сообщить C++ , что функция работает с адресом параметра. Для этого вы предваряете имя параметра звездочкой, как показано ниже: *big = 1001; «small = 1001; Следующая программа CHGPARAM.CPP использует оператор адреса для передачи адресов параметров big и small в функцию change_values. Функция, в свою очередь, использует указатели участков памяти параметров. Следовательно, изменения параметров, сделанные функцией, остаются и после завершения функции: #include <iostream.h> void change_values(int *a, int *b) { *a = 1001; *b = 1001; cout << "Значения в функции display_values" << " равны " << *a << " и " << *b << endl; } void main(void) { int big = 2002, small = 0; cout << "Значения перед функцией " << big << " и " << small << endl; change_values(&big, &small); cout << "Значения после функции " << big << " и " << small << endl; } Когда вы откомпилируете и запустите эту программу, на экране появится следующий вывод: С:\> CHGPARAM <ENTER> Значения перед функцией 2002 и 0 Значения в функции display_values равны 1001 и 1001 Значения после функции 1001 и 1001
98 Урок 10 Как видите, значения, которые функция change_values присваивает параметрам, остаются и после завершения функции. Чтобы понять, почему изменения, которые функция выполнила над переменными, остались после ее завершения, необходимо вспомнить, что функция имеет доступ к ячейке памяти каждой переменной. Если вы передаете параметры по адресу, C++ помещает адрес каждой переменной в стек, как показано на рис. 10.2. Рис. 10.2. Передача параметров по адресу. Используя указатели (адреса памяти) внутри функции, change_values может обратиться к памяти по адресу каждого параметра, изменяя значения параметров, что и требуется. Изменение значений параметров в функциях Для изменения значения параметра в функции, функция должна знать адрес параметра в памяти. Следовательно, ваша программа должна передать адрес параметра с помощью оператора адреса C++ : some_function(&some_variable); Внутри функции вы должны сообщить C++ , что функция будет работать с адресом памяти {указателем). Для этого при объявлении вы предваряете имя параметра звездочкой: void some_function(int *some_variable); Далее внутри функции вы должны употреблять звездочку перед именем переменной: *some_variable = 1001; cout << *some_variable;
Изменение значений параметров 99 Во избежание ошибок C++ не позволит вашей программе передать адрес переменной в функцию, которая не ожидает указатель в качестве параметра. Кроме того, C++ обычно генерирует предупреждение компилятора, когда ваша программа пытается передать значение в функцию, которая ожидает указатель в качестве параметра. Второй пример Если ваша программа передает указатели на параметры, параметры могут быть любого типа, например int, float или char. Функция, которая использует указатели, объявляет переменные соответствующего типа, предваряя имя каждой переменной звездочкой, подтверждающей, что такая переменная является указателем. Следующая программа SWAPVALS.CPP передает адреса двух параметров типа float в функцию swap_values. Функция в свою очередь использует указатели на каждый параметр, чтобы обменять значения параметров: #include <iostream.h> void swap_values(float *a, float *b) { float temp; temp = *a; *a = *b; *b = temp; } void main(void) { float big = 10000.0; float small = 0.00001; swap_values(&big, &small); cout << "Big содержит " « big << endl; cout << "Small содержит " << small << endl; } Как видите, программа передает параметры в функцию swap_values по адресу. Внутри функции программа использует указатели на ячейки памяти параметров. Давайте более внимательно посмотрим на действия внутри функции swap values. Как видите, функция объявляет а и Ъ как указатели на значения типа float void swap__values(float *a, float *b) Однако функция объявляет переменную temp просто как float, а не как
100 Урок 10 указатель afloat float temp; Рассмотрим следующий оператор: temp as *a; Этот оператор побуждает C++ присвоить переменной temp значение, указываемое переменной а (т. е. значение переменной big, равное 10000.0). Поскольку temp имеет тут float, присваивание корректно. Переменная-указатель представляет собой переменную, которая хранит адрес. Следующий оператор объявляет temp как указатель на ячейку памяти, содержащую значение типа float float *temp; В данном случае temp может хранить адрес значения с плавающей точкой, но не само значение. Если вы удалите оператор разыменования (*), стоящий перед переменной а внутри присваивания, то оператор будет пытаться присвоить значение, хранимое в а (которое является адресом), переменной temp. Поскольку temp может содержать значение с плавающей точкой, но не адрес значения с плавающей точкой, возникнет ошибка. Не беспокойтесь, если вы не можете свободно обращаться с указателями, вы будете изучать их более подробно в части 3. На настоящий момент, однако, просто поймите, что, если хотите изменить в ваших функциях значения параметров, вы должны использовать указатели. Использование ассемблерных листингов для лучшего понимания работы компилятора Лучшим способом понять, как компилятор C++ трактует указатели, является исследование ассемблерного вывода компилятора. Большинство компиляторов C++ обеспечивают ключ командной строки, который вы можете использовать, чтобы указать компилятору выводить ассемблерный листинг. Читая ассемблерный листинг, вы можете лучше понять, как компилятор использует стек, когда передает параметры в функцию. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из данного урока вы узнали, как изменить значение параметра внутри функции. Для этого ваши функции должны использовать указатели. Сначала вы можете найти указатели слишком сложными. Из урока 14 вы узнаете, как использовать ссылки C++ , которые упрощают процесс изменения параметров внутри функции. Однако, поскольку многие программисты С используют указатели для изменения параметров, вам необходимо знать и такой вариант
Изменение значений параметров 101 программирования. Из урока 11 вы выясните, как функции библиотеки этапа выполнения, обеспечиваемые компилятором C++ , могут ускорить программирование, позволяя быстро разрабатывать сложные программы. Однако до изучения урока 11 убедитесь, что вы освоили следующие основные концепции: 0 Пока вы не используете указатели или ссылки C++ , функция не может изменить значение параметра. 0 Когда ваша программа передает параметр в функцию, C++ помещает копию значения параметра во временный участок памяти, называемый стеком. Любые изменения, которые функция осуществляет над параметром, влияют только на эту копию, расположенную в стеке. 0 Для изменения значения параметра функция должна знать адрес соответствующей переменной. 0 Используя оператор адреса C++ (&), ваши программы могут передать адрес переменной в функцию. 0 Когда функция получает адрес переменной, она должна объявить переменную параметра как указатель (предваряя имя переменной звездочкой). 0 Если функции требуется использовать значение, на которое ссылаются (указывают) по указателю, функция должна предварять имя переменной-указателя звездочкой, т. е. оператором разыменования C++.
Урок 11 Преимущества использования библиотеки этапа выполнения Из урока 9 вы узнали, как разделить ваши программы на небольшие легко управляемые части, называемые функциями и выполняющие определенную задачу. Одно из преимуществ использования функций заключается в том, что вы можете часто применяемую функцию, созданную для одной программы, использовать в другой программе. Как вы узнаете из этого урока, большинство компиляторов C++ обеспечивают широкий набор функций, использующихся в программах и называющихся библиотекой этапа выполнения. Применение этих функций сокращает объем программирования, который вы должны выполнить самостоятельно. Вместо этого ваша программа просто вызывает функции библиотеки этапа выполнения. В зависимости от компилятора библиотека этапа выполнения может состоять из тысяч функций. В данном уроке описывается использование таких функций в ваших программах. К тому времени, когда вы закончите этот урок, вы освоите следующие основные концепции: • Библиотека этапа выполнения представляет собой набор функций, обеспечиваемых вашим компилятором, которые вы можете легко использовать в программах. • Для использования функций библиотеки этапа выполнения вы должны включить соответствующие заголовочные файлы, содержащие прототипы функций. • Некоторые компиляторы обращаются к библиотеке этапа выполнения как к интерфейсу прикладных программ или API. Большинство библиотек этапа выполнения содержат сотни прикладных функций, которые помогут вам сохранить огромное количество времени и быстро разработать сложные программы. Вы узнаете, что очень легко использовать функции библиотеки этапа выполнения! ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ Из урока 9 вы узнали, что до того, как ваши программы смогут вызвать функцию, компилятор C++ должен узнать определение или прототип функции. Поскольку функции библиотеки этапа выполнения не определены в вашей
Преимущества использования библиотеки этапа выполнения 103 программе, вы должны указать прототип для каждой библиотечной функции, которую намерены использовать. Для упрощения использования библиотечных функций компилятор C++ предоставляет заголовочные файлы, содержащие корректные прототипы. Таким образом, вашим программам необходимо просто включить требуемый заголовочный файл с помощью оператора ^include, а затем вызвать необходимую функцию. Например, следующая программа SHOWTIME.CPP будет использовать функции библиотеки этапа выполнения time и ctime для вывода текущей системной даты и времени. Прототипы этих двух функций библиотеки этапа выполнения содержатся в заголовочном файле time.fi: #include <iostream.h> #include <time.h> // Для функций библиотеки этапа выполнения void main(void) { time_t system_time; system_time = time (NULL); cout << "Текущее системное время " << ctime(&system_time) << endl; } Когда вы откомпилируете и запустите эту программу, на вашем экране появятся текущие системные дата и время: С:\> SHOWTIME <ENTER> Текущее системное время Mon Jan 01 16:13:51 1996 Как видите, программа использует функции time и ctime. В случае функции ctime программа передает адрес переменной systemjtime, используя оператор адреса, описанный в уроке 10. Для использования этих функций вам просто следует включить заголовочный файл time.h в начало вашего исходного файла. Подобным образом следующая программа SQRT.CPP использует функцию sqrt для возврата квадратного корня нескольких значений. Прототип функции sqrt находится в заголовочном файле math.h: #include <iostream.h> #include <math.h> // Содержит прототип sqrt void main(void) { cout << "Квадратный корень 100.0 равен " << sqrtA00.0) << endl; cout << "Квадратный корень 10.0 равен " << sqrtA0.0) << endl; cout << "Квадратный корень 5.0 равен " << sqrtE.0) << endl; }
104 Урок 11 Наконец, программа SYSCALL.CPP использует функцию system, прототип которой определяется в заголовочном файле stdlib.h. Функция system обеспечивает легкий способ выполнения вашей программой команды операционной системы, такой как "DIR", или другой программы: #include <stdlib.h> void main(void) { system("DIR"); } В этом случае программа использует функцию system для вызова команды MS-DOS DIR. Выберите время для эксперимента с этой программой, запуская другие команды или даже одну из программ, созданных вами ранее при изучении этой книги. ИЗУЧЕНИЕ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ Ваш компилятор C++ обеспечивает сотни функций библиотеки этапа выполнения. Документация, поставляемая с вашим компилятором, должна содержать полное описание всех функций библиотеки этапа выполнения. Если вы просмотрите эту документацию, то найдете, что функции обычно используют простые прототипы. Например, для функции sqrt вы могли бы найти следующий прототип: double sqrt(double); В данном случае прототип функции сообщает вам, что функция возвращает значение типа double и ожидает параметр тоже типа double. Аналогично этому можно найти следующий прототип для функции time: t ime_t t ime(t ime_t *); И опять прототип сообщает вам, что функция возвращает значение типа timejt (этот тип определен в заголовочном файле time.h). Функция ожидает, что ее параметр должен быть указателем на переменную типа timej. По мере чтения документации о функциях библиотеки этапа выполнения вы очень много узнаете о самих функциях и о C++ , при этом обращайте внимание на прототипы функций. Другой путь изучения библиотечных функций вашего компилятора состоит э просмотре заголовочных файлов, содержащихся в подкаталоге INCLUDE. Выделите время, например, для того, чтобы сейчас распечатать заголовочные файлы math.h, time.h и stdlib.h, которые вы использовали в программах этого урока.
Преимущества использования библиотеки этапа выполнения 105 Использование функций API В дополнение к стандартной библиотеке этапа выполнения многие компиляторы обеспеуивают функции API или интерфейс прикладных программ. Например, если вы программируете в среде Windows, то вам потребуются функции графического API, телефонного API (TAPI), API для мультимедиа и т. д. Прежде чем создавать свои собственные функции, убедитесь, что вы не нашли таких функций в API, предоставляемом вашим компилятором. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Библиотека этапа выполнения C++ предоставляет мощный набор функций, которые вы можете использовать в ваших программах. Не жалейте времени, чтобы изучить документацию по библиотеке этапа выполнения, поставляемой с вашим компилятором. Выясните смысл функций, имеющихся в библиотеке этапа выполнения. Преимущество этих функций состоит в том, что вы избавляетесь от значительного объема программирования. В уроке 12 вы научитесь обращаться с локальными переменными и областью видимости (участок вашей программы, где известно имя переменной). Прежде чем перейти •к уроку 12, убедитесь, что вы освоили следующие основные концепции: 0 Библиотека этапа выполнения представляет собой набор функций, обеспечиваемых компилятором для ваших программ. 0 Для использования функции из библиотеки этапа выполнения вы должны указать ее прототип. 0 Большинство компиляторов C++ предоставляют заголовочные файлы, которые содержат корректные прототипы для каждой библиотечной функции. 0 В дополнение к библиотеке этапа выполнения многие компиляторы C++ предоставляют функции API (интерфейс прикладных программ) для выполнения определенных задач, например программирования графики или мультимедиа.
Урок 12 Локальные переменные и область видимости Как вы уже знаете, функции позволяют разделить программу на небольшие легко управляемые части. Все функции, используемые вами до настоящего момента, были совершенно просты. Как только вашим функциям потребуется выполнить более сложную работу, они должны будут использовать переменные для реализации своих задач. Переменные, объявляемые внутри функции, называются локальными переменными. Их значения и даже сам факт, что локальные переменные существуют, известны только данной функции. Другими словами, если вы объявляете локальную переменную с именем salary в функции payroll, то другие функции не имеют доступа к значению переменной salary. Фактически, другие функции не имеют никакого представления о том, что переменная salary существует. Этот урок рассматривает область видимости переменной, или участок внутри вашей программы, в котором переменная известна. К концу этого урока вы освоите следующие основ* ные концепции: • Вы объявляете локальные переменные внутри функции точно так же, как и внутри main: указывая тип и имя переменной. • Имена переменных, используемых внутри функций, должны быть уникальными только по отношению к данной функции. • Область видимости переменной определяет участок программы, где переменная известна и доступна. • Глобальные переменные в отличие от локальных переменных известны на протяжении всей программы и доступны внутри всех функций. • Оператор глобальной области видимости C++ (::) позволяет вам управлять областью видимости переменной. Объявление локальных переменных внутри функции достаточно просто. Фактически вы уже это делали каждый раз, когда объявляли переменные внутри main. ОБЪЯВЛЕНИЕ ЛОКАЛЬНЫХ ПЕРЕМЕННЫХ Локальная переменная представляет собой переменную, определенную внутри функции. Такая переменная называется локальной, потому что ее известность ограничена данной функцией. Вы объявляете локальные переменные
Локальные переменные и область видимости 107 в начале функции после открывающей фигурной скобки: void some_function(void) { int count; float result; } Следующая программа USEBEEPS.CPP использует функцию sound speaker, которая заставляет играть встроенный компьютерный динамик столько раз, сколько указано параметром beeps. Внутри функции sound speaker локальная переменная counter хранит количество звуков, издаваемых динамиком: #include <iostream.h> void sound_beeps(int beeps) { for (int counter = 1; counter <= beeps; counter++) cout << '\a'; } void main(void) { sound_beepsB); sound_beepsC); } Как видите, функция soundJbeeps объявляет переменную counter сразу же после открывающей фигурной скобки. Поскольку counter определяется внутри функции soundjbeeps, эта переменная является локальной по отношению к soundjbeeps, и это означает, что только soundjbeeps знает об этой переменной и может к ней обращаться. О конфликте имен При объявлении локальных переменных внутри функции очень вероятно, что имя локальной переменной, объявляемой вами в одной функции, будет таким же, как и имя переменной, используемой в другой функции. Как уже упоминалось, локальные переменные известны только в текущей функции. Таким образом, если две функции используют одно и то же имя для своих локальных переменных, это не приводит к конфликту. C++ трактует имя каждой переменной как локальное по отношению к соответствующей функции. Следующая программа LCLNAME.CPP использует функцию add_values для сложения двух целочисленных значений. Эта функция присваивает свой результат локальной переменной value. Однако в main один из параметров, передаваемых в функцию, также носит имя value. Тем не менее, поскольку C++ трактует обе переменные как локальные для соответствующих функций, их имена не конфликтуют: #include <iostream.h>
108 Урок 12 int add_values(int a, int b) { int value; value = a + b; return(value); } void main (void) { int value = 1001; int other_value = 2002; cout << value << " + " << other_yalue << " = " add_values(value, other_value) << endl; О локальных переменных Локальные переменные представляют собой переменные, объявляемые внутри функции. Имя и значение локальной переменной известны только функции, внутри которой переменная объявлена. Вы должны объявлять локальные переменные в начале вашей функции сразу же после первой открывающей фигурной скобки. Имена, назначаемые локальным переменным, должны быть уникальными только для функции, внутри которой эти переменные определены. При объявлении локальной переменной внутри функции ее можно инициализировать с помощью оператора присваивания. ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ Как вы уже знаете, локальная переменная объявляется и известна только в определенной функции. В дополнение к локальным переменным C++ позволяет вашим программам объявлять глобальные переменные, которые известны на протяжении всей программы (глобально для всех функций). Чтобы объявить глобальную переменную, следует просто поместить объявление переменной в начало вашей программы вне какой-либо функции: int some_global_variable; | Объявление глобальной переменной void main (void) { // Здесь должны быть операторы программы > Следующая программа GLOBAL. CPP использует глобальную переменную с именем number. Каждая функция в программе может использовать (или из-
Локальные переменные и область видимости 109 менять) значение глобальной переменной. В данном случае каждая функция выводит текущее значение этой переменной, а затем увеличивает это значение на единицу: #include <iostream.h> int number = 1001; void first_change(void) { cout << "значение number в first_change " << number << endl; number++; } void second_change(void) { cout << "значение number в second_change " << number << endl; number++; } void main(void) { cout << "значение number в main " << number << endl; number++; first_change(); second_change(); } Как правило, следует избегать использования в ваших программах глобальных переменных. Поскольку любая функция может изменить значение глобальной переменной, сложно отследить все функции, которые потенциально могли бы изменить данную переменную. Вместо этого вашим программам следует объявлять переменную внутри main и затем передавать ее (как параметр) в функции, которым она нужна. (Помните, что в данной ситуации в стек помещается временная копия этой переменной; оригинал не изменяется.) Если имена глобальных и локальных переменных конфликтуют По мере возможности следует избегать использования в программах глобальных переменных. Однако если ваша программа должна использовать глобальную переменную, то может случиться, что имя глобальной переменной конфликтует с именем локальной переменной. При возникновении такого конфликта C++ предоставляет приоритет локальной переменной. Другими словами, программа предполагает, что в случае конфликта каждая ссылка на имя соответствует локальной переменной.
по Урок 12 Однако могут быть ситуации, когда вам необходимо обратиться к глобальной переменной, чье имя конфликтует с именем локальной переменной. В таких случаях ваши программы могут использовать глобальный оператор разрешения С++(::), если вы хотите использовать глобальную переменную. Например, предположим, что у вас есть глобальная и локальная переменные с именем number. Если ваша функция хочет использовать локальную переменную number, она просто обращается к этой переменной, как показано ниже: number = 1001; // Обращение к локальной переменной С другой стороны, если ваша функция хочет обратиться к глобальной переменной, программа использует глобальный оператор разрешения, как показано ниже: ::number = 2002; // Обращение к глобальной переменной Следующая программа GLOBLOCA.CPP использует глобальную переменную number. В дополнение к этому функция show_numbers использует локальную переменную с именем number. Эта функция использует оператор глобального разрешения для обращения к глобальной переменной: #include <iostream.h> int number = 1001; // Глобальная переменная void show_numbers(int number) { cout << "Локальная переменная number" << " содержит " « number << endl; cout << "Глобальная переменная number" << " содержит " « : :number << endl; } void main(void) { int some_value = 2002; show_numbers(some_value); } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> GLOBLOCA <ENTER> Локальная переменная number содержит 2002 Глобальная переменная number содержит 1001 Как видите, ваши программы могут выбрать глобальную или локальную переменную с помощью глобального оператора разрешения. Однако, как вы, вероятно, заметили, использование глобальных и локальных переменных
Локальные переменные и область видимости 111 может вызвать путаницу, которая в свою очередь может привести к ошибкам. Поэтому по мере возможности избегайте использования глобальных переменных. О глобальных переменных Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы. Для создания глобальной переменной вы объявляете переменную в начале вашего исходного файла вне какой-либо функции. Все функции, которые следуют за таким объявлением, могут использовать эту глобальную переменную. Однако, поскольку злоупотребление глобальными переменными может привести к ошибкам, следует избегать их использования в ваших программах везде, где это возможно. Представление об области видимости переменных При чтении книг и журнальных статей по C++ вы можете встретить термин область видимости, что определяет участок в программе, где имя переменной имеет смысл (и, следовательно, используется). Для локальной переменной область видимости ограничена функцией, внутри которой эта переменная объявлена. С другой стороны, глобальные переменные известны на протяжении всей программы. В результате глобальные переменные имеют большую область видимости. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как объявлять локальные переменные внутри функции. Как только ваши функции выполняют более или менее существенную работу, им требуются локальные переменные. В этом уроке представлены также глобальные переменные, чьи имена и значения известны на протяжении всей программы. Поскольку они могут приводить к трудно обнаруживаемым ошибкам, лучше избегать использования глобальных переменных, если только это возможно. Из урока 13 вы узнаете, как C++ позволяет объявлять две или несколько функций с одним и тем же именем, но с разными параметрами и типами возвращаемых значений. Перегружая подобным образом имена функций, вы упрощаете использование функций. Прежде чем перейти к уроку 13, убедитесь, что вы изучили следующие основные концепции: 0 Локальные переменные представляют собой переменные, объявленные внутри функции. И Локальные переменные известны только той функции, внутри которой они объявлены.
112 Урок 12 0 Несколько функций могут использовать одно и то же имя для локальной переменной без каких-либо конфликтов. 0 Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы. 0 Чтобы объявить глобальную переменную, объявите переменную в начале вашего исходного файла вне какой-либо функции. 0 Поскольку глобальные переменные могут быть легко изменены любой функцией, они привносят возможность появления трудно обнаруживаемых ошибок, поэтому избегайте использования глобальных переменных в своих программах.
Урок 13 Перегрузка функций При определении функций в своих программах вы должны указать тип возвращаемого функцией значения, а также количество параметров и тип каждого из них. В прошлом (если вы программировали на языке С), когда у вас была функция с именем add_values, которая работала с двумя целыми значениями, а вы хотели бы использовать подобную функцию для сложения трех целых значений, вам следовало создать функцию с другим именем. Например, вы могли бы использовать add_two_values и add_three_values. Аналогично, если вы хотели использовать подобную функцию для сложения значений типа float, то вам была бы необходима еще одна функция с еще одним именем. Чтобы избежать дублирования функции, C++ позволяет вам определять несколько функций с одним и тем же именем. В процессе компиляции C++ принимает во внимание количество аргументов, используемых каждой функцией, и затем вызывает именно требуемую функцию. Предоставление компилятору выбора среди нескольких функций называется перегрузкой. В этом уроке вы научитесь использовать перегруженные функции. К концу данного урока вы освоите следующие основные концепции: • Перегрузка функций позволяет вам использовать одно и то же имя для нескольких функций с разными типами параметров. • Для перегрузки функций просто определите две функции с одним и тем же именем и типом возвращаемого значения, которые отличаются количеством параметров или их типом. Перегрузка функций является особенностью языка C++, которой нет в языке С. Как вы увидите, перегрузка функций достаточно удобна и может улучшить удобочитаемость ваших программ. ПЕРВОЕ ЗНАКОМСТВО С ПЕРЕГРУЗКОЙ ФУНКЦИЙ Перегрузка функций позволяет вашим программам определять несколько функций с одним и тем же именем и типом возвращаемого значения. Например, следующая программа перегружает функцию с именем add_values. Первое определение функции складывает два значения типа int. Второе определение функции складывает три значения. В процессе компиляции C++ корректно определяет функцию, которую необходимо использовать:
114 Урок 13 #include <iostream.h> int add_values(int a, int b) { return(a + b); } int add_values(int a, int b, int c) { return (a + b + c); > void main (void) { cout << 00 + 801 s " « add_valuesB00, 801) « endl; cout << /#100 + 201 + 700 = " << add_valuesA00, 201, 700) << endl; } Как видите, программа определяет две функции с именами add_values. Первая функция складывает два значения типа int, в то время как вторая складывает три значения. Вы не обязаны что-либо предпринимать специально для того, чтобы предупредить компилятор о перегрузке, просто используйте ее. Компилятор разгадает, какую функцию следует использовать, основываясь на предлагаемых программой параметрах. Подобным образом следующая программа MSG_OVR.CPP перегружает функцию showjnessage. Первая функция с именем showjnessage выводит стандартное сообщение, параметры ей не передаются. Вторая выводит передаваемое ей сообщение, а третья выводит два сообщения: #include <iostream.h> void show_message(void) { cout << "Стандартное сообщение: " « "Учимся программировать на C++" << endl; > void show_message(char *message) { cout << message << endl; } void show_message(char *first, char *second) { cout << first << endl; cout << second << endl; }
Перегрузка функций 115 void main (void) { show_message(); show_message( "Учимся программировать на языке C++!"); show_message("B C++ нет предрассудков!", "Перегрузка — это круто!"); } КОГДА НЕОБХОДИМА ПЕРЕГРУЗКА Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем day_of_week, которая возвращает текущий день недели (О для воскресенья, 1 для понедельника,..., 6 для субботы). Ваша программа могла бы перегрузить эту функцию таким образом, чтобы она верно возвращала день недели, если ей передан юлианский день в качестве параметра, или если ей переданы день, месяц и год: int day_of_week(int j ulian_day) { // Операторы } int day_of_week(int month, int day, int year) { // Операторы } По мере изучения объектно-ориентированного программирования в C++, представленного в следующих уроках, вы будете использовать перегрузку функций для расширения возможностей своих программ. Перегрузка функций улучшает удобочитаемость программ Перегрузка функций C++ позволяет вашим программам определять несколько функций с одним и тем же именем. Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров. До появления перегрузки функций в C++ программисты языка С должны были создавать не- Перегруженные функции не обязаны возвращать значения одинакового типа по той причине, что компилятор однозначно идентифицирует функцию по ее имени и набору ее аргументов. Для компилятора функции с одинаковыми именами, но разными типами аргументов — разные функции, поэтому тип возвращаемого значения — прерогатива каждой функции. — Прим.перев.
116 Урок 13 сколько функций с почти одинаковыми именами. К сожалению, программисты, желающие использовать такие функции, должны были помнить, какая комбинация параметров соответствует какой функции. С другой стороны, перегрузка функций упрощает задачу программистов, требуя, чтобы они помнили только одно имя функции. Перегрузка функций позволяет вам указать несколько определений для одной и той же функции. В процессе компиляции C++ определит, какую функцию следует использовать, основываясь на количестве и типе передаваемых параметров. Из данного урока вы узнали, что перегружать функции достаточно просто. Из урока 14 вы узнаете, как ссылки C++ упрощают процесс изменения параметров внутри функций. Однако, прежде чем перейти к уроку 14, убедитесь, что вы изучили следующие основные концепции: Перегрузка функций предоставляет несколько "взглядов" на одну и ту же функцию внутри вашей программы. Для перегрузки функций просто определите несколько функций с одним и тем же именем и типом возвращаемого значения, которые отличаются только количеством и типом параметров. В процессе компиляции C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых параметров. Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.
Урок 14 Использование ссылок в C++ Из урока 10 вы узнали, как изменять параметры внутри функции с помощью указателей. Для использования указателей вы должны предварять имена переменных-указателей звездочкой. Использование указателей досталось в "наследство" от языка С. Чтобы упростить процесс изменения параметров, C++ вводит такое понятие как ссылка. Как вы узнаете из этого урока, ссылка представляет собой псевдоним (или второе имя), который ваши программы могут использовать для обращения к переменной. К концу данного урока вы освоите следующие осйовные концепции: • Для объявления и инициализации ссылки внутри программы объявите переменную, размещая амперсанд (&) сразу же после типа переменной, и затем используйте оператор присваивания для назначения псевдонима, например int& alias_name = variable;. • Ваши программы могут передавать ссылки в функцию в качестве параметров, а функция, в свою очередь, может изменять соответствующее значение параметра, не используя указателей. • Внутри функции вам следует объявить параметр как ссылку, размещая амперсанд (&) после типа параметра, затем можно изменять значение параметра внутри функции без помощи указателей. Как вы узнаете, использование указателей очень упрощает изменение значений параметров внутри функции. ССЫЛКА ЯВЛЯЕТСЯ ПСЕВДОНИМОМ Ссылка C++ позволяет создать псевдоним (или второе имя) для переменных в вашей программе. Для объявления ссылки внутри программы укажите знак амперсанда (&) непосредственно после типа параметра. Объявляя ссылку, вы должны сразу же присвоить ей переменную, для которой эта ссылка будет псевдонимом, как показано ниже: int& alias_name = variable; Объявление ссылки После объявления ссылки ваша программа может использовать или переменную, или ссылку:
118 Урок 14 alias_name = 1001; variable = 1001; Следующая программа SHOWJREF.CPP создает ссылку с именем aliasjtame и присваивает псевдониму переменную number. Далее программа использует как ссылку, так и переменную: #include <iostream.h> void main (void) { int number = 501; int& alias_name = number; // Создать ссылку cout << "Переменная number содержит " << number << endl; cout << "Псевдоним для number содержит " << alias_name << endl; alias_name = alias_name + 500; cout << "Переменная number содержит " << number << endl; cout << "Псевдоним для number содержит " << alias_name << endl; } Как видите, программа прибавляет 500 к ссылке aliasjtame. В итоге программа прибавляет 500 также и к соответствующей переменной number, для которой ссылка служит псевдонимом или вторым именем. Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> SHOW_REF <ENTER> Переменная number содержит 501 Псевдоним для number содержит 501 Переменная number содержит 1001 Псевдоним для number содержит 1001 В общем случае использование ссылки таким образом, как только что было показано, создает трудности для понимания. Однако вы увидите, что использование ссылок значительно упрощает процесс изменения параметров внутри функции. Объявление ссылки Ссылка C++ представляет собой псевдоним (второе имя), которое ваши программы могут использовать для обращения к переменной. Для объявления ссылки поставьте амперсанд (&) сразу же после типа переменной, а затем укажите имя ссылки, за которым следует знак равенства и имя переменной, для которой ссылка является псевдонимом: float& salary_alias = salary;
Использование ссылок в C++ 119 ИСПОЛЬЗОВАНИЕ ССЫЛОК В КАЧЕСТВЕ ПАРАМЕТРОВ Основное назначение ссылки заключается в упрощении процесса изменения параметров внутри функции. Следующая программа REFERENC.CPP присваивает ссылку с именем number_alias переменной number. Программа передает ссылку на переменную в функцию change_value, которая присваивает переменной значение 1001: #include <iostream.h> void change_value(int &alias) { alias = 1001; } void main(void) { int number; int& number_alias = number; change_value(number_alias); cout << "Переменная number содержит " « number << endl; } Как вы видите, программа передает ссылку в функцию change_value. Если вы рассмотрите объявление функции, вы обнаружите, что change_value объявляет параметр alias как ссылку на значение типа int. void change_value(int& alias) Внутри функции change_value можете изменять значение параметра без помощи указателя. В результате звездочка (*) не используется и операция внутри функции становится легче для понимания. Использование комментариев для объяснения ссылок внутри ваших программ Большинство программистов C++ знакомы с языком программирования С, и они привыкли использовать указатели внутри функции, если необходимо изменить значение параметра. В результате, если такие программисты не видят указатели внутри функций, которые используют ссылки, они могут предположить, что значения параметров не изменяются. Для предотвращения подобных промахов не забывайте размещать несколько комментариев до и внутри функций, которые изменяют параметры с помощью ссылок. В таком случае программисты С лучше поймут работу ваших функций.
120 Урок 14 Рассмотрим второй пример В уроке 10 вы использовали следующую функцию для перестановки двух значений с плавающей точкой: void swap_yalues(float *a, float *b) { float temp; temp = *a; *a = *b; *b = temp; } Как видите, функция комбинирует переменные-указатели с переменными-неуказателями. Следующая программа SWAP_REF.CPP использует ссылки на значения с плавающей точкой для упрощения функции: #include <iostream.h> void swap_values(floats a, float& b) { float temp; temp = a; a = b; b = temp; > void main(void) { float big = 10000.0; float small = 0.00001; float& big_alias = big; float& small_alias = small; swap_values(big_alias, small_alias); cout << "Big содержит " << big << endl; cout << "Small содержит " << small << endl; } Как видите, функцию swap_values сейчас легче понять, однако ваша программа имеет теперь два дополнительных имени (ссылки big_alias и small_alias)9 за которыми вы должны следить. ПРАВИЛА РАБОТЫ СО ССЫЛКАМИ Ссылка не является переменной. Один раз присвоив значение ссылке, вы уже не можете ее изменить. Кроме того в отличие от указателей вы не може-
Использование ссылок в C++ 121 те выполнить следующие операции над ссылками: • Вы не можете получить адрес ссылки, используя оператор адреса C++. • Вы не можете присвоить ссылке указатель. • Вы не можете сравнить значения ссылок, используя операторы сравнения C++. • Вы не можете выполнить арифметические операции над ссылкой, например добавить смещение. • Вы не можете изменить ссылку. По мере использования объектно-ориентированного программирования на C++ вы вернетесь к ссылкам. Использование ссылок для изменения параметров функции Из урока 10 вы узнали, что ваши программы с помощью указателей могут изменять значение параметров внутри функции. Для изменения параметра вы должны передать его адрес в функцию. Чтобы получить адрес параметра, используйте оператор адреса C++ (&). В свою очередь функция использует переменные-указатели (которые хранят адрес памяти). Для объявления переменной-указателя внутри функции предваряйте имя параметра звездочкой (*). Чтобы изменить или использовать значение параметра внутри функции, предваряйте каждое обращение к имени этого параметра оператором разыменования C++ (*). К сожалению, многие операции внутри функции комбинируют переменные-указатели и переменные-неуказатели. Ссылки C++ упрощают процесс изменения параметров функции, избавляя от операторов, которые смешивают переменные-указатели и переменные-неуказатели. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как использовать ссылки C++ для создания псевдонима или второго имени переменной. Использование ссылок может упростить функции, изменяющие значения параметров. Из урока 15 вы узнаете, что C++ позволяет вам задавать значения по умолчанию для параметров функции. При вызове функции программа может опускать значения одного или нескольких параметров и функция будет использовать значения по умолчанию. До изучения урока 15 убедитесь, что вы освоили следующие основные концепции: 0 Ссылка C++ является псевдонимом (или вторым именем) переменной.
122 Урок 14 0 Для объявления ссылки поместите знак амперсанда (&) непосредственно после типа переменной, а затем укажите имя ссылки, за которым следует знак равенства и имя переменной, для которой ссылка является псевдонимом. 0 Если вы однажды присвоили ссылке значение, вы не можете его изменить. 0 Вам следует помещать несколько комментариев до и внутри функций, которые используют ссылки для изменения значений параметра, чтобы другие программисты, читающие ваш код, сразу обратили на это внимание. 0 Чрезмерное использование ссылок может привести к слишком трудному для понимания программному коду.
Урок 15 Значения параметров по умолчанию Как вы уже знаете, C++ позволяет вам с помощью параметров передавать информацию в функции. Из урока 13 вы выяснили, что C++ также обеспечивает перегрузку функций, предусматривая определения, содержащие разное количество параметров или даже параметры разных типов. Кроме этого, в C++ при вызове функций можно опускать параметры. В таких случаях для опущенных параметров будут использоваться значения по умолчанию. Этот урок описывает, как устанавливать значения по умолчанию для параметров функций. К концу данного урока вы освоите следующие основные концепции: • C++ позволяет программам указывать для параметров значения по умолчанию. • Значения по умолчанию для параметров указываются в заголовке функции при ее определении. • Если вызов функции опускает значения одного или нескольких параметров, C++ будет использовать значения по умолчанию. • Если вызов функции опускает значение определенного параметра, то должны быть опущены и значения всех последующих параметров. Обеспечение значений по умолчанию для параметров упрощает возможность повторного использования функций (их использования несколькими программами). ОПРЕДЕЛЕНИЕ ЗНАЧЕНИЙ ПО УМОЛЧАНИЮ Обеспечить значения по умолчанию для параметров функции очень легко. Вы просто присваиваете значение параметру с помощью оператора присваивания C++ прямо при объявлении функции, как показано ниже: void some_function(int size=12, float cost=19.95) < —c= i // Операторы функции } Значения no умолчанию Следующая программа DEFAULTS.CPP присваивает значения по умолчанию параметрам а, Ьи с внутри функции show parameters. Затем программа четыре раза вызывает эту функцию, сначала не указывая параметров вообще, затем указывая значение только для а, потом значения для аи Ьи, наконец,
124 Урок 15 указывая значения для всех трех параметров: #include <iostream.h> void show_parameters(int a=l, int b=2, int c=3) { cout << "a " « a << " b " « b << " с " << с << endl; } void main (void) { show_parameters(); show__parameters A001); show_parametersA001, 2002); show_parametersA001, 2002, 3003); } Когда вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> DEFAULTS <ENTER> а 1 b 2 с 3 а 1001 Ь 2 с 3 а 1001 b 2002 с 3 а 1001 b 2002 с 3003 Как видите, если необходимо, функция использует значения параметров по умолчанию. Правила для пропуска значений параметров Если программа опускает определенный параметр для функции, обеспечивающей значения по умолчанию, то следует опустить и все последующие параметры. Другими словами, вы не можете опускать средний параметр. В случае предыдущей программы, если требовалось опустить значение параметра Ъ в showparameters', программа также должна была опустить значение параметра с. Вы не можете указать значение для а и с, опуская значение Ь. Задание значений по умолчанию Когда вы определяете функцию, C++ позволяет вам указать значения по умолчанию для одного или нескольких параметров. Если программа в дальнейших вызовах этой функции опускает один или несколько параметров, то функция будет использовать для них значения по умолчанию. Чтобы присвоить параметру значение по умолчанию, просто используйте оператор присваивания внутри определения функции. Например, следующая функция payroll указывает значения по умолчанию для параметров hours и rate:
Значения параметров по умолчанию 125 float payroll(int employ_id/ float hours = 40, float rate = 5.50) { // операторы } Когда программа опускает один параметр, она должна опускать все последующие параметры. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из данного урока вы узнали, что C++ позволяет вам указывать значения по умолчанию для параметров функции. Если программа опускает один или несколько параметров, функция использует значения по умолчанию. В следующих уроках, когда ваши программы начнут использовать объектно-ориентированное программирование на C++, вы будете применять параметры по умолчанию для инициализации различных переменных класса. Как вы уже знаете, переменная позволяет вам хранить значение определенного типа {int, float и т. д). В уроке 16 вы освоите, как хранить несколько значений одного и того же типа внутри массива. Например, ваша программа может хранить тестовые очки для 100 студентов или стоимость 50 акций. С помощью массивов хранить и использовать такие значения очень легко. До изучения урока 16 убедитесь, что вы освоили следующие основные концепции: 0 Чтобы присвоить значения по умолчанию параметрам функции, используйте оператор присваивания C++ непосредственно в определении функции. 0 Если программа опускает значения параметров при вызове функции, функция использует значения по умолчанию. 0 Если программа опускает значение одного параметра, она должна опустить значения для всех последующих параметров; программа не может опускать средний параметр. 0 Указывая параметры по умолчанию, вы тем самым облегчаете использование ваших функций, в том числе и для других программ.
Хранение информации с помощью массивов и структур Как вы уже знаете, переменная может хранить значение определенного типа. В программах на C++, которые вы создали при изучении первых двух частей этой книги, переменные хранили только одно значение. По мере увеличения сложности ваших программ в некоторых ситуациях вам потребуется работать с несколькими значениями одновременно. Например, ваша программа может работать со 100 тестовыми очками, 30 стоимостью акций или фамилиями и адресами 5000 служащих вашей компании. В этой части вы узнаете, как использовать разные типы данных C++ для хранения множества значений внутри одной и той же переменной. Как вы узнаете, использовать одну переменную для хранения множества значений очень удобно. Урок 16. Хранение значений в массивах. Урок 17. Символьные строки. Урок 18. Хранение связанной информации в структурах. Урок 19. Объединения. Урок 20. Указатели.
Урок 16 Хранение значений в массивах Как вы уже знаете, ваши программы во время выполнения хранят информацию в переменных. До сих пор каждая переменная в программе хранила только одно значение в каждый момент времени. Однако в большинстве случаев программам необходимо хранить множество значений, например 50 тестовых очков, 100 названий книг или 1000 имен файлов. Если вашим программам необходимо хранить несколько значений, они должны использовать специальную структуру данных, называемую массивом. Для объявления массива необходимо указать имя, тип массива и количество значений, которые массив будет хранить. Этот урок описывает, как программы объявляют массивы, а затем сохраняют и обращаются к информации, содержащейся в массиве. К концу данного урока вы освоите следующие основные концепции: • Массив представляет собой структуру данных, которая позволяет одной переменной хранить несколько значений. • При объявлении массива вы должны указать тип значений, хранящихся в массиве, а также количество значений (называемых элементами массива). • Все элементы внутри массива должны быть одного и того же типа, например, ш/, float или char. • Для сохранения значения внутри массива вам следует указать номер элемента массива, в котором вы хотите сохранить свое значение. • Чтобы обратиться к значению, хранящемуся внутри массива, ваши программы указывают имя массива и номер элемента. • При объявлении массива программы могут использовать оператор присваивания для инициализации элементов массива. • Программы могут передавать переменные-массивы в функции точно так же, как они передают любой другой параметр. Программы на C++ широко используют массивы. Когда в уроке 17 вы начнете работать с символьными строками (например, название книги, имя файла и т. д.), вы будете оперировать массивами символов.
130 Урок 16 ОБЪЯВЛЕНИЕ ПЕРЕМЕННОЙ МАССИВА Массив представляет собой переменную, способную хранить одно или несколько значений. Подобно переменным, используемым вашими программами до сих пор, массив должен иметь тип (например, int, char или float) и уникальное имя. В дополнение к этому вам следует указать количество значений, которые массив будет хранить. Все сохраняемые в массиве значения должны быть одного и того же типа. Другими словами, ваша программа не может поместить значения типа float, char и long в один и тот же массив. Следующее объявление создает массив с именем test_scores, который может вмещать 100 целых значений для тестовых очков: . Тип массива int test_scores[100]; у Размер массива Когда компилятор C++ встречает объявление этой переменной, он распределит достаточно памяти для хранения 100 значений типа int. Значения, хранящиеся в массиве, называются элементами массива. Массивы хранят несколько значений одного и того же типа По мере усложнения вашим программам потребуется работать с несколькими значениями одного и того же типа. Например, программы могут хранить возраст 100 служащих или стоимость 25 акций. Вместо того чтобы заставлять программу работать со 100 или с 25 переменными с уникальными именами, C++ позволяет вам определить одну переменную — массив —, которая может хранить несколько связанных значений. Для объявления массива вы должны указать тип и уникальное имя массива, а также количество элементов, которые будет содержать массив. Например, следующие операторы объявляют три разных массива: float part_cost[50]; int employee_age[100]; float stock_prices[25]; Обращение к элементам массива Как вы уже знаете, массив позволяет вашим программам хранить несколько значений в одной и той же переменной. Для обращения к определенным значениям, хранящимся в массиве, используйте значение индекса, которое указывает на требуемый элемент. Например, для обращения к первому элементу массива test_scores вы должны использовать значение индекса 0. Для обращения ко второму элементу используйте индекс 1. Подобно этому, для обращения к третьему элементу используйте индекс 2. Как показано на рис. 16.1, первый элемент массива всегда имеет индекс 0, а значение индекса последнего элемента на единицу меньше размера массива:
Хранение значений в массивах 131 Рис. 16.1. Как C++ индексирует элементы массива. Важно помнить, что C++ всегда использует 0 для индекса первого элемента массива, а индекс последнего элемента на единицу меньше размера массива. Следующая программа ARRAY. CPP создает массив с именем values, который вмещает пять целочисленных значений. Далее программа присваивает элементам значения 100, 200, 300, 400 и 500: #include <iostream.h> void main(void) { int values[5]; // Объявление массива values[0] = 100; values[1] = 200; values[2] = 300; values[3] = 400; values[4] = 500; cout << "Массив содержит следующие значения" << endl; cout << values[0] <<''<< values[1] <<''<< values[2] <<''<< values[3] <<''<< values[4] << endl; } Как видите, программа присваивает первое значение элементу 0 (valuesfOJ). Она также присваивает последнее значение элементу 4 (размер массива E) минус 1).
132 Урок 16 Использование индекса для обращения к элементам массива Массив позволяет вашим программам хранить несколько значений внутри одной и той же переменной. Для обращения к определенному значению внутри массива программы используют индекс. Говоря кратко, значение индекса указывает требуемый элемент массива. Все массивы C++ начинаются с элемента с индексом 0. Например, следующий оператор присваивает значение 100 первому элементу массива с именем scores: scores[0] = 100; Когда ваша программа объявляет массив, она указывает количество элементов, которые массив может хранить. Например, следующий оператор объявляет массив, способный хранить 100 значений типа int. int scores[1003; В данном случае массив представляет собой элементы от scores[0]j\o scores[99]. Использование индексной переменной Если ваши программы используют массив, обычной операцией является использование индексной переменной для обращения к элементам массива. Например, предположим, что переменная /содержит значение 3, следующий оператор присваивает значение 400 элементу values[3J: values[i] = 400; Следующая программа SHOWARRA.CPP использует индексную переменную /внутри цикла for для вывода элементов массива. Цикл for инициализирует / нулем, так что программа может обращаться к элементу valuesfOJ. Цикл for завершается, когда /больше 4 (последний элемент массива): #include <lostream.h> void main(void) { int values[5]; // Объявление массива int i; values[0] = 100; values[1] = 200; values[2] = 300; values[3] = 400; values[4] = 500; cout << "Массив содержит следующие значения" << endl;
Хранение значений в массивах 133 for (i = 0; i < 5; i++) cout << values[i] << ' '; } Каждый раз, когда цикл for увеличивает переменную /, программа может обратиться к следующему элементу массива. Экспериментируйте с этой программой, изменяя цикл for следующим образом: for (i = 4; i >= 0; i—) cout << values[i] << ' '; В данном случае программа будет выводить элементы массива от большего к меньшему. ИНИЦИАЛИЗАЦИЯ МАССИВА ПРИ ОБЪЯВЛЕНИИ Как вы уже знаете, C++ позволяет вашим программам инициализировать переменные при объявлении. То же верно и для массивов. При объявлении массива вы можете указать первоначальные значения, поместив их между левой и правой фигурными скобками, следующими за знаком равенства. Например, следующий оператор инициализирует массив values: int values[5] = { 100, 200# 300, 400, 500 }; Подобным образом следующее объявление инициализирует массив с плавающей точкой: float salaries[3] = { 25000.00, 35000.00, 50000.00 }; Если вы не указываете первоначальное значение для какого-либо элемента массива, большинство компиляторов C++ будут инициализировать такой элемент нулем. Например, следующее объявление инициализирует первые три из пяти элементов массива: int values[5] = { 100, 200, 300 }; Программа не инициализирует элементы values[3jn values[4]. В зависимости от вашего компилятора, эти элементы могут содержать значение 0. Если вы не указываете размер массива, который вы инициализируете при объявлении, C++ распределит достаточно памяти, чтобы вместить все определяемые элементы. Например, следующее объявление создает массив, способный хранить четыре целочисленных значения: int numbers[] = { 1, 2, 3, 4 }; ПЕРЕДАЧА МАССИВОВ В ФУНКЦИИ Ваши программы будут передавать массивы в функции точно так же, как и любые другие переменные. Функция может инициализировать массив, прибавить к массиву значения или вывести элементы массива на экран. Когда
134 Урок 16 вы передаете массив в функцию, вы должны указать тип массива. Нет необходимости указывать размер массива. Вместо этого вы передаете параметр, например number ofjelements, который содержит количество элементов в массиве: void some_function(int array[], int number_of_elements); Следующая программа ARRAYFUN.CPP передает массивы в функцию show_array, которая использует цикл for для вывода значений массивов: #include <iostream.h> void show_array(int array[], int number_of_elements) { int i; for (i = 0; i < number_of_elements; i++) cout << array[i] << ' '; cout << endl; } void main (void) { int little_numbers[5] = { 1, 2, 3, 4, 5 }; int big_numbers[3] = { 1000, 2000, 3000 }; show_array(little_numbers, 5); show_array(big_numbers, 3); } Как видите, программа просто передает массив в функцию по имени, а также указывает параметр, который сообщает функции количество элементов, содержащихся в массиве: show_array(little_numbers, 5); Следующая программа GETARRAY.CPP использует функцию get_yalues, чтобы присвоить три значения массиву numbers: #include <iostream.h> void get_values(int array[], int number_of_elements) { int i; for (i = 0; i < number_of„elements; i++) { cout << "Введите значение " << i << ": ";
Хранение значений в массивах 135 cin >> array[i]; } } void main(void) { int numbers[3]; get_valiies (numbers, 3); cout << "Значения массива" << endl; for (int i = 0; i < 3; i++) cout << numbers[i] << endl; } Как видите, программа передает массив в функцию по имени. Функция в свою очередь присваивает массиву элементы. Из урока 10 вы узнали, что, пока ваша программа не передаст параметры в функцию с помощью адреса, функция не может изменить эти параметры. Однако, как можно заметить в данном случае, функция get_values изменяет параметр-массив numbers. Как вы узнаете из урока 20, C++ действительно передает массивы в функцию, используя указатели. Таким образом, функция может изменить элементы массива, если ей это нужно. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что ваши программы могут хранить несколько значений одного и того же типа внутри массива. Программы на C++ широко используют массивы. Из урока 17 вы узнаете, что программы могут использовать массивы для хранения символьных строк. До изучения урока 17 убедитесь, что вы освоили следующие основные концепции: 0 Массив представляет собой переменную, которая может хранить одно или несколько значений одного и того же типа. 0 Для объявления массива вам следует указать тип, имя массива, а также количество значений, хранящихся в массиве. 0 Значения внутри массива называются элементами массива. 0 Первый элемент массива хранится как элемент 0 (array[0]); индекс последнего элемента массива на единицу меньше размера массива. 0 Программы часто используют индексные переменные для обращения к элементам массива.
136 Урок 16 0 Если функция воспринимает массив как параметр, она должна указать тип и имя, но не размер массива. 0 Если программа передает массив в функцию, она, как правило, передает и параметр, который сообщает функции количество элементов, содержащихся в массиве. 0 Так как C++ передает массив в функцию с помощью адреса массива, функция может изменять значения, содержащиеся в массиве.
Урок 17 Символьные строки Символьные строки хранят такую информацию, как имена файлов, названия книг, имена служащих и другие символьные сочетания. Большинство программ на C++ широко используют символьные строки. Далее вы узнаете, что в C++ символьные строки хранятся в массиве типа char, который заканчивается символом NULL (или ASCII 0). В данном уроке символьные строки рассматриваются более подробно. Вы узнаете, как хранить и обрабатывать символьные строки, а также как использовать функции библиотеки этапа выполнения, которые манипулируют символьными строками. К концу этого урока вы освоите следующие основные концепции: • Чтобы объявить символьную строку, вы должны объявить массив типа char. • Чтобы присвоить символы символьной строке, ваши программы просто присваивают символы элементам массива символьных строк. • Программы C++ используют символ NULL (ASCII 0), чтобы отметить последний символ строки. • C++ позволяет вашим программам инициализировать символьные строки при их объявлении. • Программы могут передавать символьные строки в функцию, как и любой массив. • Большинство библиотек этапа выполнения C++ обеспечивают набор функций, которые управляют символьными строками. Программы на C++ хранят символьные строки как массив типа char. Большинство программ широко используют символьные строки. Экспериментируйте с каждой программой, представленной в этом уроке, чтобы освоиться с символьными строками. Вы обнаружите, что работа с символьными строками подобна работе с массивами, описанной в уроке 16. ОБЪЯВЛЕНИЕ СИМВОЛЬНЫХ СТРОК В ПРОГРАММАХ Программисты на C++ широко используют символьные строки для хранения имен пользователей, имен файлов и другой символьной информации.
138 Урок 17 Для объявления символьной строки внутри программы просто объявите массив типа char с количеством элементов, достаточным для хранения требуемых символов. Например, следующее объявление создает переменную символьной строки с именем filename, способную хранить 64 символа (не забывайте, что символ NULL является одним из этих 64 символов): char filename[64]; Как видно из рис. 17.1, это объявление создает массив с элементами, индексируемыми от filename[0] jxofilename[63]. Рис. 17.1. C++ трактует символьную строку как массив типа char. Главное различие между символьными строками и другими типами массивов заключается в том, как C++ указывает последний элемент массива. Как вы уже знаете, программы на C++ представляют конец символьной строки с помощью символа NULL, который в C++ изображается как специальный символ '\0\ Когда вы присваиваете символы символьной строке, вы должны поместить символ NULL ('\0') после последнего символа в строке. Например, следующая программа ALPHABET.CPP присваивает буквы от А до Я переменной alphabet, используя цикл for. Затем программа добавляет символ NULL в эту переменную и выводит ее с помощью cout. #include <iostream.h> void main(void) { char alphabet [34]; // 33 буквы плюс NULL char letter; int index; for (letter = 'A', index = 0; letter <= 'Я'; letter++, index++) alphabet[index] = letter; alphabet[index] = NULL; cout << "Буквы " << alphabet; }
Символьные строки 139 Как видите, программа присваивает строке символ NULL, чтобы указать последний символ строки: alphabet[index] = NULL; Когда выходной поток cout выводит символьную строку, он по одному выводит символы строки, пока не встретит символ NULL. Короче говоря, символ NULL указывает программе последний символ в строке. Обратите внимание на цикл for, который появляется в предыдущей программе. Как видите, цикл инициализирует и увеличивает две переменные (letter и index). Когда цикл for инициализирует или увеличивает несколько переменных, разделяйте операции запятой (запятая тоже является оператором C++): for (letter = 'A', index = 0; letter <= 'Я'; letter++, index++) C++ автоматически добавляет NULL к строковым константам Все созданные вами программы использовали символьные строковые константы, заключенные внутри двойных кавычек, как показано ниже: "Это строковая константа" При создании символьной строковой константы компилятор C++ автоматически добавляет символ NULL, как показано на рис. 17.2. Рис. 17.2. Компилятор C++ автоматически добавляет символ NULL к строковым константам. Когда ваши программы выводят символьные строковые константы с помощью выходного потока cout, cout использует символ NULL (который компилятор добавляет к строке) для определения последнего символа вывода. Использование символа NULL Символьная строка представляет собой массив символов, за которыми следует символ NULL ('\0')- При объявлении символьной строки вы объявляете массив типа char. Когда программа позднее присваивает символы строке, она отвечает за добавление символа NULL, который представляет конец строки.
140 Урок 17 Если вы используете строковые константы, заключенные в двойные кавычки, компилятор C++ автоматически добавляет символ NULL. Большинство функций C++ используют символ NULL для определения последнего символа строки. Следующая программа LOOPNULL.CPP слегка изменяет предыдущую программу, используя цикл for для вывода содержимого строки: #include <iostream.h> void main(void) { char alphabet [34]; // 33 символа плюс NULL char letter; int index; for (letter = 'A', index = 0; letter <= 'Я'; letter++, index++) alphabet[index] = letter; alphabet[index] = NULL; for (index = 0; alphabet[index] != NULL; index++) cout << alphabet[index]; с out << endl; } Как видите, цикл for по одному исследует символы строки. Если символ не NULL (не последний символ строки), цикл выводит символ, увеличивает индекс, и процесс продолжается. Как 'А' отличается от "А" При рассмотрении программ на C++ вы можете встретить символы, заключенные в одинарные кавычки (например, 'А') и символы, заключенные в Рис. 17.3. Как компилятор C++ хранит символьную константу 'А' и строковую константу "А". двойные кавычки ("А"). Символ внутри одинарных кавычек представляет собой символьную константу. Компилятор C++ выделяет только один байт па-
Символьные строки 141 мяти для хранения символьной константы. Однако символ в двойных кавычках представляет собой строковую константу — указанный символ и символ NULL (добавляемый компилятором). Таким образом, компилятор будет выделять два байта для символьной строки. Рисунок 17.3 иллюстрирует, как компилятор C++ хранит символьную константу 'А' и строковую константу "А". ИНИЦИАЛИЗАЦИЯ СИМВОЛЬНОЙ СТРОКИ Как вы уже знаете из урока 16, C++ позволяет вам инициализировать массивы при объявлении. Символьные строки C++ не являются исключением. Для инициализации символьной строки при объявлении укажите требуемую строку внутри двойных кавычек, как показано ниже: char title[64] = "Учимся программировать на языке C++"; Если количество символов, присваиваемое строке, меньше размера массива, большинство компиляторов C++ будут присваивать символы NULL остающимся элементам строкового массива. Как и в случае с массивами других типов, если вы не указываете размер массива, который инициализируете при объявлении, компилятор C++ распределит достаточно памяти для размещения указанных букв и символа NULL: char title[] = "Учимся программировать на языке C++"; Следующая программа INIT_STR.CPP инициализирует символьную строку при объявлении: #include <iostream.h> void main(void) { char title[64] = "Учимся программировать на языке C++"; char lesson[64] = "Символьные строки"; cout << "Книга: " << title << endl; cout << "Урок: " << lesson << endl; } Некоторые программы, представленные в оставшейся части книги, будут инициализировать символьные строки подобным способом. Найдите время для эксперимента с этой программой, изменяя символы, присваиваемые каждой строке. ПЕРЕДАЧА СТРОК В ФУНКЦИИ Передача символьной строки в функцию подобна передаче любого массива в качестве параметра. Внутри функции вам нужно просто указать тип массива (char) и левую и правую скобки массива. Вам не надо указывать размер строки. Например, следующая программа SHOW_STR.CPP использует функцию showjstring для вывода символьной строки на экран:
142 Урок 17 #include <lostream.h> void show__string(char string[]) { cout << string << endl; > void main(void) { show_string("Привет, C++!"); show_string("Учусь программировать на C++"); } Как видите, функция showstring трактует параметр символьной строки как массив: void show_string(char string[]) Так как символ NULL указывает конец строки, функция не требует параметр, который задает количество элементов в массиве. Вместо этого функция может определить последний элемент, просто найдя в массиве символ NULL. Как вы уже знаете, функции C++ часто используют символ NULL для определения конца строки. Следующая программа STR_LEN.CPP создает функцию с именем stringjength, которая ищет символ NULL в строке для определения количества символов, содержащихся в строке. Далее функция использует оператор return для возврата длины строки вызвавшей функции. Программа передает несколько различных символьных строк в функцию, отображая длину каждой из них на экране: #include <iostream.h> int string_length(char string[]) { int i; for (i = 0; string[i] != '\0'; i++); // Ничего не делать, // но перейти к // следующему символу return(i); // Длина строки } void main(void) { char title[] = "Учимся программировать на языке C++"; char lesson[] = "Символьные строки"; cout << "Строка " << title << " содержит " << string_length(title) << " символов" << endl;
Символьные строки 143 cout << "Строка " << lesson << " содержит " « string_length(lesson) << " символов" << endl; } Как видите, функция запускается с первого символа строки (элемент 0) и затем исследует каждый элемент до тех пор, пока не встретит NULL. Рассматривая программы на C++, вы встретите целый ряд функций, которые подобным образом просматривают символьные строки в поисках символа NULL. ПРЕИМУЩЕСТВА ТОГО, ЧТО NULL ПРЕДСТАВЛЯЕТ СОБОЙ ASCII О Как вы уже знаете, символ NULL представляет собой символ ASCII 0. В уроке 7 вы изучали, что C++ использует значение 0, чтобы представлять ложь. Таким образом, поскольку символ NULL равен 0, ваши программы могут упростить многие операции цикла. Например, многие функции просматривают символьные строки символ за символом в поиске NULL. Следующий цикл for иллюстрирует, как программа может искать NULL в строке: for (index = 0; string[index] != NULL; index++) 7 Поскольку символ NULL равен 0, многие программы упрощают циклы, которые ищут NULL, как показано ниже: for (index = 0; string[index]; index++) # В данном случае пока символ, содержащийся в stringfindexj не NULL @ или ложь), цикл продолжается. ИСПОЛЬЗОВАНИЕ СТРОКОВЫХ ФУНКЦИЙ БИБЛИОТЕКИ ЭТАПА ВЫПОЛНЕНИЯ Из урока 11 вы узнали, что большинство компиляторов C++ обеспечивает обширный набор функций, называемых библиотекой этапа выполнения. Рассматривая библиотеку этапа выполнения, вы обнаружите, что она содержит много разных функций, манипулирующих строками. Например, функция strupr преобразует символьную строку в строку верхнего регистра. Подобно этому, функция strlen возвращает количество символов в строке. Большинство библиотек этапа выполнения обеспечивают даже функции, которые позволяют вам просматривать строки в поисках определенного символа. Например, следующая программа STRUPR.CPP иллюстрирует использование функций strupr и strlwr библиотеки этапа выполнения:
144 Урок 17 #include <lostream.h> #include <string.h> // Содержит прототипы // функций strupr и strlwr void main(void) { char title[] = "Учимся программировать на языке C++"; char lesson[] = "Символьные строки7'; cout << "Верхний регистр: " << strupr (title) << endl; cout << "Нижний регистр: " << strlwr (lesson) << endl; } Использование библиотечных функций, манипулирующих строками, может сохранить вам время, требуемое для программирования. Выберите время напечатать копию заголовочного файла STRING. H для определения функций манипулирования строками, которые поддерживаются библиотекой вашего компилятора. Вы должны играть по правилам Как вы уже знаете, большинство функций, которые манипулируют строками, полагаются на символ NULL как на конец строки. Если ваши программы присваивают строки символам, то следует убедиться, что они добавляют символ NULL в качестве последнего символа строки. Если ваши программы не используют NULL соответствующим образом, то функции, которые полагаются на символ NULL, будут сбиваться. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Большинство программ на C++ широко использует символьные строки. Из этого урока вы узнали, как работать со строками. Из урока 18 вы узнаете, как сохранять связанную информацию различных типов в переменных, представляющих структуры C++. Используя структуру, вы можете хранить всю информацию о служащем, например его фамилию, возраст, оклад и номер телефона, в одной переменной. Однако, прежде чем приступить к уроку 18, убедитесь, что освоили следующие основные концепции: 0 Символьная строка представляет собой массив символов, завершающийся 0 (символом NULL). 0 Вы создаете символьную строку, объявляя массив типа char. 0 Ваша программа ответственна за размещение символа NULL за последним символом строки. 0 Если программа использует строковые константы, заключенные в
Символьные строки 145 двойные кавычки, компилятор С++ автоматически добавляет символ NULL. 0 C++ позволяет вам инициализировать строки при объявлении, указывая требуемые символы внутри двойных кавычек. ЕЯ Большинство компиляторов C++ в своих библиотеках этапа выполнения обеспечивают широкий набор функций для манипулирования строками.
Урок 18 Хранение связанной информации в структурах Из урока 16 вы узнали, что C++ позволяет хранить в массиве связанную информацию одного и того же типа. Вы уже выяснили, что группировка связанных значений в массив очень удобна. В большинстве случаев программам необходимо группировать связанную информацию разного типа. Например, предположим, что ваша программа работает с информацией о служащих. Она должна отслеживать данные о фамилии, возрасте, окладе, адресе, номере служащего и т. д. Для хранения этой информации программе потребуются переменные типа char, int, float, а также символьные строки. Если вашей программе требуется хранить связанную информацию разных типов, она может использовать структуру. Вы узнаете, что структура представляет собой переменную, группирующую связанные части информации, называемые элементами, типы которых могут различаться. Группируя данные в одну переменную подобным образом, вы упрощаете ваши программы, снижая количество переменных, которыми необходимо управлять, передавать в функции и т. д. В данном уроке рассматривается создание и использование структур. К концу этого урока вы освоите следующие основные концепции: • Структуры позволяют вашим программам группировать в одной переменной связанные данные, типы которых могут различаться. • Структура состоит из одной или нескольких частей данных, называемых элементами. • Для определения структуры внутри программы следует указать имя структуры и ее элементы. • Каждый элемент структуры имеет тип, например char, int к float, и имя каждого элемента должно быть уникальным. • После того как ваша программа определит структуру, она может объявить переменные типа этой структуры. • Для изменения элементов структуры внутри функции ваши программы должны передать структуру в функцию с помощью адреса. Ваше умение понимать структуры и работать с ними облегчит использование объектно-ориентированных классов C++ в части 4. Выберите время для эксперимента с программами, представленными в этом уроке.
Хранение связанной информации в структурах 147 ОБЪЯВЛЕНИЕ СТРУКТУРЫ Структура определяет шаблон, с помощью которого ваша программа может позднее объявить одну или несколько переменных. Другими словами, ваша программа сначала определяет структуру, а затем объявляет переменные типа этой структуры. Для определения структуры ваши программы используют ключевое слово struct, за которым обычно следует имя и левая фигурная скобка. Следом за открывающей фигурной скобкой вы указываете тип и имя одного или нескольких элементов. За последним элементом вы размещаете правую закрывающую фигурную скобку. В этот момент вы можете (необязательно) объявить переменные данной структуры: struct name { int member_name_l; I Объявления элементов структуры float member_name_2; I } variable; | Объявление переменной Например, следующее определение создает структуру, содержащую информацию о служащем: struct employee { char name[64]; long employee_id; float salary; char phone[10]; int office_number; }; В данном случае определение не объявляет какие-либо переменные типа этой структуры. После того как вы определите структуру, ваша программа может объявить переменные типа этой структуры, используя имя структуры (иногда называемое структурным тэгом), как показано ниже: employee boss, worker, new_employee; Объявление переменных В данном случае оператор создает три переменные структуры employee. В некоторых случаях вы можете увидеть объявление, в котором тэг структуры предваряется ключевым словом struct, как показано ниже: struct employee boss, worker, new_employee; Ключевое слово struct является обязательным при программировании на С, так что некоторые программисты могут включать его по привычке. Однако в C++ использовать ключевое слово struct необязательно. Использование элементов структуры Структура позволяет вашим программам группировать информацию, называемую элементами, в одной переменной. Чтобы присвоить значение эле-
148 Урок 18 менту или обратиться к значению элемента, используйте оператор C++ точку (.). Например, следующие операторы присваивают значения различным элементам переменной с именем workerтипа employee: worker.employee_id = 12345; worker.salary = 25000.00; worker.office_number = 102; Для обращения к элементу структуры укажите имя переменной, за которым следует точка и имя элемента. Следующая программа EMPLOYEE.CPP иллюстрирует использование структуры типа employee. #include <iostream.h> #include <string.h> void main(void) { struct employee { char name[64]; long employee_id; float salary; char phone[10]; int office_number; } worker; // Копировать имя в строку strcpy(worker.name, "Джон Дой"); worker.employee_id = 12345; worker.salary = 25000.00; worker.office_number = 102; // Копировать номер телефона в строку strcpy(worker.phone, 55-1212"); cout << "Служащий: " << worker.name << endl; cout << "Телефон: " << worker.phone << endl; cout << "Номер служащего: " << worker.employee_id << endl; cout << "Оклад: " << worker.salary << endl; cout << "Офис: " << worker.office_number << endl; } Как видите, присваивание целому элементу и элементу с плавающей точкой очень просто. Программа использует оператор присваивания, чтобы присвоить значение соответствующему элементу. Однако обратите внимание на использование функции strcpywn копирования символьной строки в элементы пате и phone. Если вы не инициализируете элементы при объявлении переменной типа данной структуры, вы должны копировать символьные строки в символьно-строковые элементы.
Хранение связанной информации в структурах 149 Объявление переменных структуры Структуры C++ позволяют вашим программам группировать в одну переменную связанную информацию различных типов. Структура определяет шаблон для объявлений будущих переменных вашей программы. Каждая структура имеет уникальное имя (иногда называемое тэгом). Используя имя структуры, вы можете объявить переменные типа данной структуры. Биты информации, хранящиеся в структуре, называются элементами. Чтобы использовать или присвоить значение элементу, используйте оператор C++ точку, как показано ниже: variable.member = some_value; some_variable = variable.other_member; СТРУКТУРЫ И ФУНКЦИИ Если функция не изменяет структуру, вы можете передать структуру в функцию по имени. Например, следующая программа SHOW_EMP.CPP использует функцию showjemployee для вывода элементов структуры типа employee: ttinclude <iostream.h> #include <string.h> struct employee { char name [64]; long employee_id; float salary; char phone[10]; int office_number; }; void show_employee(employee worker) { cout << "Служащий: " « worker.name << endl; cout << "Телефон: " « worker.phone << endl; cout << "Номер служащего: " << worker.employee_id << endl; cout << "Оклад: " << worker.salary << endl; cout << "Офис: " << worker.office_number << endl; } void main(void) { employee worker; // Копировать имя в строку strcpy(worker.name, "Джон Дой");
150 Урок 18 worker.employee_id = 12345; worker.salary = 25000.00; worker.office_number = 102; // Копировать номер телефона в строку strcpy(worker.phone, 55-1212"); show_employee(worker); } Как видите, программа передает переменную типа данной структуры worker в функцию showjemployee по имени. Далее функция show_employee выводит элементы структуры. Однако обратите внимание, что программа теперь определяет структуру employee вне main и до функции showjemployee. Поскольку функция объявляет переменную worker типа employee, определение структуры employee должно располагаться до функции. Функции, изменяющие элементы структуры Как вы знаете, если функция изменяет параметр, вам следует передавать этот параметр в функцию с помощью адреса. Если функция изменяет элемент структуры, вы должны передавать эту структуру в функцию с помощью адреса. Для передачи переменной типа структуры с помощью адреса вы просто предваряете имя переменной оператором адреса C++ (&), как показано ниже: some_function(&worker); Внутри функции, которая изменяет один или несколько элементов, вам следует работать с указателем. Если вы используете указатель на структуру, легче всего обращаться к элементам структуры, используя следующий синтаксис: pointer_variable->member = some_value; Например, следующая программа CHG_MBR.CPP передает структуру типа employee в функцию с именем get_employee_id, которая запрашивает у пользователя идентификационный номер служащего и затем присваивает этот номер элементу структуры employeeid. Чтобы изменить элемент, функция работает с указателем на структуру: #include <iostream.h> #include <string.h> struct employee { char name[64]; long employee_id; float salary; char phone[10]; int office_number; };
Хранение связанной информации в структурах 151 void get_employee_id(employee *worker) { cout << "Введите номер служащего: "; сin >> worker->employee_id; } void main(void) { employee worker; // Копировать имя в строку s t rcpy(worker.name, "Джон Дой"); get_employee_id(ftworker); cout << "Служащий: " << worker.name << endl; cout << "Номер служащего: " << worker.employee_id << endl; } Как видите, внутри main программа передает переменную worker типа структуры в функцию get_employee_id с помощью адреса. Внутри функции get_employee_/Означение, введенное пользователем, присваивается элементу employeejd с помощью следующего оператора: cin >> worker->employee_id; Работа с указателями на структуры Если функция изменяет элемент структуры, вызвавшая программа должна передать структуру в функцию с помощью адреса. Функция, в свою очередь, использует указатель на структуру. Для обращения к элементу структуры функции следует использовать следующий формат: value = variable- >member; variable->other_member = some_value; ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Структуры позволяют вашим программам группировать связанные части информации различных типов в одной и той же переменной. Комбинируя подобным образом данные в одной переменной, ваши программы могут лучше представлять объекты, состоящие из двух или более частей, например, информацию о служащих, книгах и т. д. Из урока 19 вы узнаете, как использовать объединения C++, которые, подобно структурам, используют элементы, но в отличие от структур по-другому хранятся в памяти. Независимо от количества элементов объединение может хранить только одно значение в каждый момент времени. Прежде чем вы приступите к уроку 19, убедитесь, что освоили следующие основные концепции:
152 Урок 18 Структуры позволяют вашим программам группировать связанную информацию различных типов в одной переменной. Части информации, из которых состоит структура, называются элементами. Структура определяет шаблон, который ваши программы могут использовать для объявления переменных. После определения структуры вы можете использовать ее имя (тэг) для объявления переменных типа данной структуры. Чтобы присвоить значение или обратиться к значению элемента структуры, ваши программы используют оператор точку, например variable, member. Если функция изменяет значение элемента структуры, переменная структуры должна быть передана в функцию с помощью адреса. Если функция использует указатель на структуру, функция должна использовать формат variable->member для обращения к элементу структуры.
Урок 19 Объединения Из урока 18 вы узнали, как группировать связанную информацию в одной переменной с помощью структур C++. По мере усложнения вашим программам могут потребоваться разные способы просмотра части информации. Кроме того, программе может потребоваться работать с двумя или несколькими значениями, используя при этом только одно значение в каждый момент времени. В таких случаях для хранения данных ваши программы могут использовать объединения. В данном уроке вы изучите, как создавать и использовать объединения для хранения информации. Как вы узнаете, объединения очень похожи на структуры, описанные в уроке 18. Прежде чем вы закончите этот урок, вы освоите следующие основные концепции: • Объединения C++ очень похожи на структуры, за исключением того, как C++ хранит их в памяти; кроме того, объединение может хранить значение только для одного элемента в каждый момент времени. • Объединение представляет собой структуру данных, подобную структуре C++, и состоит из частей, называемых элементами. • Объединение определяет шаблон, с помощью которого программы далее объявляют переменные. • Для обращения к определенному элементу объединения ваши программы используют оператор C++ точку. • Чтобы изменить значения элемента объединения внутри функции, ваша программа должна передать переменную объединения в функцию с помощью адреса. • Анонимное объединение представляет собой объединение, у которого нет имени (тэга). Как вы узнаете, объединения очень похожи на структуры C++, однако способ, с помощью которого C++ хранит объединения, отличается от способа, с помощью которого C++ хранит структуры. КАК C++ ХРАНИТ ОБЪЕДИНЕНИЯ Внутри ваших программ объединения C++ очень похожи на структуры. Например, следующая структура определяет объединение с именем distance, содержащее два элемента:
154 Урок 19 union distance { int miles; long meters; >; Как и в случае со структурой, описание объединения не распределяет память. Вместо этого описание предоставляет шаблон для будущего объявления переменных. Чтобы объявить переменную объединения, вы можете использовать любой из следующих форматов: union distance { union distance { int miles; int miles; long meters; long meters; } japan, germany, franee; }; distance japan, germany, franee; Как видите, данное объединение содержит два элемента: miles и meters. Эти объявления создают переменные, которые позволяют вам хранить расстояния до указанных стран. Как и для структуры, ваша программа может присвоить значение любому элементу. Однако в отличие от структуры значение может быть присвоено только одному элементу в каждый момент времени. Когда вы объявляете объединение, компилятор C++ распределяет память для хранения самого большого элемента объединения. В случае объединения distance компилятор распределяет достаточно памяти для хранения значения типа long, как показано на рис. 19. Рис. 19. C++ распределяет память, достаточную для хранения только самого большого элемента объединения. Предположим, что ваша программа присваивает значение элементу miles, как показано ниже: japan.miles = 12123; Если далее ваша программа присваивает значение элементу meters, значение, присвоенное элементу miles, теряется. Следующая программа USEUNION.CPP иллюстрирует использование объединения distance. Сначала программа присваивает значение элементу
Объединения 155 miles и выводит это значение. Затем программа присваивает значение элементу meters. При этом значение элемента miles теряется: #include <iostream.h> void main(void) { union distance { int miles; long meters; } walk; walk.miles = 5; cout << "Пройденное расстояние в милях " << walk.miles << endl; walk.meters = 10000; cout << "Пройденное расстояние в метрах " « walk.meters << endl; } Как видите, программа обращается к элементам объединения с помощью точки, аналогичная запись использовалась при обращении к элементам структуры в уроке 18. Объединение хранит значение только одного элемента в каждый момент времени Объединение представляет собой структуру данных, которая, подобно структуре C++, позволяет вашим программам хранить связанные части информации внутри одной переменной. Однако в отличие от структуры объединение хранит значение только одного элемента в каждый момент времени. Другими словами, когда вы присваиваете значение элементу объединения, вы перезаписываете любое предыдущее присваивание. Объединение определяет шаблон, с помощью которого ваши программы могут позднее объявлять переменные. Когда компилятор C++ встречает определение объединения, он распределяет количество памяти, достаточное для хранения только самого большого элемента объединения. ПРЕДСТАВЛЕНИЕ ОБ АНОНИМНЫХ ОБЪЕДИНЕНИЯХ C++ Анонимное объединение представляет собой объединение, у которого нет имени. C++ предоставляет анонимные объединения, чтобы упростить ис-
156 Урок 19 пользование элементов объединений, предназначенных для экономии памяти или создания псевдонимов для определенного значения. Например, предположим, что вашей программе требуются две переменные miles и meters. Кроме того, предположим, что программа использует только одну из них в каждый данный момент времени. В этом случае программа могла бы использовать элементы объединения, подобного уже обсуждавшемуся объединению distance, а именно name.miles и name.meters. Следующий оператор создает анонимное (безымянное) объединение: union { int miles; long meters; }; Как видите, объявление не использует имя объединения и не объявляет переменную объединения. Программа, в свою очередь, может обращаться к элементам с именами miles и meters без помощи точки. Следующая программа ANONYM.CPP создает анонимное объединение, которое содержит элементы miles и meters. Обратите внимание, что программа трактует элементы как обычные переменные. Однако различие между элементами и обычными переменными заключается в том, что, когда вы присваиваете значение любому из этих элементов, значение другого элемента теряется: #include <iostream.h> void main(void) { union { int miles; long meters; }; miles = 10000; cout << "Значение в милях " << miles << endl; meters = 150000; cout << "Значение в метрах " << meters << endl; } Как видите, с помощью анонимного объединения, программа может сэкономить память, не используя имя объединения и точку для обращения к значениям элементов. Анонимные объединения позволяют вашим программам экономить пространство Анонимное объединение представляет собой безымянное объединение. Анонимные объединения обеспечивают вашим программам спо-
Объединения 157 соб экономии памяти, и при этом можно не использовать имя объединения и точку. Следующие операторы определяют анонимное объединение, способное хранить две символьные строки: union { char short_name[13]; char 1ong_name[255]; }; ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, как создать объединение внутри вашей программы. Вы уже поняли, что формат объединения подобен формату структуры. Однако способ, с помощью которого C++ хранит объединения, очень отличается от способа хранения структуры. Из урока 10 вы впервые узнали, что для того, чтобы функция изменила параметр, вашей программе следует передать этот параметр в функцию с помощью указателя (или адреса памяти). Начиная с десятого урока, ваши программы использовали указатели для массивов и символьных строк. В уроке 20 вы рассмотрите операции с указателями C++ с другой стороны. До изучения урока 20 убедитесь, что вы освоили следующее: 0 Когда вы объявляете объединение, компилятор C++ распределяет память, достаточную для хранения только самого большого элемента объединения. 0 Описание объединения не распределяет память, вместо этого оно обеспечивает шаблон, с помощью которого программы могут позднее объявлять переменные. 0 Программы обращаются к элементам объединения, используя точку. Когда ваша программа присваивает значение элементу объединения, то значение, присвоенное, возможно, ранее другому элементу, теряется. 0 Анонимное объединение представляет собой объединение, у которого нет имени. Когда программа объявляет анонимное объединение, она может использовать элементы такою объединения подобно любым другим переменным без точки.
Урок 20 Указатели Как вы уже знаете, программы на C++ хранят переменные в памяти. Указатель представляет собой адрес памяти, который указывает (или ссылается) на определенный участок. Из урока 10 вы узнали, что для изменения параметра внутри функции ваша программа должна передать адрес параметра (указатель) в функцию. Далее функция в свою очередь использует переменную- указатель для обращения к участку памяти. Некоторые программы, созданные вами в нескольких предыдущих уроках, использовали указатели на параметры. Аналогично этому, когда ваши программы работают с символьными строками и массивами, они обычно используют указатели, чтобы оперировать элементами массива. Так как применение указателей является общепринятым, очень важно, чтобы вы хорошо понимали их использование. Таким образом, этот урок рассматривает еще один аспект применения указателей. К концу данного урока вы освоите следующие основные концепции: • Для простоты (для уменьшения кода) многие программы трактуют символьную строку как указатель и манипулируют содержимым строки, используя операции с указателями. • Когда вы увеличиваете переменную-указатель (переменную, которая хранит адрес), C++ автоматически увеличивает адрес на требуемую величину (на 1 байт для char, на 2 байта для int, на 4 байта для float и т. д.). • Ваши программы могут использовать указатели для работы с массивами целочисленных значений или значений с плавающей точкой. Операции с указателями широко используются в C++. Выберите время для эксперимента с программами, представленными в этом уроке. ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЯ НА СИМВОЛЬНУЮ СТРОКУ Как вы уже знаете, указатель содержит адрес памяти. Когда ваша программа передает массив (например, символьную строку) в функцию, C++ передает адрес первого элемента массива. В результате совершенно обычно для функции использовать указатель на символьную строку. Чтобы объявить указатель на символьную строку, функция просто предваряет имя переменной звездочкой, как показано ниже:
Указатели 159 void some_function(char *string); Звездочка, которая предваряет имя переменной, указывает C++, что переменная будет хранить адрес памяти — указатель. Следующая программа PTR_STR.CPP использует указатель на символьную строку внутри функции showstring для вывода содержимого строки по одному символу за один раз: #include <iostream.h> void show_string(char *string) { while (*string != '\0') { cout << *string; string++; } } void main(void) { show_string( "Учимся программировать на языке C++!"); } Обратите внимание на цикл while внутри функции showstring. Условие while (*strihg /= '\0') проверяет, не является ли текущий символ, указываемый с помощью указателя string, символом NULL, который определяет последний символ строки. Если символ не NULL, цикл выводит текущий символ с помощью cout. Затем оператор string++; увеличивает указатель string таким образом, что он указывает на следующий символ строки. Когда указатель string указывает на символ NULL, функция уже вывела строку и цикл завершается. Рис. 20. Сканирование строки с помощью указателя.
160 Урок 20 Предположим, например, что строка, переданная в функцию, находится в памяти компьютера по адресу 1000. Каждый раз, когда функция увеличивает указатель string, он указывает на следующий символ (адрес 1001,1002, 1003 и т. д.), как показано на рис. 20. Второй пример Вы только что узнали, что, используя указатель, ваша функция может сканировать строку символов, пока не будет обнаружен символ NULL. Следующая программа PTR_LEN.CPP использует указатель на строку в функции stringlength для определения количества символов в строке: #include <iostream.h> int string_length(char *string) { int length = 0; while (*string != '\0') { length++; string++; } return(length); } void main(void) { char title[] = "Учимся программировать на языке C++"; cout << title << " содержит " << string_length(title) << " символов"; } Как видите, функция stringjlength сканирует символы строки до тех пор, пока не встретит символ NULL. Увеличение указателя на символьную строку Когда программа передает массив в функцию, C++ передает адрес памяти первого элемента этого массива. Используя переменную-указатель, функция может перемещаться по содержимому массива, просто увеличивая значение указателя. Например, предположим, что программа передает в функцию символьную строку "Привет". Внутри функции переменная- указатель сначала указывает на участок памяти, который содержит букву 'П\ Когда функция увеличивает указатель, то он далее указывает на участок памяти, который содержит букву 'р'. По
Указатели 161 мере увеличения функцией значения указателя, он поочередно указывает на каждую букву в строке и наконец указывает на символ NULL. Уменьшение количества операторов Чтобы определить конец символьной строки, каждая из предыдущих программ использовала следующий цикл while: while (*string != '\0') Как уже обсуждалось, символ NULL ('\0') представляет собой значение ASCII 0. Так как C++ использует значение 0, чтобы представить ложь, ваши программы могут записать предыдущий цикл следующим образом: while (*string) В данном случае пока символ, определяемый указателем строки, не равен 0 (NULL), условие оценивается как истинное и цикл будет продолжаться. Из урока 5 вы узнали, что постфиксная операция увеличения C++ позволяет вам использовать значение переменной, а затем увеличивает это значение. Многие программы на C++ используют постфиксные операции увеличения и уменьшения, чтобы сканировать массивы с помощью указателей. Например, использование постфиксной операции увеличения делает следующие циклы while идентичными: while (*string) while (*string) { cout << *string++; cout << *string; string++; } Оператор cout « *string++; заставляет C++ вывести символ, указываемый указателем string, а затем увеличить текущее значение string, чтобы он указывал на следующий символ. С помощью этих методов следующая программа SMARTPTR.CPP иллюстрирует новую реализацию функций showjstring и string_tength\ #include <lostream.h> void showjstring(char *string) { while (*string) cout << *string++; } int string_length(char *string) { int length = 0; while (*string++) length++;
162 Урок 20 return(length); } void main(void) { char title[] = "Учимся программировать на языке C++"; show_string(title); cout << " содержит " << string_length(title) << " символов"; } Если вы встретите функции C++, которые манипулируют строками с помощью указателей, то они с большой долей вероятности будут использовать подобную краткую запись. * Поскольку при переводе книги обрабатываемые символы заменены с английских на русские, то этот алгоритм работает не для всех кодировок кирилицы в DOS и Windows. — Прим. перев. Сканирование символьной строки Одно из наиболее широко употребляемых использований указателей в программах на C++ заключается в сканировании символьных строк. Для уменьшения количества кода многие программы используют следующие операторы для сканирования строки: while (*string) { // операторы string++; // продвинуть к следующему символу } Следующая функция stringjuppercase использует указатели для преобразования символов строки в символы верхнего регистра : char *string_uppercase(char* string) { char *starting_address = string; // адрес string[0]; while (*string) { if ((*string >= 'a') && (*string <= 'я')) ¦string = *string - 'a' + 'A'; string++; } return(starting_address); } Эта функция сохраняет и возвращает начальный адрес строки, ко-
Указатели 163 торый позволяет вашим программам использовать функцию следующим образом: cout << string_uppercase("Привет, мир!") << endl; ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ С ДРУГИМИ ТИПАМИ МАССИВОВ Несмотря на то что указатели широко используются с символьными строками, вы можете использовать указатели с массивами других типов. Например, следующая программа PTRFLOAT.CPP использует указатель на массив типа float для вывода значений с плавающей точкой: #include <iostream.h> void show_float(float *array, int number_of_elements) { int i; for (i = 0; i < number_of_elements; i++) cout << * array++ << endl; } void main(void) { float values[5] = {1.1, 2.2, 3.3, 4.4, 5.5}; show_float(values, 5); } Как видите, внутри функции show afloat цикл for использует значение, указываемое с помощью указателя array, а затем увеличивает этот указатель до следующего значения. В данном случае программа должна передать параметр, который задает количество элементов массива, поскольку в отличие от символьных строк массивы типа float (или /я/, long и т. д.) не используют символ NULL для определения последнего элемента. О МАТЕМАТИКЕ УКАЗАТЕЛЯ Как вы уже знаете, ваши программы могут использовать указатели на массивы любых типов. В предыдущей программе функция showjloat увеличивала указатель для продвижения по массиву типа float. Указатель указывает на участок памяти, содержащий значение определенного типа, например char, int или float. Когда функция сканирует массив с помощью указателя, функция увеличивает указатель для продвижения от одного значения к следующему. Чтобы указатель указывал на следующий элемент массива, C++ должен знать размер каждого элемента (в байтах), чтобы определить, на сколько необходимо увеличить значение указателя. Например, для продвижения указа-
164 Урок 20 теля к следующему символу в массиве, C++ должен увеличить значение указателя на 1. Однако, чтобы указать следующее значение в массиве типа int, C++ должен увеличить указатель на два байта (значение типа int занимает два байта памяти). Для значений типа float C++ увеличивает указатель на 4 байта. Зная тип значения, на которое указывает указатель, C++ знает, на сколько необходимо увеличить значение этого указателя. В ваших программах вы просто используете оператор увеличения, например pointer++. Однако за кулисами C++ увеличивает реальное значение (адрес памяти), содержащееся в указателе, на корректную величину. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Программы на C++ широко используют указатели, особенно для манипулирования строками. В данном уроке рассмотрен еще один широко используемый аспект при работе с указателями. В уроке 21 вы начнете использовать объектно-ориентированные возможности C++! Для начала вы создадите классы, подобные структурам. Ваши программы будут использовать класс для определения объекта, например file. Внутри класса вы укажете функции для манипулирования этим объектом, например print_file или deleteJlle. До перехода к уроку 21 убедитесь, что вы изучили следующее: 0 Указатели содержат адрес памяти. Когда вы передаете в функцию массив, C++ передает адрес первого элемента массива. 0 Увеличивая значение указателя, вы можете адресовать с его помощью следующий элемент массива. 0 Функции, которые манипулируют строками с помощью указателей, обычно сканируют строку до того момента, пока не найден символ NULL. 0 При использовании указателей с массивами других типов ваши функции должны знать количество элементов массива или специальный маркер конца массива. 0 При использовании указателей с массивами других типов C++ автоматически (за кулисами) увеличивает указатель (адрес памяти) на требуемую величину таким образом, чтобы данный указатель указывал на следующий элемент массива.
Использование классов C++ Объектно-ориентированное программирование фокусируется на сущностях (объектах), из которых состоит система. Например, у вас может быть объект-файл или объект-служащий и т. д. Каждый объект содержит связанные с ним данные, такие как имя файла или служащего, номер служащего и, возможно, оклад. Таким образом, объекты подобны структурам C++. Однако объекты включают также набор операций, которые ваши программы выполняют над объектами. В случае объекта-файла, вы можете печатать, удалять или даже копировать файл. Аналогично, в случае объекта-служащего вы можете напечатать данные, продвигать по службе или, возможно, даже уволить объект. C++ использует класс для хранения данных объекта и функций, которые оперируют этими данными. Эта часть рассматривает классы C++ более подробно. Уроки данной части: Урок 21. Знакомство с классами C++. Урок 22. Частные и общие данные. Урок 23. Конструктор и деструктор. Урок 24. Перегрузка операторов. Урок 25. Статические функции и элементы данных.
Урок 21 Знакомство с классами C++ Класс представляет собой главное инструментальное средство C++ для объектно-ориентированного программирования. Как вы узнаете из данного урока, класс очень похож на структуру, в которой сгруппированы элементы, соответствующие данным о некотором объекте, и оперирующие этими данными функции (называемые методами). Вы узнаете, что объект представляет собой некоторую сущность, например телефон. Класс C++ позволяет вашим программам определять все атрибуты объекта. В случае, когда объектом является телефон, класс может содержать такие элементы данных, как номер и тип телефона (кнопочный или дисковый) и функции, работающие с телефоном, например dial, answer, и hangup. Группируя данные об объекте и кодируя их в одной переменной, вы упрощаете процесс программирования и увеличиваете возможность повторного использования своего кода. Этот урок знакомит вас с классами C++ . К концу урока вы освоите следующие основные концепции: • Для определения класса программа должна указать имя класса, элементы данных класса и функции класса (методы). • Определение класса обеспечивает шаблон, с помощью которого ваши программы могут создать объекты типа этого класса, подобно тому, как программы создают переменные типа int, char и т. д. • Программа присваивает значения элементам данных класса, используя оператор точку. • Программа вызывает функцию-элемент класса, используя оператор точку. ПРЕДСТАВЛЕНИЕ ОБ ОБЪЕКТАХ И ОБЪЕКТНО-ОРИЕНТИРОВАННОМ ПРОГРАММИРОВАНИИ В известном смысле объект представляет собой сущность. Программа обычно использует переменные для хранения информации о различных реально существующих сущностях, например служащих, книгах и даже файлах. При объектно-ориентированном программировании вы фокусируетесь на предметах, образующих систему, и операциях, которые вы должны выполнять над этими предметами. Например, для объекта-файла вы могли бы иметь
168 Урок 21 операции, которые печатают, отображают или изменяют файл. В C++ вы используете класс для определения своих объектов. Ваша цель состоит в том, чтобы включить в класс столько информации об объекте, сколько требуется. Исходя из этого, можно подобрать класс, созданный вами для одной программы, и использовать его в нескольких разных программах. Класс позволяет вашим программам группировать данные и функции, которые выполняют операции над этими данными. Большинство книг и статей об объектно-ориентированном программировании называют функции класса методами. Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов и закрывающая фигурная скобка: class class_name { int data_member; // Элемент данных void show_member(int); // Функция-элемент }; После опредедения класса вы можете объявлять переменные типа этого класса (называемые объектами), как показано ниже: class_name object_one, object_two, object_three; Следующее определение создает класс employee, который содержит определения данных и метода: class employee { public: char name[64]; long employee_id; float salary; void show_employee(void) { cout << "Имя: " << name << endl; cout << "Номер служащего: " « employee_id << endl; cout << "Оклад: " « salary << endl; }; }; В данном случае класс содержит три переменные и одну функцию-элемент. Обратите внимание на использование метки public внутри определения класса. Как вы узнаете из урока 22, элементы класса могут быть частными (private) или общими (public), от чего зависит, как ваши программы обращаются к элементам класса. В данном случае все элементы являются общими, это означает, что программа может обращаться к любому элементу, используя оператор точку. После определения класса внутри вашей программы вы можете объявить объекты (переменные) типа этого класса, как показано ниже: ¦ Имя класса employee worker, boss, secretary; Переменные класса (объекты) I '—/
Знакомство с классами C++ 169 Следующая программа EMPCLASS.CPP создает два объекта employee. Используя оператор точку, программа присваивает значения элементам данных. Затем программа использует элемент show_employee для вывода информации о служащем: #include <iostream.h> #include <string.h> class employee { public: char name[64]; long employee_id; float salary; vo id show_emp1oyее(void) { cout << "Имя: " << name << endl; cout << "Номер служащего: " « employee_id << endl; cout << "Оклад: " « salary << endl; }; }; void main(void) { employee worker, boss; strcpy(worker.name, "John Doe"); worker.employee_id = 12345; worker.salary = 25000; strcpy(boss.name, "Happy Jamsa"); boss.employee_id = 101; boss.salary = 101101.00; worker.show_employee(); boss.show_employee(); } Как видите, программа объявляет два объекта типа employee — worker и boss, а затем использует оператор точку для присваивания значений элементам и вызова функции show employee. Представление об объектах Большинство программ на C++ отражают реальные существующие объекты. В известном смысле объекты представляют собой сущности, например автомобиль, собаку, часы и т. д. Обычно объект имеет несколько атрибутов и операций, которые программа должна выполнять над этими атрибутами. Например, в случае часов свойства могут включать текущее время и время будильника. Операции такого объекта могли бы содержать установку времени, установку будильника или выключение будильника. При объектно-ориентированном програм-
170 Урок 21 illlll мировании ваши программы фокусируются на объектах и операциях 11111 над этими объектами. ОПРЕДЕЛЕНИЕ МЕТОДОВ КЛАССА ВНЕ КЛАССА В предыдущем классе employee функция была определена внутри самого класса {встроенная {inline) функция). При увеличении функций определение встроенных функций внутри класса может внести беспорядок в описание класса. В качестве альтернативы вы можете поместить прототип функции внутри класса, а затем определить функцию вне класса. Ваше определение класса с прототипом становится следующим: class employee { public: char name[64]; long employee_id; float salary; void show_employee (void); | Прототип функции }; Так как разные классы могут использовать функции с одинаковыми именами, вы должны предварять имена определяемых вне класса функций именем класса и оператором глобального разрешения (::). В данном случае определение функции становится следующим: void employee: : show_employee (void) Имя класса { 1 cout << "Имя: " « name << endl; """"^ Имя элемента cout << "Номер служащего: " << employee_id << endl; cout << "Оклад: " << salary << endl; }; Как видите, приведенный код предваряется определением функции с именем класса {employee) и оператором глобального разрешения (::). Следующая программа CLASSFUN.CPP помещает определение функции showemployee вне класса, используя оператор глобального разрешения для указания имени класса: #include <iostream.h> #include <string.h> class employee { public: char name[64]; long employee_id; float salary; void show_employee(void); };
Знакомство с классами C++ 171 void employee::show_employee(void) { cout << "Имя: " << name << endl; cout << "Номер служащего: " « employee_id << endl; cout << "Оклад: " << salary << endl; }; void main (void) { employee worker, boss; strcpy(worker.name, "John Doe"); worker.employee_id = 12345; worker.salary = 25000; strcpy(bos s.name, "Happy Jamsa"); boss.employee_id = 101; boss.salary = 101101.00; worker.show_employee(); boss.show_employee(); } Методы класса Классы C++ позволяют вашим программам группировать данные объекта и функции объекта (методы), которые оперируют с этими данными, в одной переменной. У вас есть две возможности определения методов объекта. Первая состоит в том, что вы можете включить весь код функции внутрь определения класса. Несмотря на то что включение кода метода в определение класса может представляться удобным, однако, когда классы становятся сложнее и включают несколько методов, операторы функций могут вносить беспорядок в определение классов. Таким образом, многие программы определяют операторы функции вне класса. В определение класса программа должна включать прототип функции, который указывает имя функции, тип возвращаемого значения и типы параметров. Для определения функции вне определения класса ваша программа должна предварять определение функции именем класса и оператором глобального разрешения, как показано ниже: return_type class__name::function_name(parameters) { // Операторы }
172 Урок 21 ВТОРОЙ ПРИМЕР Следующая программа PEDIGREE.CPP создает класс dog, который содержит несколько полей данных и функцию showjbreed. Программа определяет функцию класса вне определения самого класса. Затем программа создает два объекта типа dog и выводит информацию о каждой собаке: #include <iostream.h> #include <string.h> class dogs { public: char breed[64]; int average_weight; int average_height; void show_dog(void); >; void dogs::show_breed(void) { cout << "Порода: " << breed << endl; cout << "Средний вес: " << average_weight << endl; cout << "Средняя высота: " << average_height << endl; } void main(void) { dogs happy, matt; strcpy(happy.breed, "Долматин"); happy.average_we i ght = 58; happy.average_he ight = 24; strcpy(matt.breed, "Колли"); matt.average_weight = 22; matt.average_height = 15; happy.show_breed(); matt.show_breed(); } ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Программы на C++ широко используют классы. Говоря кратко, классы позволяют вашим программам группировать для определенного объекта его данные и методы (функции), которые оперируют этими данными, в одной переменной. Как видите, классы очень похожи на структуры, которые вы рассматривали в уроке 18. Классы C++ являются основой объектно-ориен-
Знакомство с классами C++ 173 тированного программирования. Следующие уроки расширяют возможности, предоставляемые классами. Упоминавшаяся в этом уроке метка public, появляющаяся в определении класса, делает элементы класса доступными для всей программы. Из урока 22 вы больше узнаете о частных и общих элементах класса. До изучения урока 22 убедитесь, что освоили следующие основные концепции: 0 В известном смысле объект представляет собой сущность, с которой ваши программы выполняют разные операции. 0 Программы на C++ представляют объекты посредством классов. 0 Класс, подобно структуре, содержит элементы. Элементы класса могут хранить информацию (данные) или быть функциями (методами), которые оперируют этими данными. 0 Каждый класс имеет уникальное имя. 0 После определения класса вы можете объявлять объекты этого класса, используя имя класса в качестве типа. 0 Для обращения к элементам класса (как к данным, так и к функциям) ваши программы используют оператор точку. 0 Программы могут определять функцию класса внутри или вне определения класса. Если вы определяете функцию вне определения класса, вам следует указать имя класса и использовать оператор глобального разрешения, например class ::function.
Урок 22 Частные и общие данные В уроке 21 вы создали свои первые классы в C++. При этом вы включали метку public в определение класса, чтобы обеспечить программе доступ к каждому элементу класса. Из данного урока вы узнаете, как атрибуты public и private управляют доступом к элементам класса со стороны программы. Вы узнаете, что ваши программы могут обратиться к общим {public) элементам из любой функции. С другой стороны, ваша программа может обращаться к частным (private) элементам только в функциях данного класса. Этот урок подробно исследует частные и общие элементы. К концу данного урока вы освоите следующие основные концепции: • Чтобы управлять тем, как ваши программы обращаются к элементам класса, C++ позволяет вам определять элементы как частные или общие. • Частные элементы дают возможность классу скрыть информацию, которую программе не требуется знать. • Класс, использующий частные элементы, обеспечивает интерфейсные функции, которые обращаются к частным элементам класса. Как уже вкратце обсуждалось в уроке 21, вы должны поместить в определение класса столько информации об объекте, сколько считаете необходимым. При этом объекты становятся самообеспеченными, что может повысить возможность их повторного использования несколькими программами. СОКРЫТИЕ ИНФОРМАЦИИ Как вы уже знаете, класс содержит данные и методы (функции). Для использования класса программы просто должны знать информацию, которую хранит класс (его элементы данных) и методы, которые манипулируют данными (функции). Вашим программам не требуется знать, как работают методы. Более того, программы должны знать только, какую задачу выполняют методы. Например, предположим, что у вас есть класс file. В идеале ваши программы должны знать только то, что этот класс обеспечивает методы file.print, который печатает отформатированную копию текущего файла, или file.delete, который удаляет файл. Вашей программе не требуется знать, как эти два метода работают. Другими словами, программа должна рассматривать класс как "черный ящик". Программа знает, какие методы необходимо
Частные и общие данные 175 вызвать и какие параметры им передать, но программа ничего не знает о реальной работе, выполняющейся внутри класса (в "черном ящике"). Сокрытие информации представляет собой процесс, в результате которого программе предоставляется только минимальная информация, необходимая для использования класса. Частные и общие элементы класса помогают вам получить информацию, скрытую внутри вашей программы. В уроке 21 каждый из созданных вами классов использовал метку public для объявления всех элементов класса общими, т. е. видимыми для всей программы. Таким образом, программа могла бы непосредственно обратиться к любому элементу класса, используя оператор точку: class employee { public: char name[64]; long employee_id; float salary; void show_employee(void); } При создании класса вы могли бы иметь элементы, чьи значения используются только внутри класса, но обращаться к которым самой программе нет необходимости. Такие элементы являются частными {private), и их следует скрывать от программы. Если вы не используете метку public, то по умолчанию C++ подразумевает, что все элементы класса являются частными. Ваши программы не могут обращаться к частным элементам класса, используя оператор точку. К частным элементам класса могут обращаться только элементы самого класса. При создании класса вам следует разделить элементы на частные и общие, как показано ниже: class some_class { public: int some_variable; void initialize_private(int, float); vo id show_dat a(void); private: int key_value; I float key_number; | } Как видите, метки public и private легко позволяют определять, какие элементы являются частными, а какие общими. В данном случае программа может использовать оператор точку для обращения к общим элементам, как показано ниже: some_class object; // Создать объект object.some_variable = 1001; object.initialize_privateB002, 1.2345); obj ect.show_data() Если ваша программа пытается обратиться к частным элементам Общие элементы Частные элементы
176 Урок 22 key value или keyjiumber, используя точку, компилятор сообщает о синтаксических ошибках. Как правило, вы будете защищать элементы класса от прямого доступа к ним, делая их частными. При этом программы не могут непосредственно присваивать значения таким элементам, используя оператор точку. Вместо того чтобы присвоить значение, программа должна вызвать метод класса. Предотвращая прямой доступ к элементам данных, вы, таким образом, можете гарантировать, что им всегда будут присваиваться допустимые значения. Например, предположим, что объект nuclearjreactor вашей программы использует переменную с именем meltdown, которая всегда должна содержать значение в диапазоне от 1 до 5. Если элемент meltdown является общим, программа может непосредственно обратиться к элементу, изменяя его значение произвольным образом: nuclear_reactor.melt_down = 101 Если вместо этого вы делаете переменную частной, то можете использовать метод класса, например assignjmeltdown, чтобы присвоить значение этой переменной. Как показано ниже, функция assignjneltdown может проверять присваиваемое значение, чтобы убедиться, что оно является допустимым: int nuke::assign_meltdown(int value) { if ((value > 0) && (value <= 5)) { melt_down = value; return@); / / Успешное присваивание } else return(-1); // Недопустимое значение } Методы класса, которые управляют доступом к элементам данных, представляют собой интерфейсные функции. При создании классов вы будете использовать интерфейсные функции для защиты данных своих классов. Общие и частные элементы Классы C++ содержат данные и элементы. Чтобы указать, к каким элементам классов ваши программы могут обращаться напрямую, используя оператор точку, C++ позволяет вам определять элементы класса как общие (public) и частные (private). Таким образом, ваши программы могут непосредственно обращаться к любым общим элементам класса, используя оператор точку. С другой стороны, к частным элементам можно обратиться только через методы класса. Как правило, вы должны защищать большинство элементов данных класса, объявляя их частными. Следовательно, единственным способом, с помощью которого ваши программы могут присвоить значение элементам данных, является использование функций класса, которые способны проверить и скорректировать присваиваемые значения.
Частные и общие данные 177 ИСПОЛЬЗОВАНИЕ ОБЩИХ И ЧАСТНЫХ ЭЛЕМЕНТОВ КЛАССА Следующая программа INFOHIDE.CPP иллюстрирует использование общих и частных элементов класса. Программа определяет объект типа employee, как показано ниже: class employee { public: int assign_values(char *, long, float); void show_employee(void); int change_salary(float); long get_id(void); private: char name[64]; long employee_id; float salary; } Как видите, класс защищает все свои элементы данных, объявляя их частными. Для доступа к элементам данных программа должна использовать интерфейсные функции. Ниже приведена реализация программы INFOHIDE.CPP: #include <iostream.h> #include <string.h> class employee { public: int assign_values(char *, long, float); void show_employee(void); int change_salary(float); long get_id(void); private: char name[64]; long employee_id; float salary; }; int employee::assign_values(char *emp_name, long emp_id, float emp_salary) { s t rcpy(name, emp_name); employee_id = emp_id; if (emp_salary < 50000.0) {
178 Урок 22 salary = emp_salary; return@); // Успешно } else return(-1); // Недопустимый оклад } void employee::show_employee(void) { cout << "Служащий: " << name << endl; cout << "Номер служащего: " << employee_id << endl; cout << "Оклад: " << salary << endl; } int employee::change_salary(float new_salary) { if (new_salary < 50000.0) { salary = new_salary; return@); // Успешно } else return(-1); // Недопустимый оклад } long employee::get_id(void) { return(employee_id); } void main(void) { employee worker; if (worker.assign_values("Happy Jamsa", 101, 10101.0) == 0) { cout << "Служащему назначены следующие значения" << endl; worker.show_employee(); if (worker.change_salaryC5000.00) == 0) { r cout << "Назначен новый оклад" << endl; worker.show_employee(); > } else cout << "Указан недопустимый оклад" << endl; }
Частные и общие данные 179 Выберите время, чтобы исследовать операторы программы более подробно. Несмотря на то что программа достаточно длинна, ее функции на самом деле очень просты. Метод assign_values инициализирует частные данные класса. Метод использует оператор if, чтобы убедиться, что присваивается допустимый оклад. Метод show_employee в данном случае выводит частные элементы данных. Методы changejsalary и get Jul представляют собой интерфейсные функции, обеспечивающие программе доступ к частным данным. После успешной компиляции и запуска этой программы отредактируйте ее и попытайтесь обратиться напрямую к частным элементам данных, используя оператор точку внутри main. Так как вы не можете напрямую обратиться к частным элементам, компилятор сообщит о синтаксических ошибках. Что такое интерфейсные функции Для снижения количества возможных ошибок ограничивайте доступ программ к данным класса, определяя элементы данных класса как частные. Таким образом, программа не сможет обратиться к элементам данных класса, используя оператор точку. Вместо этого класс доЛжен определять интерфейсные функции, с помощью которых программа может присваивать значения частным элементам. Интерфейсные функции в свою очередь, могут исследовать и скорректировать значения, которые программа пытается присвоить. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА ГЛОБАЛЬНОГО РАЗРЕШЕНИЯ ДЛЯ ЭЛЕМЕНТОВ КЛАССА Если вы рассмотрите функции в программе INFOHIDE.CPP, вы обнаружите, что имена параметров функции часто предваряются символами етр_, как показано ниже: int employee::assign_values(char *emp_name# long emp_id, float emp_salary) Символы етр_ использовались, чтобы избежать конфликта между именами параметров и именами элементов класса. Если подобный конфликт имен все же происходит, вы можете разрешить его, предваряя имена элементов класса именем класса и оператором глобального разрешения (::). Следующая функция использует оператор глобального разрешения и имя класса перед именем элементов класса. Исходя из этого, любой читающий эти операторы поймет, какие имена соответствуют классу employee: int employee::assign_values(char *name, long employee_id, float salary) { strcpy(employee::name, name); employee::employee_id = employee_id; if (salary < 50000.0)
180 Урок 22 { employee::salary = salary; return@); / / Успешно } else return(-1); / / Недопустимый оклад } При создании функций, работающих с элементами класса, вам следует использовать имя класса и оператор глобального разрешения, чтобы таким образом избежать конфликта имен. Использование оператора глобального разрешения для указания элементов класса При создании функций-элементов класса возможны ситуации, когда имя локальной переменной, которое вы используете внутри функции, конфликтует с именем элемента класса. По умолчанию имя локальной переменной будет переопределять имя элемента класса. Когда происходит подобный конфликт имен, функция может использовать имя класса и оператор глобального разрешения для доступа к элементам класса, как показано ниже: class_name::member_name = some_value; ЧАСТНЫЕ ЭЛЕМЕНТЫ КЛАССА НЕ ВСЕГДА ЯВЛЯЮТСЯ ДАННЫМИ В примере, представленном в этом уроке, частные элементы были всегда элементами данных. По мере того как определение класса становится более сложным, вы, возможно, захотите создать функции, используемые другими методами класса, но для оставшейся части программы доступ к таким функциям должен быть закрыт. В подобных случаях вы просто объявляете такие методы частными элементами. Если функция класса не объявлена как общая, программа не может вызвать такую функцию, используя оператор точку. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Управляя доступом программы к элементам класса, вы снижаете возможность ошибок, которые происходят в результате злоупотребления этими элементами. Чтобы управлять доступом к элементам класса, можно использовать частные элементы. Большинство определений классов C++, которые вы встретите, будут использовать сочетание частных и общих элементов. Одна из наиболее широко используемых операций, которую ваши программы выполняют при создании объекта, представляет собой инициализацию элементов данных объекта. Из урока 23 вы узнаете, что C++ позволяет вам оп-
Частные и общие данные 181 ределять специальную функцию, называемую конструктором, которая автоматически вызывается каждый раз при создании объекта. Используя конструктор, ваша программа легко может инициализировать элементы класса. До изучения урока 23 убедитесь, что освоили следующие основные концепции: 0 Элементы класса могут быть общими или частными. Программы могут напрямую обращаться к общим элементам, используя оператор точку. С другой стороны, к частным элементам можно обращаться, используя только методы класса. 0 Если это не оговорено явно, C++ считает все элементы частными. 0 Программы присваивают значения и обращаются к частным элементам, используя интерфейсные функции. 0 При создании программы, манипулирующей элементами класса, вы можете предварять имя каждого элемента именем класса и оператором глобального разрешения (::), например employee::пате, для избежания возможных конфликтов имен.
Урок 23 Конструктор и деструктор При создании объектов одной из наиболее широко используемых операций, которую вы будете выполнять в ваших программах, является инициализация элементов данных объекта. Как вы узнали из урока 22, единственным способом, с помощью которого вы можете обратиться к частным элементам данных, является использование функций класса. Чтобы упростить процесс инициализации элементов данных класса, C++ использует специальную функцию, называемую конструктором, которая запускается для каждого создаваемого вами объекта. Подобным образом C++ обеспечивает функцию, называемую деструктором, которая запускается при уничтожении объекта. В данном уроке конструктор и деструктор рассматриваются более подробно. К концу этого урока вы освоите следующие основные концепции: • Конструктор представляет собой метод класса, который облегчает вашим программам инициализацию элементов данных класса. • Конструктор имеет такое же имя, как и класс. • Конструктор не имеет возвращаемого значения. • Каждый раз, когда ваша программа создает переменную класса, C++ вызывает конструктор класса, если конструктор существует. • Многие объекты могут распределять память для хранения информации; когда вы уничтожаете такой объект, C++ будет вызывать специальный деструктор, который может освобождать эту память, очищая ее после объекта. • Деструктор имеет такое же имя, как и класс, за исключением того, что вы должны предварять его имя символом тильды (~). • Деструктор не имеет возвращаемого значения. Термины конструктор и деструктор не должны вас пугать. Вместо этого представьте конструктор как функцию, которая помогает вам строить (конструировать) объект. Подобно этому, деструктор представляет собой функцию, которая помогает вам уничтожать объект. Деструктор обычно используется, если при уничтожении объекта нужно освободить память, которую занимал объект.
Конструктор и деструктор 183 СОЗДАНИЕ ПРОСТОГО КОНСТРУКТОРА Конструктор представляет собой метод класса, который имеет такое же имя, как и класс. Например, если вы используете класс с именем employee, конструктор также будет иметь имя employee. Подобно этому, для класса с именем dogs конструктор будет иметь имя dogs. Если ваша программа определяет конструктор, C++ будет автоматически вызывать его каждый раз, когда вы создаете объект. Следующая программа CONSTRUC.CPP создает класс с именем employee. Программа также определяет конструктор с именем employee, который присваивает начальные значения объекту. Однако конструктор не возвращает никакого значения, несмотря на то, что он не объявляется как void. Вместо этого вы просто не указываете тип возвращаемого значения: class employee { public: employee(char *, long, float); //Конструктор void show_employee(void); int change_salary(float); long get_id(void); private: char name[64]; long employee_id; float salary; >; В вашей программе вы просто определяете конструктор так же, как любой другой метод класса: employee::employee(char *name, long employee_id, float salary) { strcpy(employee::name, name); employee: : employee__id = employee_id; if (salary < 50000.0) employee::salary = salary; else // Недопустимый оклад employee::salary = 0.0; > Как видите, конструктор не возвращает значение вызвавшей функции. Для него также не используется тип void. В данном случае конструктор использует оператор глобального разрешения и имя класса перед именем каждого элемента, как уже обсуждалось в уроке 23. Ниже приведена реализация программы CONSTRUC.CPP: #include <lostream.h> #include <string.h> class employee {
184 Урок 23 public: employee(char *, long, float); void show_employee(void); int change_salary(float); long get_JLd(void); private: char name[64]; long employee_id; float salary; >; employee::employee(char *name, long employee_id, float salary) { strcpy(employee::name, name); employee::employee_id = employee_id; if (salary < 50000.0) employee::salary = salary; else // Недопустимый оклад employee::salary = 0.0; } void employee::show_employee(void) { cout << "Служащий: " << name << endl; cout << "Номер служащего: " << employee_id << endl; cout << "Оклад: " << salary << endl; } void main(void) { employee worker("Happy Jamsa", 101, 10101.0); worker.show_employee(); } Обратите внимание, что за объявлением объекта worker следуют круглые скобки и начальные значения, как и при вызове функции. Когда вы используете конструктор, передавайте ему параметры при объявлении объекта: employee worker("Happy Jamsa", 101, 10101.0); Если вашей программе потребуется создать несколько объектов employee, вы можете инициализировать элементы каждого из них с помощью конструктора, как показано ниже: employee worker("Happy Jamsa", 101, 10101.0); employee secretary("John Doe", 57, 20000.0); employee manager("Jane Doe", 1022, 30000.0);
Конструктор и деструктор 185 Представление о конструкторе Конструктор представляет собой специальную функцию, которую C++ автоматически, вызывает каждый раз при создании объекта. Обычное назначение конструктора заключается в инициализации элементов данных объекта. Конструктор имеет такое же имя, как и класс. Например, класс с именем file использует конструктор с именем file. Вы определяете конструктор внутри своей программы так же, как и любой метод класса. Единственное различие заключается в том, что конструктор не имеет возвращаемого значения. Когда вы позже объявляете объект, вы можете передавать параметры конструктору, как показано ниже: class_name object(valuel, value2# value3) Конструкторы и параметры по умолчанию Как вы уже знаете из урока 15, C++ позволяет указывать значения по умолчанию для параметров функции. Если пользователь не указывает каких-либо параметров, функция будет использовать значения по умолчанию. Конструктор не является исключением; ваша программа может указать для него значения по умолчанию так же, как и для любой другой функции. Например, следующий конструктор employee использует по умолчанию значение оклада равным 10000.0, если программа не указывает оклад при создании объекта. Однако программа должна указать имя служащего и его номер: employee::employee(char *name, long employee_ld, float salary = 10000.00) { strcpy(employee::name, name); employee::employee_id = employee_id; if (salary < 50000.0) employee::salary = salary; else // Недопустимый оклад employee::salary = 0.0; } Перегрузка конструкторов Как вы уже знаете из урока 13, C++ позволяет вашим программам перегружать определения функций, указывая альтернативные функции для других типов параметров. C++ позволяет вам также перегружать конструкторы. Следующая программа CONSOVEELCPP перегружает конструктор employee. Первый конструктор требует, чтобы программа указывала имя служащего, номер служащего и оклад. Второй конструктор запрашивает пользователя
186 Урок 23 ввести требуемый оклад, если программа не указывает его: employee::employee(char *name, long employee_id) { strcpy(employee::name, name); employee::employee_id = employee_id; do { cout << "Введите оклад для " << name << " меньше $50000: "; cln >> employee::salary; } while (salary >= 50000.0); } Внутри определения класса программа должна указать прототипы для обоих конструкторов, как показано ниже: class employee { public: employee(char *, long, float); employee(char *, long); void show_employee(void); int change_salary(float); long get_id(void); private: char name[64]; long employee__id; float salary; > Ниже приведена реализация программы CONSOVER.CPP: #include <lostream.h> #include <string.h> class employee { public: employee(char *, long, float); employee(char *, long); void show_employee(void); int change_salary(float); long get_id(void); private: char name[64] ; long employee_id; float salary; }; employee::employee(char *name, long employee_id, float salary) { strcpy(employee::name, name); employee::employee_id = employee_id; Прототипы перегруженных функций
Конструктор и деструктор 187 if (salary < 50000.0) employee::salary = salary; else // Недопустимый оклад employee::salary = 0.0; } employee: :employee (char *name, long employee_id) { strcpy(employee::name, name); employee:: employ ee_ id = employee_id; do { cout << "Введите оклад для " << name << " меньше $50000: "; cin >> employee::salary; } while (salary >= 50000.0); } void employee::show_employee(void) { cout << "Служащий: " << name << endl; cout << "Номер служащего: " << employee_id << endl; cout << "Оклад: " << salary << endl; } void main (void) { employee worker("Happy Jamsa", 101, 10101.0); employee manager("Jane Doe", 102); worker.show_employee(); manager.show_employee(); } Если вы откомпилируете и запустите эту программу, на вашем экране появится запрос ввести оклад для Jane Doe. Когда вы введете оклад, программа отобразит информацию об обоих служащих. ПРЕДСТАВЛЕНИЕ О ДЕСТРУКТОРЕ Деструктор автоматически запускается каждый раз, когда программа уничтожает объект. В следующих уроках вы узнаете, как создать списки объектов, которые увеличиваются или уменьшаются по мере выполнения программы. Чтобы создать такие динамические списки, ваша программа для хранения объектов распределяет память динамически (что вы еще не научились делать). К настоящему моменту вы можете создавать и уничтожать объекты в процессе выполнения программы. В таких случаях имеет смысл применение деструкторов.
188 Урок 23 Каждая из созданных вами до сих пор программ создавала объекты в самом начале своего выполнения, просто объявляя их. При завершении программ C++ уничтожал объекты. Если вы определяете деструктор внутри своей программы, C++ будет автоматически вызывать деструктор для каждого объекта, когда программа завершается (т. е. когда объекты уничтожаются). Подобно конструктору, деструктор имеет такое же имя, как и класс объекта. Однако в случае деструктора вы предваряете его имя символом тильды (~), как показано ниже: ~class_name (void) " указывает деструктор { // Операторы деструктора } В отличие от конструктора вы не можете передавать параметры деструктору. Следующая программа DESTRUCT.CPP определяет деструктор для класса employee: void employee::-employee(void) { cout << "Уничтожение объекта для " << name << endl; } В данном случае деструктор просто выводит на ваш экран сообщение о том, что C++ уничтожает объект. Когда программа завершается, C++ автоматически вызывает деструктор для каждого объекта. Ниже приведена реализация программы DESTRUCT.CPP: #include <iostream.h> #include <string.h> class employee { public: employee(char *, long, float); -employee(void); void show_employee(void); int change_salary(float); long get_id(void); private: char name[64]; long employee_id; float salary; }; employee::employee(char *name, long employee_id, float salary) { strcpy(employee::name, name);
Конструктор и деструктор 189 employee::employee_id = employee_id; if (salary < 50000.0) employee::salary = salary; else // Недопустимый оклад employee::salary = 0.0; } void employee::-employee(void) { cout << "Уничтожение объекта для " << name << endl; } void employee::show_employee(void) { cout << "Служащий: " << name << endl; cout << "Номер служащего: " << employee_id << endl; cout << "Оклад: " << salary << endl; } void main(void) { employee worker("Happy Jamsa", 101, 10101.0); worker.show_employee(); } Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод: С:\> DESTRUCT <ENTER> Служащий: Happy Jams a Номер служащего: 101 Оклад: 10101 Уничтожение объекта для Happy Jams a Как видите, программа автоматически вызывает деструктор, без какого- либо явного вызова функции деструктора. До настоящего момента вашим программам, вероятно, не требовалось использовать деструктор. Однако, когда программы начнут распределять память знутри объектов, вы обнаружите, что деструктор обеспечивает удобный способ освобождения памяти при уничтожении объекта. Деструкторы Деструктор представляет собой функцию, которую C++ автоматически запускает, когда он или ваша программа уничтожает объект. Деструктор имеет такое же имя, как и класс объекта; однако вы предваряете имя деструктора символом тильды (~), например -employee. В
190 Урок 23 своей программе вы определяете деструктор точно так же, как и любой другой метод класса. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Конструкторы и деструкторы представляют собой специальные функции класса, которые ваша программа автоматически вызывает при создании или уничтожении объекта. Большинство программ используют конструктор для инициализации элементов данных класса. Простые программы, создаваемые сейчас вами, вероятно, не потребуют использования деструктора. Из урока 24 вы узнаете, как перегружать операторы. Другими словами, вы можете переопределить символ плюс таким образом, что он будет добавлять содержимое одной строки к другой. Как вы уже знаете, тип (например, char, float и int) определяет набор значений, которые может хранить переменная, и набор операций, которые ваши программы могут выполнять над этой переменной. Когда вы определяете класс, вы по существу определяете тип. C++ позволяет вам указать, как ведут себя операторы с данным типом. До изучения урока 24 убедитесь, что освоили следующие основные концепции: 0 Конструктор представляет собой специальную функцию, которую ваша программа автоматически вызывает каждый раз при создании объекта. Конструктор имеет такое же имя, как и класс объекта. 0 Конструктор не имеет возвращаемого значения, но вы не указываете ему тип void. Вместо этого вы просто не указываете возвращаемое значение вообще. 0 Когда ваша программа создает объект, она может передать параметры конструктору во время объявления объекта. 0 C++ позволяет вам перегружать конструкторы и разрешает использовать значения по умолчанию для параметров. 0 Деструктор представляет собой специальную функцию, которую ваша программа вызывает автоматически каждый раз при уничтожении объекта. Деструктор имеет такое же имя, как и класс объекта, но его имя предваряется символом тильды (~).
Урок 24 Перегрузка операторов Как вы уже знаете, тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа int ваша программа может выполнять сложение, вычитание, умножение и деление. С другой стороны, использование оператора плюс для сложения двух строк лишено всякого смысла. Когда вы определяете в своей программе класс, то по существу вы определяете новый тип. А если так, C++ позволяет вам определить операции, соответствующие этому новому типу. Перегрузка оператора состоит в изменении смысла оператора (например, оператора плюс (+), который обычно в C++ используется для сложения) при использовании его с определенным классом. В данном уроке вы определите класс string и перегрузите операторы плюс и минус. Для объектов типа string оператор плюс будет добавлять указанные символы к текущему содержимому строки. Подобным образом оператор минус будет удалять каждое вхождение указанного символа из строки. К концу данного урока вы изучите следующие основные концепции: • Вы перегружаете операторы для улучшения удобочитаемости ваших программ, но перегружать операторы следует только в том случае, если это упрощает понимание вашей программы. • Для перегрузки операторов программы используют ключевое слово C++ operator. • Переопределяя оператор, вы указываете функцию, которую C++ вызывает каждый раз, когда класс использует перегруженный оператор. Эта функция, в свою очередь, выполняет соответствующую операцию. • Если ваша программа перегружает оператор для определенного класса, то CMbicjf этого оператора изменяется только для указанного класса, оставшаяся часть программы будет продолжать использовать этот оператор для выполнения его стандартных операций. • C++ позволяет перегружать большинство операторов, за исключением четырех, перечисленных в таблице 24, которые программы не могут перегружать. Перегрузка операторов может упростить наиболее общие операции класса и улучшить читаемость программы. Найдите время для эксперимента с
192 Урок 24 программами, представленными в этом уроке, и вы обнаружите, что перегрузка операторов выполняется очень просто. ПЕРЕГРУЗКА ОПЕРАТОРОВ ПЛЮС И МИНУС Когда вы перегружаете оператор для какого-либо класса, то смысл данного оператора не изменяется для переменных других типов. Например, если вы перегружаете оператор плюс для класса string, то смысл этого оператора не изменяется, если необходимо сложить два числа. Когда компилятор C++ встречает в программе оператор, то на основании типа переменной он определяет ту операцию, которая должна быть выполнена. Ниже приведено определение класса, создающее класс string. Этот класс содержит один элемент данных, который представляет собой собственно символьную строку. Кроме того, этот класс содержит несколько различных методов и пока не определяет каких-либо операторов: class string { public: string(char *); // Конструктор void str_append(char *); void chr_minus(char); void show_string(void); private: char data[256] ; }; Как видите, класс определяет функцию str append, которая добавляет указанные символы к содержимому строки класса. Аналогичным образом функция chr minus- удаляет каждое вхождение указанного символа из строки класса. Следующая программа STRCLASS.CPP использует класс string для создания двух объектов символьных строк и манипулирования ими. #include <iostream.h> #include <string.h> class string { public: string(char *); // Конструктор void str_append(char *); void chr_minus(char); void show_string(void); private: char data[256]; }; string::string(char *str) { strcpy(data, str); }
Перегрузка операторов 193 void string::str_append(char *str) { streat(data, str); } void string::chr_minus(char letter) { char temp[256]; int i, j; for (i = 0, j = 0; data[i]; i++) // Эту букву необходимо удалить? if (data[i] != letter) // Если нет, присвоить ее temp temp[j++] = data[i]; temp[j] = NULL; // Конец temp // Копировать содержимое temp обратно в data strcpy(data, temp); } void string::show_string(void) { cout << data << endl; } void main(void) { string title( "Учимся программировать на языке C++"); string lesson("Перегрузка операторов"); title.show_string(); title.str_append(" я учусь!"); title.show_string(); lesson.show_string(); lesson.chr_minus('p'); lesson.show_string(); } Как видите, программа использует функцию str_append для добавления символов к строковой переменной title. Программа также использует функцию chr_minus для удаления каждой буквы р из символьной строки lesson. В данном случае программа использует вызовы функции для выполнения этих операций. Однако, используя перегрузку операторов, программа может выполнять идентичные операции с помощью операторов плюс (+) и минус (-). При перегрузке оператора используйте ключевое слово C++ operator^вместе с прототипом и определением функции, чтобы сообщить компилятору
194 Урок 24 C++, что класс будет использовать этот метод как оператор. Например, следующее определение класса использует ключевое слово operator, чтобы назначить операторы плюс и минус функциям strjappend и chrjninus внутри класса string: class string { public: string(char *); // Конструктор void operator +(char *);| void operator -(char); | Определение операторов класса void show_string (void); private: char data[256]; >; Как йидите, класс перегружает операторы плюс и минус. Как уже упоминалось, когда класс перегружает оператор, он должен указать функцию, которая реализует операцию, соответствующую этому оператору. В случае оператора плюс определение такой функции становится следующим: void string::operator +(char *str) { strcat(data, str); } Как видите, определение этой функции не содержит имени, поскольку здесь определяется перегруженный оператор класса. Для перегрузки оператора плюс программа не изменила обработку, которая осуществляется внутри функции (код этой функции идентичен коду предыдущей функции strjappend). Вместо этого программа просто заменила имя функции ключевым словом operator и соответствующим оператором. Следующая программа OPOVERLD.CPP иллюстрирует использование перегружаемых операторов плюс и минус: #include <lostream.h> #include <string.h> class string { public: string(char *); // Конструктор void operator +(char *); void operator -(char); void show_string(void); private: char data[256]; }; string::string(char *str) { strcpy(data, str); }
Перегрузка операторов 195 void string::operator +(char *str) { streat(data, str); } void string::operator -(char letter) { char temp[256]; int i, j; for (i = 0, j = 0; data[i]; i++) if (data[i] != letter) temp[j++] = data[i]; temptj] = NULL; strcpy(data, temp); } void string::show_string(void) { cout << data << endl; } void main(void) { string title( "Учимся программировать на C++"); string lesson("Перегрузка операторов"); title.show_string(); title + " я учусь!"; title.show_string(); lesson.show_string(); lesson - 'p'; lesson.show_string(); } Как видите, программа использует перегруженные операторы: // Добавить текст " я учусь!" title + " я учусь!"; lesson - 'р'; // Удалить букву 'р' В данном случае синтаксис оператора законен, но немного непривычен. Обычно вы используете оператор плюс в выражении, которое возвращает результат, например, как в операторе some str = title + "текст ";. Когда вы определяете оператор, C++ предоставляет вам полную свободу в отношении поведения оператора. Однако, как вы помните, ваша цель при перегрузке
196 Урок 24 операторов состоит в том, чтобы упростить понимание ваших программ. Поэтому следующая программа STR_OVER.CPP немного изменяет предыдущую программу, чтобы позволить ей выполнять операции над переменными типа string, используя синтаксис, который более согласуется со стандартными операторами присваивания: #include <iostream.h> #include <string.h> class string { public: string(char *); // Конструктор char * operator +(char *); char * operator -(char); void show_string(void); private: char data[256]; }; string::string(char *str) { strcpy(data, str); } char * string::operator +(char *str) { return(strcat(data, str)); } char * string::operator -(char letter) { char temp[256]; int i, j; for (i = 0, j = 0; data[i]; i++) if (data[i] != letter) temp[j++] = data[i]; temp[j] = NULL; return(strcpy(data, temp)); } void string::show_string(void) { cout << data << endl; > void main (void) { string title("Учимся программировать на C++"); string lesson("IIeperpy3xa операторов");
Перегрузка операторов 197 title. show_st;ring (); title = title + " я учусь!"; title. show_string (); lesson.show_string(); lesson = lesson - 'p'; lesson.show_string(); } Изменив перегруженные операторы плюс и минус таким образом, чтобы они возвращали указатель на символьную строку, программа может теперь использовать эти операторы в привычном для оператора присваивания виде: title = title + " учимся программировать!"; lesson = lesson - 'р'; Второй пример При создании ваших собственных типов данных с помощью классов наиболее общей операцией будет проверка, являются ли два объекта одинаковыми. Используя перегрузку, ваши программы могут перегрузить операторы равенства (==), неравенства (!=) или другие операторы сравнения. Следующая программа COMP_STR.CPP добавляет новый оператор в класс string, который проверяет, равны ли два объекта string. Используя перегрузку операторов, ваши программы могут проверять, содержат ли строковые объекты одинаковые строки, как показано ниже: if (some_string == another_string) Ниже приведена реализация программы COMP_STR.CPP: #include <iostream.h> #include <string.h> class string { public: string(char *); // Конструктор char * operator +(char *); char * operator -(char); int operator ==»(string); void show_string(void); private: char data[256] ; }; string::string(char *str) { strcpy(data, str); }
198 Урок 24 char * string::operator +(char *str) { return(strcat(data, str)); > char * string::operator -(char letter) { char temp[256]; int i, j; for (i = 0, j = 0; data[i]; i++) if (data[i] != letter) temp[j++] = data[i]; temp[j] = NULL; return(strcpy(data, temp)); } int string::operator ==(string str) { int i; for (i = 0; data[i] == str.data[i]; i++) if ((data[i] == NULL) && (str.data[i] == NULL)) returnA); // Равно return @); / / He равно > void string::show_string(void) { cout << data << endl; } void main (void) { string title( "Учимся программировать на C++"); string lesson("Перегрузка операторов"); string str( "Учимся программировать на C++"); if (title == lesson) cout << "title и lesson равны" << endl; if (str == lesson) cout << "str и lesson равны" << endl; if (title == str) cout << "title и str равны" << endl; } Как видите, перегружая операторы подобным образом, вы упрощаете понимание ваших программ.
199 Перегрузка операторов ОПЕРАТОРЫ, КОТОРЫЕ ВЫ НЕ МОЖЕТЕ ПЕРЕГРУЗИТЬ В общем случае ваши программы могут перегрузить почти все операторы C++. В табл. 24 перечислены операторы, которые C++ не позволяет перегружать. Таблица 24. Операторы C++, которые ваши программы не могут перегрузить. Оператор ¦ ?; Назначение Выбор элемента Указатель на элемент Разрешение области видимости Условный оператор сравнения Пример object, member object. *member classname:: member с = (a > b) ? a : b; ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Перегрузка операторов — это возможность назначать новый смысл операторам при использовании их с определенным классом. Используя перегрузку операторов, вы можете повысить удобочитаемость ваших программ и облегчить их понимание, выражая операции класса более понятным образом. Из урока 25 вы узнаете, как разделить данные между объектами с помощью элемента static и как использовать методы класса, когда никакие объекты класса не объявляются. До изучения урока 25 убедитесь, что вы освоили следующее: 0 Чтобы перегрузить оператор, вы должны определить класс, которому оператор будет назначен. 0 Когда вы перегружаете оператор, перегрузка действует только для класса, в котором он определяется. Если программа использует оператор с неклассовыми переменными (например, переменными типа Шили float), используется стандартное определение оператора. 0 Чтобы перегрузить оператор класса, используйте ключевое слово C++ operator для определения метода класса, который C++ вызывает каждый раз, когда переменная класса использует оператор. 0 C++ не позволяет вашим программам перегружать оператор выбора элемента (.), оператор указателя на элемент (.*), оператор разрешения области видимости (::) и условный оператор сравнения (?:).
Урок 25 Статические функции и элементы данных До настоящего момента каждый создаваемый вами объект имел свой собственный набор элементов данных. В зависимости от назначения вашего приложения могут быть ситуации, когда объекты одного и того же класса должны совместно использовать один или несколько элементов данных. Например, предположим, что вы пишете программу платежей, которая отслеживает рабочее время для 1000 служащих. Для определения налоговой ставки программа должна знать условия, в которых работает каждый служащий. Пусть для этого используется переменная класса state_of_work. Однако, если все служащие работают в одинаковых условиях, ваша программа могла бы совместно использовать этот элемент данных для всех объектов типа employee. Таким образом, ваша программа уменьшает необходимое количество памяти, выбрасывая 999 копий одинаковой информации. Для совместного использования элемента класса вы должны объявить этот элемент как static (статический). Этот урок рассматривает шаги, которые вы должны выполнить для совместного использования элемента класса несколькими объектами. К концу этого урока вы освоите следующие основные концепции: • C++ позволяет иметь объекты одного и того же типа, которые совместно используют один или несколько элементов класса. • Если ваша программа присваивает значение совместно используемому элементу, то все объекты этого класса сразу же получают доступ к этому новому значению. • Для создания совместно используемого элемента данных класса вы должны предварять имя элемента класса ключевым словом static. • После того как программа объявила элемент класса как staticy она должна объявить глобальную переменную (вне определения класса), которая соответствует этому совместно используемому элементу класса. • Ваши программы могут использовать ключевое слово static, чтобы сделать метод класса вызываемым в то время, как программа, возможно, еще не объявила каких-либо объектов данного класса. СОВМЕСТНОЕ ИСПОЛЬЗОВАНИЕ ЭЛЕМЕНТА ДАННЫХ Обычно, когда вы создаете объекты определенного класса, каждый объект получает свой собственный набор элементов данных. Однако возможны та-
Статические функции и элементы данных 201 кие ситуации, при которых объектам одного и того же класса необходимо совместно использовать один или несколько элементов данных {статические элементы данных). В таких случаях объявляйте элементы данных как общие или частные, а затем предваряйте тип ключевым словом static, как показано ниже: private: static int shared_value; После объявления класса вы должны объявить элемент как глобальную переменную вне класса, как показано ниже: int class_name::shared_value; Следующая программа SHARE_IT.CPP определяет класс bookseries, совместно использующий элемент page_c&unt, который является одинаковым для всех объектов (книг) класса (серии). Если программа изменяет значение этого элемента, изменение сразу же проявляется во всех объектах класса: #include <iostream.h> #include <string.h> class book_series { public: book_series(char *, char *, float); void show_book(void); void set_pages(int); private: static int page_count; char title[64]; char author[64]; float price; }; int book_series::page_count; void book_series::set_pages(int pages) { page_count = pages; } book_series::book_series(char *title# char *author/ float price) { strcpy(book_series::title, title); strcpy(book_series::author, author); book_series::price = price; } void book_series::show_book(void)
202 Урок 25 { cout << "Заголовок: " << title << endl; cout << "Автор: " << author << endl; cout << "Цена: " << price << endl; cout << "Страницы: " << page_count << endl; } void main (void) { book_series programming( "Учимся программировать на C++", "Jamsa", 22.95); book_series word( "Учимся работать с Word для Windows", "Wyatt", 19.95); word.set_pagesB56); programming.show_book(); word.show_book(); cout << endl << "Изменение page_count " << endl; programming.s et_pagesE12); programming.show_book(); word.show_book(); } Как видите, класс объявляет page_count как static int. Сразу же за определением класса программа объявляет элемент page_count как глобальную переменную. Когда программа изменяет элемент page_count, изменение сразу же проявляется во всех объектах класса bookjseries. Совместное использование элементов класса В зависимости от вашей программы возможны ситуации, когда вам потребуется совместно использовать определенные данные несколькими экземплярами объекта. Для этого объявите такой элемент как static. Далее объявите этот элемент вне класса как глобальную переменную. Любые изменения, которые ваша программа делает с этим элементом, будут немедленно отражены в объектах данного класса. Использование элементов с атрибутами public static, если объекты не существуют Как вы только что узнали, при объявлении элемента класса как static этот элемент совместно используется всеми объектами данного класса. Однако возможны ситуации, когда программа еще не создала объект, но ей необходимо использовать элемент. Для использования элемента ваша программа
Статические функции и элементы данных 203 должна объявить его как public и static. Например, следующая программа USE_MBR.CPP использует элементpage_count из класса book_series, даже если объекты этого класса не существуют: #include <lostream.h> #include <string.h> class book_series { public: static int page_count; private: char title[64]; char author[64]; float price; }; int book_series::page_count; void main(void) { book_series::page_count = 256; cout << "Текущее значение page_count равно " << book_series::page_count << endl; } В данном случае, поскольку класс определяет элемент класса page_count как public, программа может обратиться к этому элементу класса, даже если объекты класса bookjseries не существуют. ИСПОЛЬЗОВАНИЕ СТАТИЧЕСКИХ ФУНКЦИЙ-ЭЛЕМЕНТОВ Предыдущая программа иллюстрировала использование статических элементов данных. Подобным образом C++ позволяет вам определить статические функции-элементы (методы). Если вы создаете статический метод, ваша программа может вызывать такой метод, даже если объекты не были созданы. Например, если класс содержит метод, который может быть использован для данных вне класса, вы могли бы сделать этот метод статическим. Ниже приведен класс menu, который использует esc-последовательность драйвера ANSI для очистки экрана дисплея. Если в вашей системе установлен драйвер ANSI.SYS, вы можете использовать метод clear_screen для очистки экрана. Поскольку этот метод объявлен как статический, программа может использовать его, даже если объекты типа menu не существуют. Следующая программа CLR_SCR.CPP использует метод clearjscreen для очистки экрана дисплея:
204 Урок 25 #include <lostream.h> class menu { public: static void clear_screen(void); // Здесь должны быть другие методы private: int number_of_menu_options; }; void menu::clear_screen(void) { cout << 'N033' << "W't } void main (void) { menu::clear_screen(); } Так как программа объявляет элемент clearjscreen как статический, она может использовать эту функцию для очистки экрана, даже если объекты типа menu не существуют. Функция clearscreen использует esc-последователь- ность ANSI Esc[2Jдля очистки экрана. Использование в ваших программах методов класса По мере создания методов класса возможны ситуации, когда функция, созданная вами для использования классом, может быть полезна для операций вашей программы, которые не включают объекты класса. Например, в классе menu была определена функция clearjscreen, которую вы, возможно, захотите использовать в программе. Если ваш класс содержит метод, который вы захотите использовать вне объекта класса, поставьте перед его прототипом ключевое слово static и объявите этот метод как public: public: static void clear_screen(void); Внутри вашей программы для вызова такой функции используйте оператор глобального разрешения, как показано ниже: menu::clear_screen(); ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что, если предварять элемент данных класса ключевым словом static, все объекты данного класса могут совместно использо-
Статические функции и элементы данных 205 вать этот элемент. Когда элемент данных является общим, ваши программы могут обращаться к его значению, даже если объекты этого класса не существуют. Подобно этому, если ваши программы предваряют общий метод класса ключевым словом static, они могут использовать эту функцию для операций, которые не включают объекты класса. Из урока 26 вы узнаете, как использовать наследование для построения объекта из одного или нескольких уже существующих объектов. Использование наследования для создания новых объектов может сэкономить огромные усилия, затрачиваемые на программирование. До изучения урока 26 убедитесь, что освоили следующие основные концепции: 0 Когда вы объявляете элемент класса как static, то такой элемент может совместно использоваться всеми объектами данного класса. 0 После того как ваша программа объявляет элемент класса как static, она должна вне определения класса объявить глобальную переменную, соответствующую совместно используемому элементу класса. 0 Если вы объявляете элемент как public и static, ваша программа может использовать такой элемент, даже если объекты данного класса не существуют. Для обращения к этому элементу ваша программа должна использовать оператор глобального разрешения, например class_name::member_name. 0 Если вы объявляете общую статическую функцию-элемент, ваша программа может вызывать эту функцию, даже если объекты данного класса не существуют. Для вызова данной функции программа должна использовать оператор глобального разрешения, например menu:.clear_screen().
Наследование и шаблоны Главное преимущество объектно-ориентированного программирования заключается в том, что класс, который вы создаете для одной программы, часто может быть использован в другой. Как вы узнаете из этой части, C++ не только позволяет вашим программам повторно использовать классы, но также позволяет строить один класс из другого. Когда вы строите один класс из другого, новый класс наследует характеристики начального класса. В этой части вы освоите, как использовать возможности наследования C++ для экономии значительного объема программирования. К концу данной части вы очень много узнаете о концепциях объектно-ориентированного программирования. Урок 26. Наследование. Урок 27. Множественное наследование. Урок 28. Частные элементы и друзья. Урок 29. Использование шаблонов функций. Урок 30. Использование шаблонов классов.
Урок 26 Наследование Цель объектно-ориентированного программирования состоит в повторном использовании созданных вами классов, что экономит ваше время и силы. Если вы уже создали некоторый класс, то возможны ситуации, что новому классу нужны многие или даже все особенности уже существующего класса, и необходимо добавить один или несколько элементов данных или функций. В таких случаях C++ позволяет вам строить новый объект, используя характеристики уже существующего объекта. Другими словами, новый объект будет наследовать элементы существующего класса (называемого базовым классом). Когда вы строите новый класс из существующего, этот новый класс часто называется производным классом. В этом уроке впервые вводится наследование классов в C++ . К концу данного урока вы изучите следующие основные концепции: • Ели ваши программы используют наследование, то для порождения нового класса необходим базовый класс, т.е. новый класс наследует элементы базового класса. • Для инициализации элементов производного класса ваша программа должна вызвать конструкторы базового и производного классов. • Используя оператор точку, программы могут легко обращаться к элементам базового и производного классов. • В дополнение к общим (public) (доступным всем) и частным (private) (доступным методам класса) элементам C++ предоставляет защищенные (protected) элементы, которые доступны базовому и производному классам. • Для разрешения конфликта имен между элементами базового и производного классов ваша программа может использовать оператор глобального разрешения, указывая перед ним имя базового или производного класса. Наследование является фундаментальной концепцией объектно-ориентированного программирования. Выберите время для экспериментов с программами, представленными в этом уроке. И вы обнаружите, что реально наследование реализуется очень просто и может сохранить огромные усилия, затрачиваемые на программирование.
210 Урок 26 ПРОСТОЕ НАСЛЕДОВАНИЕ Наследование представляет собой способность производного класса наследовать характеристики существующего базового класса. Например, предположим, что у вас есть базовый класс employee: class employee { public: employee(char *, char *, float); void show_employee(void); private: char name[64]; char position[64]; float salary; }; Далее предположим, что вашей программе требуется класс manager, который добавляет следующие элементы данных в класс employee: float annual_bonus; char сompany_c ar[б4 ]; int stock_options; В данном случае ваша программа может выбрать два варианта: во-первых, программа может создать новый класс manager, который дублирует многие элементы класса employee, или программа может породить класс типа manager из базового класса employee. Порождая класс manager из существующего класса employee, вы снижаете объем требуемого программирования и исключаете дублирование кода внутри вашей программы. Для определения этого класса вы должны указать ключевое слово class, имя manager, следующее за ним двоеточие и имя employee, как показано ниже: class manager : public employee { Производный класс II Здесь определяются элементы — Базовый класс }; Ключевое слово public, которое предваряет имя класса employee, указывает, что общие (public) элементы класса employee также являются общими и в классе manager. Например, следующие операторы порождают класс manager. class manager : public employee { public: manager (char *, char *, char *, float, float, int); void show_manager(void); private: float annual_bonus; char company_car[64]; int stock_options; };
Наследование 211 Когда вы порождаете класс из базового класса, частные элементы базового класса доступны производному классу только через интерфейсные функции базового класса. Таким образом, производный класс не может напрямую обратиться к частным элементам базового класса, используя оператор точку. Следующая программа MGR_EMP.CPP иллюстрирует использование наследования в C++ , создавая класс manager из базового класса employee: #include <lostream.h> #include <string.h> class employee { public: employee (char *, char *, float); void show_employee(void); private: char name[64]; char position[64]; float salary; >; employee::employee(char *name/ char *position,float salary) { strcpy(employee::name, name); strcpy(employee::position, position); employee::salary = salary; } void employee::show_employee(void) { cout << "Имя: " << name << endl; cout << "Должность: " << position << endl; cout << "Оклад: $" << salary << endl; } class manager : public employee { public: manager (char *, char *, char *, float, float, int); void show_manager(void); private: float annual_bonus; char company_car[64]; int stock_options; }; manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) : employee(name, position, salary)
212 Урок 26 { strcpy(manager::company_car, company_car); manager::annual_bonus = bonus; manager::stock_options = stock_options; } void manager::show_manager(void) { show_employee(); cout << "Машина фирмы: " << company_car << endl; cout << "Ежегодная премия: $" << annual_bonus << endl; cout << "Фондовый опцион: " « stock_options << endl; > void main(void) { employee worker("Джон Дой", "Программист", 35000); manager boss("Джейн Дой", "Вице-президент ", "Lexus", 50000.0, 5000, 1000); worker.show_employee(); boss. show__manager (); } Как видите, программа определяет базовый класс employee, а затем определяет производный класс manager. Обратите внимание на конструктор manager. Когда вы порождаете класс из базового класса, конструктор производного класса должен вызвать конструктор базового класса. Чтобы вызвать конструктор базового класса, поместите двоеточие сразу же после конструктора производного класса, а затем укажите имя конструктора базового класса с требуемыми параметрами: manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options) : employee(name, position, salary) { Конструктор базового класса strcpy(manager::company_car, company_car); manager::annual_bonus = bonus; manager::fitock_options = stock_options; } Также обратите внимание, что функция showjnanager вызывает функцию show employee, которая является элементом класса employee. Поскольку класс manager является производным класса employee, класс manager может обращаться к общим элементам класса employee, как если бы все эти элементы были определены внутри класса manager.
Наследование 213 Представление о наследовании Наследование представляет собой способность производного класса наследовать характеристики существующего базового класса. Простыми словами это означает, что, если у вас есть класс, чьи элементы данных или функции-элементы могут быть использованы новым классом, вы можете построить этот новый класс в терминах существующего (или базового) класса. Новый класс в свою очередь будет наследовать элементы (характеристики) существующего класса. Использование наследования для построения новых классов сэкономит вам значительное время и силы на программирование. Объектно-ориентированное программирование широко использует наследование, позволяя вашей программе строить сложные объекты из небольших легко управляемых объектов. Второй пример Предположим, например, что вы используете следующий базовый класс book внутри существующей программы: class book { public: book(char *, char *, int); void show_book(void); private: char title[64]; char author[64]; int pages; }; Далее предположим, что программе требуется создать класс library_card, который будет добавлять следующие элементы данных в класс book: char catalog[64]; int checked_out; // 1, если проверена, иначе О Ваша программа может использовать наследование, чтобы породить класс library cardиз класса book, как показано ниже: class library_card : public book { public: library_card(char *, char *, int, char *, int); void show_card(void); private: char catalog[64]; int checked_out; }; Следующая программа BOOKCARD.CPP порождает класс library card из класса book.
214 Урок 26 #include <iostream.h> #include <string.h> class book { public: book(char *, char *, int); void show_book(void); private: char title[64]; char author[64]; int pages; }; book::book(char *title, char *author, int pages) { strcpy(book::title, title); s t rcpy(book::author, author); book::pages = pages; } void book::show_book(void) { cout << "Название: " << title << endl; cout << "Автор: " << author << endl; cout << "Страниц: " << pages << endl; } class library_card : public book { public: library_card(char *, char *, int, char *, int); void show_card"(void); private: char catalog[64]; int checked_out; }; library_card::library_card(char *title, char *author, int pages, char *catalog, int checked_out) : book(title, author, pages) { strcpy (library_card: :catalog, catalog).? library_card::checked_out = checked_out; } void library_card::show_card(void) { show_book(); cout << "Каталоп " << catalog << endl;
Наследование 215 i f (checked_out) cout << "Статус: проверена" << endl; else cout << "Статус: свободна" << endl; } void main(void) { library_card card( "Учимся программировать на языке C++ ", "Jamsa", 272, 01СРР", lb- card .show_card(); } Как и ранее, обратите внимание, что конструктор library_cardвызывает конструктор класса book для инициализации элементов класса book. Кроме того, обратите внимание на использование функции-элемента showjbook класса book внутри функции showjcard. Поскольку класс library_card наследует методы класса book, функция showjcard может вызвать этот метод (show book) без помощи оператора точки, как если бы этот метод был методом класса library card. ЧТО ТАКОЕ ЗАЩИЩЕННЫЕ ЭЛЕМЕНТЫ При изучении определений базовых классов вы можете встретить элементы, объявленные как public, private и protected (общие, частные и защищенные). Как вы знаете, производный класс может обращаться к общим элементам базового класса, как будто они определены в производном классе. С другой стороны, производный класс не может обращаться к частным элементам базового класса напрямую. Вместо этого для обращения к таким элементам производный класс должен использовать интерфейсные функции. Защищенные элементы базового класса занимают промежуточное положение между частными и общими. Если элемент является защищенным, объекты производного класса могут обращаться к нему, как будто он является общим. Для оставшейся части вашей программы защищенные элементы являются как бы частными. Единственный способ, с помощью которого ваши программы могут обращаться к защищенным элементам, состоит в использовании интерфейсных функций. Следующее определение класса book использует метку protected, чтобы позволить классам, производным от класса book, обращаться к элементам title, author и pages напрямую, используя оператор точку: class book { public: book(char *, char *, int); void show_book(void); protected: char title[64]; char author[64]; int pages; };
216 Урок 26 Если вы предполагаете, что через некоторое время вам придется породить новые классы из создаваемого сейчас класса, установите, должны ли будущие производные классы напрямую обращаться к определенным элементам создаваемого класса, и объявите такие элементы защищенными, а не частными. Защищенные элементы обеспечивают доступ и защиту Как вы уже знаете, программа не может обратиться напрямую к частным элементам класса. Для обращения к частным элементам программа должна использовать интерфейсные функции, которые управляют доступом к этим элементам. Как вы, вероятно, заметили, наследование упрощает программирование в том случае, если производные классы могут обращаться к элементам базового класса с помощью оператора точки. В таких случаях ваши программы могут использовать защищенные элементы класса. Производный класс может обращаться к защищенным элементам базового класса напрямую, используя оператор точку. Однако оставшаяся часть вашей программы может обращаться к защищенным элементам только с помощью интерфейсных функций этого класса. Таким образом, защищенные элементы класса находятся между общими (доступными всей программе) и частными (доступными только самому классу) элементами. РАЗРЕШЕНИЕ КОНФЛИКТА ИМЕН Если вы порождаете один класс из другого, возможны ситуации, когда имя элемента класса в производном классе является таким же, как имя элемента в базовом классе. Если возник такой конфликт, C++ всегда использует элементы производного класса внутри функций производного класса. Например, предположим, что классы book и library_cardиспользуют элемент price. В случае класса book элемент price соответствует продажной цене книги, например $22.95. В случае класса library_cardprice может включать библиотечную скидку, например $18.50. Если в вашем исходном тексте не указано явно (с помощью оператора глобального разрешения), функции класса library card будут использовать элементы производного класса (library_card). Если же функциям класса library_card необходимо обращаться к элементу price базового класса (book), они должны использовать имя класса book и оператор разрешения, например book::price. Предположим, что функции showjcard необходимо вывести обе цены. Тогда она должна использовать следующие операторы: cout << "Библиотечная цена: $" << price << endl; cout << "Продажная цена: $" << book: :price << endl;
Наследование 217 ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что наследование в C++ позволяет вам строить (порождать) новый класс из существующего класса. Строя таким способом один класс из другого, вы уменьшаете объем программирования, что, в свою очередь, экономит ваше время. Из урока 27 вы узнаете, что C++ позволяет вам порождать класс из двух или нескольких базовых классов. Использование нескольких базовых классов для порождения класса представляет собой множественное наследование. До изучения урока 27 убедитесь, что освоили следующие основные концепции: 0 Наследование представляет собой способность производить новый класс из существующего базового класса. 0 Производный класс — это новый класс, а базовый класс — существующий класс. 0 Когда вы порождаете один класс из другого (базового класса), производный класс наследует элементы базового класса. 0 Для порождения класса из базового начинайте определение производного класса ключевым словом class, за которым следует имя класса, двоеточие и имя базового класса, например class dalmatian : dog. 0 Когда вы порождаете класс из базового класса, производный класс может обращаться к общим элементам базового класса, как будто эти элементы определены внутри самого производного класса. Для доступа к частным данным базового класса производный класс должен использовать интерфейсные функции базового класса. 0 Внутри конструктора производного класса ваша программа должна вызвать конструктор базового класса, указывая двоеточие, имя конструктора базового класса и соответствующие параметры сразу же после заголовка конструктора производного класса. 0 Чтобы обеспечить производным классам прямой доступ к определенным элементам базового класса, в то же время защищая эти элементы от оставшейся части программы, C++ обеспечивает защищенные {protected) элементы класса. Производный класс может обращаться к защищенным элементам базового класса, как будто они являются общими. Однако для оставшейся части программы защищенные элементы эквивалентны частным. 0 Если в производном и базовом классе есть элементы с одинаковым именем, то внутри функций производного класса C++ будет использовать элементы производного класса. Если функциям производного класса необходимо обратиться к элементу базового класса, вы должны использовать оператор глобального разрешения, например base class:.member.
Урок 27 Множественное наследование Из урока 26 вы узнали, что можно построить один класс из другого, наследуя его характеристики. Оказывается, C++ позволяет порождать класс из нескольких базовых классов. Когда ваш класс наследует характеристики нескольких классов, вы используете множественное наследование. Как вы узнаете из данного урока, C++ полностью поддерживает множественное наследование. К концу этого урока вы изучите следующие основные концепции: • Если вы порождаете класс из нескольких базовых классов, то получаете преимущества множественного наследования. • При множественном наследовании производный класс получает атрибуты двух или более классов. • При использовании множественного наследования для порождения класса конструктор производного класса должен вызвать конструкторы всех базовых классов. • При порождении класса из производного класса вы создаете иерархию наследования (иерархию классов). Множественное наследование является мощным инструментом объектно-ориентированного программирования. Экспериментируйте с программами, представленными в этом уроке, и вы обнаружите, что построение класса из уже существующего значительно экономит усилия на программирование. ПРОСТОЙ ПРИМЕР Предположим, к примеру, у вас есть класс computer screen: class computer_screen { public: computer_screen(char *, long, int, int); void show_screen(void); private: char type[32]; long colors; int x_resolution; int y_resolution; };
Множественное наследование 219 Предположим, что у вас есть также класс mother Jboard. class mother_board { public: mother_board(int, int, int); void show_mother_board(void); private: int processor; int speed; int RAM; }; Используя эти два класса, можно породить класс computer, что показано ниже: class computer : public computer_screen/ public mother_board { public: computer (char *, int, float, char *, long, int, int, int, int, int); void show_computer(void); private: char name[64]; int hard_disk; float floppy; }; Как видите, этот класс указывает свои базовые классы сразу после двоеточия, следующего за именем класса computer. class computer : public computer_screen, public mother_board I Базовые классы Следующая программа COMPUTER.CPP порождает класс computer, используя базовые классы computer screen и mother Jboard: #include <iostream.h> #include <string.h> class computer_screen { public: computer_screen(char *, long, int, int); void show_screen(void); private; char type[32]; long colors; int x_resolution; int y_resolution; }; computer_screen::computer_screen(char *type,
220 Урок 27 long colors, int x_res/ int y_res) { strcpy(computer_screen::type, type); computer_screen::colors = colors; computer_screen::x_resolution = x_res; computer_screen::y_resolution = y_res; } void computer_screen::show_screen(void) { cout << "Тип экрана: " << type << endl; cout << "Цветов: " << colors << endl; cout << "Разрешение: " << x_resolution << " на " << y_resolution << endl; } class mother_board { public: mother_board(int, int, int); void show_mother_board(void); private: int processor; int speed; int RAM; >; mother_board::mother_board(int processor, int speed, int RAM) { motherjboard::processor = processor; mother_board::speed = speed; mother_board::RAM = RAM; } void mother_board::show_mother_board(void) { cout << "Процессор: " << processor << endl; cout << "Частота: " << speed << "МГц" << endl; cout << "ОЗУ: " << RAM << " МВайт" « endl; } class computer : public computer__screen, public mother_board { public: computer(char *, int, float, char *, long, int, int, int, int, int) ; void show_computer(void); private: char name[64] ;
Множественное наследование 221 int hard_disk; float floppy; >; i computer::computer(char *name, int hard_disk, float floppy, char ¦screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer_screen(screen, colors, x_res, y_res), mother_board(processor, speed, RAM) { strcpy(computer::name, name); computer::hard_disk = hard_disk; computer::floppy = floppy; } void computer::show_computer(void) { cout << "Тип: " << name << endl; cout << "Жесткий диск: " « hard_disk << "МВайт" << endl; cout << "Гибкий диск: " << floppy << "МВайт" << endl; show_mother_board(); show_screen(); } void main(void) { computer my_pc("Compaq", 212, 1.44, "SVGA", 16000000, 640, 480, 486, 66, 8); my_jpc. show_computer (); } Если вы проанализируете конструктор класса computer, то обнаружите, что он вызывает конструкторы классов motherboard и computer screen, как показано ниже: computer::computer(char *name, int hard_disk, float floppy, char ¦screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer_screen(screen, colors, x_res y_res), mother_board(processor, speed, RAM) ПОСТРОЕНИЕ ИЕРАРХИИ КЛАССОВ При использовании наследования в C++ для порождения одного класса из другого возможны ситуации, когда вы порождаете свой класс из класса, который уже, в свою очередь, является производным от некоторого базового класса. Например, предположим, вам необходимо использовать класс computer к&к базовый для порождения класса workstation, как показано ниже:
222 Урок 27 class workstation : public computer { public: workstation (char *operating_system, char *name/ int hard_disk, float floppy, char ¦screen, long colors, int x_res, int y_res, int processor, int speed, int RAM); void show_work_station(void); private: char operating_system[64]; }; Конструктор класса workstation просто вызывает конструктор класса computer, который в свою очередь вызывает конструкторы классов computer screen и motherboard: work_station: :workstation( char *operating_system, char *name, int hard_disk, float floppy, char ¦screen, long colors, int x_res, int y_res, int processor, int speed, int RAM) : computer (name, hard_disk, floppy, screen, colors, x_res, y_res, processor, speed, RAM) { strcpy (workstation: :operating_systern, operating_system); } В данном случае класс computer выступает в роли базового класса. Однако вы знаете, что класс сотрШегбыл порожден из классов computerjscreen и motherboard. В результате класс workstation наследует характеристики всех трех классов. На рис. 27 показано, что порождение классов приводит к иерархии классов. computer_screen motheir_board computer workstation Рис. 27. Построение иерархии классов. Когда ваши программы будут интенсивно использовать наследование, ваша иерархия классов, а следовательно, и количество наследуемых элементов, может стать довольно длинной.
Множественное наследование 223 ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Множественное наследование представляет собой возможность порождать класс из нескольких базовых классов. При использовании множественного наследования производный класс получает характеристики (элементы) существующих базовых классов. Поддержка множественного наследования в C++ предоставляет вашим программам огромные возможности объектно- ориентированного программирования. Из урока 28 вы узнаете, как обеспечить доступ к частным элементам класса со стороны других классов или функций других классов, которые вы указываете как друзей. Используя таких друзей, вы можете предоставить определенным функциям прямой доступ к элементам класса, одновременно обеспечивая их защиту от остальной части программы. Прежде чем перейти к уроку 28, убедитесь, что вы изучили следующее: 0 Множественное наследование является способностью порожденного класса наследовать характеристики нескольких базовых классов. 0 Для порождения класса из нескольких базовых после имени нового класса и двоеточия вы указываете имена базовых классов, разделяя их запятыми, например class cabbit: public cat, public rabbit. 0 При определении конструктора производного класса вы должны вызвать конструкторы всех базовых классов, передавая им необходимые параметры. 0 При порождении классов может случиться так, что используемый вами базовый класс реально порожден из других базовых классов. Если так, то ваша программа создает иерархию классов. Если вызывается конструктор вашего производного класса, то вызываются также и конструкторы наследуемых классов (последовательно).
Урок 28 Частные элементы и друзья Как вы уже знаете, ваши программы могут обращаться к частным (private) элементам класса только с помощью функций-элементов этого же класса. Используя частные элементы класса вместо общих во всех ситуациях, где это только возможно, вы уменьшаете возможность программы испортить значения элементов класса, так как программа может обращаться к таким элементам только через интерфейсные функции (которые управляют доступом к частным элементам). Однако в зависимости от использования объектов вашей программы, иногда вы можете существенно увеличить производительность, позволяя одному классу напрямую обращаться к частным элементам другого. В этом случае уменьшаются издержки (требуемое время выполнения) на вызов интерфейсных функций. В подобных ситуациях C++ позволяет определить класс в качестве друга {friend) другого класса и разрешает классу-другу доступ к частным элементам этого другого класса. В этом уроке объясняется, как ваши программы могут указать, что два класса являются друзьями. К концу данного урока вы освоите следующие основные концепции: • Используя ключевое слово friend, класс может сообщить C++, кто является его другом, т. е. другими словами, что другие классы могут обращаться напрямую к его частным элементам. • Частные элементы класса защищают данные класса, следовательно, вы должны ограничить круг классов-друзей только теми классами, которым действительно необходим прямой доступ к частным элементам искомого класса. • C++ позволяет ограничить дружественный доступ определенным набором функций. Частные {private) элементы позволяют вам защищать классы и уменьшить вероятность ошибок. Таким образом, вы должны ограничить использование классов-друзей настолько, насколько это возможно. Иногда программа напрямую может изменить значения элементов класса, это увеличивает вероятность появления ошибок. ОПРЕДЕЛЕНИЕ ДРУЗЕЙ КЛАССА C++ позволяет друзьям определенного класса обращаться к частным элементам этого класса. Чтобы указать C++, что один класс является другом {friend) другого класса, вы просто помещаете ключевое слово friendи имя соответству-
Частные элементы и друзья 225 ющего класса-друга внутрь определения этого другого класса. Например, приведенный ниже класс book объявляет класс librarian своим другом. Поэтому объекты класса librarian могут напрямую обращаться к частным элементам класса book, используя оператор точку: class book { public: book (char *, char *, char *); void show_book(void); friend librarian; private: char title[64]; char author[64]; char catalog[64]; }; Как видите, чтобы указать друга, необходим только один оператор внутри определения класса. Например, следующая программа VIEWBOOK.CPP использует librarian в качестве друга класса book. Следовательно, функции класса librarian могут напрямую обращаться к частным элементам класса book. В данном случае программа использует функцию change_catalog класса librarian для изменения номера карточки каталога определенной книги: #include <lostream.h> #include <string.h> class book { public: book(char *, char *, char *); void show_book(void); friend librarian; private: char title[64]; char author[ 64 ]; char catalog[64]; }; book::book(char *title, char *author, char *catalog) { strcpy(book::title, title); strcpy(book::author, author); strcpy(book::catalog, catalog); } void book::show_book(void) { cout << "Название: " << title << endl; cout << "Автор: " << author << endl; cout << "Каталог: " << catalog << endl; }
226 Урок 28 class librarian { public: void change_catalog(book *, char *); char *get_catalog(book); }; void librarian::change_catalog(book *this_book, char *new_catalog) { strcpy(this_book->catalog, new_catalog); } char *librarian::get_catalog(book this_book) { static char catalog[64]; strcpy(catalog, this_book.catalog); return(catalog); } void main (void) { book programming( ''Учимся программировать на языке C++", "Jamsa", "Р101"); librarian library; programming.show_book(); library.change_catalog(&programming/ "Легкий C++ 101"); programming.show_book(); } Как видите, программа передает объект book в функцию changejcatalog класса librarian по адресу. Поскольку эта функция изменяет элемент класса book, программа должна передать параметр по адресу, а затем использовать указатель для обращения к элементу этого класса. Экспериментируйте с данной программой, попробуйте удалить оператор friend из определения класса book. Поскольку класс librarian больше не имеет доступа к частным элементам класса book, компилятор C++ сообщает о синтаксических ошибках при каждой ссылке на частные данные класса book. О друзьях класса Обычно единственный способ, с помощью которого ваши программы могут обращаться к частным элементам класса, заключается в использовании интерфейсных функций. В зависимости от использования объектов программы иногда может быть удобным (или более эффективным с точки зрения скорости вычислений) разрешить одному классу обращаться к частным элементам другого. Для этого вы должны информи-
Частные элементы и друзья 227 ровать компилятор C++, что класс является другом (friend). Компилятор, в свою очередь, позволит классу-другу обращаться к частным элементам требуемого класса. Чтобы объявить класс другом, поместите ключевое слово friend и имя класса-друга в секцию public определения класса, как показано ниже: class abbott { public: friend costello; // Общие элементы private: // Частные элементы >; Как друзья отличаются от защищенных (protected) элементов Из урока 26 вы узнали, что в C++ существуют защищенные {protected) элементы класса, что позволяет производным классам обращаться к защищенным элементам базового класса напрямую, используя оператор точку. Помните, что к защищенным элементам класса могут обращаться только те классы, которые являются производными от данного базового класса, другими словами, классы, которые наследуют элементы базового класса (защищенные элементы класса являются как бы частными по отношению к остальным частям программы). Классы-друзья C++ обычно не связаны между собой узами наследования. Единственный способ для таких не связанных между собой классов получить доступ к частным элементам другого класса состоит в том, чтобы этот другой класс информировал компилятор, что данный класс является другом. ОГРАНИЧЕНИЕ КОЛИЧЕСТВА ДРУЗЕЙ Как вы только что узнали, если вы объявляете один класс другом другого класса, вы обеспечиваете классу-другу доступ к частным элементам данных этого другого класса. Вы также знаете и то, что чем больше доступа к частным данным класса, тем больше шансов на внесение ошибок в программу. Следовательно, если доступ к частным данным другого класса необходим только нескольким функциям класса, C++ позволяет указать, что только определенные функции дружественного класса будут иметь доступ к частным элементам. Предположим, например, что класс librarian, представленный в предыдущей программе, содержит много разных функций. Однако предположим, что только функциям change catalog и get_catalog необходим доступ к частным элементам класса book. Внутри определения класса book мы можем ограничить доступ к частным элементам только этими двумя функциями, как показано ниже:
228 Урок 28 class book { public: book(char *, char *, char *); void showJx>ok(void); friend char *librarian::get_catalog(book); friend void librarian: :change_catalog( book *, char *); private: char title[64]; char author[64]; char catalog[64]; }; Как видите, операторы^ел*/содержат полные прототипы всех дружественных функций, которые могут напрямую обращаться к частным элементам. О функциях-друзьях Если ваша программа использует друзей для доступа к частным данным класса, вы можете ограничить количество функций-элементов класса- друга, который может обращаться к частным данным, используя дружественные функции. Для объявления функции-друга укажите ключевое слово friend, за которым следует полный прототип, как показано ниже: public: friend class_name::function_name(parameter types); Только функции-элементы, указанные как друзья, могут напрямую обращаться к частным элементам класса, используя оператор точку. Если ваша программа начинает ссылаться на один класс из другого, вы можете получить синтаксические ошибки, если порядок определения классов неверен. В данном случае определение класса book использует прототипы функций, определенные в классе librarian. Следовательно, определение класса librarian должно предшествовать определению класса book. Однако если вы проанализируете класс librarian, то обнаружите, что он ссылается на класс book: class librarian { public: void change_catalog(book *, char *); char *get_catalog(book); }; Поскольку вы не можете поставить определение класса book перед определением класса librarian, C++ позволяет вам объявить класс book, тем самым сообщая компилятору, что такой класс есть, а позже определить его. Ниже показано, как это сделать: class book; // объявление класса
Частные элементы и друзья 229 Следующая программа LIMITFRI.CPP использует дружественные функции для ограничения доступа класса librarian к частным данным класса book. Обратите внимание на порядок определения классов: #include <lostream.h> #include <string.h> class book; class librarian { public: void change_catalog(book *, char *); char *get_catalog(book); }; class book { public: book(char *, char *, char *); vo id show_book(void); friend char *librarian::get_catalog(book); friend void librarian: :change_catalog( book *, char *); private: char title[64]; char author[64]; char catalog[64]; }; book::book(char ¦title, char *author, char *catalog) { strcpy(book::title, title); s t rcpy(book::author, author); strcpy(book::catalog, catalog); } void book::show_book(void) { cout << "Название: " << title << endl; cout << "Автор: " « author << endl; cout << "Каталог: " « catalog << endl; } void librarian::change_catalog(book *this_book, char *new_catalog) { strcpy(this_book->catalog, new_catalog); } char *librarian::get_catalog(book this_book)
230 Урок 28 { static char catalog[64]; strcpy(catalog, thi s_book.catalog); return(catalog); } void main(void) { book programming( "Учимся программировать на C++", "Jamsa", "P101"); librarian library; programming.showjbook(); library.change_catalog(&programming, "Легкий C++ 101"); programming.show_book(); } Как видите, программа сначала использует объявление, чтобы сообщить компилятору, что класс book будет определен позже. Поскольку объявление извещает компилятор о классе book, определение класса librarian может ссылаться на класс book, который еще не определен в программе. Что такое идентификатор класса Идентификатор представляет собой имя, например имя переменной или класса. Если ваши программы используют дружественные классы, то может случиться, что определение одного класса ссылается на другой класс (его имя или идентификатор), о котором компилятор C++ еще ничего не знает. В таких случаях компилятор C++ будет сообщать о синтаксических ошибках. Чтобы избавиться от ошибок типа "что следует определять сначала", C++ позволяет вам включать в начало исходного текста программы объявление класса, тем самым вводя идентификатор класса: class class_name; Эта строка сообщает компилятору, что ваша программа позже определит указанный класс, а пока программе разрешается ссылаться на этот класс. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ В данном уроке вы изучили, как использовать классы-друзья для обращения к частным элементам другого класса напрямую с использованием оператора
Частные элементы и друзья 231 точки. В уроке 29 вы изучите, как использовать в C++ шаблоны функций для упрощения определения подобных функций. Но прежде чем перейти к уроку 29, убедитесь, что вы освоили следующее: 0 Использование в ваших программах на C++ друзей позволяет одному классу обращаться к частным элементам другого класса напрямую, используя оператор точку. 13 Для объявления одного класса другом {friend) другого класса вы должны внутри определения этого другого класса указать ключевое слово friend, за которым следует имя первого класса. 0 После объявления класса другом по отношению к другому классу, все функции-элементы класса-друга могут обращаться к частным элементам этого другого класса. 0 Чтобы ограничить количество дружественных методов, которые могут обращаться к частным данным класса, C++ позволяет указать дружественные функции. Для объявления функции-друга вы должны указать ключевое слово friend, за которым следует прототип функции, которой, собственно, и необходимо обращаться к частным элементам класса. 0 При объявлении дружественных функций вы можете получить синтаксические ошибки, если неверен порядок определений классов. Если необходимо сообщить компилятору, что идентификатор представляет имя класса, который программа определит позже, вы можете использовать оператор такого вида class class_name;.
Урок 29 Использование шаблонов функций При создании функций иногда возникают ситуации, когда две функции выполняют одинаковую обработку, но работают с разными типами данных (например, одна использует параметры типа int, а другая типа float). Вы уже знаете из урока 13, что с помощью механизма перегрузки функций можно использовать одно и то же имя для функций, выполняющих разные действия и имеющих разные типы параметров. Однако, если функции возвращают значения разных типов, вам следует использовать для них уникальные имена (см. примечание к уроку 13). Предположим, например, что у вас есть функция с именем max, которая возвращает максимальное из двух целых значений. Если позже вам потребуется подобная функция, которая возвращает максимальное из двух значений с плавающей точкой, вам следует определить другую функцию, например finax. Из этого урока вы узнаете, как использовать шаблоны C++ для быстрого создания функций, возвращающих значения разных типов. К концу данного урока вы освоите следующие основные концепции: • Шаблон определяет набор операторов, с помощью которых ваши программы позже могут создать несколько функций. • Программы часто используют шаблоны функций для быстрого определения нескольких функций, которые с помощью одинаковых операторов работают с параметрами разных типов или имеют разные типы возвращаемых значений. • Шаблоны функций имеют специфичные имена, которые соответствуют имени функции, используемому вами в программе. • После того как ваша программа определила шаблон функции, она в дальнейшем может создать конкретную функцию, используя этот шаблон для задания прототипа, который включает имя данного шаблона, возвращаемое функцией значение и типы параметров. • В процессе компиляции компилятор C++ будет создавать в вашей программе функции с использованием типов, указанных в прототипах функций, которые ссылаются на имя шаблона. Шаблоны функций имеют уникальный синтаксис, который может быть на первый взгляд непонятен. Однако после создания одного или двух шаблонов вы обнаружите, что реально их очень легко использовать.
Использование шаблонов функций 233 СОЗДАНИЕ ПРОСТОГО ШАБЛОНА ФУНКЦИИ Шаблон функции определяет типонезависимую функцию. С помощью такого шаблона ваши программы в дальнейшем могут определить конкретные функции с требуемыми типами. Например, ниже определен шаблон для функции с именем max, которая возвращает большее из двух значений: template<class Т> Т тах(Т а, Т Ь) { if (a > Ь) return(а); else return(b); } Буква Т в данном случае представляет собой общий тип шаблона. После определения шаблона внутри вашей программы вы объявляете прототипы функций для каждого требуемого вам типа. В случае шаблона max следующие прототипы создают функции типа float и int. float max(float, float); int max(Int, Int); Когда компилятор C++ встретит эти прототипы, то при построении функции он заменит тип шаблона Г указанным вами типом. В случае с типом float функция max после замены примет следующий вид: float max(float, float); template<class T> T max(T a, T b) { if (a > b) return(a); else return(b) ; } float max (float a, float b) { if (a > b) return(a); else return(b); } Следующая программа МАХ_ТЕМР.СРР использует шаблон max для создания функции типа int и float. #include <iostream.h> template<class T> T max(T a, T b)
234 Урок 29 { if (a > b) return (a); else return(b); } float max(float, float); int max (int, int); void main (void) { cout << "Максимум 100 и 200 равен " << maxA00, 200) « endl; cout << "Максимум 5.4321 и 1.2345 равен " << maxE.4321, 1.2345) « endl; } В процессе компиляции компилятор C++ автоматически создает операторы для построения одной функции, работающей с типом int, и второй функции, работающей с типом float. Поскольку компилятор C++ управляет операторами, соответствующими функциям, которые вы создаете с помощью шаблонов, он позволяет вам использовать одинаковые имена для функций, которые возвращают значения разных типов. Вы не смогли бы это сделать, используя только перегрузку функций, как обсуждалось в уроке 13. Использование шаблонов функций По мере того как ваши программы становятся более сложными, возможны ситуации, когда вам потребуются подобные функции, выполняющие одни и те же операции, но с разными типами данных. Шаблон функции позволяет вашим программам определять общую, или типо- независимую, функцию. Когда программе требуется использовать функцию для определенного типа, например int или float, она указывает прототип функции, который использует имя шаблона функции и типы возвращаемого значения и параметров. В процессе компиляции C++ создаст соответствующую функцию. Создавая шаблоны, вы уменьшаете количество функций, которые должны кодировать самостоятельно, а ваши программы могут использовать одно и то же имя для функций, выполняющих определенную операцию, независимо от возвращаемого функцией значения и типов параметров. ШАБЛОНЫ, КОТОРЫЕ ИСПОЛЬЗУЮТ НЕСКОЛЬКО ТИПОВ Предыдущее определение шаблона для функции max использовало единственный общий тип Г. Очень часто в шаблоне функции требует-
Использование шаблонов функций 235 ся указать несколько типов. Например, следующие операторы создают шаблон для функции showjarray, которая выводит элементы массива. Шаблон использует тип Г для определения типа массива и тип 77 для указания типа параметра count template<class T,class Tl> void show_array(T *array/Tl count) { Tl index; for (index = 0; index < count; index++) cout << array[index] << ' '; cout << endl; } Как и ранее, программа должна указать прототипы функций для требуемых типов: void show_array(int *, int); void show_array(float *, unsigned); Следующая программа SHOW_TEM.CPP использует шаблон для создания функций, которые выводят массивы типа int и тты float ttinclude <iostream.h> template<class T,class Tl> void show_array( T *array,Tl count) { Tl index; for (index = 0; index < count; index++) cout << array[index] << ' '; cout << endl; } void show_array(int *, int); void show_array(float *, unsigned); void main (void) { int pages[] = { 100, 200, 300, 400, 500 }; float prices[] = { 10.05, 20.10, 30.15 }; show_array(pages, 5); show_array(prices, 3); } Шаблоны и несколько типов По мере того как шаблоны функций становятся более сложными, они могут обеспечить поддержку нескольких типов. Например, ваша про-
236 Урок 29 грамма может создать шаблон для функции с именем array jsort, которая сортирует элементы массива. В данном случае функция может использовать два параметра: первый, соответствующий массиву, и второй, соответствующий количеству элементов массива. Если программа предполагает, что массив никогда не будет содержать более 32767 значений, она может использовать тип int для параметра размера массива. Однако более универсальный шаблон мог бы предоставить программе возможность указать свой собственный тип этого параметра, как показано ниже: template<class Т, class Tl> void array_sort(T array[], Tl elements) { // операторы } С помощью шаблона array_sort программа может создать функции, которые сортируют маленькие массивы типа float (менее 128 элементов) и очень большие массивы типа int, используя следующие прототипы: void array_sort(float, char); void array_sort(int, long); ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Как вы уже знаете, использование шаблонов функций уменьшает объем программирования, позволяя компилятору C++ генерировать операторы для функций, которые отличаются только типами возвращаемых значений и параметров. Из урока 30 вы узнаете, как использовать шаблоны для создания типо- независимых, или общих, классов. До изучения урока 30 убедитесь, что вы освоили следующие основные концепции: 0 Шаблоны функций позволяют вам объявлять типонезависимые, или общие, функции. 0 Когда вашей программе требуется использовать функцию с определенными типами данных, она должна указать прототип функции, который определяет требуемые типы. 0 Когда компилятор C++ встретит такой прототип функции, он создаст операторы, соответствующие этой функции, подставляя требуемые типы. 0 Ваши программы должны создавать шаблоны для общих функций, которые работают с отличающимися типами. Другими словами, если вы используете с какой-либо функцией только один тип, нет необходимости применять шаблон.
Использование шаблонов функций 237 0 Если функция требует несколько типов, шаблон просто назначает каждому типу уникальный идентификатор, например Т, Т1 и Т2. Позже в процессе компиляции компилятор C++ корректно назначит типы, указанные вами в прототипе функции.
Урок 30 Использование шаблонов классов Из урока 29 вы узнали, как в C++ использовать шаблоны функций для создания общих, или типонезависимых, функций. Определяя шаблоны функций, вы заставляете компилятор C++ создавать в случае необходимости функции, которые отличаются типом возвращаемого значения или типами параметров. Если возникает необходимость создавать подобные функции, отличающиеся только используемыми типами, то может возникнуть необходимость и создания общих классов. А если так, то ваши программы могут определять шаблоны классов. В этом уроке рассмотрены действия вашей программы, необходимые для объявления и дальнейшего использования шаблонов классов. К концу данного урока вы освоите следующие основные концепции: • Используя ключевое слово template и символы типов (например, Г, 77 и Т2) ваши программы могут создать шаблон класса — определение шаблона класса может использовать эти символы для объявления элементов данных, указания типов параметров и возвращаемого значения функций-элементов и т.д. • Для создания объектов класса с использованием шаблонов ваши программы просто ссылаются на имя класса, за которым внутри угловых скобок следуют типы (например, <int,float>), каждому из которых компилятор назначает символы типов и имя переменной. • Если у класса есть конструктор, с помощью которого вы инициализируете элементы данных, вы можете вызвать этот конструктор при создании объекта с использованием шаблона, например class_name<int,float>valuesB00);. • Если компилятор C++ встречает объявление объекта, он создает класс из шаблона, используя соответствующие типы. Как и в случае с шаблонами функций, шаблоны классов на первый взгляд могут показаться достаточно сложными, однако если вы хоть раз создали и использовали пару шаблонов классов, то обнаружили, что дело это вполне простое. СОЗДАНИЕ ШАБЛОНА КЛАССА Предположим, к примеру, вы создаете класс массива, в котором есть методы для вычисления суммы и среднего значения хранимых в массиве чисел. Предположим, что вы работаете с массивом типа int, и ваш класс мог бы выглядеть так:
Использование шаблонов классов 239 class array { public: array(int size); long sum(void); int average_value(void); void show_array(void); int add_value(int); private: int *data; int size; int index; }; Следующая программа I_ARRAY.CPP использует класс array для работы со значениями типа int. #include <iostream.h> #include <stdlib.h> class array { public: array(int size); long sum (void); int average_value(void); vo id show_array(void); int add_value(int); private: int *data; int size; int index; }; array::array(int size) { data = new int[size]; if (data == NULL) { cerr << "Недостаточно памяти - программа завершается " << endl; exit(l); } array::size = size; array::index = 0; } long array: : sum (void) { long sum = 0;
240 Урок 30 for (int i = 0; i < index; i++) sum += data[i]; return (sum); } int array::average_value(void) { long sum = 0; for (int i = 0; i < index; i++) sum += data[i]; return (sum / index); } void array::show_array(void) { for (int i = 0; i < index; i++) cout << data[i] << ' '; cout << endl; } int array: : add_value (int value) { if (index == size) return(-1); // Массив полон else { data[index] = value; index++; return@); // Успешно } } void main(void) { array numbers A00); // массив из 100 эл-тов int i; for (i = 0; i < 50; i++) numbers.add_value(i); numbers.show_array(); cout << "Сумма чисел равна " << numbers. sum() << endl; cout << "Среднее значение равно " << numbers. average_value () << endl; }
Использование шаблонов классов 241 Как видите, программа распределяет 100 элементов массива, а затем заносит в массив 50 значений с помощью метода add_value. В классе array переменная index отслеживает количество элементов, хранимых в данный момент в массиве. Если пользователыпытается добавить больше элементов, чем может вместить массив, функция add_value возвращает ошибку. Как видите, функция average_value использует переменную index для определения среднего значения массива. Программа запрашивает память для массива, используя оператор new, который подробно рассматривается в уроке 31. Шаблоны классов По мере того как количество создаваемых вами классов растет, вы обнаруживаете, что некоторый класс, созданный для одной программы (или, возможно, для этой), очень похож на требующийся вам сейчас. Во многих случаях классы могут отличаться только типами. Другими словами, один класс работает с целочисленными значениями, в то время как требующийся вам сейчас должен работать со значениями типа float. Чтобы увеличить вероятность повторного использования существующего кода, C++ позволяет вашим программам определять шаблоны классов. Если сформулировать кратко, то шаблон класса определяет типонезависимый класс, который в дальнейшем служит для создания объектов требуемых типов. Если компилятор C++ встречает объявление объекта, основанное на шаблоне класса, то для построения класса требуемого типа он будет использовать типы, указанные при объявлении. Позволяя быстро создавать классы, отличающиеся только типом, шаблоны классов сокращают объем программирования, что, в свою очередь, экономит ваше время. Пойдем дальше. Теперь предположим, что вашей программе необходимо работать с массивом значений с плавающей точкой, кроме того, что она работает с целочисленным массивом. Один из способов обеспечить поддержку массивов различных типов состоит в создании разных классов. С другой стороны, используя шаблоны классов, вы можете избавиться от необходимости дублировать классы. Ниже представлен шаблон класса, который создает общий класс array: template<class T, class Tl> class array { public: arraydnt size); Tl sum (void); T average_value(void); void show_array(void); int add_value(T); private: T *data; int size; int index; >;
242 Урок 30 Этот шаблон определяет символы типов Г и Т1. В случае массива целочисленных значений Т будет соответствовать int, aTl — long. Аналогичным образом для массива значений с плавающей точкой значения 7и 77равны float. Теперь потратьте время, чтобы убедиться, что вы поняли, как компилятор C++ будет подставлять указанные вами типы вместо символов Г и 77. Далее, перед каждой функцией класса вы должны указать такую же запись со словом template. Кроме того, сразу же после имени класса вы должны указать типы класса, например array<T, Tl>::average_value. Следующий оператор иллюстрирует определение функции average value для этого класса: template<class Т, class Tl> Т array<T, Tl>::average_value(void) { Tl sum = 0; int i; for (i = 0; i < index; i++) sum += data[i]; return (sum / index); } После создания шаблона вы можете создавать класс требуемого типа, указывая имя класса, а за ним в угловых скобках необходимые типы, как показано ниже: I array< int, long> numbers A00); ^^^^ Имя шаблона array<float# float > values B00); Типы шаблона Программа GENARRAY.CPP использует шаблон класса array для создания двух классов, один из которых работает со значениями типа int, а второй — со значениями типа float. #include <lostream.h> #include <stdlib.h> template<class T, class Tl> class array { public: array(int size); Tl sum(void); T average_value(void); void show_array(void); int add_value(T); private: T *data; int size; int index; }; template<class T, class Tl>
Использование шаблонов классов 243 array<T, Tl>::array(int size) { data = new T[size]; if (data == NULL) { cerr << "Недостаточно памяти - программа завершается" << endl; exit(l); } array::size = size; array::index = 0; } template<class T, class Tl> Tl array<T, Tl>::sum(void) { Tl sum = 0; for (int i = 0; i < index; i++) sum += data[i] ; return(sum); } template<class T, class Tl> T array<T, Tl>::average_value(void) { Tl sum = 0; for (int i = 0; i < index; i++) sum += data[i]; return(sum / index); } template<class T, class Tl> void array<T# Tl>::show_array(void) { for (int i = 0; i < index; i++) cout << data[i] << ' '; cout << endl; } template<class T, class Tl> int array<T, Tl>::add_value(T value) {
244 Урок 30 if (index == size) return(-1); // Массив полон else { data[index] = value; index*+; return@); // Успешно } } void main(void) { // Массив из 100 элементов array<int, long> numbersA00); // Массив из 200 элементов array<float# float> valuesB00); int i; for (i = 0; i < 50; i++) numbers.add_value(i); numbers.show_array(); cout << "Сумма чисел равна " << numbers. sum () << endl; cout << "Среднее значение равно " << numbers. average_value () « endl; for (i = 0; i < 100; i++) values.add_value(i * 100); values.show_array(); cout << "Сумма чисел равна " << values.sum() << endl; cout << "Среднее значение равно " << values. average__value () « endl; > Лучшим способом понять шаблоны классов будет напечатать две копии этой программы. В первой копии замените все символы Г и 77 на int и long. A во второй замените Г и 77 на float. Объявление объектов, основанных на шаблоне класса Для создания объектов с использованием шаблона класса вы просто должны указать имя шаблона класса, за которым между левой и правой угловыми скобками укажите типы, которыми компилятор заменит символы Г, 77, 72 и т. д. Затем ваша программа должна указать имя объекта (переменной) со значениями параметров, которые вы хотите передать конструктору класса, как показано ниже:
Использование шаблонов классов 245 template_class_name<typel, type2> object_name( parameter!., parameter2); Когда компилятор C++ встречает такое объявление, он создает класс, основанный на указанных типах. Например, следующий оператор использует шаблон класса array для создания массива типа char, в котором хранится 100 элементов: array<char, int> small_numbersA00); ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что шаблоны классов помогут вам избавиться от дублирования кода программы, если вам необходимы объекты похожих классов, которые отличаются только типом. Поскольку шаблоны классов могут быть сложными, они могут вас смутить. Когда вы определяете ваш класс, начните с определения, как будто бы вы создаете класс для конкретного типа. После того как вы полностью опишете класс, определите какие элементы необходимо изменить, чтобы работать с объектами различных типов. Теперь замените типы этих элементов такими символами, как, например, Т, Т1, Т2ит.д. Программы, представленные в данном уроке, использовали оператор C++ new для динамического (во время выполнения программы) распределения памяти для массива. В уроке 31 вы подробно ознакомитесь с оператором new. Прежде чем перейти к уроку 31, убедитесь, что вы изучили следующее: 0 Шаблоны классов позволяют избавиться от дублирования кода для таких классов, чьи объекты отличаются только типом их элементов. 0 Для создания шаблона класса предварите определение класса ключевым словом template и символами типов, например Ти Т1. 0 Далее вы должны предварить определение каждой функции класса таким же оператором с ключевым словом template. Кроме того, укажите типы шаблона между левой и правой угловыми скобками, а выражение в угловых скобках поместите между именем класса и оператором разрешения области видимости, например class_name<T, Tl>::function_name. 0 Для создания класса с использованием шаблона укажите имя класса и замещающие значения для типов между левой и правой угловыми скобками, например class_name<int, long> object.
Расширенные возможности C++ В первых пяти частях этой книги были представлены C++ и концепции объектно-ориентированного программирования, которые вам необходимо знать для создания мощных программ. В этой части вы расширите ваши знания о C++ сведениями о распределении памяти во время выполнения программ и знаниями о том, как выполнять файловые операции в C++. Уроки этой части представлены как "Расширенные возможности C++", но не позволяйте им запугать вас. Вы обнаружите, что концепции, представленные в этой части, не сложнее тех, которые вы уже освоили. Урок 31. Использование свободной памяти в C++. Урок 32. Управление свободной памятью. Урок 33. Дополнительные возможности cin и cout. Урок 34. Файловые операции В/В в C++. Урок 35. Встроенные функции и ассемблерные коды. Урок 36. Использование аргументов командной строки* Урок 37. Использование констант и макрокоманд. Урок 38. Полиморфизм. Урок 39. Использование исключительных ситуаций C++ для обработки ошибок.
Урок 31 Использование свободной памяти в C++ Как вы уже знаете, если ваша программа объявляет массив, компилятор C++ распределяет память для хранения его элементов. Однако представляется возможным, что до некоторого времени размер массива может быть не так велик, чтобы вместить все необходимые данные. Например, предположим, что вы создали массив для хранения 100 акций. Если позже вам потребуется хранить более 100 акций, вы должны изменить свою программу и перекомпилировать ее. С другой стороны, вместо распределения массива фиксированного размера ваши программы могут запрашивать необходимое количество памяти динамически, т.е. во время выполнения. Например, если программе необходимо следить за акциями, она могла бы запросить память, достаточную для хранения 100 акций. Аналогично, если программе необходимы только 25 акций, она могла бы запросить меньше памяти. Распределяя подобным образом память динамически, ваши программы непрерывно изменяют свои потребности без дополнительного программирования. Если ваши программы запрашивают память во время выполнения, они указывают требуемое количество памяти, а C++ возвращает указатель на эту память. C++ распределяет память из областей памяти, которые называются свободной памятью. В этом уроке рассматриваются действия, которые должна выполнить ваша программа для динамического распределения, а впоследствии освобождения памяти во время выполнения. К концу данного урока вы освоите следующие основные концепции: • Чтобы запросить память во время выполнения, ваши программы должны использовать оператор C++ new. • При использовании оператора new программы указывают количество требуемой памяти. Если оператор new может успешно выделить требуемый объем памяти, он возвращает указатель на начало области выделенной памяти. • Если оператор new не может удовлетворить запрос на память вашей программы (возможно, свободной памяти уже не осталось), он возвращает указатель NULL. • Чтобы позже освободить память, распределенную с помощью оператора new, ваши программы должны использовать оператор C++ delete. Динамическое распределение памяти во время выполнения является чрезвычайно полезной возможностью. Экспериментируйте с программами,
250 Урок 31 представленными в данном уроке. И вы поймете, что динамическое распределение памяти реально выполняется очень просто. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА new Оператор C++ new позволяет вашим программам распределять память во время выполнения. Для использования оператора new вам необходимо указать количество байтов памяти, которое требуется программе. Предположим, например, что вашей программе необходим 50-байтный массив. Используя оператор new, вы можете заказать эту память, как показано ниже: char *buffer = new char[50]; Говоря кратко, если оператор new успешно выделяет память, он возвращает указатель на начало области этой памяти. В данном случае, поскольку программа распределяет память для хранения массива символов, она присваивает возвращаемый указатель переменной, определенной как указатель на тип char. Если оператор new не может выделить запрашиваемый вами объем памяти, он возвратит NULL-указатель, который содержит значение 0. Каждый раз, когда ваши программы динамически распределяют память с использованием оператора new, они должны проверять возвращаемое оператором new значение, чтобы определить, не равно ли оно NULL. Зачем необходимо динамически распределять память с использованием new Многие программы интенсивно используют массивы для хранения множества значений определенного типа. При проектировании своих программ программисты обычно пытаются объявить массивы с размерами, достаточными для удовлетворения будущих потребностей программы. К сожалению, если случится так, что потребности программы когда-нибудь превысят подобные ожидания программиста, то кому-то придется редактировать и перекомпилировать такую программу. Вместо редактирования и перекомпилирования программ, которые просто запрашивают память с запасом, вам следует создавать свои программы таким образом, чтобы они распределяли требуемую им память динамически во время выполнения, используя оператор new. В этом случае ваши программы могут адаптировать использование памяти в соответствии с вашими изменившимися потребностями, избавляя вас от необходимости редактировать и перекомпилировать программу. Например, следующая программа USE_NEW.CPP использует оператор new для получения указателя на 100-байтный массив: #include <iostream.h> void main(void)
Использование свободной памяти в C++ 251 { char *pointer = new char[100]; if (pointer != NULL) cout << "Память успешно выделена'' << endl; else cout << "Ошибка выделения памяти" << endl; } Как видите, программа сразу проверяет значение, присвоенное оператором new переменной-указателю. Если указатель содержит значение NULL, значит new не смог выделить запрашиваемый объем памяти. Если же указатель содержит не NULL, следовательно, new успешно выделил память и указатель содержит адрес начала блока памяти. Если new не может удовлетворить запрос на память, он возвратит NULL При использовании оператора new для выделения памяти может случиться так, что ваш запрос не может быть удовлетворен, поскольку нет достаточного объема свободной памяти. Если оператор new не способен выделить требуемую память, он присваивает указателю значение NULL. Проверяя значение указателя, как показано в предыдущей программе, вы можете определить, был ли удовлетворен запрос на память. Например, следующий оператор использует new для распределения памяти под массив из 500 значений с плавающей точкой: float *array = new float[100]; Чтобы определить, выделил ли оператор new память, ваша программа должна сравнить значение указателя с NULL, как показано ниже: if (array != NULL) cout << "Память выделена успешно" << endl; else cout << "new не может выделить память" << endl; Предыдущая программа использовала оператор new для выделения 100 байт памяти. Поскольку эта программа "жестко закодирована" на объем требуемой памяти, возможно, вам потребуется ее редактировать и перекомпилировать, если возникнет необходимость, чтобы программа выделила меньше или больше памяти. Как уже кратко обсуждалось, одна из причин для динамического распределения памяти состоит в том, чтобы избавиться от необходимости редактировать и перекомпилировать программу при изменении требований к объему памяти. Следующая программа ASKJV1EM.CPP запрашивает у пользователя количество байт памяти, которое необходимо выделить, и затем распределяет память, используя оператор new:
252 Урок 31 #include <iostream.h> void main(void) { int size; char *pointer; cout << "Введите размер массива, до 30000: "; cin >> size; if (size <= 30000) { pointer = new char[size]; if (pointer != NULL) cout << "Память выделена успешно" << endl; else cout << "Невозможно выделить память" << endl; } } Когда ваши программы используют оператор new для динамического распределения памяти, то вполне вероятно, что они сами знают, сколько памяти необходимо выделить. Например, если программа распределяет память для хранения информации о служащих, она, возможно, сохранила количество служащих в файле. Следовательно, при запуске она может прочитать количество служащих из файла, а затем выделить соответствующее количество памяти. Следующая программа NOMEMORY.CPP выделяет каждый раз память для 10000 символов до тех пор, пока оператор new не сможет больше выделить память из свободной памяти. Другими словами, эта программа удерживает выделенную память, пока не использует всю доступную свободную память. Если программа успешно выделяет память, она извещает об этом сообщением. Если память больше не может быть выделена, программа выводит сообщение об ошибке и завершается: #include <iostream.h> void main(void) { char *pointer; do { pointer = new char[10000]; if (pointer != NULL) cout << "Выделено 10000 байт" << endl; else cout << "Больше нет памяти" << endl; } while (pointer != NULL); }
Использование свободной памяти в C++ 253 Замечание: Если выработаете в среде MS-DOS, то, возможно, будете удивлены тем, что свободная память исчерпается после того, как программа выделит 64 Кбайт. Большинство работающих в MS-DOS компиляторов C++ по умолчанию используют малую модель памяти, которая обеспечивает только 64 Кбайт свободной памяти. Аналогично, если вы используете среду MS-DOS, то наибольшая область памяти, к которой могут обратиться ваши программы, может быть ограничена 64 Кбайт. О свободной памяти Каждый раз при запуске вашей программы компилятор C++ устанавливает отдельную область неиспользуемой памяти, которая называется свободной памятью. Используя оператор new, ваша программа может выделить память из этой свободной памяти во время выполнения. Используя свободную память для распределения требуемой памяти, ваши программы не стеснены фиксированными размерами массивов. Размер свободной памяти может изменяться в зависимости от вашей операционной системы и модели памяти компилятора. Если увеличивается количество динамической памяти, требуемой вашими программами, вам необходимо убедиться, что вас не сдерживают ограничения свободной памяти вашей системы. ОСВОБОЖДЕНИЕ ПАМЯТИ, ЕСЛИ ОНА БОЛЬШЕ НЕ НУЖНА Как вы знаете, оператор C++ new позволяет вашим программам выделять память динамически во время выполнения. Если вашей программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete вы просто указываете этому оператору указатель на данную область памяти, как показано ниже: delete pointer; Следующая программа DEL_MEM.CPP использует оператор delete для освобождения выделенной с помощью оператора new памяти: #include <iostream.h> #include <string.h> void main(void) { char *pointer = new char[100]; strcpy(pointer, "Учимся программировать на языке C++"); cout << pointer << endl; delete pointer; }
252 Урок 31 #include <iostream.h> void main(void) { int size; char *pointer; cout << "Введите размер массива, до 30000: "; cin >> size; if (size <= 30000) { pointer = new char[size]; if (pointer != NULL) cout << "Память выделена успешно" << endl; else cout << "Невозможно выделить память" << endl; } } Когда ваши программы используют оператор new для динамического распределения памяти, то вполне вероятно, что они сами знают, сколько памяти необходимо выделить. Например, если программа распределяет память для хранения информации о служащих, она, возможно, сохранила количество служащих в файле. Следовательно, при запуске она может прочитать количество служащих из файла, а затем выделить соответствующее количество памяти. Следующая программа NOMEMORY.CPP выделяет каждый раз память для 10000 символов до тех пор, пока оператор new не сможет больше выделить память из свободной памяти. Другими словами, эта программа удерживает выделенную память, пока не использует всю доступную свободную память. Если программа успешно выделяет память, она извещает об этом сооб1 щением. Если память больше не может быть выделена, программа выводит сообщение об ошибке и завершается: #include <lostream.h> void main(void) { char *pointer; do { pointer = new char[10000]; if (pointer != NULL) cout << "Выделено 10000 байт" << endl; else cout << "Больше нет памяти" << endl; > while (pointer != NULL); }
Использование свободной памяти в C++ 253 Замечание: Если выработаете в среде MS-DOS, то, возможно, будете удивлены тем, что свободная память исчерпается после того, как программа выделит 64 Кбайт. Большинство работающих в MS-DOS компиляторов C++ по умолчанию используют малую модель памяти, которая обеспечивает только 64 Кбайт свободной памяти. Аналогично, если вы используете среду MS-DOS, то наибольшая область памяти, к которой могут обратиться ваши программы, может быть ограничена 64 Кбайт. О свободной памяти Каждый раз при запуске вашей программы компилятор C++ устанавливает отдельную область неиспользуемой памяти, которая называется свободной памятью. Используя оператор new, ваша программа может выделить память из этой свободной памяти во время выполнения. Используя свободную память для распределения требуемой памяти, ваши программы не стеснены фиксированными размерами массивов. Размер свободной памяти может изменяться в зависимости от вашей операционной системы и модели памяти компилятора. Если увеличивается количество динамической памяти, требуемой вашими программами, вам необходимо убедиться, что вас не сдерживают ограничения свободной памяти вашей системы. ОСВОБОЖДЕНИЕ ПАМЯТИ, ЕСЛИ ОНА БОЛЬШЕ НЕ НУЖНА Как вы знаете, оператор C++ new позволяет вашим программам выделять память динамически во время выполнения. Если вашей программе больше не нужна выделенная память, она должна ее освободить, используя оператор delete. Для освобождения памяти с использованием оператора delete вы просто указываете этому оператору указатель на данную область памяти, как показано ниже: delete pointer; Следующая программа DEL_MEM.CPP использует оператор delete для освобождения выделенной с помощью оператора new памяти: #include <iostream.h> #include <string.h> void main(void) { char *pointer = new char[100]; strcpy(pointer, ''Учимся программировать на языке C++"); cout << pointer << endl; delete pointer; }
254 Урок 31 По умолчанию, если ваша программа не освобождает выделенную ей память до своего завершения, операционная система автоматически освобождает эту память после завершения программы. Однако если ваша программа использует оператор delete для освобождения памяти по мере того, как она (память) становится ненужной, то эта память вновь становится доступной для других целей (возможно, для вашей программы, которая опять будет использовать оператор new, или для операционной системы). Второй пример Следующая программа ALLOCARR.CPP выделяет память для хранения массива из 1000 целочисленных значений. Затем она заносит в массив значения от 1 до 1000, выводя их на экран. Потом программа освобождает эту память и распределяет память для массива из 2000 значений с плавающей точкой, занося в массив значения от 1.0 до 2000.0: #include <iostream.h> void main(void) { int *int_array = new int[1000]; float *float_array; int i; if (int_array != NULL) { for (i = 0; i < 1000; i++) int_array[i] =1+1; for A = 0; i < 1000; i++) cout << int_array[i] << ' '; delete int_array; > float_array = new float[2000]; if (float_array != NULL) { for (i = 0; 1 < 2000; 1++) float_array[i] = A + 1) * 1.0; for A = 0; 1 < 2000; i++) cout << float_array[1] << ' '; delete float_array; > }
Использование свободной памяти в C++ 255 Как правило, ваши программы должны освобождать память с помощью оператора delete по мере того, как память становится программам не нужна. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Из этого урока вы узнали, что ваши программы могут распределять память динамически во время выполнения из неиспользуемой памяти, называемой свободной памятью, используя для этих целей оператор new. Выделяя подобным образом память динамически, программы могут удовлетворять ваши изменяющиеся запросы без редактирования и перекомпиляции самой программы. Из урока 32 вы узнаете, как управлять распределением свободной памяти, и что делают операторы new, если они не могут удовлетворить запрос на распределение памяти. Прежде чем перейти к уроку 32, убедитесь, что вы изучили следующее: 0 Способность выделять память динамически во время выполнения снимает с ваших программ зависимость от фиксированных размеров массивов. 0 Если оператор new успешно выделяет требуемую вашей программой память, он возвращает указатель на начало области этой памяти. 0 Если оператор new не может выделить требуемую вашей программой память, он возвращает NULL-указатель, который содержит значение 0. 0 Каждый раз, когда ваши программы распределяют память динамически с использованием оператора new, они должны проверять значение возвращаемого оператором new указателя, чтобы определить, не равен ли он NULL, что указывает на невозможность выделения памяти. 0 Используя указатель на массив, ваши программы могут обращаться к памяти, выделенной с помощью оператора new. 0 Оператор new выделяет память из блока неиспользуемой памяти, называемой свободной памятью. 0 В зависимости от вашей операционной системы и модели памяти компилятора размер свободной памяти может быть различным. В среде MS-DOS свободная память может быть ограничена 64 Кбайт. 0 Если вашей программе больше не нужна выделенная память, она должна освободить ее (вернуть в свободную память), используя для этого оператор delete.
Урок 32 Управление свободной памятью Из урока 31 вы узнали, что при выполнении ваши программы могут использовать оператор new для динамического распределения памяти из свободной памяти. Если оператор new успешно выделяет память, ваша программа получает на нее указатель. Если оператор new не может выделить требуемую память, он присваивает вашей переменной-указателю значение NULL. В зависимости от назначения вашей программы, вы, возможно, захотите, чтобы программа выполнила определенные операции, если new не может удовлетворить запрос на память. Из этого урока вы узнаете, как заставить C++ вызвать специальную функцию, если new не может удовлетворить запрос на память. К концу данного урока вы освоите следующие основные концепции: • Вы можете создать свой собственный обработчик ситуации, когда памяти недостаточно — функции, которую C++ вызывает, если new не может удовлетворить запрос на память. • C++ позволяет вам определить собственный оператор new для выделения и, возможно, инициализации памяти. • C++ позволяет вам определить собственный оператор delete для освобождения памяти. Как вы узнаете, с помощью собственных операторов new и delete вы можете лучше управлять ошибками при недостаточности памяти. СОЗДАНИЕ ОБРАБОТЧИКА ДЛЯ ОПЕРАЦИЙ СО СВОБОДНОЙ ПАМЯТЬЮ Как вы уже знаете из урока 31, если оператор new не может выделить требуемую память из свободной памяти, он присваивает значение NULL вашей переменной-указателю. Следующая программа USE_FREE.CPP неоднократно вызывает оператор new, выделяя каждый раз 1000 байт, пока свободная память не исчерпается: #include <lostream.h> void main(void) { char *pointer;
Управление свободной памятью 257 do { pointer = new char[1000]; if (pointer != NULL) cout << "Выделено 1000 байт" << endl; else cout << "Свободной памяти нет " << endl; } while (pointer); } Как видите, программа просто выполняет цикл, пока new не присвоит указателю значение NULL. Если вы хотите, чтобы new выполнил другие действия (что-нибудь отличное от тупого возвращения значения NULL), когда он не может удовлетворить запрос на память, то сначала вам следует определить функцию, которую должна вызывать ваша программа, если памяти недостаточно для удовлетворения запроса. Например, следующая функция end_pro- gram выводит на экран сообщение, а затем использует функцию библиотеки этапа выполнения exit для завершения программы: void end_program(void) { cout << "Запрос на память не может быть удовлетворен" << endl; exit(l); } Чтобы заставить C++ вызывать функцию end_program, если new не может удовлетворить запрос на память, вам необходимо вызвать функцию set_new_handler, указав ей функцию end_program в качестве параметра, как показано ниже: set_new_handler(end_program); Следующая программа ENDJFREE.CPP вызывает функцию end_program, если new не может удовлетворить запрос на память: #include <lostream.h> #include <stdlib.h> // Прототип exit #include <new.h> // Прототип set_new_handler void end_program(void) { cout << "Запрос на память не может быть удовлетворен" << endl; exit(l); } void main(void) { char *pointer;
258 Урок 32 set_new_handler(end_program); do { pointer = new char[10000]; cout << "Выделено 10000 байт" << endl; } while A); > В данном случае программа просто завершается, если new не может выделить память из свободной памяти. В зависимости от потребностей вашей программы вы могли бы использовать функцию для выделения памяти из другого источника, например из расширенной памяти компьютера, которая существует в среде MS-DOS. Кроме того, ваша программа могла бы освободить память, распределенную ею для других целей, чтобы сделать доступной свободную память. Обеспечивая вашим программам возможность создавать обработчик ситуации отсутствия памяти, C++ предоставляет вам полный контроль над процессом распределения памяти. СОЗДАНИЕ СОБСТВЕННЫХ ОПЕРАТОРОВ new И delete Как вы знаете, C++ позволяет вашим программам перегружать операторы. Аналогично вы можете перегрузить операторы new и delete, чтобы изменить их поведение. Например, предположим, что вы выделяете 100 байт памяти для хранения супер-секретных данных о вашей компании. Когда вы в дальнейшем освобождаете эту память с помощью оператора delete, освобождается буфер, который содержал эту память, т.е. те самые 100 байт, содержащие супер-секретные данные о вашей компании. Предположим, корпоративный шпион (и программист) имеет доступ к вашему компьютеру, его программа теоретически может распределить тот же 100-байтный массив в памяти вашего компьютера и изучить ваши супер-секреты. Перегружая оператор delete, ваша программа может сначала заполнить этот буфер нулями или другими бессмысленными символами, а потом освободить эту память. Следующая программа MYDELETE.CPP перегружает оператор delete. Она сначала перезаписывает 100 байт, на которые указывает указатель, а затем освобождает память, используя для этого функцию библиотеки этапа выполнения./^: #include <iostream.h> #include <stdlib.h> #include <string.h> static void operator delete(void *pointer) { char *data = (char *) pointer; int i;
Управление свободной памятью 259 for (i = 0; i < 100; i++) data[i] = 0; cout << "Секрет в безопасности!" << endl; free(pointer); } void main(void) { char *pointer = new char[100]; strcpy(pointer, "Секреты моей компании"); delete pointer; } При запуске программа выделяет память для строкового массива с помощью оператора new. Затем она копирует секреты компании в эту строку. В дальнейшем программа использует перегруженный оператор delete для освобождения памяти. Внутри функции delete приведенный ниже оператор присваивает значение переменной pointer указателю на символьную строку: char *data = (char *) pointer; Символы (char *), которые называются оператором приведения типов, предназначены только для того, чтобы сообщить компилятору C++, что функция знает, что она присваивает указатель типа void (см. выше параметры функции) указателю типа char. Если вы опустите оператор приведения типов, программа не откомпилируется. Затем функция копирует нули в 100 байт буфера и освобождает память, используя для этого функцию библиотеки этапа выполнения free. Очень важно отметить, что эта функция (оператор delete) работает только с областью памяти размером 100 байт. Поскольку данная программа выделяет память только один раз, она работает корректно. Если вы измените программу таким образом, чтобы выделялось только десять байт памяти и не сделаете подобных изменений в этой функции, то она перезапишет 90 байт памяти, которые ваша программа, возможно, использовала для других целей, приведя к ошибке. Однако, используя функции библиотеки этапа выполнения, ваши программы могут получить больше информации о размере области памяти, на которую указывает определенный указатель. Подобным образом следующая программа NEW_OVER.CPP перегружает оператор C++ new. В данном случае перегруженная функция помещает символьную строку "Учимся программировать на языке C++!" в начало выделяемой памяти: #include <lostream.h> #include <alloc.h> #include <string.h>
260 Урок 32 static void *operator new(size_t size) { char *pointer; pointer = (char *) malloc(size); if (size > strlen( "Учимся программировать на языке C++!")) strcpy(pointer, "Учимся программировать на языке C++!"); return(pointer); } void main(void) { char *str = new char[100]; cout << str << endl; } Как видите, функция new использует для выделения памяти функцию malloc библиотеки этапа выполнения. Если размер выделяемой памяти достаточен для хранения строки "Учимся программировать на языке C++!", данная функция использует функцию strcpy библиотеки этапа выполнения для копирования строки в область памяти. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ По мере того как ваши программы становятся более сложными, вы будете выделять память в процессе выполнения, используя оператор new. Из этого урока вы узнали, как изменить поведение оператора new, сначала определяя функцию-обработчик, которую вызывает ваша программа, если new не может удовлетворить запрос на память, а затем с помощью перегрузки самого оператора new. Из урока 33 вы узнаете новые способы использования входного потока cin и выходного потока соШ для усовершенствования возможностей ввода и вывода ваших программ. Прежде чем перейти к уроку 33, убедитесь, что вы изучили следующее: 0 Если оператор new не может удовлетворить запрос на память, то по умолчанию он присваивает значение NULL соответствующему указателю. 0 Если вашим программам необходима другая обработка в том случае, когда new не может удовлетворить запрос на память, ваши программы могут определить свои собственные обработчики. Используя функцию setnewhandler, программа может заставить new вызвать вашу функцию, если невозможно удовлетворить запрос на память. 0 C++ позволяет вашим программам перегружать операторы new и delete. Однако, прежде чем это сделать, вы должны иметь четкое представление о свободной памяти (куче) и функциях библиотеки этапа выполнения, которые ею манипулируют.
Урок 33 Дополнительные возможности tin и cout На всем протяжении этой книги вы использовали выходной поток cout для вывода информации на экран дисплея. Аналогично, многие из ваших программ использовали входной поток cin для чтения информации с клавиатуры. Оказывается, cin и cout представляют собой классовые объекты, определяемые и создаваемые с помощью заголовочного файла iostream.h. Как объекты cin и cout поддерживают различные операторы и операции. Из данного урока вы узнаете, как расширить возможности ввода и вывода, используя функции, встроенные в классы cin и cout. К концу этого урока вы освоите следующие основные концепции: • Заголовочный файл iostream.h содержит определения классов, которые вы можете проанализировать, чтобы лучше понять потоковый ввод/вывод. • Используя метод cout. width, ваши программы могут управлять шириной вывода. • Используя метод cout.fill, ваши программы могут заменить пустые выходные символы (табуляцию и пробелы) некоторым определенным символом. • Для управления количеством цифр, выводимых выходным потоком cout для значений с плавающей точкой, ваши программы могут использовать метод cout.setprecision. • Для вывода и ввода по одному символу за один раз ваши программы могут использовать потоковые методы cout.put и cin.get. • Используя метод cin.getline, ваши программы могут вводить целую строку за один раз. Почти любая создаваемая вами на C++ программа будет использовать cout или cin для выполнения операций В/В (ввода/вывода). Выберите время для экспериментов с программами из этого урока. ЧТО ВНУТРИ iostream.h Начиная с урока 1, каждая написанная вами на C++ программа включала заголовочный файл iostream.h. Этот файл содержит определения, позволяющие вашим программам использовать cout для выполнения вывода и cin для выполнения ввода. Более точно, этот файл определяет классы istream и ostream
262 Урок 33 (входной поток и выходной поток), a cin и cout являются переменными (объектами) этих классов. Выберите время, чтобы напечатать файл iostream.h. Он находится в подкаталоге INCLUDE. Определения в этом файле достаточно сложны. Однако если вы пройдете по файлу медленно, то обнаружите, что большинство определений являются просто определениями классов и констант. Внутри файла вы найдете объявления переменных cin и cout. ИСПОЛЬЗОВАНИЕ cout Как вы уже знаете, cout представляет собой класс, который содержит несколько разных методов. Следующие программы иллюстрируют использование некоторых методов, которые ваши программы могут применять для форматирования вывода. Из урока 3 вы узнали, что манипулятор setw позволяет вашим программам указать минимальное количество символов, которое может занять следующее выходное значение: #include <lostream.h> #include <iomanip.h> void main(void) { cout << "Мое любимое число" << setwC) << 1001 << endl; cout << "Мое любимое число" << setwD) << 1001 << endl; cout << "Мое любимое число" << setwE) << 1001 << endl; cout << "Мое любимое число" << setwF) << 1001 << endl; } Подобным образом метод cout.width позволяет вам указать минимальное количество символов, которое будет использовать cout для вывода следующего значения. Следующая программа COUTWIDT.CPP использует функцию cout.width для выполнения работы, аналогичной той, которую выполняет setw, что и показано ниже: #include <lostream.h> #include <iomanip.h> void main(void) { int i; for (i = 3; i < 7; i++) { cout << "Мое любимое число"; cout.width(i); cout << 1001 << endl; > > Если вы откомпилируете и запустите вашу программу, на экране дисплея
Дополнительные возможности tin и cout 263 появится следующий вывод: С:\> COUTWIDT <ENTER> Мое любимое число1001 Мое любимое число1001 Мое любимое число 1001 Мое любимое число 1001 Подобно манипулятору setw, ширина, выбираемая с помощью функции cout.width, действует только для следующего выходного значения. Использование символа-заполнителя Если вы используете манипулятор setw или функцию cout. width для управления шириной вывода, cout будет помещать пробелы до (или после для выровненных влево) значений, как это и требуется. В зависимости от назначения вашей программы вы, возможно, захотите использовать символ, отличный от пробела. Предположим, например, что ваша программа создает такую таблицу: Таблица информации Профиль компании 10 Доходы и убытки компании 11 Члены правления компании 13 В данном случае вывод предваряет номера страниц точками. Функция cout.fill позволяет вам указать символ, который cout будет использовать для заполнения пустого пространства. Следующая программа COUTFILL.CPP создает таблицу, подобную приведенной выше: #include <iostream.h> #include <iomanip.h> void main(void) { cout << "Таблица информации" << endl; cout.fillC . '); cout << "Профиль компании" << setwB0) << 10 << endl; cout << "Доходы и убытки компании" << setwA2) << 11 «.< endl; cout << "Члены правления компании" << setwA4) << 13 << endl; } Если вы однажды выбрали символ-заполнитель с помощью cout.fill, он будет оставаться действительным, пока вы не измените его повторным вызовом cout.fill.
264 Урок 33 Управление цифрами значений с плавающей точкой Если вы используете cout для вывода зндчения с плавающей точкой, то обычно не можете сделать каких-либо предположений о том, сколько цифр будет выводить cout по умолчанию. Однако, используя манипулятор setprecision, вы можете указать количество требуемых цифр. Следующая программа SET- PREC.CPP использует манипулятор setprecision для управления количеством цифр, которые появятся справа от десятичной точки: #include <iostream.h> #include <iomanip.h> void main(void) { float value = 1.23456; int i; for (i = 1; i < 6; i++) cout << setprecision(i) << value << endl; } Когда вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод: С:\> SETPREC <ENTER> 1.2 1.23 1.235 1.2346 1.23456 Если вы используете манипулятор setprecision для изменения точности, ваша установка действует до тех пор, пока программа повторно не использует setprecision. ВЫВОД И ВВОД ОДНОГО СИМВОЛА ЗА ОДИН РАЗ В зависимости от назначения вашей программы вам, возможно, потребуется выводить символы на дисплей или читать с клавиатуры по одному символу за один раз. Для вывода одного символа за один раз ваши программы могут использовать функцию cout.put. Следующая программа COUTPUT.CPP использует эту функцию для вывода на экран сообщения Учимся программировать на языке C++/по одному символу за раз: #include <lostream.h> void main(void) {
Дополнительные возможности tin и cout 265 char string[] = "Учимся программировать на языке C++!"; int i; for (i = 0; string[i]; i++) cout.put(string[i]); } Библиотека этапа выполнения предоставляет функцию с именем toupper, которая возвращает заглавный эквивалент строчной буквы. Следующая программа COUTUPPR.CPP использует функцию toupper для преобразования символа в верхний регистр, а затем выводит эту букву с помощью cout.put #include <iostream.h> #include <ctype.h> // прототип toupper void main(void) { char string[] = "C++ language"; int i; for (i = 0; string[i]; i++) cout.put(toupper(string[i])); cout << endl << "Результирующая строка: " << string << endl; } Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод*: С:\> COUTUPPR <ENTER> C++ LANGUAGE Результирующая строка: C++ language ЧТЕНИЕ ВВОДА С КЛАВИАТУРЫ ПО ОДНОМУ СИМВОЛУ ЗА РАЗ Точно так же, как cout предоставляет функцию cout.put для вывода символа, tin предоставляет функцию cin.get, которая позволяет вам читать один символ данных. Чтобы воспользоваться функцией cin.get, вы просто присваиваете переменной возвращаемый этой функцией символ, как показано ниже: letter = cin.get(); Следующая программа CIN_GET.CPP выводит сообщение, в ответ на которое вам необходимо ввести Y или N. Затем она повторяет в цикле вызов cin.get для чтения символов, пока не получит Y или N: К сожалению, функция toupper применима только к английским буквам. — Прим. перев.
266 Урок 33 #include <iostream.h> #include <ctype.h> void main(void) { char letter; cout << "Хотите продолжать? (Y/N): "; do { letter = cin.getO; // Преобразовать к верхнему регистру letter = toupper(letter); } while ((letter != 'Y') && (letter != 'N')); cout << endl << "Вы ввели " << letter << endl; } ЧТЕНИЕ С КЛАВИАТУРЫ ЦЕЛОЙ СТРОКИ Как вы уже знаете, при использовании tin для выполнения ввода с клавиатуры, tin использует пустые символы, такие как пробел, табуляция или возврат каретки, для определения, где заканчивается одно значение и начинается другое. Во многих случаях вы захотите, чтобы ваши программы считывали целую строку данных в символьную строку. Для этого программы могут использовать функцию cin.getline. Для использования cin.getline вам необходимо указать символьную строку, в которую будут помещаться символы, а также размер строки, как показано ниже: cin.getline(string, 64); Когда cin.get читает символы с клавиатуры, она не будет читать символов больше, чем может вместить строка. Удобным способом определить размер массива является использование оператора C++ sizeof, как показано ниже: cin.getline(string, sizeof(string)); Если позже вы измените размер массива, то вам не нужно будет искать и изменять каждый оператор с cin.get, встречающийся в вашей программе. Вместо этого оператор sizeof "будет использовать корректный размер массива. Следующая программа GETLINE.CPP использует функцию cin.getline для чтения с клавиатуры строки текста: #include <iostream.h> void main(void) { char string[128]; cout << "Введите строку текста и нажмите Enter" << endl; cin.getline(string, sizeof(string));
Дополнительные возможности tin и cout 267 cout << "Вы ввели: " << string << endl; } Когда вы читаете символы с клавиатуры, то, возможно, вам понадобится читать символы вплоть до и включая определенный символ. Когда такой символ будет прочитан, возможно, вы захотите завершить операцию ввода. Для выполнения подобной операции ваша программа может передать искомый символ в cin.getline. Например, следующий вызов заставляет функцию cin.getline читать строку текста, пока не встретится возврат каретки, или пока не будут прочитаны 64 символа, или пока не встретится буква Я: cin.getline(string, 64, 'Я'); Следующая программа UNTIL_Z.CPP использует cin.getline для чтения строки текста или символов вплоть до появления буквы Я (включая и эту букву): #include <iostream.h> void main(void) { char string[128]; cout << "Введите строку текста и нажмите Enter" << endl; cin.getline(string, sizeof(string), 'Я'); cout << "Вы ввели: " << string << endl; } Откомпилируйте и запустите эту программу. Экспериментируйте с различными строками текста. Некоторые из них начинайте с буквы Я, некоторые заканчивайте буквой Я, а некоторые пусть вообще не содержат букву Я. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Каждая созданная вами на C++ программа будет, вероятно, использовать tin или cout для выполнения операций ввода и вывода. Этот урок посвящен некоторым манипуляторам В/В и функциям, которые вы можете использовать с потоками tin и cout. По мере усложнения ваших программ они часто будут сохранять информацию в файлах. Из урока 34 вы узнаете, как в C++ выполнять операции файлового ввода и вывода. Прежде чем приступить к изучению урока 34, убедитесь, что вы освоили следующие основные концепции: 0 tin и cout являются объектами (переменными) классов istream и ostream, которые определены в заголовочном файле iostream.h. А если так, они предоставляют функции, которые ваши программы могут вызывать для решения определенных задач.
268 Урок 33 0 Функция cout. width позволяет вашим программам указать минимальное количество символов, которые будет использовать следующее выходное значение. 0 Функция cout fill позволяет вашим программам указать символ, который cout будет использовать для заполнения пустого пространства, устанавливаемого с помощью cout .width или setw. 0 Манипулятор setprecision позволяет вашим программам управлять количеством цифр, выводимых справа от десятичной точки для значений с плавающей точкой. 0 Функции cin.get и cout.put позволяют вашим программам вводить или выводить один символ. 0 Функция cin.getline позволяет вашим программам читать строку текста с клавиатуры.
Урок 34 Файловые операции В/В в C++ По мере усложнения ваших программ они будут сохранять и получать информацию, используя файлы. Если вы знакомы с файловыми манипуляциями в языке С, вы сможете использовать подобные методы и в C++. Кроме того, как вы узнаете из этого урока, C++ предоставляет набор классов файловых потоков, с помощью которых можно очень легко выполнять операции ввода и вывода (В/В) с файлами. К концу данного урока вы освоите следующие основные концепции: • Используя выходной файловый поток, вы можете писать информацию в файл с помощью оператора вставки (<<). • Используя входной файловый поток, вы можете читать хранимую в файле информацию с помощью оператора извлечения (>>). • Для открытия и закрытия файла вы используете методы файловых классов. • Для чтения и записи файловых данных вы можете использовать операторы вставки и извлечения, а также некоторые методы файловых классов. Многие программы, которые вы создадите в будущем, будут интенсивно использовать файлы. Выберите время для экспериментов с программами, представленными в данном уроке. И вы обнаружите, что в C++ выполнять файловые операции очень просто. ВЫВОД В ФАЙЛОВЫЙ ПОТОК Из урока 33 вы узнали, что cout представляет собой объект типа ostream (выходной поток). Используя класс ostream, ваши программы могут выполнять вывод в cout с использованием оператора вставки или различных методов класса, например cout.put. Заголовочный файл iostream.h определяет выходной поток cout. Аналогично, заголовочный файл fstream.h определяет класс выходного файлового потока с именем ofstream. Используя объекты класса ofstream, ваши программы могут выполнять вывод в файл. Для начала вы должны объявить объект типа ofitream, указав имя требуемого выходного файла как символьную строку, что показано ниже: ofstream file_object("FILENAME.EXT");
270 Урок 34 Если вы указываете имя файла при объявлении объекта типа ofstream, C++ создаст новый файл на вашем диске, используя указанное имя, или перезапишет файл с таким же именем, если он уже существует на вашем диске. Следующая программа OUT_FILE.CPP создает объект типа ofstream и затем использует оператор вставки для вывода нескольких строк текста в файл BOOKINFO.DAT: #include <fstream.h> void main(void) { ofstream book_file("BOOKINFO.DAT"); book_file << "Учимся программировать на языке C++/ " «"Вторая редакция" << endl; book_file << "Jamsa Press" << endl; book_file << 2.95" << endl; } В данном случае программа открывает файл BOOKINFO.DAT и затем записывает три строки в файл, используя оператор вставки. Откомпилируйте и запустите эту программу. Если вы работаете в среде MS-DOS, можете использовать команду TYPE для вывода содержимого этого файла на экран: С:\> ТУРЕ BOOKINFO.DAT <ENTER> Учимся программировать на языке C++/ Вторая редакция Jamsa Press $22.95 Как видите, в С++ достаточно просто выполнить операцию вывода в файл. ЧТЕНИЕ ИЗ ВХОДНОГО ФАЙЛОВОГО ПОТОКА Только что вы узнали, что, используя класс ofstream, ваши программы могут быстро выполнить операции вывода в файл. Подобным образом ваши программы могут выполнить операции ввода из файла, используя объекты типа ifstream. Опять же, вы просто создаете объект, передавая ему в качестве параметра требуемое имя файла: ifstream input_file("FILENAME.EXT"); Следующая программа FILE_IN.CPP открывает файл BOOKINFO.DAT, который вы создали с помощью предыдущей программы, и читает, а затем отображает первые три элемента файла: #include <iostream.h> #include <fstream.h> void main(void)
Файловые операции В/В в C++ 271 { ifstream input_file("BOOKINFO.DAT"); char one[64], two[64], three[64]; input_file >> one; input_.file >> two; input_file >> three; cout << one << endl; cout << two << endl; cout << three << endl; } Если вы откомпилируете и запустите эту программу, то, вероятно, предположите, что она отобразит первые три строки файла. Однако, подобно cin, входные файловые потоки используют пустые символы, чтобы определить, где заканчивается одно значение и начинается другое. В результате при запуске предыдущей программы на дисплее появится следующий вывод: С:\> FILE_IN <ENTER> Учимся программировать на Чтение целой строки файлового ввода Из урока 33 вы узнали, что ваши программы могут использовать cin.getline для чтения целой строки с клавиатуры. Подобным образом объекты типа ifstream могут использовать getline для чтения строки файлового ввода. Следующая программа FILELINE.CPP использует функцию getline для чтения всех трех строк файла BOOKINFO.DAT: #include <iostream.h> #include <fstream.h> void main(void) { ifstream input_file("BOOKINFO.DAT"); char one[64], two[64], three[64]; input_file.getline(one, sizeof(one)); input_file.getline(two, sizeof(two)); input_file.getline(three, sizeof(three)); cout << one << endl; cout << two << endl; cout << three << endl; }
272 Урок 34 В данном случае программа успешно читает содержимое файла, потому что она знает, что файл содержит три строки. Однако во многих случаях ваша программа не будет знать, сколько строк содержится в файле. В таких случаях ваши программы будут просто продолжать чтение содержимого файла, пока не встретят конец файла. ОПРЕДЕЛЕНИЕ КОНЦА ФАЙЛА Обычной файловой операцией в ваших программах является чтение содержимого файла, пока не встретится конец файла. Чтобы определить конец файла, ваши программы могут использовать функцию ео/потокового объекта. Эта функция возвращает значение 0, если конец файла еще не встретился, и 1, если встретился конец файла. Используя цикл while, ваши программы могут непрерывно читать содержимое файла, пока не найдут конец файла, как показано ниже: while (! input_file.eof()) { // Операторы } В данном случае программа будет продолжать выполнять цикл, пока функция eof возвращает ложь @). Следующая программа TESTJEOF.CPP использует функцию eof для чтения содержимого файла BOOKINFO.DAT, пока не достигнет конца файла: #include <iostream.h> #include <fstream.h> void main(void) { ifstream input_file("BOOKINFO.DAT"); char line[64]; while (! input_file.eof()) { input_file.getline(line, sizeof(line)); cout << line << endl; } } Аналогично, следующая программа WORD_EOF.CPP читает содержимое файла по одному слову за один раз, пока не встретится конец файла: #include <iostream.h> #include <fstream.h>
Файловые операции В/В в C++ 273 void main(void) { ifstream input_file("BOOKINFO.DAT"); char word [64]; while (! input_flie.eof()) { input_file >> word; cout << word << endl; } } И наконец, следующая программа CHAR_EOF.CPP читает содержимое файла по одному символу за один раз, используя функцию get, пока не встретит конец файла: #include <iostream.h> #include <fstream.h> void main (void) { ifstream input_file("BOOKINFO.DAT"); char letter; while (! input_file.eof()) { letter = input_file.get(); cout << letter; } > ПРОВЕРКА ОШИБОК ПРИ ВЫПОЛНЕНИИ ФАЙЛОВЫХ ОПЕРАЦИЙ Программы, представленные до настоящего момента, предполагали, что во время файловых операций В/В не происходят ошибки. К сожалению, это сбывается не всегда. Например, если вы открываете файл для ввода, ваши программы должны проверить, что файл существует. Аналогично, если ваша программа пишет данные в файл, вам необходимо убедиться* что операция прошла успешно (к примеру, отсутствие места на диске, скорее всего, помешает записи данных). Чтобы помочь вашим программам следить за ошибками, вы можете использовать функцию./**//файлового объекта. Если в процессе файловой операции ошибок не было, функция возвратит ложь @). Одна-
274 Урок 34 ко, если встретилась ошибка, функция fail возвратит истину. Например, если программа открывает файл, ей следует использовать функцию}**//, чтобы определить, произошла ли ошибка, как это показано ниже: ifstream input_file("FILENAME.DAT"); if (input_file.fail()) { cerr << "Ошибка открытия FILENAME.EXT" << endl; exit(l); } Таким образом, программы должны убедиться, что операции чтения и записи прошли успешно. Следующая программа TEST_ALL.CPP использует функцию fail для проверки различных ошибочных ситуаций: #include <iostream.h> #include <fstream.h> void main(void) { char line[256]; ifstream input_file("BOOKINFO.DAT"); if (input_file.fail()) cerr << "Ошибка открытия BOOKINFO.DAT" << endl; else { while ((! input_file.eof()) && (! input_file.fail())) { input_file.getline(line, sizeof(line)); if (! input_file.fail()) cout << line << endl; } } } ЗАКРЫТИЕ ФАЙЛА, ЕСЛИ ОН БОЛЬШЕ НЕ НУЖЕН При завершении вашей программы операционная система закроет открытые ею файлы. Однако, как правило, если вашей программе файл больше не нужен, она должна его закрыть. Для закрытия файла ваша программа должна использовать функцию close, как показано ниже: input_file.close(); Когда вы закрываете файл, все данные, которые ваша программа писала в этот файл, сбрасываются на диск, и обновляется запись каталога для этого файла.
Файловые операции В/В в C++ 275 УПРАВЛЕНИЕ ОТКРЫТИЕМ ФАЙЛА В примерах программ, представленных в данном уроке, файловые операции ввода и вывода выполнялись с начала файла. Однако, когда вы записываете данные в выходной файл, вероятно, вы захотите, чтобы программа добавляла информацию в конец существующего файла. Для открытия файла в режиме добавления вы должны при его открытии указать второй параметр, как показано ниже: ifstream output_file("FILENAME.EXT", ios::app); В данном случае параметр iosr.app указывает режим открытия файла. По мере усложнения ваших программ они будут использовать сочетание значений для режима открытия файла, которые перечислены в табл. 34. Таблица 34. Значения режимов открытия. Режим открытия Назначение ios::app Открывает файл в режиме добавления, располагая файловый указатель в конце файла. ios::ate Располагает файловый указатель в конце файла. ios:: in Указывает открыть файл для ввода. ios::nocreate Если указанный файл не существует, не создавать файл и возвратить ошибку. ios::noreplace Если файл существует, операция открытия должна быть прервана и должна возвратить ошибку. ios::out Указывает открыть файл для вывода. ios::trunc Сбрасывает (перезаписывает) содержимое существующего файла. Следующая операция открытия файла открывает файл для вывода, используя режим iosr.noreplace, чтобы предотвратить перезапись существующего файла: ifstream output_file("FILENAME.EXT", ios::out I ios:rnoreplace); ВЫПОЛНЕНИЕ ОПЕРАЦИЙ ЧТЕНИЯ И ЗАПИСИ Все программы, представленные в данном уроке, выполняли файловые операции над символьными строками. По мере усложнения ваших программ, возможно, вам понадобится читать и писать массивы и структуры. Для этого ваши программы могут использовать функции read и write. При использовании функций read и write вы должны указать буфер данных, в который данные будут читаться или из которого они будут записываться, а также длину буфера в байтах, как показано ниже: input_filе.read(buffer, sizeof(buffer)); output_file.write(buffer, sizeof(buffer));
276 Урок 34 Например, следующая программа STRU_OUT.CPP использует функцию write для вывода содержимого структуры в файл EMPLOYEE.DAT: #include <iostream.h> #include <fstream.h> void main (void) { struct employee { char name[64]; int age; float salary; } worker = { "Джон Дой", 33, 25000.0 }; ofstream emp_file("EMPLOYEE.DAT"); emp_file.write((char *) fcworker, sizeof(employee)); > Функция write обычно получает указатель на символьную строку. Символы (char *) представляют собой оператор приведения типов, который информирует компилятор, что вы передаете указатель на другой тип. Подобным образом следующая программа STRU_IN.CPP использует метод read для чтения из файла информации о служащем: #include <iostream.h> #include <fstream.h> void main(void) { struct employee { char name[64]; int age; float salary; } worker = { "Джон Дой", 33, 25000.0 }; ifstream emp_file("EMPLOYEE.DAT"); emp_file.read((char *) fcworker, sizeof(employee)); cout << worker.name << endl; cout << worker.age << endl; cout << worker.salary << endl; } ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ По мере усложнения ваших программ вы регулярно будете использовать файловые операции. Выберите время для экспериментов с программами, представленными в этом уроке. Из урока 35 вы узнаете, как улучшить произ-
277 Файловые операции В/В в C++ водительность ваших программ с использованием встроенных функций. Прежде чем перейти к уроку 35, убедитесь, что вы изучили следующее: 0 Заголовочный файл fstream.h определяет классы ifstream и ofstream, с помощью которых ваша программа может выполнять операции файлового ввода и вывода. 0 Для открытия файла на ввод или вывод вы должны объявить объект типа ifstream или ofstream, передавая конструктору этого объекта имя требуемого файла. 0 После того как ваша программа открыла файл для ввода или вывода, она может читать или писать данные, используя операторы извлечения (») и вставки («). 0 Ваши программы могут выполнять ввод или вывод символов в файл или из файла, используя функции get и put. 0 Ваши программы могут читать из файла целую строку, используя функцию getline. 0 Большинство программ читают содержимое файла, пока не встретится конец файла. Ваши программы могут определить конец файла с помощью функции eof 0 Когда ваши програм м ы выполняют файловые операции, они должны проверять состояние всех операций, чтобы убедиться, что операции выполнены успешно. Для проверки ошибок ваши программы могут использовать функцию/ш7. 0 Если вашим программам необходимо вводить или выводить такие данные, как структуры или массивы, они могут использовать методы read и write. 0 Если ваша программа завершила работу с файлом, его следует закрыть с помощью функции close.
Урок 35 Встроенные функции и ассемблерные коды Начиная с урока 8, ваши программы интенсивно использовали функции. Как вы уже знаете, единственное неудобство при использовании функций состоит в том, что они увеличивают издержки (увеличивают время выполнения), помещая параметры в стек при каждом вызове. Из данного урока вы узнаете, что для коротких функций можно использовать метод, называемый встроенным кодом, который помещает операторы функции для каждого ее вызова прямо в программу, избегая таким образом издержек на вызов функции. Используя встроенные {inline) функции, ваши программы будут выполняться немного быстрее. К концу этого урока вы освоите следующие основные концепции: • Для улучшения производительности за счет уменьшения издержек на вызов функции вы можете заставить компилятор C++ встроить в программу код функции, подобно тому, как это делается при замещении макрокоманд. • Используя встроенные {inline) функции, ваши программы остаются удобными для чтения (читающий программу видит вызов функции), но вы избегаете издержек на вызов функции, которые вызваны помещением параметров в стек и их последующим извлечением из стека, а также переходом к телу функции и последующим возвратом из нее. • В зависимости от требований, предъявляемых к вашей программе, иногда вам потребуется использовать язык ассемблера для решения определенной задачи. • Для упрощения применения программирования на языке ассемблера C++ позволяет определить функции на встроенном языке ассемблера внутри ваших программ на C++. ВСТРОЕННЫЕ ФУНКЦИИ Когда вы определяете в своей программе функцию, компилятор C++ переводит код функции в машинный язык, сохраняя только одну копию инструкций функции внутри вашей программы. Каждый раз, когда ваша программа вызывает функцию, компилятор C++ помещает в программу специальные инструкции, которые заносят параметры функции в стек и затем выполняют переход к инструкциям этой функции. Когда операторы функции завершаются, выполнение программы продолжается с первого оператора, который следует
Встроенные функции и ассемблерные коды 279 за вызовом функции. Помещение аргументов б стек и переход в функцию и из нее вносит издержки, из-за которых ваша программа выполняется немного медленнее, чем если бы она размещала те же операторы прямо внутри программы при каждой ссылке на функцию. Например, предположим, что следующая программа CALLBEEP.CPP вызывает функцию showmessage, которая указанное число раз выдает сигнал на динамик компьютера и затем выводит сообщение на дисплей: #include <iostream.h> void show__message(int count, char "message) { int i; for (i = 0; i < count; i++) cout << '\a'; cout << message << endl; } void main(void) { show_messageC, "Учимся программировать на языке C++"); show_messageB, "Урок 35"); } Следующая программа NO_CALL.CPP не вызывает функцию showmes- sage. Вместо этого она помещает внутри себя те же операторы функции при каждой ссылке на функцию: #include <iostream.h> void main (void) { int i; for (i = 0; i < 3; i++) cout << '\a'; cout << " Учимся программировать на языке C++" << endl; for (i = 0; i < 2; i++) cout << #\a'; cout << "Урок 35" << endl; } Обе программы выполняют одно и то же. Поскольку программа NO_CALL не вызывает функцию showjnessage, она выполняется немного быстрее, чем
280 Урок 35 программа CALLBEEP. В данном случае разницу во времени выполнения определить невозможно, но, если в обычной ситуации функция будет вызываться 1000 раз, вы, вероятно, заметите небольшое увеличение производительности. Однако программа NO_CALL более запутана, чем ее двойник CALL- BEEP, следовательно, более тяжела для восприятия. При создании программ вы всегда должны попытаться определить, когда лучше использовать обычные функции, а когда лучше воспользоваться встроенными функциями. Для более простых программ предпочтительно использовать обычные функции. Однако, если вы создаете программу, для которой производительность имеет первостепенное значение, вам следовало бы уменьшить количество вызовов функций. Один из способов уменьшения количества вызовов функций состоит в том, чтобы поместить соответствующие операторы прямо в программу, как только что было сделано в программе NO_CALL. Однако, как вы могли убедиться, замена только одной функции внесла значительную путаницу в программу. К счастью, C++ предоставляет ключевое слово inline, которое обеспечивает лучший способ. Использование ключевого слова inline При объявлении функции внутри программы C++ позволяет вам предварить имя функции ключевым словом inline. Если компилятор C++ встречает ключевое слово inline, он помещает в выполнимый файл (машинный язык) операторы этой функции в месте каждого ее вызова. Таким образом, можно улучшить читаемость ваших программ на C++, используя функции, и в то же время увеличить производительность, избегая издержек на вызов функций. Следующая программа INLINE.CPP определяет функции max и min как inline: #include <iostream.h> inline int max(int a, int b) { if (a > b) return (a); else return(b); > inline int min (int a, int b) { if (a < b) return (a); else return(b); ) void main (void) { cout « "Минимум из 1001 и 2002 равен " «
Встроенные функции и ассемблерные коды 281 mindOOl, 2002) << endl; cout << ''Максимум из 1001 и 2002 равен " « maxA001, 2002) << endl; } i В данном случае компилятор C++ заменит каждый вызов функции на соответствующие операторы функции. Производительность программы увеличивается без ее усложнения. О встроенных функциях Если компилятор C++ перед определением функции встречает ключевое слово inline, он будет заменять обращения к этой функции (вызовы) на последовательность операторов, эквивалентную выполнению функции. Таким образом ваши программы улучшают производительность, избавляясь от издержек на вызов функции и в то же время выигрывая в стиле программы, благодаря использованию функций. ВСТРОЕННЫЕ ФУНКЦИИ И КЛАССЫ Как вы уже знаете, при определении класса вы определяете функции этого класса внутри или вне класса. Например, класс employee определяет свои функции внутри самого класса: class employee { public: employee(char *name, char *position, float salary) { strcpy(employee::name, name); strcpy(employee::positIon, position); employee::salary = salary; } void show_employee(void) { cout << "Ийя: " << name << endl; cout << "Должность: " << position << endl; cout << "Оклад: $" << salary << endl; } private: char name [64]; char position[64]; float salary; }; Размещая подобным образом функции внутри класса, вы тем самым объявляете их встроенными {inline). Если вы создаете встроенные функции класса этим способом, C++ дублирует функцию для каждого создаваемого объекта этого класса, помещая встроенный код при каждой ссылке на метод (функцию) класса. Преимущество такого встроенного кода состоит в увеличении производи-
282 Урок 35 тельности. Недостатком является очень быстрое увеличение объема самого определения класса. Кроме того, включение кода функции в определение класса может существенно запутать класс, делая его элементы трудными для восприятия. Для улучшения читаемости определений ваших классов вы можете вынести функции из определения класса, как вы обычно и делаете, и разместить ключевое слово inline перед определением функции. Например, следующее определение заставляет компилятор использовать встроенные операторы для функции show_employee: inline void employee: :show_employee(void) { cout << "Имя: " << name << endl; cout << "Должность: " << position << endl; cout << "Оклад: $" << salary << endl; } ИСПОЛЬЗОВАНИЕ ОПЕРАТОРОВ ЯЗЫКА АССЕМБЛЕРА Как вы знаете из урока 1, программисты могут создавать программы, используя широкий спектр языков программирования. Затем компилятор преобразует операторы программы в машинный код (нули и единицы), который понимает компьютер. Каждый тип компьютеров поддерживает промежуточный язык, называемый языком ассемблера, который попадает в категорию между машинным языком и языком программирования, таким как C++. Язык ассемблера использует другие символы для представления инструкций машинного языка. В зависимости от назначения ваших программ, возможно, вам потребуется выполнить операции низкого уровня, для которых необходимо использовать операторы языка ассемблера. В таких случаях вы можете использовать оператор C++ asm для встраивания операторов языка ассемблера в программу. Большинство создаваемых вами программ не потребуют операторов языка ассемблера. Следующая программа USE_ASM.CPP использует оператор asm, чтобы вставить операторы языка ассемблера, необходимые для озвучивания динамика компьютера в среде MS-DOS: #include <lostrearn.h> void main (void) { cout << "Сейчас будет звонить!" << endl; asm { MOV AH,2 MOV DL,7 INT 21H > cout << "Есть!" << endl; }
Встроенные функции и ассемблерные коды 283 Как видите, используя оператор asm, программа комбинирует C++ и операторы языка ассемблера. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Встроенные функции улучшают производительность ваших программ, уменьшая издержки на вызов функций. Из этого урока вы узнали, как и где использовать встроенные функции в своих программах. Вы также узнали, что иногда вашим программам необходимо использовать язык ассемблера для решения определенных задач. Из урока 36 вы узнаете, как ваши программы могут обратиться к аргументам командной строки, которые пользователь вводит при запуске программы. Однако, прежде чем перейти к уроку 36, убедитесь, что вы освоили следующие основные концепции: 0 Помещение параметров в стек и переход к функции и из нее вносит издержки, из-за которых ваша программа выполняется немного медленнее. 0 Ключевое слово inline заставляет компилятор C++ заменять вызов функции эквивалентной последовательностью операторов, которые бы выполняла эта функция. Поскольку встроенные операторы избавляют от издержек на вызов функции, программа будет выполняться быстрее. 0 Если вы используете встроенные функции внутри класса, каждый создаваемый вами объект использует свои собственные встроенные операторы. Обычно все объекты одного и того же класса совместно используют один и тот же код функции. 0 Ключевое слово asm позволяет вам встраивать операторы языка ассемблера в программы на C++.
Урок 36 Использование аргументов командной строки Как вы знаете, большинство команд, вводимых вами в ответ на системную подсказку, позволяют включать дополнительную информацию, такую как имя файла. Например, при использовании команды MS-DOS COPY для копирования содержимого одного файла во второй файл вы указываете в командной строке имена обоих файлов. Аналогично, если ваш компилятор основан на командной строке, вы должны включать имя вашего исходного файла при вызове компилятора. В этом уроке рассмотрены способы, с помощью которых ваши программы на C++ обращаются к аргументам командной строки. К концу данного урока вы освоите следующие основные концепции: • Программы на C++ трактуют аргументы командной строки как параметры функции main. • По традиции C++ передает два (иногда три) параметра в main, которые в большинстве программ называются argc и argv. • Параметр argc содержит количество аргументов командной строки, передаваемых в вашу программу. • Параметр argv представляет собой массив указателей на символьные строки, каждая из которых соответствует одному параметру командной строки. • В зависимости от компилятора ваши программы могут получить доступ к третьему параметру с именем env, который представляет собой массив указателей на символьные строки, указывающих на переменные среды. Способность программ обращаться к аргументам командной строки повышает количество способов, которыми вы можете использовать одну и ту же программу. Например, вы можете создать свою собственную программу, используемую для копирования любого исходного файла, который вы указываете в качестве первого аргумента командной строки, в целевой файл, который вы указываете в качестве второго аргумента командной строки. ДОСТУП К argv И argc Если вы запускаете программу из системной подсказки, командная строка, которую вы вводите, становится вашей командной строкой: С:\> COPY SOURCE.DOC TARGET.DOC <ENTER>
Использование аргументов командной строки 285 В данном случае командная строка указывает команду (COPY) и два аргумента (имя файлов SOURCE.DOC и TARGET.DOC). Чтобы разрешить вашей программе доступ к командной строке, C++ передает два параметра в функцию main: void main(int argc, char *argv[]) Первый параметр argc содержит количество элементов в массиве argv. Например, в случае предыдущей команды COPY параметр argc должен содержать значение 3 (он включает имя команды и два аргумента). Следующая программа SHOWARGC.CPP использует параметр argc для вывода количества аргументов командной строки: #include <iostream.h> void main(int argc, char *argv[]) { cout << "Количество аргументов командной строки равно " << argc << endl; } Выберите время для эксперимента с этой программой, вызывая ее с разным количеством параметров, как показано ниже: С:\> SHOWARGC ABC <ENTER> Количество аргументов командной строки равно 4 В зависимости от типа компилятор может рассматривать аргументы, которые группируются внутри двойных кавычек, в качестве одного аргумента: С:\> SHOWARGC "Это один аргумент" <ENTER> Количество аргументов командной строки равно 2 Рис. 36. Массив argv указывает аргументы командной строки.
286 Урок 36 Второй параметр функции main с именем argv представляет собой массив указателей на символьные строки, которые содержат индивидуальные части командной строки. Например, рис. 36 иллюстрирует, как элементы массива argv могли бы указывать записи командной строки. Следующая программа SHOWARGV.CPP использует оператор for для вывода элементов массива argv (командная строка программы). Программа запускается с первого элемента массива (имя программы) и затем выводит каждый элемент, пока значение переменной цикла не станет больше, чем argc. #include <lostream.h> void main(int argc, char *argv[]) { int i; for (i = 0; i < argc; i++) cout << "argv[" << i << "] содержит " << argv[i] << endl; } Откомпилируйте и запустите эту программу, используя командную строку, подобную следующей: С:\> SHOWARGV ABC <ENTER> argv[0] содержит SHOWARGV.EXE argv[l] содержит А argv[2] содержит В argv[3] содержит С Доступ к аргументам командной строки Для увеличения количества задач, выполняемых программой, C++ позволяет вашей программе обращаться к аргументам командной строки, используя два параметра, которые C++ передает в main. Первый параметр argc содержит количество аргументов командной строки (включая имя программы). Второй параметр argv представляет собой массив указателей на символьные строки. Каждая символьная строка соответствует аргументу командной строки. Чтобы обратиться к аргументам командной строки, измените заголовок функции main, как показано ниже: void main(int argc, char *argv[]) ВЫПОЛНЕНИЕ ЦИКЛА, ПОКА argv HE СОДЕРЖИТ NULL Как вы уже знаете, программы C++ используют символ NULL для завершения символьной строки. Подобным способом C++ использует символ NULL, чтобы отметить последний элемент массива argv. Следующая программа ARGVNULL.CPP изменяет оператор for предыдущей программы, чтобы вы-
Использование аргументов командной строки 287 полнять цикл по элементам argv, пока текущий элемент argv не будет равен NULL: #include <iostream.h> i void main(int argc, char *argv[]) { int i; for (i = 0; argv[i] != NULL; i++) cout << "argv[" << i << "] содержит " << argv[i] << endl; } ТРАКТОВКА argv КАК УКАЗАТЕЛЯ Как вы уже знаете, C++ позволяет вам ббращаться к элементам массивов, используя указатели. Следующая программа ARGVPTR.CPP трактует argv как указатель на указатель символьной строки (другими словами, указатель на указатель), чтобы вывести содержимое командной строки: #include <iostream.h> void main(int argc, char **argv) { int i = 0; while (*argv) cout << "argv[" << i++ << "] содержит " « *argv++ << endl; } Выберите время, чтобы проанализировать объявление параметра argv в main: void main(int argc, char **argv) Первая звездочка в объявлении сообщает компилятору C++, что argv представляет собой указатель. Вторая звездочка сообщает компилятору, что argv представляет собой указатель на указатель — в данном случае указатель на указатель типа char. Представьте argv как массив указателей. Каждый элемент массива в данном случае указывает на массив типа char. ИСПОЛЬЗОВАНИЕ АРГУМЕНТОВ КОМАНДНОЙ СТРОКИ Следующая программа FILESHOW.CPP использует аргументы командной строки для вывода содержимого указанного пользователем файла на экран. Например, чтобы использовать программу FILESHOW для вывода содержимого файла AUTOEXEC.BAT из корневого каталога, ваша командная строка становится следующей:
288 Урок 36 С:\> FILESHOW \AUTOEXEC.BAT <Enter> Следующие операторы реализуют программу FILESHOW.CPP. Эта программа начинается с проверки параметра argc, чтобы убедиться, что пользователь указал файл в командной строке. Если пользователь включает имя файла, параметр argc будет содержать значение 2. Далее программа открывает и выводит содержимое файла, используя методы, которые вы изучали в уроке 34. Как видите, если программа не может открыть указанный файл, она выводит сообщение об ошибке и завершается: #include <lostream.h> #include <fstream.h> #include <stdlib.h> void main(int argc, char *argv[]) { char line[256]; if (argc < 2) { cerr << "Вы должны указать имя файла" << endl; exit(l); } if stream input_f ile(argv[l] ); if (input_file.fail()) cerr << "Ошибка открытия BOOKINFO.DAT" << endl; else { while ((! input_file.eof()) && (! input_file.faiK))) { input_file.getline(line, sizeof(line)); if (! input_file.fail()) cout << line << endl; > } } ДОСТУП К ПЕРЕМЕННЫМ СРЕДЫ ОПЕРАЦИОННОЙ СИСТЕМЫ Как вы знаете, большинство операционных систем позволяют вам определять переменные среды, к которым ваши программы могут обращаться для определения разных параметров, таких как командный путь. Например, если вы используете среду MS-DOS, вы устанавливаете или выводите переменные среды
Использование аргументов командной строки 289 с помощью команды SET. В зависимости от типа вашего компилятора вы можете обращаться к переменным среды из вашей программы, используя третий параметр main с именем env. Подобно параметру argv, параметр env представляет собой указатель на массив указателей на символьные строки. Также, подобно argv, C++ завершает этот массив символом NULL. Если ваш компилятор поддерживает параметр env, вы можете изменить заголовок функции main, как показано ниже: void main(int argc, char *argv[], char *env[]) Следующая программа SHOWENV.CPP выполняет цикл по элементам массива env для вывода переменных среды программы: #include <iostream.h> void main(int argc, char *argv[], char *env[]) { while (*env) cout << *env++ << endl; } Как видите, программа просто выполняет цикл по элементам массива env, пока не встретит указатель NULL, который указывает последнюю запись массива. Если вы откомпилируете и запустите эту программу, она выведет ваши переменные среды, как показано ниже: С:\> SHOWENV <ENTER> ТЕМР=С: \WINDOWS\TEMP PROMPT=$p$g COMSPEC=C:\WINDOWS\COMMAND.COM РАТН=С: \WINDOWS; С: \DOS Доступ к переменным среды В зависимости от типа компилятора, ваши программы могут обращаться к переменным среды операционной системы, используя третий параметр функции main с именем env. Подобно параметру argv, параметр env представляет собой массив указателей на символьные строки, каждый из которых указывает переменную среды. Чтобы обратиться к переменным среды, используя параметр env, измените заголовок функции main следующим образом: void main(int argc, char *argv[], char *env[]) ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Чтобы увеличить количество приложений, поддерживаемых вашей программой, C++ позволяет использовать аргументы командной строки. Из урока 37 вы узнаете, как с помощью макрокоманд и констант можно упростить ваше
290 Урок 36 программирование или создать более удобочитаемый код. Однако, прежде чем перейти к уроку 37, убедитесь, что вы освоили следующие основные концепции: 0 Когда вы запускаете программу из системной подсказки, информация, которую вы вводите, становится командной строкой программы. 0 Чтобы позволить вашим программам обращаться к командной строке, C++ передает функции main два параметра: argc и argv. 0 Параметр argc содержит количество аргументов командной строки. 0 Параметр argv представляет собой массив указателей на символьные строки, каждая из которых содержит аргумент командной строки. 0 В зависимости от вашего компилятора программа может обращаться к третьему параметру main с именем env, который представляет собой массив указателей на символьные строки, содержащие переменные среды.
Урок 37 Использование констант и макрокоманд Чтобы улучшить удобочитаемость программы, C++ поддерживает именованные константы и макрокоманды. Например, используя именованную константу, вы можете заменить цифровое значение, такое как 50, внутри вашего исходного кода смысловой константой, такой как CLASS_SIZE. Когда другой программист читает ваш код, он не сможет предположить, что означает цифровое значение 50. Если же вместо этого он каждый раз будет видеть CLASS_SIZE, то он поймет, что это значение соответствует числу студентов в классе. Аналогично, используя макрокоманды, ваши программы могут заменить сложные выражения типа result = (х*у-3) * (х*у-3) * (х*у-3); вызовом функции с именем CUBE, как показано ниже: result = CUBE(x*y-3); В данном случае макрокоманда не только улучшает удобочитаемость вашего кода, но и упрощает ваш оператор, уменьшая вероятность ошибки. Этот урок рассматривает именованные константы и макрокоманды более подробно. К концу данного урока вы освоите следующие основные концепции: • Чтобы сделать программы легче для чтения, программисты часто заменяют цифровые значения более понятными по смыслу именованными константами. • Используя именованные константы в вашей программе вместо цифровых значений, вы можете сделать свои программы более легкими для изменения в будущем. • C++ позволяет программам заменять выражения смысловыми именами макрокоманд. • До компиляции программы компилятор C++ запускает специальную программу, называемую препроцессором, чтобы заменить каждую константу или макрокоманду соответствующим значением. • Макрокоманды выполняются быстрее, чем функции, но увеличивают размер выполняемой программы. • Большинство компиляторов C++ имеют предопределенные константы и макрокоманды, которые вы можете использовать в своих программах.
292 Урок 37 ИСПОЛЬЗОВАНИЕ ИМЕНОВАННЫХ КОНСТАНТ Именованная константа — это просто имя, которому вы присваиваете постоянное значение (константу). Такая константа в отличие от значения переменной не может изменяться по мере выполнения программы. Вы создаете именованную константу, используя директиву препроцессора #define (специальную инструкцию для препроцессора компилятора). Например, следующий оператор определяет именованную константу CLASS_SIZE как значение 50: #define CLASS_SIZE 50 Чтобы отличить именованную константу от переменной, большинство программистов используют для именованных констант буквы верхнего регистра. Например, следующая программа CONSTANT.CPP определяет и выводит именованную константу CLASS_SIZE: #include <iostream.h> #de?ine CLASS_SIZE 50 // Число студентов в классе void main(void) { cout << "Константа CLASS_SIZE равна " « CLASS_SIZE << endl; > Как видите, программа определяет константу, используя директиву #define в начале исходного кода. После того как вы определяете константу, вы можете использовать ее значение на протяжении всей программы, просто обращаясь к имени значения константы. Замечание: Предыдущее определение константы не заканчивается тонкой с запятой. Если вы поставите точку с запятой в конце определения, препроцессор включит ее в ваше определение. Например, если вы в директиве #define предыдущей программы поставите точку с запятой после значения 50, препроцессор в дальнейшем каждый экземпляр константы CLASS'SIZEзаменит значением 50 с точкой с запятой E0;), что, очень вероятно, приведет к синтаксической ошибке. Что такое директивы препроцессора Прежде чем приступить к компиляции программы, компилятор C++ запускает специальную программу, которая называется препроцессором. Препроцессор ищет в программе строки, начинающиеся с символа #, например ^include или #define. Если препроцессор, например, встречает директиву #include, он включает указанный в ней файл в ваш исходный файл, как будто бы вы сами печатали содержимое включаемого файла в вашем исходном коде. Каждая программа, которую вы создали при изучении данной книги, использовала директиву ttinclude,
Использование констант и макрокоманд 293 чтобы заставить препроцессор включить содержимое заголовочного файла iostream.h в ваш исходный файл. Если препроцессор встречает директиву #define, он создает именованную константу или макрокоманду. В дальнейшем, если препроцессор встречает имя константы или макрокоманды, он заменяет это имя значением, указанным в директиве ttdefine. Если вы определяете константы в своих программах, C++ не ограничивает вас в использовании только цифровых значений. Вы можете также использовать константы для хранения символьных строк и значений с плавающей точкой. Например, следующая программа BOOKINFO.CPP использует директиву #define для создания трех констант, которые содержат информацию об этой книге: #include <iostream.h> #define TITLE "Учимся программировать на языке C++" #define LESSON 37 #define PRICE 22.95 void main(void) { cout << "Название книги: " << TITLE << endl; cout << "Текущий урок: " << LESSON << endl; cout << "Цена: $" << PRICE << endl; } Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод: С:\> BOOKINFO <ENTER> Название книги: Учимся программировать на языке C++ Текущий урок: 37 Цена: $22.95 Использование #define для создания именованных констант Для улучшения читаемости ваших программ заменяйте числовые значения в исходном коде константами со смысловыми именами. Для определения именованной константы ваши программы должны использовать директиву препроцессора ttdefine. Размещайте свои константы в верхней части вашего исходного файла. Кроме того, чтобы отличать константы от переменных, большинство программистов для имен констант используют буквы верхнего регистра. Например, следующая директива ttdefine создает константу с именем SECONDSJ>ER_HOUR
294 Урок 37 #define SECONDS_PER_HOUR 3600 Во время компиляции программы препроцессор C++ будет заменять каждый экземпляр имени SECONDS_PER_HOUR числовым значением 3600. Обратите внимание, что определение константы не заканчивается точкой с запятой. Если вы поставите после 3600 точку с запятой, препроцессор C++ в дальнейшем заменит каждый экземпляр имени SECONDS_PER_HOUR его значением с точкой с запятой C600;), что, очень вероятно, приведет к синтаксической ошибке. ИСПОЛЬЗОВАНИЕ ИМЕНОВАННЫХ КОНСТАНТ ДЛЯ УПРОЩЕНИЯ ИЗМЕНЕНИЯ КОДА Кроме того, что именованные константы делают вашу программу легче для восприятия, они еще и облегчают модификацию программ. Например, следующий фрагмент кода несколько раз ссылается на число 50 (количество студентов в классе): #include <iostream.h> void main(void) { int test_scores[50]; char grades[50]; int student; for (student = 0; student < 50; student++) get_test_score(student); for (student = 0; student < 50; student++) calculate_grade(student); for (student = 0; student < 50; student++) print_grade(student); } Предположим, например, что количество студентов в классе увеличилось до 55. В этом случае вы должны отредактировать предыдущую программу, чтобы заменить каждый экземпляр значения 50 значением 55. В следующей программе применен другой подход, она использует именованную константу CLASS_SIZE: #include <iostream.h> ttdefine CLASS_SIZE 50 void main(void)
Использование констант и макрокоманд 295 { int test_scores[CLASS_SIZE]; char grades[CLASS_SIZE]; int student; for (student = 0; student < CLASS_SIZE; student++) get__test_score(student); for (student = 0; student < CLASS_SIZE; studentЧ-+) calculate_grade(student); for (student = 0; student < CLASS_SIZE; student++) print_grade(student); } В данном случае для изменения количества студентов во всей программе вам необходимо изменить только одну строку, которая содержит директиву #define, определяющую эту константу: #define CLASS_SIZE 55 ЗАМЕНА ВЫРАЖЕНИЙ МАКРОКОМАНДАМИ Если ваши программы выполняют реальные вычисления, то в общем случае ваш код будет содержать сложные выражения типа: result = (х*у-3) * (х*у-3) * (х*у-3); В данном случае программа вычисляет куб выражения (х*у-3). Чтобы улучшить читаемость вашей программы и уменьшить вероятность внесения ошибок из-за опечаток, создайте макрокоманду с именем CUBE, которую ваша программа может использовать следующим образом: result = CUBE(x*y-3); И опять общепринятым среди программистов считается использование больших букв для имен макрокоманд, чтобы отличить их от функций. Для создания макрокоманды вы должны использовать директиву препроцессора ^define. Например, следующий оператор создает макрокоманду CUBE: #define CUBE(x) ((х)*(х)*(х)) Как видите, программа определяет макрокоманду CUBE для умножения параметра хна самого себя дважды. Следующая программа SHOWCUBE.CPP использует макрокоманду CUBE для вывода куба значений от 1 до 10: #include <iostream.h> #define CUBE(x) ((x)*(x)*(x))
296 Урок 37 void main (void) { for (int i = 1; i <= 10; i++) cout << "Для " << i << " куб равен " << CUBE(i) << endl; > При компиляции этой программы препроцессор C++ заменит каждый экземпляр макрокоманды CUBE соответствующим определением. Другими словами, замена макрокоманды препроцессором приведет к следующему коду: #include <iostream.h> ttdefine CUBE(x) ((x)*(x)*(x)) void main(void) { for (int i = 1; i <= 10; i++) cout << "Для " << i << " куб равен " << ((i) * (i) * (i)) << endl; } Обратите внимание, что предыдущая макрокоманда поместила параметр хвнутрь круглых скобок, использовав запись ((х)*(х)*(х)) вместо (х*х*х). При создании макрокоманд вы должны помещать параметры в круглые скобки, как показано выше, тогда можете быть уверены, что C++ трактует ваши выражения именно так, как вы хотите. Как вы помните из урока 5, C++ использует старшинство операций для определения порядка выполнения арифметических операций. Предположим, например, что программа использует макрокоманду CUBE с выражением 3+5-2, как показано ниже: result = CUBEC+5-2); Если макрокоманда заключает свой параметр в круглые скобки, то препроцессор сгенерирует следующий оператор: result = (C+5-2) * C+5-2) * C+5-2)); Однако, если в определении макрокоманды опустить круглые скобки, препроцессор сгенерирует следующий оператор: result = C+5-2*3+5-2*3+5-2); Если вы вычислите оба выражения, то обнаружите, что их результаты отличаются. Заключая аргументы макрокоманды в круглые скобки, вы избавитесь от подобных ошибок. ЧЕМ МАКРОКОМАНДЫ ОТЛИЧАЮТСЯ ОТ ФУНКЦИЙ Определение макрокоманды не является функцией. Если программа использует функцию, то в выполняемую программу помещается только одна копия операторов функции. Каждый раз при вызове функции ваша программа по-
Использование констант и макрокоманд 297 мещает параметры в стек и затем выполняет переход к коду функции. После завершения функции программа удаляет параметры из стека и переходит обратно к оператору, который следует непосредственно за вызовом функции. В случае с макрокомандой препроцессор заменяет в вашем коде каждую ссылку на макрокоманду соответствующим определением макрокоманды. Например, если предыдущая программа использует макрокоманду CUBE в 100 различных местах, препроцессор подставит код макрокоманды 100 раз. Используя макрокоманды, вы избегаете издержек на вызов функции (издержек на помещение параметров в стек и удаление их оттуда, а также издержек на выполнение перехода к коду функции и возврат из него). Это происходит благодаря тому, что в случае с макрокомандой препроцессор встраивает в тело программы соответствующие операторы. Однако, поскольку препроцессор заменяет каждую ссылку на макрокоманду соответствующим кодом, макрокоманды увеличивают размер вашей выполняемой программы. ИСПОЛЬЗОВАНИЕ МАКРОКОМАНД ПРЕДОСТАВЛЯЕТ БОЛЬШУЮ ГИБКОСТЬ Вы можете использовать макрокоманды в своих программах различным образом. Однако имейте в виду, что цель использования макрокоманд состоит в упрощении кодирования и улучшении восприятия ваших программ. Следующая программа MACDELAY.CPP иллюстрирует гибкость макрокоманд. Кроме того, эта программа поможет вам лучше представить, как препроцессор заменяет имя макрокоманды соответствующими операторами: #include <iostream.h> #define delay(x) { \ cout << "Задержка на " « x << endl; \ for (long int 1 = 0; 1 < x; 1++) \ ; \ } void main(void) { delayA00000L); delayB00000L); delayC00000L); } В данном случае, поскольку определение макрокоманды занимает несколько строк, это определение помещает один символ обратного слэша (\) в конце каждой строки, которая имеет продолжение. Когда препроцессор встретит ссылку на макрокоманду, он заменит эту ссылку операторами, которые появляются в определении макрокоманды.
298 Урок 37 ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Макрокоманды и именованные константы предназначены для улучшения восприятия ваших программ и упрощения программирования. Данный урок описывает создание и использование именованных констант и макрокоманд в ваших кодах. Из урока 38 вы узнаете, что такое полиморфизм, который позволяет объектам изменять форму во время выполнения программы. Однако, прежде чем приступить к уроку 38, убедитесь, что вы освоили следующие основные концепции: 0 Макрокоманды и именованные константы облегчают чтение ваших программ, заменяя сложные выражения и числовые константы смысловыми именами. 0 Заменяя на протяжении всей программы числовые выражения именованными константами, вы уменьшаете количество изменений, которые вам придется выполнить позже, если значение константы потребуется изменить. 0 В процессе компиляции компилятор C++ использует специальную программу, которая называется препроцессором, для замены каждой именованной константы или макрокоманды соответствующим значением. 0 Макрокоманды выполняются быстрее функций, но они увеличивают размер вашей выполняемой программы. 0 Если определение макрокоманды выходит за пределы одной строки, вы должны в конце каждой строки поместить символ обратного слэша (\), чтобы информировать препроцессор о том, что определение продолжается на следующей строке.
Урок 38 Полиморфизм Когда программисты говорят о C++ и объектно-ориентированном программировании, то очень часто употребляют термин полиморфизм. В общем случае полиморфизм представляет собой способность объекта изменять форму. Если вы разделите этот термин на части, то обнаружите, что поли означает много, а морфизм относится к изменению формы. Полиморфный объект, следовательно, представляет собой объект, который может принимать разные формы. Этот урок вводит понятие полиморфизма и рассматривает, как использовать полиморфные объекты внутри ваших программ для упрощения и уменьшения кбда. К концу данного урока вы освоите следующие основные концепции: • Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы. • C++ упрощает создание полиморфных объектов. • Для создания полиморфных объектов ваши программы должны использовать виртуальные (virtual) функции. • Виртуальная (virtual) функция — это функция базового класса, перед именем которой стоит ключевое слово virtual. • Любой производный от базового класс может использовать или перегружать виртуальные функции. • Для создания полиморфного объекта вам следует использовать указатель на объект базового класса. ЧТО ТАКОЕ ПОЛИМОРФИЗМ Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Предположим, например, что вы программист, работающий в телефонной компании, и вам необходимо написать программу, которая эмулирует телефонные операции. Поскольку вы знакомы с тем, как люди используют телефон, вы быстро выберете общие операции, такие как набор номера, звонок, разъединение и индикация занятости. С помощью этих операций вы определите следующий класс: class phone { public: void dial (char *number) { cout « "Набор номера " <<
300 Урок 38 number << endl; } void answer (void) { cout << "Ожидание ответа" << endl; } void hangup(void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl;} phone(char *number) { strcpy(phone::number, number); }; private: char number[13]; }; Следующая программа PHONEONE.CPP использует класс phone для создания объекта-телефона: #include <lostream.h> #include <string.h> class phone { public: void dial(char *number) { cout << "Набор номера " << number << endl; } void answer (void) { cout << "Ожидание ответа" << endl; } void hangup(void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl; } phone(char *number) { strcpy(phone::number, number); }; private: char number[13]; }; void main(void) { phone telephone(55-1212"); telephone.dial(12-555-1212"); } Если вы продемонстрируете программу вашему боссу, то он или она скажет, что ваша программа не делает различий между дисковым и кнопочным телефонами, и что она не поддерживает платные телефоны, когда пользователь должен заплатить 25 центов, чтобы позвонить. Поскольку вы знаете наследование, то примете решение породить классы touch jtone и pay_phone из класса phone, как показано ниже: class touchstone : phone { public: void dial (char *number) { cout << "Пик пик Набор номера " << number << endl; } touchstone(char *number) : phone(number) { } }; class pay_phone : phone { public: void dial(char *number) { cout << "Пожалуйста, оплатите "
Полиморфизм 301 « amount << " центов" << endl; cout << "Набор номера " << number << endl; } pay_phone(char ¦number, int amount) : phone(number) { pay_phone::amount = amount; } private: int amount; }; Как видите, классы touch tone и pay_phone определяют свой собственный метод dial. Если вы предположите, что метод dial класса phone основан на дисковом телефоне, то вам не потребуется создавать класс для дискового телефона. Следующая программа NEWPHONE.CPP использует эти классы для создания объектов rotary, touchjone и pay_phone\ #include <iostream.h> #include <string.h> class phone { public: void dial (char *number) { cout << "Набор номера " << number << endl; } void answer(void) { cout << "Ожидание ответа" << endl; } void hangup(void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl; } phone(char *number) { strcpy(phone::number, number); >; protected: char number[13]; }; class touch_tone : phone { public: void dial(char *number) { cout << "Пик пик Набор номера " << number << endl; } touch_tone(char *number) : phone(number) { > }; class pay_phone : phone { public: void dial(char *number) { cout << "Пожалуйста, оплатите " << amount << " центов" << endl; cout << "Набор номера " << number << endl; } pay_phone(char *number, int amount) : phone(number) { pay_phone: : amount = amount; } private: int amount; };
302 Урок 38 void main(void) { phone rotary(03-555-1212"); rotary.dial(02-555-1212"); touch_tone telephone(55-1212"); telephone.dial(12-555-1212"); pay_phone city_phone(55-1111", 25); city_phone.dial(12-555-1212"); } Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод: С:\> NEWPHONE <Enter> Набор номера 602-555-1212 Пик пик Набор номера 212-555-1212 Пожалуйста, оплатите 25 центов Набор номера 212-555-1212 Как уже упоминалось, полиморфный объект представляет собой такой объект, который изменяет форму во время выполнения программы. Предыдущая программа, например, не использовала полиморфные объекты. Иначе говоря, в ней нет объектов, которые бы изменяли форму. СОЗДАНИЕ ПОЛИМОРФНОГО ОБЪЕКТА-ТЕЛЕФОНА После того как вы показали начальству вашу новую телефонную программу, ваше руководство сказало вам, что ваш объект-телефон должен уметь эмулировать дисковый, кнопочный или платный телефон на выбор. Другими словами, для одного звонка объект-телефон мог бы представлять кнопочный аппарат, для другого выступал бы как платный телефон и т.д. Иначе говоря, от одного звонка к другому ваш объект-телефон должен изменять форму. В этих разных классах телефона существует единственная отличающаяся функция — это метод dial. Для создания полиморфного объекта вы сначала определяете функции базового класса, которые отличаются от функций производных классов тем, что они виртуальные, предваряя их прототипы ключевым словом virtual, как показано ниже: class phone { public: virtual void dial(char *number) { cout << "Набор номера " « number << endl; } void answer(void) { cout << "Ожидание ответа" << endl; } void hangup(void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl; }
Полиморфизм 303 phone(char *number) { strcpy(phone::number, number); }; protected: char number[13]; }; Далее в программе вы создаете указатель на объект базового класса. Для вашей телефонной программы вы создадите указатель на базовый класс phone: phone *poly_phone; Для изменения формы объекта вы просто присваиваете этому указателю адрес объекта производного класса, как показано ниже: poly_phone = (phone *) &home_phone; Символы (phone *), которые следуют за оператором присваивания, являются оператором приведения типов, который сообщает компилятору C++, что все в порядке, необходимо присвоить адрес переменной одного типа {touchjom) указателю на переменную другого типа {phone). Поскольку ваша программа может присваивать указателю объекта poly_phone адреса различных объектов, то этот объект может изменять форму, а следовательно, является полиморфным. Следующая программа POLYMORP.CPP использует этот метод для создания объекта-телефона. После запуска программы объект poly_phone меняет форму с дискового телефона на кнопочный, а затем на платный: #include <lostream.h> #include <string.h> class phone { public: virtual void dial(char *number) { cout << "Набор номера " << number << endl; } void answer (void) { cout << "Ожидание ответа" << endl; } void hangup(void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl; } phone(char *number) { strcpy(phone::number, number); }; protected: char number[13]; }; class touch_tone : phone { public: void dial (char *number) { cout << "Пик пик Набор номера " << number << endl; } touchstone(char *number) : phone(number) { } };
304 Урок 38 class pay_phone : phone { public: void dial(char *number) { cout << "Пожалуйста, оплатите " << amount << " центов" << endl; cout << "Набор номера " << number << endl; } pay_phone(char ¦number, int amount) : phone(number) { pay_phone::amount = amount; } private: int amount; >; void main (void) { pay_phone city_phone(02-555-1212", 25); touch_tone home_phone(55-1212"); phone rotary(01-555-1212"); // Сделать объект дисковым телефоном phone *poly_phone = fcrotary; poly_phone->dial("818-555-1212"); // Заменить форму объекта на кнопочный телефон poly_phone = (phone *) &home_phone; poly_phone->dial(03-555-1212"); // Заменить форму объекта на платный телефон poly_phone = (phone *) &city_phone; poly_phone->dial(12-555-1212"); } Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод: С:\> POLYMORP <ENTER> Набор номера 818-555-1212 Пик пик Набор номера 303-555-1212 Пожалуйста, оплатите 25 центов Набор номера 212-555-1212 Поскольку объект poly_phone изменяет форму по мере выполнения программы, он является полиморфным. Полиморфные объекты могут изменять форму во время выполнения программы Полиморфный объект представляет собой такой объект, который может изменять форму во время выполнения программы. Для создания полиморфного объекта ваша программа должна использовать указатель на объект базового класса. Далее программа должна присвоить этому указателю адрес объекта производного класса. Каждый раз, ког-
Полиморфизм 305 да программа присваивает указателю адрес объекта другого класса, объект этого указателя (который является полиморфным) изменяет форму. Программы строят полиморфные объекты, основываясь на виртуальных функциях базового класса. ЧТО ТАКОЕ ЧИСТО ВИРТУАЛЬНЫЕ ФУНКЦИИ Как вы уже знаете, для создания полиморфного объекта ваши программы определяют один или несколько методов базового класса как виртуальные функции. Производный класс может определить свою собственную функцию, которая выполняется вместо виртуальной функции базового класса, или использовать базовую функцию (другими словами, производный класс может и не определять свой собственный метод). В зависимости от программы иногда не имеет смысла определять виртуальную функцию в базовом классе. Например, объекты производных типов могут настолько сильно отличаться, что им не нужно будет использовать метод базового класса. В таких случаях вместо определения операторов для виртуальной функции базового класса ваши программы могут создать чисто виртуальную функцию, которая не содержит операторов. Для создания чисто виртуальной функции ваша программа указывает прототип функции, но не указывает ее операторы. Вместо них программа присваивает функции значение ноль, как показано ниже: class phone { public: virtual void dial(char *number) =0; // Чисто виртуальная / / функция void answer (void) { cout << "Ожидание ответа" << endl; } void hangup (void) { cout << "Звонок выполнен-повесить трубку" << endl; } void ring(void) { cout << "Звонок, звонок, звонок" << endl; } phone(char *number) { strcpy(phone::number, number); }; protected: char number[13]; }; Каждый производный класс, определенный в вашей программе, должен определить свою функцию вместо чисто виртуальной функции базового класса. Если производный класс опустит определение функции для чисто виртуальной функции, компилятор C++ сообщит о синтаксических ошибках. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Полиморфизм представляет собой способность объекта изменять форму во время выполнения программы. В этом уроке рассмотрены шаги, которые вам необходимо выполнить для создания полиморфных объектов. Из урока 39 вы узнаете, как использовать исключительные ситуации в C++ для обес-
306 Урок 38 печения надежности вашей программы. Прежде чем приступить к уроку 39, убедитесь, что вы освоили следующие основные концепции: 0 Полиморфный объект может изменять форму во время выполнения программы. 0 Вы создаете полиморфные объекты, используя классы, порожденные от существующего базового класса. 0 В базовом для полиморфного объекта классе вы должны определить одну или несколько функций как виртуальные {virtual). 0 В общем случае полиморфные объекты отличаются использованием виртуальных функций базового класса. 0 Для создания полиморфного объекта вам необходимо создать указатель на объект базового класса. 0 Для изменения формы полиморфного объекта вы просто направляете указатель на различные объекты, присваивая новый адрес объекта указателю на полиморфный объект. 0 Чисто виртуальная функция — это виртуальная функция базового класса, для которой в базовом классе не определены операторы. Вместо них базовый класс присваивает такой функции значение 0. 0 Производные классы должны обеспечить определение функции для каждой чдсто виртуальной функции базового класса.
Урок 39 Использование исключительных ситуаций C++ для обработки ошибок После того как вы создали и отладили (удалили ошибки) несколько программ, вы уже способны предвидеть ошибки, которые могут встретиться в программе. Например, если ваша программа читает информацию из файла, ей необходимо проверить, существует ли файл и может ли программа его открыть. Аналогично, если ваша программа использует оператор new для выделения памяти, ей необходимо проверить и отреагировать на возможное отсутствие памяти. По мере увеличения размера и сложности ваших программ вы обнаружите, что необходимо включить много таких проверок по всей программе. Из этого урока вы узнаете, как использовать исключительные ситуации C++ для упрощения проверки и обработки ошибок. К концу данного урока вы освоите следующие основные концепции: • Исключительная ситуация (exception) представляет собой неожиданное событие — ошибку — в программе. • В ваших программах вы определяете исключительные ситуации как классы. • Чтобы заставить ваши программы следить за исключительными ситуациями, необходимо использовать оператор C++ try. • Для обнаружения определенной исключительной ситуации ваши программы используют оператор C++ catch. • Для генерации исключительной ситуации при возникновении ошибки ваши программы используют оператор C++ throw. • Если ваша программа обнаруживает исключительную ситуацию, она вызывает специальную (характерную для данной исключительной ситуации) функцию, которая называется обработчиком исключительной ситуации. • Некоторые (старые) компиляторы не поддерживают исключительные ситуации C++. C++ ПРЕДСТАВЛЯЕТ ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ КАК КЛАССЫ Ваша цель при использовании исключительных ситуаций C++ состоит в упрощении обнаружения и обработки ошибок в программах. В идеале, если ваши программы обнаруживают неожиданную ошибку (исключительную си-
308 Урок 39 туацию), им следует разумным образом ее обработать вместо того, чтобы просто прекратить выполнение. В программах вы определяете каждую исключительную ситуацию как класс. Например, следующие ситуации определяют три исключительные ситуации для работы с файлами: class file_open_error {}; class file_read_error {}; class file_write_error {}; Позже в этом уроке вы создадите исключительные ситуации, которые используют переменные и функции-элементы класса. А пока просто поверьте, что каждая исключительная ситуация соответствует классу. КАК ЗАСТАВИТЬ C++ ПРОВЕРЯТЬ ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ Прежде чем ваши программы могут обнаружить и отреагировать на исключительную ситуацию, вам следует использовать оператор C++ try для разрешения обнаружения исключительной ситуации. Например, следующий оператор try разрешает обнаружение исключительной ситуаций для вызова функции file_copy\ try { file_COpy("SOURCE.TXT", "TARGET.TXT"); }; Сразу же за оператором try ваша программа должна разместить один или несколько операторов catch, чтобы определить, какая исключительная ситуация имела место (если она вообще была): try { file_COpy("SOURCE.TXT", "TARGET.TXT"); }; catch (file_open_error) { cerr << "Ошибка открытия исходного или целевого файла" < < endl; exit(l); } catch (file_read_error) { cerr << "Ошибка чтения исходного файла" << endl; exit(l); } catch (file_write_error) { cerr << "Ошибка записи целевого файла" << endl; exit(l); }
Использование исключительных ситуаций C++ 309 Как видите, приведенный код проверяет возникновение исключительных ситуаций работы с файлами, определенных ранее. В данном случае независимо от типа ошибки код просто выводит сообщение и завершает программу. В идеале ваш код мог бы отреагировать и не так — возможно, попытаться исключить причину ошибки и повторить операцию. Если вызов функции прошел успешно и исключительная ситуация не выявлена, C++ просто игнорирует операторы catch. ИСПОЛЬЗОВАНИЕ ОПЕРАТОРА throw ДЛЯ ГЕНЕРАЦИИ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ Сам C++ не генерирует исключительные ситуации. Их генерируют ваши программы, используя оператор C++ throw. Например, внутри функции file_copy программа может проверить условие возникновения ошибки и сгенерировать исключительную ситуацию: void file_copy(char *source, char *target) { char line[256]; ifstream input_file(source); ofstream output_file(target); if (input_file.fail()) throw(f i1e_open_error); else if (output_file.fail()) throw(f i1e_ppen_error); else { while ((! input_file.eof()) && (! input_file.fail())) { input_file.getline(line, sizeof(line)); if (I input_file.fail()) output_file << line << endl; else throw(file_read_error); if (output_file.fail()) throw(file_write_error); } } } Как видите, программа использует оператор throw для генерации определенных исключительных ситуаций. Как работают исключительные ситуации Когда вы используете исключительные ситуации, ваша программа проверяет условие возникновения ошибки и, если необходимо, генерирует исключительную ситуацию, используя оператор throw. Когда
310 Урок 39 C++ встречает оператор throw, он активизирует соответствующий обработчик исключительной ситуации (функцию, чьи операторы вы определили в классе исключительной ситуации). После завершения функции обработки исключительной ситуации C++ возвращает управление первому оператору, который следует за оператором try, разрешившим обнаружение исключительной ситуации. Далее, используя операторы catch, ваша программа может определить, какая именно исключительная ситуация возникла, и отреагировать соответствующим образом. ОПРЕДЕЛЕНИЕ ОБРАБОТЧИКА ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ Когда ваша программа генерирует исключительную ситуацию, C++ запускает обработчик исключительной ситуации (функцию), чьи операторы вы определили в классе исключительной ситуации. Например, следующий класс исключительной ситуации nukejneltdown определяет операторы обработчика исключительной ситуации в функции nukejneltdown'. class nuke_meltdown { public: nuke_me1tdown(void) { cerr << "\а\а\аРаботаю! Работаю! Работаю!" << endl; } }; В данном случае, когда программа сгенерирует исключительную ситуацию nuke meltdown, C++ запустит операторы функции nukejneltdown, прежде чем возвратит управление первому оператору, следующему за оператором try, разрешающему обнаружение исключительной ситуации. Следующая программа MELTDOWN.CPP иллюстрирует использование функции nukejneltdown. Эта программа использует оператор try для разрешения обнаружения исключительной ситуации. Далее программа вызывает функцию add_u232 с параметром amount. Если значение этого параметра меньше 255, функция выполняется успешно. Если же значение параметра превышает 255, функция генерирует исключительную ситуацию nukejneltdown'. #include <iostream.h> class nuke_meltdown { public: nuke_me1tdown(void) { cerr << "\а\а\аРаботаю! Работаю! Работаю!" << endl; } >; vo id add_u2 3 2(int amount) { if (amount < 255) cout << "Параметр add_u232 в порядке" << endl;
Использование исключительных ситуаций C++ 311 else throw nuke_meltdown(); } void main(void) { try { add_u232B55); } catch (nuke_meltdown) { cerr << "Программа устойчива" << endl; } } Если вы откомпилируете и запустите эту программу, на экране дисплея появится следующий вывод: С:\> MELTDOWN <ENTER> Работаю! Работаю! Работаю! Программа устойчива Если вы проследите исходный код, который генерирует каждое из сообщений, то сможете убедиться, что поток управления при возникновении исключительной ситуации проходит в обработчик исключительной ситуации и обратно к оператору catch. Так, первая строка вывода генерируется обработчиком исключительной ситуации, т.е. функцией nukejneltdown. Вторая строка вывода генерируется в операторе catch, который обнаружил исключительную ситуацию. Определение обработчика исключительной ситуации Когда C++ обнаруживает в программе исключительную ситуацию, он запускает специальную функцию, которая называется обработчиком исключительной ситуации. Для определения обработчика исключительной ситуации вы просто создаете функцию в классе исключительной ситуации, которая имеет такое же имя, как и сама исключительная ситуация (подобно конструктору). Когда ваша программа в дальнейшем сгенерирует исключительную ситуацию, C++ автоматически вызовет соответствующий обработчик. В идеале обработчик исключительной ситуации должен выполнить операции, которые бы исправили ошибку, чтобы ваша программа могла повторить операцию, ставшую причиной ошибки. После завершения обработки исключительной ситуации выполнение вашей программы продолжается с первого оператора, следующего за оператором try, разрешившего обнаружение исключительной ситуации.
312 Урок 39 ИСПОЛЬЗОВАНИЕ ЭЛЕМЕНТОВ ДАННЫХ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ В предыдущих примерах ваши программы, используя оператор catch, могли определить, какая именно исключительная ситуация имела место, и отреагировать соответствующим образом. В идеале, чем больше информации об исключительной ситуации могут получить ваши программы, тем лучше они смогут отреагировать на ошибку. Например, в случае с исключительной ситуацией file_open error вашей программе необходимо знать имя файла, который вызвал ошибку. Аналогично, для файловых исключительных ситуаций file_read_error или file_иrite error программа, возможно, захочет узнать расположение байта, на котором произошла ошибка. Чтобы сохранить подобную информацию об исключительной ситуации, ваши программы могут просто добавить элементы данных в класс исключительной ситуации. Если в дальнейшем ваша программа сгенерирует исключительную ситуацию, она передаст эту информацию функции обработки исключительной ситуации в качестве параметра, как показано ниже: throw file_open_error(source); throw f i1e_read_errorC44); В обработчике исключительной ситуации эти параметры могут быть присвоены соответствующим переменным класса (очень похоже на конструктор). Например, следующие операторы изменяют исключительную ситуацию file_open_error, чтобы присвоить имя файла, который вызвал ошибку, соответствующей переменной класса: class file_open_error { public: file_open_error(char *filename) { strcpy(file_open_error::filename, filename); } char filename[255]; }; ОБРАБОТКА НЕОЖИДАННЫХ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ Из урока 11 вы узнали, что компиляторы C++ предоставляют функции библиотеки этапа выполнения, которые вы можете использовать в своих программах. Если вы читали документацию по этим функциям, то могли обратить внимание на функции, которые генерируют определенные исключительные ситуации. В таких случаях ваши программы должны проверять соответствующие исключительные ситуации. По умолчанию если ваша программа генерирует исключительную ситуацию, которая не улавливается (программа не имеет соответствующего обработчика исключительной ситуации), то запустится стандартный обработчик, предоставляемый языком C++. В
Использование исключительных ситуаций C++ 313 большинстве случаев стандартный обработчик завершит вашу программу. Следующая программа UNCAUGHT.CPP иллюстрирует, как стандартный обработчик исключительной ситуации завершает выполнение вашей программы: #include <iostream.h> class some_exception { }; void main(void) { cout << "Перед генерацией исключительной ситуации" << endl; throw some_exception(); cout << "Исключительная ситуация сгенерирована" << endl; } В данном случае, когда программа генерирует исключительную ситуацию (которая не улавливается программой), C++ вызывает стандартный обработчик исключительной ситуации, который завершает программу. Поэтому последний оператор программы, который выводит сообщение о генерации исключительной ситуации, никогда не выполняется. Вместо использования стандартного обработчика исключительной ситуации C++ ваши программы могут определить свой собственный стандартный обработчик (обработчик по умолчанию). Чтобы сообщить компилятору C++ о своем стандартном обработчике, ваши программы должны использовать функцию библиотеки этяпа выполнения setunexpected. Прототип функции setjunexpected опреде- ле в заголовочном файле except.h. ОБЪЯВЛЕНИЕ ГЕНЕРИРУЕМЫХ ФУНКЦИЕЙ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ Как вы уже знаете, прототип функции позволяет вам определить тип возвращаемого функцией значения и типы ее параметров. Если ваши программы используют исключительные ситуации, вы также можете использовать прототип функции, чтобы указать генерируемые данной функцией исключительные ситуации. Например, следующий прототип функции сообщает компилятору, что функция power_plant может генерировать исключительные ситуации melt_down и radiationleak: void power_p1ant(long power_needed) throw (melt_down, radiation_leak); Включая подобным образом возможные исключительные ситуации в прототип функции, вы можете легко сообщить другому программисту, который будет читать ваш код, какие исключительные ситуации ему необходимо проверять при использовании данной функции.
314 Урок 39 ИСКЛЮЧИТЕЛЬНЫЕ СИТУАЦИИ И КЛАССЫ При создании класса вы, возможно, захотите определить исключительные ситуации, характерные для данного класса. Чтобы создать исключительную ситуацию, характерную для конкретного класса, просто включите эту исключительную ситуацию в качестве одного из общих {public) элементов класса. Например, следующее описание класса string определяет две исключительные ситуации: class string { public: string(char *str); void fill_string(*str); void show_string(void); int string_length(void); class string_empty { }; class string_overflow {}; private: int length; char string[255]; }; Как видите, этот класс определяет исключительные ситуации stringjempty и stringoverflow. В своей программе вы можете проверить наличие исключительной ситуации, используя оператор глобального разрешения if имя класса, как показано ниже: try { some_string.fill_string(some_long_string); }; catch (string::string_overflow) { cerr << "Превышена длина строки, символы отброшены" << endl; } ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Исключительные ситуации предназначены для упрощения и усовершенствования обнаружения и обработки ошибочных ситуаций в ваших программах. Для проверки и обнаружения исключительных ситуаций ваши программы должны использовать операторы try, catch и throw. Ваши знания исключительных ситуаций зависят от опыта программирования на C++. Прежде чем продолжить программировать на C++, убедитесь, что вы освоили следующие основные концепции: 0 Исключительная ситуация представляет собой неожиданную ошибку в вашей программе.
315 Использование исключительных ситуаций C++ 0 Ваши программы должны обнаруживать и обрабатывать (реагировать на) исключительные ситуации. 0 В программах вы определяете исключительную ситуацию как класс. 0 Вы должны использовать оператор try, чтобы заставить компилятор C++ разрешить обнаружение исключительных ситуаций. 0 Вы должны разместить оператор catch сразу же после оператора try, чтобы определить, какая именно исключительная ситуация имела место (если она вообще была). 0 C++ сам не генерирует исключительные ситуации. Ваши программы генерируют исключительные ситуации с помощью оператора throw. 0 Если ваша программа обнаруживает исключительную ситуацию, она вызывает специальную функцию, которая называется обработчиком исключительной ситуации. 0 Если ваши программы используют исключительные ситуации, вы можете указать в прототипе функции, что эта функция способна генерировать исключительную ситуацию. 0 При чтении документации по библиотеке этапа выполнения обращайте внимание на то, что некоторые функции способны генерировать исключительные ситуации. 0 Если ваша программа генерирует, но не улавливает (и не обрабатывает) исключительную ситуацию, C++ будет вызывать стандартный обработчик исключительной ситуации. 0 В заголовочном файле except.h определены прототипы функций, которые ваши программы могут использовать для указания своих собственных стандартных обработчиков неулавливаемых исключительных ситуаций.
Предметный указатель asm (оператор) 282 break (оператор) 66 cerr, использование для вывода 28 tin 50 входной поток, перенаправление 53 чтение ввода с клавиатуры 50 один символ за один раз 264 строки за один раз 266 cout 19-20, 22 вывод на экран 19 вывод одного символа за один раз 264 спецсимволы управления выводом 24 точность представления чисел с плавающей точкой 264 ширина выходного поля 28, 262 ctime (функция) 103 public и private 175 do while (оператор) 76 else (оператор) 58 endl (символ конца строки) 25 eof (функция) 272 float (тип данных) 89 for (оператор) 69 выполнение составных операторов 71 продвижение цикла с применением операции увеличения 70 if-else обработка, общее описание 60 //(оператор) 56 ifstream 270 #include 16 INCLUDE (подкаталог) 16 inline (встроенные функции) 280 inline (ключевое слово) 280 int (тип данных) 31 iomanip.h, использование 261 iostream.h, использование 261 NULL (символ) 144, 251, 257 и строковые константы 138 преимущества использования 143 ofstream 270 ostream (выходной поток) см. cout setw (модификатор) 28 sizeof (оператор) 266 strcpy (функция) 148 switch (оператор) 65 time (функция) 103 void main (void) 17 while (оператор) 74 Арифметические операции основные 40 перегрузка 192 Базовый класс 210 Бесконечные циклы 73 Библиотека этапа выполнения 102 функции строковые 143 функция ctime 103 strlwr 143 strupr 143 time 103 Ввод/вывод, перенаправление и tin 53 Восьмеричные значения, отображение 27 Вывод в стандартное устройство ошибок 28 символов 19 управление шириной 28 Выходной поток сегг, использование 28 cout, общее описание 19-20, 22 Главная программа (main) 17 Глобальные переменные 108 конфликты имен 109 область видимости 111 Группирование операторов {} 19 Деструктор 182 Заголовочные файлы 16 iomanip.h 261 iostream.h 261 Значения вывод нескольких за один раз 23 отображение восьмеричных и шестнадцатеричных 27 сравнение двух 56 Значения параметров по умолчанию 123 правила использования 124 Имена переменных слова, которые нельзя использовать для имен 32-33 смысловое значение 32 Именованные константы 291 директивы препроцессора 292 использование для упрощения изменения кода 294 создание с помощью #define 293 Исключительные ситуации (ошибки) и классы 314 обработка непредусмотренных 312 обработчики 310 оператор catch 308 throw 309 fry 308
Предметный указатель 317 разрешение обработки 308 Итеративная обработка 69 Клавиатура, чтение ввода 50 Классы C++ базовый, определение 210 методы 167 наследование 209 множественное 218 построение иерархии 221 простое 210 объекты и объектно-ориентированное программирование 167 общие и частные данные 174 перегрузка операторов 191 операторы, которые нельзя перегружать 199 операторы плюс и минус 192 функции 167 встроенные 280 деструктор 182 конструктор 182 статические члены 200 шаблоны 238 объявление объектов 244 создание 238 элементы дружественные 224 конфликты имен 211 совместное использование 200 частные и дружественные 174, 224 Командная строка, аргументы 284 argv и argc 284 выполнение цикла по argv 286 доступ к установкам среды операционной системы 288 Комментарии 37 Константы см. Именованные константы Конструктор 182 Локальные переменные 106 конфликты имен 107 область видимости 111 Макрокоманды 291 замена выражений 295 отличие от функций 296 Массивы 129 инициализация при объявлении 133 передача в функцию 133 хранение нескольких значений 130 элементы, доступ 130 Математические операции основные 40 перегрузка 192 Наследование 209 множественное 218 построение иерархии классов 221 простое 210 Несоответствие типов, ошибки 52 Объединения 153 анонимные 155 способ хранения в C++ 153 Объекты и объектно-ориентированное программирование 167 Оператор 15 break 66 do while 76 else 58 for (цикл for) 69 //56 switch 65 while 74 группирование 19 повторение 69 программы 15 простой 57 составной 57 Оператор вставки 20 присваивания 33 старшинство 47 ассемблера 282 отношений 56 Операции C++ 40 таблица 46 логические 56 старшинство 47 уменьшения 45 управление порядком 48 Ошибки переполнения 48 синтаксические 11 Параметры, изменение значений 94 передача в функцию 85 Перегруженные функции 113 когда использовать 115 ясность программ 115 Перегрузка операторов 191 Переменные 30 имена 32 использование смысловых 32 объявление 30 типы 31 Переполнение, ошибки 48 Повторение операторов 69 определенное количество раз 69 пока выполняется некоторое условие 74 Полиморфизм 299 как полиморфные объекты изменяют форму 304 создание объектов 302 чисто виртуальные функции 305 Постувеличение, оператор 43 Предувеличение, оператор 43 Программы C++ исключительные ситуации (ошибки) и классы 314 обработка непредусмотренных 312 обработчики 310 оператор catch 308 throw 309 try 308 разрешение обработки 308
318 Предметный казатель компилятор 10 компиляция 9 операции 40 операторы break 66 do while 76 else 58 for (цикл for) 69 //56 switch 65 w/r/fe 74 группирование 19 повторение 69 составные 57 проверка нескольких условий 61 управление несколькими условиями 61 функции 82 void main (void) 17 вызов 85 перегруженные ИЗ шаблоны 232 Свободная память C++ 249 оператор new 250 освобождение памяти 253 создание обработчика ошибок 2256 создание собственных операторов new и delete 258 Синтаксические ошибки 11 Символ новой строки (\п) 25 Символьные константы 139 Символьные строки 137 инициализация 141 использование соШцдя отображения 19 использование указателей 158 передача в функции 141 постоянные, создание 137 продвижение указателя 160 сканирование 162 Скобки (группирующие символы) 19 Сокрытие информации 174 Сообщения, вывод на экран 22 Специальные символы, таблица 24 Ссылки C++ 117 в качестве параметров 119 изменение параметров функций 121 создание псевдонимов 117 правила использования 120 Стандартное устройство ошибок, вывод 28 Статическая функция-элемент 200 Строковые константы 139 Структуры 146 и функции 149 переменные, объявление 147 указатель на структуру 150 назначение 114 элементы 147 функции, изменяющие элементы структуры 150 Увеличение переменных 42 Указатели 158 математика указателей 163 на переменную 158 массив несимвольного типа 163 символьную строку 158 продвижение по символьной строке 160 Условия ложные 58 проверка нескольких 61, 65 Файловые операции ввода/вывода 269 закрытие файла 274 запись в файловый поток 269 операции чтения и записи 275 определение конца файла 272 проверка ошибок 273 чтение из файлового потока 270 чтение целой строки 271 Функции 82, 113 close 21А ео/272 fail 273 getline 27 \ inline (встроенные) 280 main 285 read и write 275 strcpy 148 библиотеки этапа выполнения 102 виртуальные 302 возвращаемое значение 90 возвращение результата 88 вызов 85 не возвращающие значений 90 перегруженные ИЗ передача информации в функции 85 передача массивов в функции 133 прототипы 91 шаблоны 232 Шаблоны 232 объявление объектов на их основе 244 Шестнадцатеричные значения 27 Экран, вывод с помощью cout 19