close

Вход

Забыли?

вход по аккаунту

?

Budagov

код для вставкиСкачать
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное автономное
образовательное учреждение высшего образования
САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
АЭРОКОСМИЧЕСКОГО ПРИБОРОСТРОЕНИЯ
ПРОГРАММИРОВАНИЕ НА VISUAL C++
Учебно-методическое пособие
Под общей редакцией А. Г. Степанова
УДК 004.43
ББК 32.973.26-018-1
П78
Рецензенты:
Институт проблем машиноведения РАН
доктор технических наук профессор А. А. Бобцов
Утверждено
редакционно-издательским советом университета
в качестве учебно-методического пособия
Авторы: А. С. Будагов, В. С. Васильева, А. С. Галкина,
В. А. Дубровская, Н. В. Зуева, А. Г. Степанов,
Д. О. Шишкин
П78Программирование на Visual C++: учеб.-метод. пособие / под
общ. ред. А. Г. Степанова. – СПб.: ГУАП, 2018. – 201 с.
Рассматриваются вопросы, связанные с начальным обучением
языку программирования C++. Приводится описание интегрированной среды программирования Visual C++ и основных приемов работы
с ней в рамках консольных приложений. Выделены вопросы, касающиеся операций и операторов языка. Приводится технология создания приложений MFC для Windows. Описываются методы подготовки программы к выполнению, ее тестирования и отладки. Рассмотрен
ряд вопросов, касающихся приемов программирования, в том числе
приведен цикл лабораторных работ, позволяющих практически реализовать полученные знания.
Предназначено для студентов, обучающихся по направлениям
«Прикладная информатика» и «Бизнес – информатика». Также пособие может быть использовано в составе дисциплины «Информатика»
при подготовке по направлениям, для которых изучение языков программирования высокого уровня является обязательным.
УДК 004.43
ББК 32.973.26-018-1
© Санкт-Петербургский государственный
университет аэрокосмического
приборостроения, 2018
1. ИНТЕГРИРОВАННАЯ СРЕДА ПРОГРАММИРОВАНИЯ
VISUAL C++ И ОСНОВНЫЕ ПРИЕМЫ РАБОТЫ С НЕЙ
1.1. Общие сведения о языке программирования C
и его диалектах
Язык программирования1 С появился в последней четверти ХХ
века как язык программирования высокого уровня, предназначенный для создания операционных систем2. Его авторы Брайан Керниган и Деннис Ритчи (Brian Kernighan and Dennis Ritchie) были
сотрудниками фирмы, занимавшейся разработкой операционной
системы для только что появившейся мини ЭВМ PDP-11. На тот
момент в мире были широко известны различные версии операционных систем, разработанных фирмой IBM для своих вычислительных машин IBM-360 и IBM-370. К сожалению, операционные
системы фирмы IBM оказались крайне трудоемкими в разработке
и недостаточно надежными в эксплуатации, что во многом породило появившиеся как раз тогда и существующие до сих пор мифы о
сверх сложности программы операционной системы.
Высокая трудоемкость и большая стоимость написания операционных систем для машин IBM-360 и IBM-370 во многом связана
с тем, что к их созданию был привлечен коллектив из нескольких
тысяч человек, которые писали программы на языке ассемблера3.
Занимаясь разработкой своей операционной системы, Керниган и
Ритчи создали новую для того времени технологию ее написания.
Ими было предложено вести разработку операционной системы на
1 Язык программирования – формальная знаковая система, предназначенная
для записи программ. Программа обычно представляет собой некоторый алгоритм
в форме, понятной для исполнителя (например, компьютера). Язык программирования определяет набор лексических, синтаксических и семантических правил, используемых при составлении компьютерной программы. Он позволяет программисту точно определить то, на какие события будет реагировать компьютер, как будут
храниться и передаваться данные, а также какие именно действия следует выполнять над этими данными при различных обстоятельствах.
2 Операционная система – это комплекс программ, обеспечивающий выполнение других программ, распределение ресурсов, ввод-вывод данных, взаимодействие
с оператором.
3 Язык ассемблера (автокод) – язык программирования низкого уровня. В отличие от языка машинных кодов, позволяет использовать более удобные для человека мнемонические (символьные) обозначения команд. При этом для перевода
с языка ассемблера в понимаемый процессором машинный код требуется специальная программа, называемая ассемблером. Обычно каждый тип или даже марка процессора имеют собственный набор кодов команд и, как следствие, собственный ассемблер
3
языке программирования высокого уровня1, позволяющем, при
необходимости, для ускорения выполнения часто встречающихся
действий делать специальные вставки на языке ассемблера в коды
создаваемой компилятором программы. Поскольку ни один из существовавших в то время в мире трансляторов2 или компиляторов3
не давал таких возможностей, Керниган и Ритчи были вынуждены
разработать как собственный язык программирования, удовлетворяющий этим требованиям, так и компилятор с этого языка. В результате появился первоначально язык A, потом он был модернизирован и на его основе был создан язык B. Дальнейшее развитие
языка B привело к появлению языка C, который авторами рассматривался как внутренний чисто технологический язык, не предназначенный для широкого распространения. Как результат, был
создан собственный компилятор с языка C, применение которого
позволило относительно небольшим коллективом написать устойчиво работающую операционную систему RT-11 для вычислительной установки PDP-11.
Достигнутые Керниганом и Ритчи результаты вдохновили и других разработчиков операционных систем. Очень быстро язык C стал
базовым языком программирования для написания операционных
систем для всех вновь создаваемых или модернизируемых вычислительных установок. Как следствие, начиная с определенного времени на всех вычислительных машинах появились компиляторы
с языка C. Фактически это означало, что программы, написанные
на языке C, могли компилироваться и выполняться на вычислительной установке любого типа. В результате написанные на языке C программы, если они не использовали ассемблерные вставки,
оказывались машинно-независимыми. Все это заставило обратить
внимание на язык C и прикладных программистов.
1 Язык высокого уровня – язык программирования, понятия и структура которого удобны для восприятия человеком. В отличие от языка программирования низкого уровня, одному оператору языка высокого уровня ставится в соответствие не
одна, а множество машинных команд.
2 Транслятор – программа, преобразующая: программу, написанную на одном
(входном) языке в программу, представленную на другом (выходном) языке. Обычно результаты работы транслятора представляют собой уже исполняемые машинные коды.
3 Компилятор – программа, выполняющая преобразование программы, написанной на одном алгоритмическом языке, в программу на языке, близком к машинному, и в определенном смысле эквивалентную первой. В отличие от транслятора,
результаты работы компилятора перед выполнением требуют еще дополнительной
обработки.
4
Поскольку Керниган и Ритчи планировали свой язык только
для внутреннего применения, с целью упрощения компилятора
они отказались от ряда возможностей, которые обеспечивали другие распространенные компиляторы того времени. Как следствие,
программирование на языке C оказывалось более сложным и трудоемким по сравнению с другими языками высокого уровня. Однако
этот недостаток с лихвой окупался тем, что созданная программа
могла быть выполнена на любом вычислителе.
Последующее развитие систем программирования на языке
С привело к появлению целого набора инструментов: классический
С, Turbo С, Turbo С++, Borland С++, С#, C++. В большинстве случаев разработчики новых компиляторов вводили в правила языка
некоторые дополнения и использовали собственные термины, например, турбо оболочка, интегрированная среда разработки, среда
программирования и т.п. Все это позволяет говорить о возникновении нескольких диалектов языка С.
Классический C [1] (рис. 1) или Plain C был разработан в лабораториях Bell Labs в начале 1970-х годов для разработки используемой
на компьютерах разработки фирмы Digital операционной системы
UNIX.
Система программирования Turbo C (рис. 2) представляет собой
интегрированную среду разработки и компилятор языка программирования Си от фирмы Borland.
Если классический C предусматривал при подготовке программ
последовательный ручной запуск редактора текста и компилятора,
Рис. 1. Среда разработки классического C
5
Рис. 2. Среда разработки Turbo C
то среда разработки Turbo C уже обеспечивала автоматизацию взаимодействия редактора, компилятора, компоновщика и отладчика.
Turbo С также обеспечивал компиляцию более 16000 строк в минуту, гипертекстовую онлайн-справку, поддерживал все существовавшие на тот момент модели памяти и более 450 функций библиотеки
стандартных программ [2].
Первоначально среда программирования использовалась для
создания программ, запускаемых под управлением операционной
системы DOS, но с появлением и распространением операционных
систем Windows и Windows NT были предложены средства для разработки приложений для них [3].
Borland C++ (рис. 3) – среда программирования (IDE), разработанная фирмой Borland для создания программ на языках программирования Си и C++. Она поддерживает объектно-ориентированное
программирование. За время своего развития эта среда разработки
дополнялась специализированными библиотеками, предназначенными для быстрой разработки приложений. В частности, примером применения объектно-ориентированного подхода для создания
приложений под DOS стала библиотека Turbo Vision, в то время как
аналогичным примером применения объектно-ориентированного
6
Рис. 3. Среда разработки Borland С++
подхода для создания приложений под Windows стала библиотека
Object Windows Library [4].
Язык программирования (среда разработки) C# (рис. 4) более известный как C Sharp также является объектно-ориентированным
языком программирования. Он был разработан в 1998–2001 годах
группой инженеров под руководством Андерса Хейлсберга в компании Microsoft как язык разработки приложений для платформы
Microsoft.NET Framework и впоследствии был стандартизирован.
Рис. 4. Среда разработки C#
7
Его синтаксис наиболее близок к C++ и Java. Язык имеет статическую типизацию, поддерживает полиморфизм, перегрузку операторов (в том числе операторов явного и неявного приведения типа), делегаты, атрибуты, события, свойства, обобщённые типы и методы,
итераторы, анонимные функции с поддержкой замыканий, LINQ,
исключения, комментарии в формате XML [5], [6]. За время своего
существования выпущено несколько версий C#.
Разработка языка С++ началась в 1979 году. Изначально новый
язык назывался “C с классами”, но затем имя было изменено на
C++ – это должно было подчеркнуть, как его происхождение от C,
так и его превосходство над последним. Целью создания C++ было
дополнение C возможностями, удобными для масштабной разработки программного обеспечения, с сохранением гибкости, скорости и
портативности C. Схожесть синтаксиса языков позволяет большинству программ языка C работать и на C++ [7].
C++ (рис. 5) является компилируемым строго типизированным
языком программирования общего назначения [8]. Он поддерживает разные парадигмы программирования: процедурную, обобщённую, функциональную, однако наибольшее внимание уделено
Рис. 5. Среда разработки C++
8
поддержке объектно-ориентированного программирования. Нововведениями C++ в сравнении с C стали:
– поддержка объектно-ориентированного программирования
через классы. C++ предоставляет все четыре возможности ООП –
абстракцию, инкапсуляцию, наследование (в том числе и множественное) и полиморфизм;
– поддержка обобщенного программирования через шаблоны
функций и классов;
– использование стандартной библиотеки (с некоторыми модификациями) и библиотеки шаблонов (Standard Template Library,
STL), которая предоставляет обширный набор обобщенных контейнеров и алгоритмов;
– дополнительные типы данных;
– обработка исключений;
– виртуальные функции;
– пространства имён;
– встраиваемые (inline) функции;
– перегрузка (overloading) операторов – один из способов реализации полиморфизма, заключающийся в возможности одновременного
существования в одной области видимости нескольких различных
вариантов применения оператора, имеющих одно и то же имя, но различающихся типами параметров, к которым они применяются;
– перегрузка имен функций – возможность использования одноименных подпрограмм: процедур или функций в языках программирования;
– ссылки и операторы управления свободно распределяемой памятью.
C++ широко используется для разработки программного обеспечения, являясь одним из популярных языков программирования.
Область его применения включает создание операционных систем,
разнообразных прикладных программ, драйверов устройств, приложений для встраиваемых систем, высокопроизводительных серверов, а также развлекательных приложений (игр).
Для создания программ на языке С++ может использоваться
линейка продуктов Microsoft Visual Studio от компании Microsoft
(рис. 6), включающих интегрированную среду разработки программного обеспечения и ряд других инструментальных средств [9].
Она непрерывно модернизируется. Так, например, существуют ее
версии 2010, 2012 (установленная сейчас в компьютерных классах),
2015, 2017 годов.
9
Рис. 6. Окно выбора инструментов Visual Studio
Microsoft Visual C++ (MSVC) – интегрированная среда разработки приложений на языке C++, разработанная корпорацией Microsoft и поставляемая либо как часть комплекта Microsoft
Visual Studio, либо отдельно в виде бесплатного функционально
ограниченного комплекта Visual C++ Express Edition. Она сменила
интегрированную среду разработки Microsoft QuickC [10].
С помощью системы Visual Studio можно создавать [11]:
– приложения и игры, которые выполняются не только на платформе Windows, но и на Android и iOS;
– веб-сайты и веб-службы на основе ASP.NET, JQuery, AngularJS
и других популярных платформ;
– приложения для самых разных платформ и устройств, включая, но не ограничиваясь: Office, SharePoint, Holloes, Kinect и «Интернета вещей»;
– игры и графические приложения для разных устройств
Windows, включая Xbox, с поддержкой DirectX.
10
Обычно программисты любят использовать то программное средство, которым они пользуются. Так и мы остановили свой выбор на
реализации С++ в Visual Studio 2012. Если вам мало имеющихся
в этой системе возможностей и вам требуется что-то еще, то используйте иные версии. А нашей первостепенной задачей является обучение студентов языку программирования С++, а не исследование
возможностей Visual Studio.
1.2. Разновидности программ, создаваемых с помощью интегрированной среды программирования Visual C++
Подготовка и исполнение программ, в том числе и написанных
на языке C, требует наличия на машине программиста набора специальных программных средств. К их числу, в первую очередь,
относится редактор текста. С его помощью программист может набирать, корректировать и сохранять исходный текст программы.
Если подготовленный текст удовлетворяет правилам языка программирования, то он может быть обработан компилятором с соответствующего языка и универсальной программой редактора
связей (компоновщик) для получения выполняемых кодов. В свою
очередь редактор связей может (и должен) использовать различные
библиотеки ранее написанных функций. Наконец, готовая программа может выполняться самостоятельно или под управлением
программы отладчика. Все перечисленные программы могут существовать в виде самостоятельных исполняемых файлов, однако
практика программирования привела к их объединению в единой
программной оболочке, которая часто называется интегрированной
средой программирования.
Такая среда существует и для программирования на Visual
C++. Она предназначена для создания программ, выполняемых
под управлением операционной системы Windows, с использованием библиотечных средств. Обычно такие программы называют
приложениями для Windows. Кроме этого, интегрированная среда
программирования Visual C++ позволяет создавать самостоятельные программы, которые не взаимодействуют непосредственно
с Windows, но имеют собственный интерфейс, реализуемый через
клавиатуру, и экран и работающий в символьном режиме. Такие
программы принято называть консольными1 приложениями.
1 В первых компьютерных системах консолью называлось устройство для вывода системных сообщений и ввода команд оператора
11
Приложения для Windows представляют основной практический интерес. Их создание позволяет дополнять программный мир
вашего (и не только вашего) компьютера новыми возможностями. Фактически, владея технологией создания приложений для
Windows, вы можете писать новые прикладные программы, которые дальше могут жить самостоятельной жизнью. К сожалению,
освоение методов создания приложений для Windows требует осознания многих дополнительных вещей, связанных именно со спецификой Windows. В этом случае вполне естественно предполагать,
что программист владеет самим языком C++.
Консольные приложения используют простейший текстовый
интерфейс и могут выполняться под управлением интегрированной
среды программирования. В то же время консольные приложения
удобны для освоения собственно правил и методов программирования на языке C++ и освобождают начинающего программиста от
необходимости немедленного освоения достаточно обширного материала, относящегося к взаимодействию с Windows.
Существует два вида консольных приложений. Консольные приложения CLR взаимодействуют с так называемой стандартизованной средой выполнения программ (CLR). Она является составной
частью операционной системы и позволяет приложениям, написанным на самых различных языках программирования, выполняться
без изменения и перекомпиляции своего исходного кода. Использование CLR, в частности, позволяет программисту включать в состав
своих программ откомпилированные фрагменты других программ,
хранящихся библиотеках. Консольные приложения Win32 компилируются непосредственно в код процессора ЭВМ и могут быть
выполнены им. Как следствие, эти приложения не имеют никаких
дополнительных составляющих, не относящихся к самому языку
C++. Именно поэтому начальное изучение языка целесообразно начать с создания именно консольных приложений Win32.
Создаваемые с помощью интегрированной среды программирования программы содержат несколько файлов, которые хранятся
в специальной папке проекта. Проект в Visual C++ – это совокупность файлов, содержащих код программы, а также вспомогательных файлов, необходимых для компоновки и выполнения программы. Результаты компиляции и компоновки программы хранятся
в папке проекта в самостоятельных папках.
Кроме проектов в Visual C++ возможно создание так называемого решения (solution). Решение позволяет объединить несколько
12
проектов связанной тематики и представляет собой более высокий
уровень иерархии по отношению к проекту.
1.3. Начальный запуск интегрированной среды программирования Visual Studio C++
Проще всего запустить интегрированную среду программирования Visual C++ дважды щелкнув пиктограмму (рис. 7) на рабочем
столе вашего компьютера. Если там ее по каким-то причинам нет,
попробуйте поискать программу через меню кнопки Пуск → Программы → Microsoft Visual Studio C++. Если пункта Visual Studio
C++ нет и там, загляните в папку Visual Studio или воспользуйтесь
строкой поиска меню Пуск. Если и это не дает результата, то задумайтесь над вопросом – есть ли вообще Visual Studio C++ на вашем
компьютере?
Если запуск Visual Studio, производится впервые, то в окне выбора параметров среды пользователю необходимо указать язык, который он будет использовать по умолчанию при следующих запусках
Visual Studio. В данном случае выбирается Visual C++. Запустившись на выполнение, интегрированная среда программирования
Visual C++ позволяет выполнить настройку своих экранных форм,
создать новый проект и вернуться к ранее сохраненному проекту
(рис. 8).
Как результат, открывается начальное окно интегрированной
среды программирования рис. 9.
Если мы создаем новое Visual C++ приложение, оно автоматически наполняется необходимыми в конкретном случае стандартными кодами, обеспечивающими, соответственно, либо связь
с Windows, либо настройку конкретного текстового интерфейса.
Рис. 7. Пиктограмма, используемая для запуска Visual Studio
13
Рис. 8. Интегрированная среда программирования Visual Studio
Рис. 9. Начальное окно интегрированной среды программирования
Visual Studio, открывающееся после запуска
Поэтому при создании нового проекта запустится Мастер создания
приложений, ответив на вопросы которого вы и дадите знать системе, что собственно вы и собираетесь делать.
14
1.4. Создание проекта консольного приложения Win32
Создание проекта консольного приложения начинается с выбора в меню пункта Файл → Создать → Проект. После выбора команды создания проекта появится диалоговое окно Создание проекта,
показанное на рис. 10. В левой части диалогового окна отображены типы проектов, которые можно создавать. Правая панель отображает список шаблонов, доступных для выбранного слева типа
проектов. Выберите Консольное приложение Win32. Выбранный
шаблон используется мастером приложений при создании файлов,
составляющих проект. После выбора шаблонов вводятся Имя проекта и папки решения, а также ее расположение.
После выбора Файл → Создать → Проект… вызывается окно мастера создания приложений, показывающее текущие параметры
проекта (рис. 11). В простейшем варианте выберите Консольное
приложение Win32 и щелкните кнопку ОК. Откроется первое окно
мастера создания консольного приложения Win32 (рис. 12). Нажмите кнопку Далее и перейдите во второе окно мастера, которое
позволяет произвести настройки приложения (рис. 13) и создать
проект. Установите в нем ключ Пустой проект. Закончив свою работу, Мастер приложений вернётся к начальному окну интегрированной среды программирования Visual Studio (рис. 9), а в обозревателе решений будут отображены имя выбранного решения и проекта
(рис. 14).
Рис. 10. Создание проекта. Открытие диалогового окна
15
Рис. 11. Окно создания нового проекта
Рис. 12. Первое окно мастера создания консольного приложения Win32
16
Рис. 13. Второе окно мастера создания консольного приложения Win32
Рис. 14. Окно интегрированной среды программирования Visual Studio
после работы мастера Win32 (обозреватель решений)
17
Рис. 15. Добавление к проекту нового элемента исходного кода
Рис. 16. Окно Добавление нового элемента
18
Рис. 17. Окно редактора с пустым файлом
Мастер приложений создает только заготовку проекта с пустыми папками, предназначенными для хранения так называемых
заголовочных файлов, файлов исходного кода и файлов ресурсов.
При дальнейшей работе над проектом некоторые файлы придется
создавать вручную, выполняя команды главного меню, а некоторые
сформируются автоматически.
На первом этапе работы основной интерес для нас представляет
создание файла для хранения исходного кода нашей программы.
Для того, чтобы это сделать, необходимо в обозревателе решений
(рис. 14) выбрать Файл исходного кода → Добавить → Создать элемент… . В открывшемся окне надо указать Файл C++ и присвоить
ему имя (рис. 15). Как результат, в папке проекта появится файл
с расширением .cpp, (рис. 16), а на экран выйдет связанное с этим
файлом окно редактора текста (рис. 17).
Лабораторная работа №1.
Начальный запуск и использование
консольного приложения Win32
Цель работы: Обучение работе в среде Visual Studio, получение
начальных практических навыков создания, компиляции, а также
редактирования и сохранения кода программы на высокоуровневом языке Visual C++.
19
Задание
Научитесь выполнять начальные действия с интегрированной
среды программирования Visual C++ и создайте свой первый проект в консольном приложении Win32.
Порядок выполнения работы
1. Запустите интегрированную среду программирования Visual
C++ дважды щелкнув пиктограмму (см. рис. 7) на рабочем столе.
2. В начальном окне интегрированной среды программирования
Visual Studio, открывающемся после запуска, выберите в меню пункта Файл → Создать → Проект.
3. После выбора команды Создание проекта в левой части диалогового окна Создать проект выберите Тип проекта язык Visual C++
Win32, в правой части окна Шаблоны выберите Консольное приложение Win32. Внизу диалогового окна в поле Имя проекта введите
имя, в поле Расположение введите диск и папку для сохранения создающегося проекта (узнать у преподавателя). Тогда в поле Решение
автоматически появится имя, аналогичное имени проекта. Нажмите на кнопку ОК.
4. В первом окне Мастера приложений Win32 текущим параметром проекта укажите Консольное приложение и нажмите на кнопку Далее (рис. 12).
5. Во втором окне Мастера приложений Win32 укажите тип приложения Консольное приложение, дополнительные параметры Пустой проект и нажмите на кнопку Готово (рис. 13).
6. В окне интегрированной среды программирования Visual
Studio в части Обозреватель решений отобразится имя решения и
имя проекта.
7. В созданном проекте нет файлов. Для добавления файла к созданному проекту надо выделить в Обозревателе решений проект и
выбрать в меню Проект команду Добавить новый элемент. Появится
диалоговое окно Добавление нового элемента, в левой части которого в Категории надо выделить в Visual C++ элемент Код, а в правой
части окна в Шаблоны надо выделить в Файл C++ (.срр) и указать
имя. Внизу окна введите имя файла и нажмите на кнопку Добавить
(рис. 15).
8. После добавления нового элемента новый файл, имя которого отображается на вкладке, добавляется в проект и отобразится
в окне редактора. Файл будет пустым и в нём ничего не отобразится
(рис. 17).
20
9. Изучите пример 1 содержащий вариант простой программы
на языке C++1. Он нужен нам для выявления некоторых основных
составляющих любой программы, написанной на языке C++. Прежде всего, просто взгляните на программу и попробуйте понять, что
она будет делать.
Пример 1. Простая программа на языке С++
#include <iostream>
using namespace std;
void main () //простая программа
{
int num;
num = 1;
cout << "menj zovut Ivanov Ivan.\n J na "<<num<<" kurse.";
cout <<endl;
}
Если вы считаете, что программа должна вывести нечто на экран
дисплея, то вы совершенно правы! Просмотрим теперь ее текст. Первая строка программы указывает компилятору необходимость подключения информации, содержащуюся в файле iostream. Вторая
строка using namespace std; создает пользовательское пространство
имен в программе. Как результат, к именам стандартной библиотеки добавятся функции вывода cout и endl. Используя это пользовательское пространство, мы имеем возможность относительно просто программировать вывод. Любая программа на языке C++ состоит из одной или более функций, являющихся основными модулями, из которых она собирается. Наша программа состоит из одной
функции main, и круглые скобки указывают именно на то, что main
() – имя функции, а void указывает на то, что функция не возвращает значения. Пояснения (комментарии) к программе начинаются
с символов //. Тело функции, состоящее из операторов, заключается
между фигурными скобками { }.
Каждый оператор в программе на языке C++ заканчивается символом точка с запятой. Программа содержит операторы трех видов:
– int num; оператор описания переменной num, которая хранит
целые (int) значения;
– num = 1; оператор присваивания константы 1 в ячейку num;
1 К сожалению, в используемой нами версии Visual Studio нет возможности из
консольных приложений, написанных на языке C++, выводить информацию на
экран символами кириллицы. Поэтому здесь и далее буквы русского языка заменены на соответствующие символы латыни. В дальнейшем мы покажем, как можно
относительно несложными средствами избавиться от этого недостатка
21
– cout << оператор вывода на экран.
Перейдите в окно редактора и введите приведённый выше код.
Во время набора текста программы в окне редактора происходит
диагностика синтаксиса набираемых команд. Строки с предполагаемыми ошибками помечаются слева жёлтой вертикальной чертой.
Компиляция и запуск программ выполняется с помощью опций
пункта основного меню Отладка. При выборе этого пункта открывается окно подменю, в котором есть команды компиляции, диагностики и запуска приложения. Для компиляции, редактирования
связей и запуска программы на выполнение без использования команд отладчика из меню Отладка выбирается команда Запуск без
отладки или Ctrl+F5. (рис. 18). Если во время выполнения компиляции программы возникают какие-то ошибки, то в окне редактора
строки с ошибками помечаются слева зелёной вертикальной чертой, а вам предлагается продолжить или прекратить выполнение
программы (рис. 19).
Исправление синтаксических ошибок в программе необходимо
для ее успешной компиляции. Для просмотра ошибок, предупреждений и сообщений, выдаваемых в процессе компиляции, предлагается Окно ошибок, а в разделе Описание указывается вид ошибки и
способ её устранения (рис. 19). Для исправления ошибки необходимо в окне редактора поместить курсор в строку, содержащую сообщение об ошибке, и отредактировать ее. После чего можно повторно
запускать программу на компиляцию, редактирование связей и выполнение (Ctr+F5).
Рис. 18. Запуск программы
22
Рис. 19. Окно редактора при обнаружении ошибки во время компиляции
Рис. 20. Результат выполнения программы
После исправления всех ошибок и запуска программы, предлагаемой в пункте 9, появится окно с результатом выполнения (рис. 20).
23
Контрольные вопросы
1. Зачем могут использоваться приложения для Windows?
2. Чем консольные приложения отличаются от приложений для
Windows?
3. Зачем нужна операционная система?
4. Чем транслятор отличается от компилятора?
5. Что такое проект в Visual C++?
6. Что понимается под решением в Visual C++?
7. Чем языки высокого уровня отличаются от языков низкого
уровня?
8. Какие виды консольных приложений вы знаете?
9. Как можно запустить интегрированную среду программирования Visual C++:
10. Что значит «машинно-независимая программа»?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, текст
программы и результаты её выполнения, выводы, которые можно
сделать по результатам выполненной работы. Вариант актуального
на текущий учебный год титульного листа отчета приведен на сайте
ГУАП [12].
24
2. ОПЕРАЦИИ И ПОСЛЕДОВАТЕЛЬНО ВЫПОЛНЯЮЩИЕСЯ
ОПЕРАТОРЫ ЯЗЫКА C++
2.1. Выполнение команд процессором
После подачи электропитания на вычислительную установку
процессор начинает выполнять последовательность команд, имеющуюся в его памяти. Сами команды представляют собой комбинации шестнадцатиразрядных двоичных чисел. Если нет никаких
специальных указаний, то эти команды выполняются последовательно одна за другой. По своему назначению команды процессора
могут быть использоваться для решения трех групп задач.
Первая группа задач – это манипуляции с данными, хранящимися в неких других (отличных от ячеек самой команды) ячейках
памяти. Примером такой команды могут быть копирование содержимого одной ячейки памяти в другую, сложение содержимого
двух ячеек памяти, запись результата в одну из них и т.п.
Ко второй группе задач относится управление выполнением последовательности команд. Например, существует команда, позволяющая в зависимости от значения некоторого условия перейти
к выполнению не следующей команды, а команды, хранящейся по
некоторому адресу.
Решение третьей группы задач обеспечивают команды, которые
позволяют связать конкретную ячейку памяти с каким-то устройством ввода – вывода. Раньше на старых вычислительных установках номенклатура таких устройств для конкретной машины была
строго определена. Как следствие, в системе команд процессора
существовали команды вывода, связанные с конкретным устройством и позволявшие отображать с его помощью в окружающий
мир содержимое некой ячейки памяти. Аналогично существовали
и команды ввода, позволявшие занести в выбранную ячейку воздействие на устройство ввода. В настоящее время такие команды
не выделяются в самостоятельную группу и реализуются как совокупность команд первой и второй группы.
Языки программирования высокого уровня, хотя они и более
удобны для восприятия человеком, в том или ином виде все равно
ориентируются на выполнение аналогичных действий. Так, среди операторов1 языка программирования можно выделить наборы
1 Оператор – в программировании – фраза алгоритмического языка, определяющая законченный этап обработки данных.
25
операторов, относящихся соответственно к первой, второй и третьей группам. Поскольку важнейшей особенностью языков программирования высокого уровня является генерация нескольких
машинных команд одним оператором, операторы языка однозначно компилируются в последовательность элементарных машинных
команд. Это обстоятельство позволяет рассматривать программирование ЭВМ в терминах уже языка высокого уровня.
2.2. Организация информации в памяти и типы данных
Принцип работы любой программы, выполняемой на вычислительной установке, основан на манипуляции данными, находящимися в памяти. Организация памяти ЭВМ стандартна. Существует
элементарные ячейки памяти, каждая из которых предназначена
для хранения одного логического утверждения истина или ложь.
Аппаратно такая ячейка представляет собой триггер1.
Элементарные ячейки объединяются в слова. Обычно в слово
входит восемь элементарных ячеек. Поскольку каждая элементарная ячейка хранит информацию размером в 1 бит, то хранящаяся в восьмиразрядном слове информация имеет размер 8 бит или
1 байт. Очень часто объединение восьми элементарных ячеек (байт
информации) называют просто ячейкой или словом (рис. 21). Аппаратная структура памяти такова, что при обращении к ней одновременно считывается содержимое не одной элементарной ячейки, а
целого восьмиразрядного или даже шестнадцатиразрядного слова.
В некоторых случаях удобно мысленно разбивать ячейку на две части, так называемые полубайты. Обычно такой прием используется
при записи содержимого ячейки в шестнадцатиразрядном коде (см.
табл. 1)2. Поэтому для обозначения, например, десятичного числа
110 не обязательно пользоваться его полным двоичным представлением 01101110, а достаточно записать его в шестнадцатеричном
представлении 6E. Еще пример: десятичное число 51 в двоичном
виде представляется как 00110011 и в шестнадцатеричном как 33.
Размера восьмиразрядного слова может не хватать для хранения, например, команды процессора или числа. Тогда в рассмотрение вводится последовательность слов (два, четыре, шесть и т.д.).
Каждое слово такой последовательности имеет собственный адрес,
1 Триггером называется устройство, предназначенное для хранения логического
утверждения Истина (True) или Ложь (False)
2 Для обозначения двухпозиционных десятичных цифр 10, 11, 12, 13, 14, 15 используются символы латинского алфавита соответственно A, B, C, D, E, F
26
Адрес слова
Номер разряда слова
Номер разряда
полубайта
Содержимое ячейки
7
3
Восьмиразрядное слово
Старший полубайт слова
Младший полубайт слова
6
5
4
3
2
1
0
2
1
0
3
2
1
0
0 или 1 0 или 1 0 или 1 0 или 1 0 или 1 0 или 1 0 или 1 0 или 1
Рис. 21. Организация данных в слове памяти
Таблица 1
Представление данных, хранящихся в полубайте,
в разных системах счисления
Представление содержимого полубайта
Двоичное
Десятичное
Шестнадцатеричное
0000
0
0
0001
1
1
0010
2
2
0011
3
3
0100
4
4
0101
5
5
0110
6
6
0111
7
7
1000
8
8
1001
9
9
1010
10
A
1011
11
B
1100
12
C
1101
13
D
1110
14
E
1111
15
F
отличающийся от адреса предыдущего слова на 1. При обращении
к такому составному слову достаточно знать адрес первого слова и
количество восьмиразрядных слов (байтов), в которых хранится
интересующая информация. Если разрядность команды задается
конструкцией процессора и его системой команд, то программист
27
Таблица 2
Типы данных языков С и C++
Язык С
Мнемоника
Размер занимаемой
памяти
в байтах
bool
отсутствует
char
1
unsigned
1
char
wchar_t отсутствует
short
2
unsigned
short
2
int
2
long
4
unsigned
long
4
float
4
double
8
long
double
отсутствует
Язык C++
Диапазон представления данных
Размер занимаемой
памяти
в байтах
Диапазон представления данных
отсутствует
От –128 до 127
1
1
True или false
От –128 до 127
От 0 до 255
1
От 0 до 255
отсутствует
От –32768
до 32767
2
От 0 до 65535
От –32768
до 32767
От 0 до 65535
2
От –32768
до 32767
От 2147483647
до –2147483648
От 0
до 42949667295
От (+ или –)
1,0E-37 до (+ или
–) 1,0Е38
От (+ или –)
1,0Е-307 до
(+ или –) 1,0Е308
отсутствует
2
4
4
4
4
8
8
От 0 до 65535
От 2147483647
до –2147483648
От 2147483647
до –2147483648
От 0 до
42949667295
От (+ или –)
1,0E-37 до (+ или
–) 1,0Е38
От (+ или –)
1,0Е-307 до
(+ или –) 1,0Е308
От (+ или –)
1,0Е-307 до
(+ или –) 1,0Е308
имеет возможность влиять на размер составного слова, указывая
для него так называемый тип данных.
Языки программирования высокого уровня, в частности языки С и C++, предлагают программисту набор своих типов данных,
приведенный в табл. 2. Разрабатывая свою программу, программист обязан задуматься над вопросом: что именно будет храниться в каждом используемом им слове памяти. Выбирая тип данных,
программист задает и количество слов, используемых для хранения данных, и способ их кодирования в памяти машины.
28
2.3. Кодирование информации в памяти
Существует, по крайней мере, два способа кодирования чисел
в памяти машины. Целые числа представляются в двоичном виде.
В зависимости от размера слова, используемого для хранения числа, меняется и возможный диапазон его изменения (см. табл. 2 типы
данных char, unsigned char, wchar_t, short, unsigned short, int,
long, unsigned long). Хранимые числа могут быть положительными и отрицательными (ключевое слово signed), или только положительными (ключевое слово unsigned). Так, например, десятичное
положительное число 51 типа char в восьмиразрядном слове будет
закодировано так, как показано на рис. 22.
Для представления отрицательных чисел используется так называемый дополнительный код1. Если число отрицательное, то
старший (в нашем случае седьмой) разряд отрицательного числа
всегда равен единице, а если число положительное – нулю. Число
в дополнительном коде образуется как инверсия всех разрядов слова с добавлением единицы к младшему разряду. Так отрицательное
число –51 будет кодироваться как 11001101.
При использовании типа данных unsigned char хранящееся
в слове число всегда считается положительным, а дополнительный
код не используется.
Кодирование числа типа данных short, unsigned short, int,
long, unsigned long отличается от предыдущего только количеством используемых последовательно размещенных в памяти слов
и, соответственно, диапазоном хранимых данных (рис. 23).
Адрес байта
Номера разрядов байта
7
6
5
4
3
2
1
0
0
0
1
1
0
0
1
1
Знак
Рис. 22. Кодирование целого восьмиразрядного двоичного числа
1 Использование дополнительного кода для хранения отрицательных чисел позволяет заменить арифметическую операцию вычитания процессора операцией сложения. Поэтому арифметические операции с целыми числами проводятся непосредственно с данными, хранящимся в ячейке памяти, без дополнительного их раскодирования и обратного кодирования.
29
Адрес байта
Адрес байта + 1
Номера разрядов составного шестнадцатиразрядного слова
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
0
0
0
0
0
0
0
0
0
0
1
1
0
0
1
1
Знак
Рис. 23. Кодирование целого составного шестнадцатиразрядного слова
Для хранения вещественных чисел используется их экспоненциальное представление и, как следствие, специальное кодирование.
Под экспоненциальным представлением числа понимается его запись с использованием показательной функции с основанием 10. Так,
десятичная дробь 1234,5 может быть записана в виде 1234,5 × 100
или 123,45 × 101 или 12,345 × 102. Именно поэтому такое представление числа иногда называют представлением с плавающей точкой.
Дробная часть числа называется мантиссой, а значение степени показательной функции – порядком. В последнем примере мантисса
равна 12,345, а порядок равен 2.
Как и мантисса, так и порядок могут принимать и положительные, и отрицательные значения. Кроме этого, существует множество вариантов экспоненциального представления одного и того же
вещественного числа. Поэтому для определенности используют так
называемое нормализованное представление числа. В этом случае
десятичную дробь записывают без целых чисел таким образом, чтобы старший разряд дробной части был значащим1. Рассмотренная
выше десятичная дробь в нормализованном представлении запишется как 0,12345 × 104.
Способ кодирования вещественного числа в слове памяти предполагает отдельное хранение двоичного представления порядка и
мантиссы нормализованного числа в разных разрядах составного
слова2. Чем больше разрядов отводится под запись мантиссы, тем
выше точность представления числа. Чем больше разрядов занимает порядок, тем шире диапазон от наименьшего отличного от нуля
числа до наибольшего числа, представимого в машине при задан× 100
1 Исключение составляет число 0,0, которое записывается в виде 0 или 0,0
2 Если мантисса или порядок имеют отрицательный знак, то их значения
нятся в дополнительном коде.
30
хра-
Адрес байта
Адрес байта +1
Адрес байта +2
Адрес байта +3
31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1
Порядок
Мантисса
Знак порядка
Знак числа
Рис. 24. Кодирование вещественных чисел
составным тридцатидвухразрядным словом
ном формате. Количество используемых для хранения числа слов
памяти и разрядность хранения порядка и мантиссы задается типом используемых данных (см. табл. 2).
На рис. 24 показан способ представления в памяти числа типа
float. Для хранения данных типа double используется в два раза
большее количество слов памяти и свой вариант разбиения составного слова на порядок и мантиссу.
2.4. Идентификаторы и объявление переменных
Поскольку физически данные программы оказываются содержимым конкретных ячеек памяти машины, для их отыскания
достаточно знать адрес первой ячейки, связанной с данными, и по
типу данных определить общее число используемых для хранения
элементарных ячеек. Такой подход имел место на самой ранней
стадии программирования и оказался крайне неудобным из-за отсутствия наглядности в записи программы. Действительно, если
память современной машины содержит несколько десятков, а то
и сотен миллионов ячеек памяти, то обращение к ним по номерам
было бы крайне неразумным. Уже первые трансляторы использовали прием, основанный на использовании так называемых идентификаторов. Идентификатором называется символическое имя
ячейки памяти1.
Переменная – идентификатор, адресующий к ячейке памяти,
содержимое которой может быть изменено в процессе выполнения
программы. Для того, чтобы воспользоваться идентификатором,
1 Идея идентификаторов реализована, например, и в современном мобильном телефоне, когда вместо набора семи или одиннадцатиразрядного телефонного номера
мы выбираем из телефонной книги имя, поставленное в соответствие интересующему нас номеру.
31
его надо предварительно объявить. Каждый язык программирования содержит свои правила составления таких имен, общим является то, что программист вправе сам придумать имя переменной,
что позволяет ему сохранить в нем смысловое значение. В языке
C++ имеются следующие ограничения на имена:
– имена переменных могут включать латинские буквы ABCDEFGHI
JKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz в верхнем и нижнем
регистре, цифры 1234567890 и знак подчеркивания _. Никакие другие символы не допускаются;
– буквы верхнего регистра в именах отличаются от букв нижнего регистра, то есть имя aA отличается от имени aa;
– имя должно начинаться с буквы или с нижнего подчеркивания;
– совпадения имен идентификаторов с так называемыми ключевыми1 словами не допускается;
– количество символов в имени переменной не должно превышать 2048;
– имя переменной определяется по первым 31 символу2.
Возможные варианты идентификаторов языка C++: i, j, name,
summa, res, gruppa_nomer, radius, raDius, engineer, CBox, C_ZAPIS.
Обычное соглашение, принятое в C++, заключается в том, что имена, начинающиеся со строчных букв, используются как имена переменных программы, в то время как имена, начинающиеся с прописных букв, в частности с буквы C, используются как имена классов.
Если мы хотим использовать переменную в своей программе, то
мы должны выполнить явное ее объявление в тексте. Объявление
переменной представляет собой инструкцию компилятору зарезервировать в памяти последовательность ячеек для хранения данных, определяемую типом объявляемой переменной, и закрепить
за ней соответствующий идентификатор. Фактически в результате объявления создается экземпляр переменной с заданной типом
внутренней организацией памяти и собственным однозначно определяемым в пределах одной программы именем. В языке C++ объявление переменных осуществляется с помощью указания в тексте
1 Ключевые слова – набор специальных слов, написанных символами латыни и
имеющих определенный смысл с точки зрения конструкций языка программирования. Ключевыми словами обозначаются, в частности, операторы языка и встроенные функции языка (будут обсуждаться далее). В языках С и C++ ключевые слова
записываются только строчными буквами
2 В языке С значащими были только первые 8 символов
32
программы типа данных переменной, а также ее идентификатора.
Так, например, для объявления двух целых переменных i, j мы
должны включить в текст своей программы строчку int i, j;.
Аналогично выполняется объявление переменных других типов
данных, например
float summa, res;
double radius;
Обратите внимание, объявление переменной заканчивается символом точка с запятой. При объявлении переменной ей можно присвоить некоторое начальное значение (инициализация переменной
в момент объявления) int i=0, j=5; или double radius=1.0;. Если
в момент объявления инициализация переменной не производится,
то память под нее выделяется, а вот ее содержимое является случайным. При использовании такой переменной в тексте программы
ей обязательно должно быть задано некоторое начальное значение.
Выполняя команду объявления переменной, компилятор создает (пополняет) специальную внутреннюю таблицу распределения
памяти. В этой таблице хранится сам идентификатор, его тип (размер), адрес первой ячейки памяти, используемой для его хранения.
Всякий раз, обрабатывая очередной идентификатор, далее встречающийся в тексте программы, компилятор сверяется с этой таблицей и вместо имени переменной в коды создаваемой программы подставляет ее конкретный физический адрес.
2.5. Константы языка C++
Сами по себе идентификаторы используются для обозначения
адресов некоторых ячеек памяти машины. В таких ячейках могут
храниться собственно данные или коды команд процессора. Идентификатор, связанный с ячейкой, хранящей данные, может рассматриваться как имя переменной или константы. В отличие от
переменной, константа – идентификатор, адресующий к ячейке,
содержимое которой один раз устанавливается перед выполнением
программы и не может быть изменено. Обычно константы применяют для задания некоторых постоянных величин, используемых
программой.
Целочисленная константа представляет собой число, записанное
в явном виде, например 2147. В языке C++ целые числа можно записывать в десятичной, восьмеричной и шестнадцатеричной формах.
Если первая цифра константы находится в диапазоне от 1 до 9, число считается десятичным. Если первая цифра 0, а вторая число, на33
ходящееся в диапазоне от 0 до 7, то константа восьмеричная. Если
первыми двумя символами являются 0x или 0X, то константа шестнадцатеричная.
Если при записи целочисленной константы не указан суффикс,
то она хранится в памяти машины в ячейке типа int. Суффикс
представляет собой буквы, которые помещаются в конце числовой
константы для обозначения ее типа. Суффикс l или L целого числа
означает, что целое число является константой long. Суффикс u или
U означает константу unsigned int. Суффикс ul в любой комбинации символов в любом регистре означает константу unsigned long.
Примеры констант суффиксами: 25L, 0XABL, 016U, 123456LU.
Вещественные константы хранятся в памяти машины в формате с плавающей точкой и могут обозначаться в обычной (например,
2.123) и экспоненциальной (например, 0.213E1) формах записи. По
умолчанию такие константы имеют тип double. Чтобы константа
имела тип float, необходимо указать суффикс f или F. Для типа
long double используется суффикс l или L.
Символьные константы в языке C++ удобнее всего задавать
в одинарных кавычках. Так, запись вида сhar litera = 'A'; скорее всего приведет к занесению в соответствующую одноразрядную
ячейку числа 65. Оговорка «скорее всего» связана с тем, что, скорее всего, ваш компьютер использует кодировку ASCII. Поскольку
компьютер по каким-то причинам может работать и с другой кодировкой символов, занесение в ячейку символа 'A' оказывается предпочтительнее записи туда обычной десятичной константы, которая
в разных кодировках может иметь разный смысл.
Кодовые таблицы содержат наборы так называемых непечатных
или управляющих символов. Свое название они получили исходя
из того, что таким символам не ставится в соответствие никакая литера. С другой стороны, такие символы могут использоваться для
управления выводом информации на бумагу или экран. К их числу
относятся символ '\n' – новая строка, символ '\r' – возврат каретки
и некоторые другие. Как и в случае с печатными символами, в тексте программы можно указать число, соответствующее коду символа или его обозначение, состоящее из двух печатных символов,
первым из которых является символ '\'.
2.6. Создание и использование массивов данных
Рассмотренные выше примеры объявления переменных предусматривали создание одиночных констант или переменных, обра34
щение к которым осуществляется только по имени. Практика программирования широко использует переменные, обращение к которым ведется как по имени, так и по номеру. Таким методом пользуются в тех случаях, когда приходится принимать во внимание
набор однородных по смыслу, но разных по содержанию данных.
В языках программирования имеется возможность создавать
переменные табличного типа, когда обращение к данным ведется
по имени и номеру (индексу) внутри этого имени. Такие переменные
обычно называются массивами. По определению массивом называются последовательно упорядоченные в памяти компьютера данные
одного типа.
Если мы хотим воспользоваться массивом, то, прежде всего, мы
должны его явно объявить. Объявление массива в языке C++ предусматривает указание типа данных элементов массива, имени массива и количества элементов в массиве.
Пример 2. Объявления массива
const int MAX = 4;
long stavka (MAX);
float result[4];
В примере 2 компилятору дается указание зарезервировать в памяти четыре последовательные ячейки типа float (каждая размером 4 байта) для хранения переменной по имени result. В результате этого действия у программиста появляется возможность работать
с четырьмя независимыми ячейками памяти result[0], result[1],
result[2] и result[3]. Обратите внимание, в объявлении массива
указано общее количество его элементов. В то же время нумерация
элементов массива начинается с номера 0. Как следствие, элемент
массива result[4] не существует, а попытка обратиться к нему приводит к сложной и плохо определяемой ошибке программирования.
На рис. 25 показано размещение массива result начиная с некого адреса памяти. Поскольку для этого массива задан тип данных
float, каждый элемент массива занимает в памяти 4 байта или 2
слова. Соответственно весь массив занимает в памяти 16 байт или
8 слов.
Если для обращения к обычной переменной в тексте программы
достаточно указать ее имя, то для обращения к элементу массива
необходимо использовать соответствующий идентификатор с указанием номера элемента. Этот номер может быть задан явно в виде
константы, например, как result[1] или неявно в виде ссылки на
другую переменную result[i]. В этом случае предполагается, что
35
Адрес байта +20
Адрес байта Адрес слова
Адрес слова +4
Адрес слова +2
Адрес слова +6 Адрес байта +21
Адрес байта +1
r esult [ 0 ]
Массив result
r esult [ 1 ]
r esult [ 2 ]
r esult [ 3 ]
Рис. 25. Организация массива
из четырех ячеек типа float в памяти
переменная i была ранее объявлена неким целым типом данных,
например, int, а в ячейку i ранее было занесено некоторое число
в диапазоне от 0 до 3 в соответствии с допустимыми значениями номеров элементов массива.
Массивы удобно использовать при программировании повторяющихся действий с ячейками памяти. Например, если результат
вычисляется по однотипной формуле, то для его получения в каждой ячейке массива достаточно просто повторить вычисления. Очевидно, что если формула не зависит от номера ячейки массива i, то
каждая ячейка массива будет содержать одно и то же число. Понятно, что в этом случае массив не нужен и достаточно ограничиться
использованием одной переменной. А вот если номер элемента массива входит в формулу, то в элементы массива result[0], result[1],
result[2] и result[3] получат разные значения.
Некоторые языки программирования допускают использование
так называемых многомерных массивов (двухмерных, трехмерных
и т. п.). Действительно, на первый взгляд кажется очень удобным,
например, для хранения матрицы чисел использовать двухмерный
массив. В этом случае элемент матрицы, имеющий два индекса (номер строки и номер столбца) непосредственно отображается при обращении к массиву как двухпозиционный номер его элемента. Для
того, чтобы объявить двухмерный массив, нужно в объявлении указать последовательно два индекса matrix [13] [21];, а для того, чтобы его инициализировать, надо записать int MAS [3] [2] = { {1, 1},
{2, 2}, {3, 3} }.
36
В некоторых случаях многомерный (двухмерный) массив удобно
хранить в памяти как одномерный. Действительно, если нам надо
хранить матрицу в массиве, то мы можем использовать тот факт,
что количество элементов в ее строке известно и не зависит от номера строки. Тогда мы можем для хранения матрицы размером M
столбцов и N строк создать массив размером M*N элементов и хранить в нем строки матрицы последовательно одна за другой. Тогда
первые M элементов массива представляют собой первую строку матрицы, вторые вторую строку и так далее. А для пересчета номера
элемента одномерного массива k( 0 ≤ k ≤ M*N – 1) по заданным двум
номерам элемента матрицы i(1 ≤ i ≤ M) и j( 1 ≤ j ≤ N) можно использовать несложную формулу k=i – 1 +M*(j – 1).
Заполнение ячеек массива можно производить задавая константы в качестве номеров ячеек и аргументов функции. Конечно, если
речь идет всего о трех переменных, то необходимо написать всего
три строки текста, что не представляет особых затруднений. Однако
ситуация существенно поменяется если в массиве будет, например,
1000 переменных. Тогда оказывается удобным воспользоваться
входящими в состав языков программирования так называемыми
операторами цикла1. В этом случае номер ячейки и формулу для
вычисления значения достаточно один раз запрограммировать в общем виде используя идентификатор переменной. Далее надо дать
указание оператору цикла последовательно изменять значение этой
переменной и повторять вычисления по формуле необходимое количество раз (например, 1000 раз в соответствии с размером массива).
2.7. Операции языка C++
Основная задача, которая решалась разработчиками языков программирования – это снижение затрат труда, требуемого на подготовку программного обеспечения. Базовая система команд процессора позволяет обеспечить только самые минимальные потребности
программиста в части обработки данных. Так, например, очень небольшое число существующих в мире процессоров способно имеют
в своем составе команду деления чисел с плавающей точкой. На
практике необходимые программистам команды процессора эмулируются2 программным обеспечением. Это означает, что разработ1 Подробнее операторы цикла будут рассмотрены далее в соответствующем подразделе
2 Эмуляция (англ. emulation) – воспроизведение программными или аппаратными средствами либо их комбинацией работы других программ или устройств.
37
чиками компиляторов заранее написаны последовательности кодов
команд процессора, позволяющие в конечном итоге получить желаемый результат. Такие последовательности включаются в коды
программы в результате компиляции определенного зарезервированного символа или группы символов, встретившихся в тексте
программы. Операция – инструкция языка программирования, которая однозначно обрабатывается компилятором в виде генерации
стандартной последовательности кодов процессора.
В качестве участников операции – операндов могут выступать
константы или переменные. Запись операции совместно с операндами обычно называется выражением. Если подряд используется
несколько операций, то порядок их выполнения определяется приоритетом (чем меньше номер, тем выше приоритет и раньше выполняется операция). Если операции имеют одинаковый приоритет, то
они выполняются слева направо. При необходимости, последовательность выполнения операций может регулироваться круглыми
скобками (сначала выполняются действия в скобках). Хотя многие
операции реализованы во всех языках программирования, их конкретный набор, а также обозначения в разных языках программирования разный. В языке C++ определены следующие операции
с одним операндом:
– операция ссылки на элемент массива [ ], пример: name[4];
– операция ссылки на элемент структуры . (точка), пример:
ObjectName.ItemName;
– операция изменения знака – (минус), пример: -5;
– операция логического отрицания ! , пример: !6;
– операции автоувеличения ++ и автоуменьшения --, примеры:
name[++a], name[a--];
– операция определения размера объекта sizeof (), пример:
sizeof (name);
– операция взятия адреса &;
– операция ссылки по указателю *;
– побитный сдвиг влево <<, вправо >> ;
– операция выделения памяти под объект new;
– операция освобождения ранее занятой памяти delete.
Кроме этого, определены следующие операции с двумя операндами:
– умножение *, деление /, взятие модуля %;
– сложение +, вычитание -;
– больше >, меньше <;
38
– меньше или равно <=;
– больше или равно >=;
– равно ==;
– не равно != ;
– логическое И &&;
– поразрядное логическое И &;
– логическое ИЛИ ||;
– поразрядное логическое ИЛИ |;
– побитовая инверсия ^.
2.8. Понятие оператора
В любом случае программирование ЭВМ сводится к заданию последовательности выполнения команд процессора. Поскольку любая программа представляет собой некоторую последовательность
действий, язык описания этой последовательности может быть самым различным. С одной стороны, может быть создано описание
действий в виде общих конструкции обычного разговорного языка
(построить, вычислить, перевезти, обеспечить). С другой стороны,
программа может быть сведена до уровня элементарных машинных
команд процессора. И в том и в другом случае речь идет о разработке алгоритма (последовательности действий). Очевидно, что если
предполагается выполнять программу на ЭВМ, то ее алгоритм должен быть разработан с учетом возможностей его реализации, а размер его элементарных определяется возможностями их обработки.
В крайнем случае, такая разработка может быть доведена до элементарных команд процессора. Необходимо помнить, что обозначения в алгоритмах стандартизованы (см. рис. 26).
Система команд процессора отражает текущее состояние микроэлектроники и строится исходя из принципов достаточности и технической реализуемости с учетом требуемого быстродействия. При
программировании реальных задач ее возможностей оказывается
явно недостаточно, что приводит к необходимости создания неких
более крупных конструкций языка, которые выполняют определенные действия. Появление таких конструкций, которые получили название операторов, связано с появлением языков высокого
уровня. Практика их применения наработала набор типовых операторов, состав и назначение которых постоянно совершенствуется
с учетом потребностей пользователей. Существующий набор операторов языков высокого уровня ориентирован на пользователя (а
не на процессор) и позволяет программисту решить подавляющее
39
большинство практических задач. Каждый из операторов выполняет вполне конкретные действия, связанные с изменением данных
в памяти и (или) управлением последовательностью выполнения
команд. Поэтому можно говорить о программировании в терминах
операторов языка, когда строго определено их назначение, а сам
оператор представляет собой функциональную элементарную единицу программирования.
Понятие оператора во многом схоже с понятием операции. И в том
и в другом случае речь идет о снижении трудозатрат на создание программного обеспечения. В обоих случаях в соответствующее место
итоговой программы подставляется некая заранее созданная заготовка кодов процессора, реализующая конкретный оператор или
операцию. Тем не менее, существует определенное отличие оператора
от операции, заключающееся в том, что оператор является самостоятельной базовой фундаментальной конструкцией программы, которая, в отличие от операции, описывает (задает) конкретные действия
алгоритма. Операция может многократно входить в состав одного
оператора и не может быть самостоятельно выполнена (операции выполняются только в составе оператора). В целом оператор – это конструкция более высокого уровня, чем операция.
Таким образом, оператор – самостоятельная конструкция языка
программирования, которая может быть отдельно откомпилирована и выполнена в виде заранее определенной последовательности
кодов процессора. В состав оператора могут входить аргументы,
константы, переменные, операции и другие операторы.
При составлении рисунков алгоритмов программ принято пользоваться стандартными обозначениями. Некоторые из них приведены на рис. 26. Обозначения имеют следующий смысл:
– процесс – действия, приводящие к изменению данных;
– предопределенный процесс – выполнение ранее созданного алгоритма;
– решение – действие приводящее к изменению последовательности выполнения операторов программы;
– ввод–вывод – действия по вводу выводу информации на внешние устройства;
– пуск – останов – точки начала и конца алгоритма;
– соединитель – обозначение точек разрыва на линиях связи (например, для переноса линии на следующую страницу);
– модификация – действия по изменению кодов ранее созданной
программы;
40
– класс, объект – соответственно обозначения класса и объекта;
– связь, синхронизация – обозначения последовательности выполнения алгоритма и связей между объектами.
При записи программ на языке C++ оператор от оператора отделяются символом точки с запятой. Может существовать так называемый пустой оператор, который не задает никаких действий.
В текст программы на языке C++ могут вставляться так называемые комментарии. Комментарий – это последовательность символов, которая никак не обрабатывается компилятором. Комментарий, начинающийся с символов //, включает только часть строки
справа от себя. Если необходимо охватить комментарием несколько
строк, то можно воспользоваться символами /* и */. Все то, что находится между ними, рассматривается как комментарий.
Операторы могут группироваться в так называемые блоки. Символом начала блока является открывающаяся фигурная скобка, а
символом конца блока – закрывающаяся фигурная скобка.
Пример 3. Структура фрагмента программы
// начало фрагмента программы
/* место для записи оператора 1*/;
/* место для записи оператора 2*/;
; // пустой оператор
{ // начало блока операторов
/* место для записи оператора 3*/;
/* место для записи оператора 4*/;
} // конец блока операторов
// конец фрагмента программы
2.9. Оператор присваивания
Оператор присваивания обеспечивает занесение информации
в ячейки памяти, связанные с идентификатором и имеет символ
равенства (=). Необходимо обратить внимание на то обстоятельство, что в отличие обычного равенства, которое выполняется
всегда, оператор присваивания имеет динамические свойства (зависит от времени). При его выполнении результат вычислений
правой части оператора заносится в ячейку памяти, указанную
слева от знака равенства, число в которой имело одно значение
до выполнения оператора и другое после его выполнения. Задавая последовательность операторов присваивания, мы можем
программировать запись данных в ячейки памяти ЭВМ, в связи
с чем для его обозначения в алгоритме лучше всего подходит символ процесс (рис. 26).
41
42
Комментарий.
Символ используют для добавления описательных
комментариев или пояснительных записей в целях
объяснения или примечаний. Пунктирные линии в
символе комментария связаны с соответствующим
символом или могут обводить группу символов. Текст
комментариев или примечаний должен быть помещен
около ограничивающей фигуры.
Соединитель. Символ отображает выход в часть схемы и
вход из другой части этой схемы и используется для
обрыва линии и продолжения ее в другом месте.
Соответствующие символы - соединители должны
содержать одно и то же уникальное обозначение.
Символ отображает выход во внешнюю среду и вход
из внешней среды (начало или конец схемы
программы, внешнее использование и источник или
пункт назначения данных).
Граница цикла
Терминатор
Символ, состоящий из двух частей, отображает начало и
конец, цикла. Обе части символа имеют один и тот же
идентификатор. Условия для инициализации,
приращения, завершения и т. д. помещаются внутри
символа в начале или в конце в зависимости от
расположения операции, проверяющей условие.
Решение
Символ отображает модификацию команды или группы
команд с целью воздействия на некоторую последующую
функцию (установка переключателя, модификация
индексного регистра или инициализация программы)
Символ отображает решение или функцию
переключательного типа, имеющую один вход и ряд
альтернативных выходов, один и только один из которых
может быть активизирован после вычисления условий,
определенных внутри этого символа. Соответствующие
результаты вычисления могут быть записаны по
соседству с линиями, отображающими эти пути.
Подготовка
Рис. 26. Некоторые обозначения, используемые при записи алгоритмов
Линия. Символ отображает поток
данных или управления
Символ отображает любой процесс,
выполняемый человеком
Предопределенный
процесс
Ручная
операция
Символ отображает предопределенный процесс,
состоящий из одной или нескольких операций
или шагов программы, которые определены в
другом месте (в подпрограмме, модуле).
Процесс
Символ отображает данные, вводимые вручную
во время обработки с устройств любого типа
(клавиатура, переключатели, кнопки, световое
перо, полоски со штриховым кодом)
Символ отображает данные, представленные
на носителе в удобочитаемой форме
(машинограмма, документ для оптического
или магнитного считывания, микрофильм,
рулон ленты с итоговыми данными, бланки
ввода данных).
Символ отображает хранимые данные в виде,
пригодном для обработки, носитель данных не
определен
Символ отображает данные, носитель данных
не определен
Символ отображает функцию обработки данных
любого вида (выполнение определенной операции
или группы операций, приводящее к изменению
значения, формы или размещения информации или к
определению, по которому из нескольких
направлений потока следует двигаться).
Ручной ввод
Документ
Запоминаемые
данные
Данные
Начало
программы
d= static_cast
<double> (a)/3
a=1
Вывод
на
дисплей
a=a+1
Конец
программы
Рис. 27. Алгоритм программы преобразования типов данных
из примера 4
Пример 4. Использование оператора присваивания
#include <iostream>
using namespace std;
void main() //начало программы
{int a; double d;
a=1;
a=a+1;
d= static_cast <double> (a)/3; //Преобразование переменной типа int к типу double
сout << d;
сout << endl;
}
Вариант алгоритма этой программы представлен на рис. 27.
2.10. Ввод с клавиатуры и вывод на экран в языке С
Смысл операций ввода-вывода заключается в изменении содержимого некоторой ячейки памяти из внешней среды (например,
с клавиатуры) – ввод или в отображении содержимого ячейки памяти в окружающей среде (например, на дисплее) – вывод. С операциями ввода и вывода приходится сталкиваться при создании любой
даже самой простой программы. Традиционно языки программирования содержали в своем составе специальные операторы или команды ввода-вывода. Отличительной особенностью языка С является то обстоятельство, что роль операторов ввода-вывода выполняют
специальные функции из стандартной библиотеки языка. Поэтому
при начальном обучении программированию на С приходится зна43
комиться с правилами использования стандартных функций printf
и scanf, осуществляющих соответственно вывод на экран дисплея и
ввод с клавиатуры данных программы.
Функции классического языка С printf и scanf осуществляют
вывод и ввод информации применительно к ячейке памяти, идентификатор которой указывается вторым в списке переменных после запятой. При выполнении операции ввода ставится ссылка на
адрес переменной (например, &name), а при выполнении операции
вывода просто имя переменной (например, name). Значение переменной представляется в виде, который можно первоначально задать.
Для его описания используется термин формат, в связи с чем функции часто называют функциями форматированного ввода-вывода.
Строка описания форматов, включающая в себя некий текст и содержащая в позиции вывода спецификацию формата, указывается
в качестве первого аргумента функции до запятой:
printf (Ya uchus v GUAP v gruppe 84501\n", num);.
В состав библиотеки языка C, кроме printf, включено еще несколько похожих друг на друга функций, использующих форматированный ввод-вывод на различные устройства. Названия этих
функций отличаются друг от друга только первой буквой. Все
функции форматированного ввода-вывода требуют в своем списке
аргументов строки описания форматов, поэтому ниже будет использоваться термин «семейство функций ...printf и ...scanf».
Строка описания форматов может содержать любые символы,
кроме %, символы управления печатью ('\t' – табуляция, '\n' – перевод строки) и спецификации форматов. Спецификации формата
для функций семейства ...printf (функции вывода) имеют следующую форму1:
%[флаги] [ширина] [.точность] [h|l|L] тип,
где квадратные скобки указывают необязательный параметр, а символ «|» означает «ИЛИ». В табл. 3 дается список типов преобразований, входной тип аргумента и выходной тип после преобразования.
Необязательные компоненты спецификации формата. Флаг:
– – (минус) левое выравнивание результата. Если не задан, результат выравнивается справа;
– +(плюс) – результат начинается со знака (–) или (+);
–  (пробел) – результат начинается со знака (–) или с пробела.
1 Традиционно в литературе по программированию необязательные параметры
помещают в квадратные скобки
44
Таблица 3
Значения символов типов форматированного ввода – вывода
Символ
d
i
o
u
x или X
f
e или E
g или G
C
S
%
Аргумент
Выходной аргумент
Числовые данные
Целое
целое десятичное со знаком
Целое
целое десятичное со знаком
Целое
целое восьмеричное без знака
Целое
целое десятичное без знака
Целое
целое шестнадцатеричное без знака
Плавающее
значение в форме [-]dddd.dddd
Плавающее
значение в форме [-]d.dddde[+/-]ddd
Плавающее
значением в форме либо f, либо e
Символьные данные
Символьное
один символ
указатель на
печатает символы до тех пор, пока не
строку
встретится нулевое окончание
печатается только знак %.
Ширина выводимого значения задается одним из двух способов:
– непосредственно с помощью строки десятичных цифр, причем
при указании ширины поля, начинающейся с нуля, не значащие
позиции заполняются нулями;
– косвенно с помощью знака (*) спецификация ширины ставится
в списке аргументов.
Спецификация точности всегда начинается с точки (.), отделяющей ее от предшествующей спецификации ширины и задается либо
непосредственно, либо косвенно (*).
Использование при форматном выводе спецификации ширины,
точности и флагов левого или правого выравнивания позволяют организовывать печать результатов вычислений в экранное поле вывода фиксированного размера или в колонку таблицы определенной
ширины. При этом ширина и количество значащих цифр не зависят
от значения выводимого параметра, а определяются только спецификацией формата. С помощью спецификации точности можно
также осуществлять округление выводимых на печать значений до
требуемого числа разрядов, не изменяя при этом значения самих используемых переменных.
Модификаторы размера:
– h для типов d, i, o, u, x данные интерпретируются как короткое целое;
45
Начало
программы
KK=12345;
DD=12.345;
Вывод KK
в разных
представлениях
Вывод DD
в разных
представлениях
Конец программы
Рис. 28. Алгоритм программы из примера 5
– l для типов d, i, o, u, x как длинное целое; для типов e, f, g
как число двойной точности;
– L для типов e, f, g как длинное с двойной точностью.
Возвращаемое значение каждой функции равно количеству выводимых байт. В случае ошибки printf возвращает EOF.
Пример 5. Классический форматный вывод в C
#include <stdio.h>
int main ()
{
// Иллюстрируются возможности работы с спецификацией формата printf
long KK; double DD;
KK=12345; DD=12.345;
printf ("KK=%d!\n", KK); // Печатается KK=12345!
printf ("KK=%8d!\n",KK); // Печатается KK= 12345!
printf ("KK=%+8d!\n",KK);// Печатается KK= +12345!
printf ("KK=%-+8d!\n",KK);
// Печатается KK=+12345 !
printf ("KK=%- 8d!\n",KK);
// Печатается KK= 12345 !
printf ("KK=%08d!\n",KK);// Печатается KK=00012345!
printf ("DD=%13.6e!\n",DD);
// Печатается DD= 1.234500e+01!
printf ("DD=%13.6E!\n",DD);
// Печатается DD= 1.234500E+01!
printf ("DD=%13.4e!\n",DD);
// Печатается DD= 1.2345e+01!
printf ("DD=%13.2e!\n",DD);
// Печатается DD= 1.23e+01!
return 0;
}
46
Для функций ввода (семейство …sсanf) спецификация формата
имеет вид:
%[*] [ширина] [h|l|L] тип.
Флаг * подавляет назначение следующего поля ввода. Необязательный параметр [ширина] определяет максимальное число вводимых символов. Параметры [h|l|L] тип определяют формат аргументов аналогично функциям семейства …printf.
Пример 6. Классический форматный ввод в C1
#include <stdio.h>
int main()
{
int a; float b;
printf("input a input b \n");
// Обратите внимание, перед идентификаторами переменных a и b стоят символы &
scanf_s("%d", &a);
scanf_s("%f", &b);
return 0;
}
К сожалению, правила записи аргументов функций ввода вывода, используемых в языке C, оказались достаточно сложными, особенно для начинающих программистов.
2.11. Ввод с клавиатуры и вывод на экран в Visual C++
В языке C++ сохранилась возможность использования функций
printf и scanf_s, однако добавлена и другая, основанная уже не на
функциях, а на технологии классов и объектов. Операция вывода
данных на консоль может быть реализована и с помощью ключевого слова cout. Поэтому вывод на экран может быть описан следующей строкой кода:
cout << "Ya uchus v GUAP v gruppe 84501\n";
Скорее всего, вы обратили внимание на пару символов <<. В классическом языке С так обозначается операция побитного сдвига влево. Однако в рассматриваемой строке кода имеется такое свойство
объектно-ориентированного языка, как полиморфизм2. В зависимости от контекста программы компилятор подключит либо одну,
1 Классическая функция scanf на практике оказалась источником большого количества ошибок и в современных реализациях библиотек заменена на функцию
scanf_s .
2 Полиморфизм (polymorphism) (от греческого polymorphos) – это свойство, которое позволяет одно и то же имя использовать для решения двух или более схожих,
но технически разных задач.
47
либо другую последовательность действий. В данном случае выполнится отправка данных, указанных справа, в поток cout. Отметим,
что при вводе данных используется инверсный символ >>, также использующий свойство полиморфизма Visual C++.
В рассматриваемом примере кода программы есть еще одна пара
символов, требующая пояснения. Это управляющие потоком вывода символы \n. Таким способом указывается необходимость последующей печати с новой строки. Полный набор поддерживаемых
управляющих символов показан в табл. 4.
Сформулируем общие правила программирования ввода и вывода в языке C++. Ввод с клавиатуры осуществляется через поток cin,
используя для этого операцию извлечения из потока >>. Чтобы прочитать два целых значения с клавиатуры в переменные num1 и num2,
можно написать следующий оператор:
cin>> num1>> num2;
Данные передаются из cin в каждую из двух переменных по очереди. Ведущие пробелы игнорируются и первое введенное с клавиатуры число поступает в переменную num1, второе число в переменную num2. Между числами должен быть пробел или пробелы для
разделения значений. Операция потокового ввода завершается при
Таблица 4
Значения управляющих символов
Название символа
Символ
ASCII
Код
C++
Десятичный
код ASCII
Шестнадцатеричный
код ASCII
Новая строка
NL (LF)
\n
10
0xA
Горизонтальная
табуляция
HT
\t
9
0x9
VT
\v
11
0xB
BS
\b
8
0x8
Возврат каретки
CR
\r
13
0xD
Предупреждение
BEL
\a
7
0x7
Обратный слеш
\
\\
92
0x5C
Знак вопроса
?
\?
63
0x3F
Одинарная кавычка
‘
\’
39
0x27
Двойная кавычка
“
\”
34
0x22
Вертикальная
табуляция
Возврат на одну
позицию
48
нажатии на клавишу <Enter>. Потоковый ввод автоматически распознают переменные любого фундаментального типа. Например,
последняя операция читает целое значение в num1, затем значение
с плавающей точкой в num3 и ещё одно целое в num2.
Вывод на экран осуществляется через поток вывода cout с использованием операции вставки в поток <<.
Пример 7. Возможности ввода и вывода в C++
// Возможности ввода и вывода в C++
#include <iostream>
using namespace std;
int main ()
{
// Объявление и инициализация переменных разных типов
int num1;
float pi=3.1415f;
double num2;
char a='A';
Рис. 29. Результат выполнения программы из примера 7
49
Начало
программы
pi=3.1415f
char a='A';
Вывод на
экран
значений
переменных
Запрос на
ввод двух
переменных
Конец программы
Клавиатурный
ввод
Рис. 30. Алгоритм программы из примера 7
// Сообщение на экран – запрос на ввод двух переменных
cout <<"vvedute num1, num2 \n";
cin>>num1>>num2; // Собственно клавиатурный ввод
cout << "num1="<<num1<< " num2="<< num2; // Вывод на экран значений переменных
cout <<"\n pi="<<pi<<" a="<<a; // Перевод строки и снова
//вывод на экран значений переменных
cout<<endl; // Перевод строки
return 0;
}
Результат выполнения этого примера приведен на рис. 29.
Форматирование вывода может осуществляться с помощью символов новой строки \n и табуляции \t, но выравнивание колонок
в нескольких строках вывода потребует дополнительных возможностей языка C++. С этой проблемой позволят справиться манипуляторы. Манипулятор модифицирует способ управления выводом
данных в поток. Манипуляторы определены в заголовочном файле
<iomanip>, поэтому понадобится добавить в начало файла директиву #include. Манипулятор setw(n) выводит значение, следующее за
ним, в поле заданной шириной n. Например: cout<<setw(6)<<num1<<s
etw(6)<<num2<<num3;. В примере 8 переменные num1 и num2 будут выводиться в поля шириной 6 символов. К переменной num3 действие
манипуляторов не относятся.
50
Лабораторная работа №2.
Базовые операции ввода-вывода C++
Цель работы: Ознакомление с вводом выводом в C++ настолько,
чтобы в дальнейшем использовать его в примерах в процессе дальнейшего изучения языка
Задание
Научитесь выполнять начальные действия по созданию программы в консольном приложении Win32.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Наберите пример к данной лабораторной работе и выполните
его несколько, раз, изменяя значения переменных, вводимых с клавиатуры.
3. Создайте новый проект в новом решении. Напишите программу, в которой бы латынью вводились с клавиатуры ваша фамилия,
№ курса, на котором вы учитесь, сколько вам лет (учитывая месяцы) и выводилась введенная информация. Используйте при выводе
данных управляющие символы \n, \t, \а.
4. Создайте новый проект в новом решении. Напишите программу, выводящую целые числа в три строки и три столбца, с использованием манипуляторов. Числа для вывода:
1
20
300
10 100
200 2
3
30
Контрольные вопросы
1. Как в памяти машины кодируются целые числа?
2. Как в памяти машины кодируются вещественные числа?
3. Как в памяти машины кодируются символы?
4. Как преобразовать целое число в вещественное?
5. Как преобразовать вещественное число в целое?
6. Что понимается под мантиссой числа?
7. Что понимается под порядком числа?
8. Зачем нужен дополнительный код?
9. Что понимается под нормализованным представлением числа?
10. Зачем нужны манипуляторы?
51
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, рисунок алгоритма разработанной программы, текст программы и
результаты её выполнения, выводы, которые можно сделать по результатам выполненной работы.
Пример 8. Вариант текста и алгоритм (рис. 31) программы
// Ввод-вывод с использованием класса iostream и манипуляторов
#include <iostream>
#include <iomanip>
using namespace std;
int main() // Главная программа
{// Объявление и инициализация переменных
float pi = 3.1415f;
int num1, num3;
double num2;
char a = 'A';
cout << "vvedute num1, num2 \n";
// Вывод строки
cin >> num1 >> num2;
// Ввод с клавиатуры
num3 = 3 * num1;
// Вычисления
cout <<"num1=" << num1 << " num2="<< num2 << " num3=" << num3 << '\n'; // Вывод строки
cout << setw(6) << num1 << setw(6) << num2 << setw(6) << '#' << num3 << "\n";// Манипуляторы
cout << "pi=" << pi << "\n a=" << a;
// Вывод строки
cout << endl;
// Вывод строки
return 0; }
Начало
программы
pi = 3.1415f
Вычисления
Вывод
строки
Вывод строки
Манипуляторы
Вывод строки
Ввод с
клавиатуры
Конец
программы
Рис. 31. Алгоритм программы ввода-вывода
с использованием манипуляторов
52
2.12. Выполнение арифметических операций
и приведение данных
Арифметические операции с целыми числами выполняются в результате выполнения команд процессора непосредственно с данными, хранящимися в указанных процессору ячейках памяти. Если
разрядность процессора совпадает с разрядностью слов данных,
операция, например, сложения, выполнится за одну машинную команду. Если размер слова памяти превышает разрядность процессора, то для выполнения операции компилятором генерируется код,
обеспечивающий выполнение действие сначала с младшими разрядами слов операндов операции, а потом и со старшими. Очевидно,
что если в процессе выполнения операции над младшими разрядами операндов произошел перенос единицы в старший разряд, то он
будет учтен при выполнении операции над старшими разрядами
операндов. Отсюда следует, что в зависимости от типов участвующих, например, в выполнении операции сложения данных компилятор генерирует разный код.
Арифметические операции с дробными числами, записанными в экспоненциальной форме (мантисса и порядок), оказываются
куда более сложными, чем операции с целыми числами. Так, для
выполнения операции сложения двух вещественных чисел необходимо уровнять их порядки. Например, для сложения чисел
0,12345 × 104 и 0,12 × 101 необходимо привести второе слагаемое к виду
0,00012 × 104 и только после этого выполнить операцию сложения,
результатом которой будет 0,123456 × 104. Получившаяся сумма уже
оказалась в нормализованном виде. Если бы этого не было, то перед
записью результата в ячейку его надо было бы нормализовать, то
есть обеспечить не нулевой старший разряд мантиссы числа за счет
регулировки его порядка.
Из сказанного следует, что если для реализации арифметической операции с целыми числами достаточно одной машинной команды, то действия с дробными числами оказываются более сложными. Для выполнения таких вычислений компилятору приходится включать в состав программы последовательности команд процессора, обеспечивающую нормализацию чисел, их кодирование
в ячейке и декодирование, уравнивание порядков и ряд других. Все
эти действия выполняет набор библиотечных возможностей компилятора, которые называются арифметикой с плавающей точкой.
Задав тип данных, программист дает указание компилятору выделить необходимую память для их хранения, а также подключить
53
функции библиотеки с плавающей точкой для обеспечения выполнения операций с этими данными. Как результат, выполнение одной арифметической команды с плавающей точкой может потребовать несколько десятков элементарных машинных команд1.
Отдельную проблему представляют совместные действия с целыми и дробными числами или числами, представленными разной
длиной разрядной сетки. Поскольку арифметические операции могут выполняться только с данными одинакового типа, для каждой
выполняемой операции компилятор должен выполнить преобразования операндов одного типа операндов к другому. Такие действия
называются приведением. Если, например, требуется сложить значение типа int со значением типа real, то первый операнд первоначально приводится к типу данных real, после чего и выполняется
сложение. Эта операция генерируется компилятором автоматически по неким правилам, суть которых сводится к следующему:
операнд с относительно простым способом кодирования приводится к типу операнда с самым сложным способом кодирования. Как
правило, если существует опасность ошибки при автоматическом
преобразовании типов данных, он выдает диагностическое сообщение, например, вида: "warning =: преобразование 'double' в 'int',
возможна потеря данных".
Существуют ситуации, когда программисту оказывается необходимым принудительно выполнить приведение к интересующему
его типу данных. Например, в вещественной ячейке записано дробное число, а нас интересует только его целая часть. В языке C++
существует ключевое слово static_cast, после которого в символах
< > указывается тип данных, к которому надо привести результат
вычисления выражения и само выражение, заключенное в круглые
скобки. Если переменная abcd имеет тип double, то результат выполнения static_cast <int> (abcd), будет иметь тип int2. Отметим,
что заложенные в компилятор правила приведения в большинстве
случаев устраивают всех. Однако для того, чтобы обезопасить себя
1 Иногда, но очень редко, встречаются процессоры, которые могут выполнять
арифметические команды над нормализованными дробными числами. Хотя такая
команда выполняется много дольше команды с целыми числами, все равно это оказывается быстрее, чем выполнение нескольких требуемых для операции команд
обычного процессора.
2 Существует форма записи принудительного приведения «в старом стиле».
В этом случае перед переменной без использования ключевых слов в круглых скобках указывается тип данных, к которому надо выполнить приведение, например,
(int) (abcd).
54
от возможной ошибки, рекомендуется стараться не смешивать в одной операции операнды разных типов или принудительно указывать на необходимость преобразования типов данных с помощью
инструкции static_cast.
Пример 9. Преобразование данных без выполнения операции
деления
Пусть a, b, c, d, f, j описаны в программе как целые переменные
и имеют начальные значения b=2, c=3, d=6, f=5, j=1. Необходимо
вычислить a=b + c * d – (f + j). Компилятор учитывает приоритет
операций, задаваемый, в том числе, и круглыми скобками. Поэтому итоговая последовательность вычислений будет состоять из действий, приведенных в табл. 5.
Таблица 5
Последовательность действий
при вычислении математического выражения a=b + c * d – (f + j)
Номер
действия
Вид действия
Результат
вычисления
1
(f + j)
6
2
c*d
18
3
b + результат действия 2
20
4
результат действия 3 – результат действия 1
14
5
результат действия 4 присваивается переменной a
14
Результаты первых четырех действий находятся во внутренней
памяти и теряются при завершении вычислений. В примере 9 вычисления производятся с целыми числами, преобразования типов
данных не требуются и результат получается таким, как и ожидается. Однако если среди арифметических операций присутствует
деление, то результат может оказаться неожиданным.
Пример 10. Преобразование данных с выполнением операции
деления
Пусть a, b, c, d, f, j описаны в программе как целые переменные
и имеют начальные значения b=2, c=3, d=6, f=5, j=1, как и в предыдущем примере. Отличие заключается в формуле, которая описывает результат вычислений a=b + c/d – (f + j). Итоговая последовательность вычислений будет состоять из действий, приведенных
в табл. 6.
55
Таблица 6
Последовательность действий при вычислении
математического выражения a=b + c/d – (f + j)
Номер
действия
Вид действия
Результат
вычисления
1
2
3
4
5
(f + j)
c/d
b + результат действия 2
результат действия 3 – результат действия 1
результат действия 4 присваивается переменной a
6
0
2
–4
–4
Неожиданным оказался результат деления 3/6 в действии 2.
Вместо ожидаемых 0,5 получен 0, то есть в вычислении потеряна
дробная часть. Компилятор сгенерирует код, в результате выполнения которого при делении целого числа на целое во временной памяти сохранится целый результат. Если бы хотя бы одна из переменных, участвующих в делении была бы с плавающей точкой, то и
результат был бы с плавающей точкой.
Для того, чтобы обеспечить правильность вычислений, необходимо явно задать приведение хотя бы одного из операндов операции
деления к типу с плавающей точкой. Для этого программируемое
на языке C++ выражение преобразуется к виду
a= b + static_cas t < double > (c)/d – (f + j).
При явном приведении в действии 2 одной из переменных к типу
с плавающей точкой во временной памяти получаем результат 0,5
(табл. 7). Поскольку в дальнейших действиях участвует переменная
типа double, то и результаты выполнения действий 3 и 4 имеют тип
double и содержат дробную часть. А вот при выполнении оператора присваивания в действии 5 дробная часть будет отброшена, поскольку тип переменной a указан явно как int.
Таблица 7
Последовательность действий при вычислении математического
выражения a= b + static_cas t < double > (c)/d – (f + j)
Номер
действия
Вид действия
Результат
вычисления
1
(f + j)
6
2
static_cas t < double > (c)/d
0,5
3
4
5
b + результат действия 2
результат действия 3 – результат действия 1
результат действия 4 присваивается переменной a
2,5
–3,5
–3
56
3. МЕТОДЫ ПОДГОТОВКИ ПРОГРАММЫ
К ВЫПОЛНЕНИЮ, ТЕСТИРОВАНИЯ И ОТЛАДКИ
3.1. Общая схема прохождения задачи
Программа, написанная на любом языке программирования,
так или иначе, должна быть обработана одной из систем автоматизации программирования для создания выполняемых кодов. Очевидно, что транслятор (компилятор, интерпретатор) может работать
только с информацией, представленной в машинно-читаемом виде.
В настоящее время наибольшее распространение получили методы
программирования с использованием так называемых турбо-оболочек или интегрированных сред. В их состав включается набор программных средств, позволяющих единообразно организовать процесс набора текста программы, ее компиляцию, редактирование
связей, выполнение, отладку. Таким образом, кроме правил языка
программирования, программисту приходится осваивать еще правила и методы работы с конкретной турбо-оболочкой или, пользуясь современной терминологией, программной средой.
Общая процедура создания программного обеспечения представляет собой многошаговый процесс с большим числом обратных связей. Обобщенная схема создания программного обеспечения представлена на рис. 32.
Процесс подготовки программы к выполнению начинается с разработки алгоритма. Далее на основе алгоритма разрабатывается
команды (текст) программы. Следующая стадия – это набор текста программы. Для этого созданы специальные программы, получившие название Редакторов текста. В своей работе программа
редактора текста имитирует работу пишущей машинки. Так, при
нажатии символьной клавиши на клавиатуре, редактор текста запоминает код символа и записывает его в свой специальный рабочий файл. Часть этого файла, так называемое рабочее окно, выводится на дисплей. На экране дисплея высвечиваются изображения
символов, коды которых попадают в настоящий момент в рабочее
окно. Очевидно, что размер рабочего окна согласован с разрешающей способностью дисплея. Специальными командами, задаваемыми мышью или с клавиатуры, можно изменять положение рабочего
окна по отношению к началу рабочего файла и последовательно его
просматривать.
Рабочий файл может быть сохранен на диске, скопирован и
в свою очередь отредактирован (подвергнут изменениям). Такие
57
Получение
задания
a
b
c
Разработка
общего
алгоритма
Нет
Да
Редактирование
связей
Кодирование
(написание
программы)
c
Есть ошибки?
Нет
Да
Тестирование
и отладка
Набор текста
с помощью
редактора
Есть ошибки?
Да
Нет
Выполнение
программы
Компиляция
a
b
Есть ошибки?
Есть ошибки?
Нет
Да
Сдача
программы
заказчику
b
d
c
Рис. 32. Обобщенный алгоритм создания программного обеспечения
изменения в электронном виде выполняются гораздо проще, чем
изменения текста, напечатанного на бумаге с помощью пишущей
машинки, поскольку в итоге они сводятся к замене одной записи
в файле на другую. Редакторы содержат так называемые групповые операции, позволяющие вставлять, копировать, удалять, изменять по определенному правилу фрагменты текста, что позволяет
существенно повысить производительность труда оператора ЭВМ по
сравнению с обычной машинисткой.
Некоторые редакторы текста, специально ориентированные на
набор именно текстов программ, могут в своем составе содержать
дополнительные средства автоматизации, позволяющие автоматически вводить окончания служебных слов, предлагать библиотечные варианты текстов на выбор и вставлять их по указанию оператора, а также ряд других возможностей, определяемых спецификой
языка программирования.
Подготовленные с помощью редактора текста коды программы
подвергаются последующей обработке. В зависимости от используе58
ôàéë.ÿçûê
áèáëèîòåêà.*
ôàéë.ÿçûê
Òðàíñëÿòîð
Êîìïèëÿòîð
ôàéë.obj
äîïôàéë.obj
Ðåäàêòîð ñâÿçåé
áèáëèîòåêà.obj
ôàéë.exe
ôàéë.ÿçûê
áèáëèîòåêà.*
Âûïîëíåíèå
ïðîãðàììû
ïðîöåññîðîì
Èíòåðïðåòàòîð
ôàéë.exe
ôàéë.obj
ôàéë.exe
Ðåçóëüòàòû
Ðåçóëüòàòû
Рис. 33. Преобразования файлов при трансляции, компиляции,
интерпретации, редактировании связей и выполнении
мой технологии (транслятор, компилятор, интерпретатор) производятся соответственно коды выполняемой программы, так называемый объектный файл или выполняется строка оттранслированного
текста программы (рис. 33).
С помощью редактора текста производятся и все последующие
изменения исходного текста программы (файл.язык).
3.2. Ошибки этапов подготовки программы к выполнению
В процессе преобразования текстового файла в коды, которые
могут быть исполнены процессором, транслятор (компилятор, интерпретатор) может выдать разнообразные диагностические сообщения. В подавляющем большинстве случаев причиной появления
таких сообщений является несоблюдение программистом правил
языка программирования, в результате чего система не может создать последовательность исполняемых кодов. Если такие ошибки
есть, то необходимо вернуться к исходному тексту программы и
внести соответствующие изменения. Компилятор обычно непосредственно указывает строку программы, содержащую ошибку, дает ее
59
Рис. 34. Отображение ошибки этапа компиляции
описание и предлагает воспользоваться системой помощи. Пример
диагностики ошибки этапа компиляции показан на рис. 34.
Отдельную группу диагностических сообщений может выдавать
программа редактора связей. В большинстве случаев она определяет ситуации, связанные с неправильным указанием библиотечных
функций или классов. В случае возникновения таких ошибок необходимо проверить все внешние имена и убедиться в исправности
и доступности библиотеки.
В некоторых случаях могут выдаваться диагностические сообщения предупреждающего или рекомендательного характера, наличие
которых не останавливает процесс перевода программы в коды. Тем
не менее, наличие таких сообщений является чрезвычайно серьезным сигналом, и программист должен внести в текст программы изменения, которые предотвратят появление таких сообщений.
3.3. Ошибки этапа выполнения,
автоматически определяемые процессором
Программа, запущенная на выполнение, может выполнить некоторые действия, которые с точки зрения разработчиков процессора являются незаконными. Типичный пример такой операции –
деление на ноль. В структуру процессора заложены проверочные
действия, перехватывающие подобную ситуацию, и приводящие
к возникновению логического прерывания процессора. Стандартно
это прерывание обрабатывается в виде передачи управления операционной системе и, как следствие, к прекращению выполнения
пользовательской программы. Можно привести и другие примеры
ошибок этапа выполнения.
60
Корректно написанная программа не должна допускать возникновение ошибок этапа выполнения за счет введения дополнительных средств внутреннего алгоритмического контроля, поэтому рассматриваемый оператор выполняет скорее отладочные, а не основные функции.
Лабораторная работа №3.
Операции в базовой арифметике
и преобразование типов данных
Цель работы: Ознакомление с типами данных и методами арифметических вычислений данных различных типов
Задание
Научитесь создавать линейные программы, выполняющие арифметические действия с данными разных типов.
Порядок выполнения работы
1. Согласуйте с преподавателем номер своего варианта задания
(табл. 8).
2. Создайте новый проект.
3. Напишите программу, реализующую вычисления математического выражения в соответствии со своим заданием без использования приведения данных. Убедитесь в том, что результат вычислений вашей программы совпадает с результатом, приведенным
в таблице (результат a1).
4. Напишите программу, реализующую вычисления математического выражения в соответствии со своим заданием и использующую приведение данных при выполнении операции деления.
Убедитесь в наличии диагностического сообщения компилятора
"warning =: преобразование 'double' в 'int', возможна потеря данных". Убедитесь в том, что результат вычислений вашей программы
совпадает с результатом, приведенным в таблице (результат a2).
5. Напишите программу, реализующую вычисления математического выражения в соответствии со своим заданием и использующую приведение данных при выполнении операции деления и записывающую результат вычислений в ячейку с плавающей точкой.
Убедитесь в том, что при построении решения отсутствуют диагностические сообщения компилятора класса "warning". Убедитесь
в том, что результат вычислений вашей программы совпадает с результатом, приведенным в таблице (результат a3).
61
6. Сравните результаты вычислений трех написанных вами программ.
7. Создайте новый проект для выполнения задания 2 (табл. 8).
Напишите программу, которая осуществляла бы перевод вводимых
с клавиатуры1 данных в соответствии со своим вариантом задания
2 (табл. 8):
а) секунд в минуты и секунды;
б) минут в часы и минуты;
в) часов в дни и часы;
г) дни в месяцы и года, считая среднюю продолжительность месяца 30 дней.
Контрольные вопросы
1. В чем отличие принципа выполнения арифметических операций с целыми числами от операций с дробными числами?
2. Что понимается под приведением типа данных?
3. Какой смысл диагностического сообщения компилятора
"warning =: преобразование 'double' в 'int', возможна потеря данных"?
4. В каких случаях требуется принудительное приведение числа
к конкретному типу данных?
5. Что такое приоритет операции?
6. Как можно искусственно задать приоритет операции?
7. Какие действия надо выполнить для выполнения операции
сложения двух дробных чисел?
8. Какие действия надо выполнить для выполнения операции
умножения двух дробных чисел?
9. Какие действия надо выполнить для выполнения операции
вычитания двух дробных чисел?
10. Какие действия надо выполнить для выполнения операции
деления двух дробных чисел?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, цель работы, номер варианта задания, рисунки алгоритмов разработанных программ, текст программ
и результаты их выполнения. Должно быть представлено сравнение
полученных результатов вычислений программы с табличными
1
62
рекомендуется использовать операцию % остаток от деления целых чисел
Целочисленные
вычисления
Начало
Присвоение в
целочисленную ячейку
static_cast<double>
Задание
начальных
значений
Присвоение в ячейку
с плавающей точкой
static_cast<double>
aint1 = b + c / d - (f + j)
aint2 = b + static_cast<double> (c) / d - (f + j)/
adouble3 = b + static_cast<double> (c) / d - (f + j)
Вывод
результата
Конец
Рис. 35. Вариант исполнения алгоритма программы из примера 11
(табл. 9). Кроме этого, в отчете должны быть сформулированы выводы, которые можно сделать по результатам выполненной работы.
Пример 11. Разработанная программа ее алгоритм (рис. 35)
#include <iostream>
#include <iomanip>
using namespace std;
void main()
{
int b, c, d, f, j; // Исходные данные
int aint1, aint2; // Результат вычислений
double adouble3;
// Задание начальных значений
b = 3;
c = 4;
d = 3;
f = 6;
j = 3;
aint1 = b + c / d – (f + j); // Целочисленные вычисления
// Присвоение в целочисленную ячейку. Есть диагностика компилятора
aint2 = b + static_cast<double> (c) / d – (f + j);
// Присвоение в ячейку с плавающей точкой. Диагностики компилятора нет
adouble3 = b + static_cast<double> (c) / d – (f + j);
cout << "a1=" << aint1 << " a2=" << aint2 << " a3=" << adouble3;
cout << endl;
}
63
Варианты заданий по преобразованию данных
Таблица 8
Варианты индивидуальных заданий к лабораторной работе №3
Номер
варианта
Значение
Математическое
выражение
b
c
Результат
d
f
j
a1
a2
a3
Вариант
задания 2
1
a = b + c/d – (f + j) 10 11
8
20
9
-18
-17
-17.625
а)
2
a = b – c/d + (f – j) 10 11
8
20
9
20
19
19.625
б)
3
a = (b – c)/d + f – j 10 11
8
20
9
11
10
10.875
в)
4
a = b * c /(d + f ) – j 10 11
8
20
9
-6
-5
-5.07143
г)
8
20
9
а)
5
a = b * c / d – f – j
-16
-15
-15.25
6
a = b + c/d – (f + j) 15 16 13 25 15 -24
-23
-23.7692
б)
7
a = b – c/d + (f – j) 15 16 13 25 15
24
23
23.7692
в)
8
a = (b – c)/d + f – j 15 16 13 25 15
10
9
9.92308
г)
9
a = b * c /(d + f ) – j 15 16 13 25 15
-9
-8
-8.68421
а)
10 11
10
a = b * c / d – f – j
15 16 13 25 15 -22
-21
-21.5385
б)
11
a = b + c/d – (f + j)
5
7
3
13
5
-11
-10
-10.6667
в)
12
a = b – c/d + (f – j)
5
7
3
13
5
11
10
10.6667
г)
13
a = (b – c)/d + f – j
5
7
3
13
5
8
7
7.33333
а)
14
a = b * c /(d + f ) – j 5
7
3
13
5
-3
-2
-2.8125
б)
15
a = b * c / d – f – j
5
7
3
13
5
-7
-6
-6.33333
в)
16
a = b + c/d – (f + j)
8
9
7
18
8
-17
-16
-16.7143
г)
17
a = b – c/d + (f – j)
8
9
7
18
8
17
16
16.7143
а)
18
a = (b – c)/d + f – j
8
9
7
18
8
10
9
9.85714
б)
19
a = b * c /(d + f ) – j 8
9
7
18
8
-6
-5
-5.12
в)
9
7
18
8
20
a = b * c / d – f – j
-16
-15
-15.7143
г)
21
a = b + c/d – (f + j) 13 14 11 23 13 -22
-21
-21.7273
а)
22
a = b – c/d + (f – j) 13 14 11 23 13
22
21
21.7273
б)
23
a = (b – c)/d + f – j 13 14 11 23 13
10
9
9.0909
в)
24
a = b * c /(d + f ) – j 13 14 11 23 13
-8
-7
-7.64706
г)
13 14 11 23 13 -20
-19
-19.4545
а)
25
64
a = b * c / d – f – j
8
3.4. Задача тестирования
Мы уже неоднократно указывали, что, в сущности, процессор
не может делать ничего, кроме считывания данных из памяти, их
преобразования по строгим и достаточно ограниченным правилам и
записи результатов назад в память. Однако эти операции процессор
может выполнять миллионы раз в секунду, а самих ячеек в его распоряжении находится несколько сотен или даже тысяч миллионов.
Тем не менее, мы хотим быть уверены (или, хотя бы, хотим надеяться), что все эти операции будут выполнены правильно и в интересах решаемой нами задачи. К сожалению, абсолютной уверенности
в этом вопросе нет и быть не может. Даже если отбросить в сторону
возможность технической неисправности ЭВМ, все равно сохраняется вероятность неправильного кодирования и, как следствие, неправильного выполнения создаваемой нами программы. Важность
этого вопроса существенно зависит от той прикладной области, для
которой создается программа. Безусловно, уровень надежности
программы, обеспечивающей управление авиалайнером, должен
быть существенно более высоким, чем у программы, занимающейся обработкой социологических опросов, хотя и в последнем случае
факт неправильной работы программы должен быть предметом самого пристального разбирательства.
Многочисленные попытки доказать факт правильности созданной программы, к сожалению, закончились неудачей. На настоящий момент человечество вынуждено смириться с мыслью, что программы содержат и будут содержать ошибки. Не существует метода
создания безошибочных программ и, как следствие, программирование может рассматриваться только как искусство, но не как наука.
Тем не менее, позволить себе не использовать компьютер в практической деятельности человек уже не может. Как следствие, на
передний план выдвигается поиск средств, позволяющих если не
полностью исключить, то, по крайней мере, минимизировать количество ошибок в программе. Большинство таких методов относится уже к области искусства программирования, однако существует
ряд некоторых относительно несложных приемов, позволяющих
повысить надежность тестирования и программирования. Некоторые из них из них реализованы в рамках интегрированной среды
программирования, некоторые другие обеспечиваются принципом
построения операционной системы. Что касается самого программиста, то он должен весьма тщательно относиться к написанию кода
65
программы и разработке тестов. Также надо обязательно обращать
самое серьезное внимание на диагностические сообщения системы
и немедленно принимать меры по их устранению даже в том случае,
когда, на первый взгляд, под этими сообщениями не скрывается
особой угрозы для целостности и работоспособности программы.
Подготовка программы на языке высокого уровня с помощью редактора текста и устранение синтаксических ошибок представляет
собой достаточно сложный процесс для начинающих заниматься
программированием. Именно поэтому у них особый восторг вызывает факт отсутствия диагностических сообщений об ошибках и запуск программы на выполнение. Начинающие обычно считают, что
они, наконец, преодолели все трудности и теперь можно пожинать
плоды своих усилий. Опытные программисты знают, что только тут
перед ними встают основные проблемы, связанные с созданием программного обеспечения, и переходят к этапу тестирования созданной ими программы.
Тестированием обычно называют процесс испытания программы на предмет ее работы с заданным набором входных данных
(тестом). По своей сути процесс тестирования представляет собой
проверку реакции программы на заранее подготовленные наборы
входных данных. Важно отметить то обстоятельство, что реакция
программы на тест должна быть известна до того, как она появится. Поэтому сами тесты разрабатываются вместе с алгоритмом программы еще до начала ее кодирования.
Очень важным является вопрос о глубине и объеме тестирования. Всеобъемлющим (исчерпывающим) может называться тестирование программы по абсолютно всем наборам входных данных.
Если входные данные представлены одной четырех байтовой ячейкой памяти типа int, то общее количество возможных тестов программы в этом случае превышает 4 миллиарда штук. Если входные
данные размещены в двух независимых ячейках памяти, то для исчерпывающего тестирования необходимо создать наборы данных,
включающие в себя все возможные комбинации. Поэтому в реальных задачах говорить об исчерпывающем тестировании не приходится. Поэтому приходится говорить об искусстве тестирования,
когда сознательно создаются наборы данных, позволяющие установить факт наличия ошибки в программе при минимальном числе
испытаний.
Вообще целью тестирования является установить факт наличия
ошибки в программе. Поэтому перед разработчиком теста стоит за66
дача разрушить готовую программу, что само по себе является не
таким уж простым делом. Очень часто квалификация разработчика
теста должна быть выше квалификации составителя программы.
Для реализации тестирования может потребоваться разработка
специального программного средства (генератора тестов), которое
по сложности может оказаться соизмеримым с испытуемой программой.
Вернемся к лабораторной работе №3. Задания на выполнение
этой лабораторной работы, приведенные в таблице 8, на самом деле
содержат и тест, который должен быть выполнен в процессе ее выполнения. Сформулированная задача на написание программы
вычисления математических выражений в каждом варианте предусматривает фиксированный набор входных данных (колонки Значение), которые даже не вводятся с помощью операторов ввода, а
непосредственно задаются в тексте программы. Результат вычислений программы также описан, рассчитан предварительно и занесен
в колонки Результат. Таким образом, студенту задан тест, выполнение которого должна обеспечить написанная им программа1.
Очевидно, что если результаты вычисления не совпадают с приведенными в табл. 8, то написанная студентом программа содержит
ошибку. С другой стороны, если результаты вычислений совпали
с табличными, то совсем не обязательно, что в программе ошибки
нет.
Если при написании программы из лабораторной работы №3 не
использовался, например, клавиатурный ввод данных, то вероятность еще какой-либо программной ошибки весьма мала. Правда
при выполнении такая программа всегда дает только один и тот же
результат. Нет никакого смысла выполнять ее второй раз, поскольку этот уже один раз полученный результат достаточно запомнить.
А вот если мы попытаемся изменить исходные данные такой программы в ее тексте, то мы получим другую программу, которую все
равно надо тестировать. Например, если написать программу с использованием для ввода данных объекта cin, то для проверки ее работоспособности предложенного теста мало. Возникает, например,
вопрос, как поведет себя программа, если вводимые с клавиатуры
1 Интегрированная среда программирования Visual C++ содержит в своем составе средства автоматизации тестирования (пункт Тест главного меню). Однако на
данном этапе обучения мы будем использовать полностью ручное тестирование задавая программе наборы входных данных и убеждаясь в правильности ее реакции
на них.
67
значения будут отрицательными или нулевыми в разных комбинациях. Кроме этого, интересно, что получится при обработке больших чисел, например, миллионов или миллиардов. Ну и, наконец,
с повестки дня не снимается вопрос, что будет с программой, если
пользователь по ошибке ввел с клавиатуры дробные числа или даже
просто текстовые строки.
Очевидно, что исчерпывающее тестирование программы из лабораторной работы №3, написанной с использованием объекта cin,
невозможно. Как следствие, программист должен задуматься над
дополнительными тестами, разработать их, например, в виде таблиц и проверить работоспособность программы на каком-то ограниченном количестве примеров, учитывающих приведенные выше
соображения. Именно тут и проявится его искусство тестирования.
И не надо говорить, что нечего вводить в программу заведомо неправильные данные (например, строки текста). Если вашей программой кто-то будет пользоваться, то почти наверняка найдется человек, который сделает это. А при неадекватной реакции программы
он будет рассказывать всем остальным, что вы пишите программы,
которые работают неправильно. И хорошо, если ваша программа не
будет при этом управлять авиалайнером.
3.5. Отладка и программные средства отладки
Тест считается успешным, если он указал на факт наличия
ошибки в программе1. Если тест ошибок не нашел, то это означает
только то, что ошибки не были найдены. К сожалению, это обстоятельство не является доказательством факта их отсутствия вообще.
Как говорят в таких случаях программисты, просто их плохо искали. Если обнаружен факт наличия ошибки в программе, то это
означает, что необходимо принимать меры по их устранению.
Отладкой называется процесс отыскания конкретного оператора
или конкретных операторов программы, являющегося или являющихся причиной возникновения ошибки, и внесение в него или
в них изменений с целью устранения выявленной ошибки. Отметим, что если ошибка устранена в процессе отладки, то процесс тестирования программы придется полностью повторить.
Процесс отладки во многом сходен с процессом поиска преступника в детективном романе. Приходится анализировать имеющиеся
1 Точно так в медицине говорят о положительных результатах анализа, если
в проведенное лабораторное исследование выявило заболевание
68
Рис. 36. Рабочее окно отладчика
доказательства (результаты тестирования), косвенные улики, круг
подозреваемых и тому подобное. В процессе отладки чрезвычайно
полезно пользоваться тем, что Эркюль Пуаро называл маленькими
клеточками серого вещества. Их активное применение позволяет
экономить время, затрачиваемое на процесс отладки и последующего тестирования.
Интегрированная среда программирования Visual C++ включает в свой состав специальный отладчик. Им можно воспользоваться
из меню Отладка (рис. 36).
Для автоматизации процесса отладки в составе отладчика имеется ряд возможностей, позволяющих обеспечить пошаговое (пооператорное) выполнение программы, чтение и запись содержимого любой интересующей ячейки памяти и тому подобное. Можно
создать точку останова на любом выполняемом операторе проекта.
Тогда после запуска программы на выполнение ее операторы авто69
матически выполняются один за другим до тех пор, пока не будет
достигнут оператор, помеченный точкой останова. Автоматическое
выполнение операторов прекращается и программисту предоставляется ряд возможностей.
Остановленная в режиме отладки программа показывает в окне,
где отображен текст программы (окно кода), оператор, который должен быть выполнен на следующем шаге. Он выделяется специальным маркером. Программист может задать один из трех возможных шагов продолжения выполнения программы – шаг с заходом,
шаг с обходом или шаг с выходом1. Выполнив шаг, программа снова
останавливается, а маркер указывает на следующий выполняемый
оператор программы. Состояние, в котором находится программа
в этот момент, иногда называют точкой приостановки.
Процесс выполнения программы по шагам обычно называется
трассировкой. Она позволяет увидеть последовательность выполнения операторов программы2. Если задается шаг с заходом, то выполнится следующий оператор программы. Отличие шага с заходом
от шага с обходом проявится в том случае, если текущим оператором будет оператор вызова функции. В первом случае выполнится
первый оператор функции3, во втором следующий после точки вызова функции оператор. Наконец, шаг с выходом приведет к тому,
что пошаговое выполнение функции завершится, и все операторы
функции до ее конца будут выполнены автоматически. Далее будет
выполняться первый после точки вызова текущей процедуры оператор4.
Если программа уже подготовлена к отладке (Visual C++ создает
для этого специальный модуль), то можно начать пошаговое выполнение программы с самого начала нажатием, например, функциональной клавиши F10. Тогда задание точки останова не потребует-
1 Имеются так называемые горячие комбинации клавиш, указанные в пункте
меню Отладка (рис. 36), позволяющие выполнить эти шаги. При их использовании
не надо каждый раз открывать главное меню и выбирать соответствующий пункт
2 Алгоритм программы может быть линейным, как это показано на рис. 37, или
же он может иметь разветвления
3 Мы еще не занимались функциями, но, поверьте на слово, такие существуют.
Это просто вызываемые по имени программы, оформленные соответствующим способом и содержащие свою последовательность кодов
4 Кроме этого, существует еще команда Специальный шаг с заходом. Ее назначение – помочь в отладке конкретной функции. Команда Специальный шаг с заходом
появляется в контекстном меню только если точка выполнения находится на строке кода, содержащего вложенную функцию. Вы познакомитесь с нею позднее, когда научитесь писать функции
70
ся, поскольку программа с самого начала будет находиться в точке
приостановки.
Если программа находится в режиме приостановки, то можно,
например, посмотреть значения локальных переменных. В точке
приостановки программы открывается дополнительное окно Видимые, в котором приводятся текущие значения переменных программы с указанием их типа.
Наконец, в точке приостановки можно продолжить автоматическое выполнение программы (в меню Отладка команда Продолжить) или закончить отладку (в меню отладка команда Остановить
отладку).
Лабораторная работа №4.
Тестирование и отладка программы
Цель работы: Изучение возможностей отладчика в среде Visual
C++ и использование его для отладки программы.
Задание
Научитесь выполнять отладку программы с помощью средств
интегрированной среды программирования.
Порядок выполнения работы
1. Вернитесь к программе вычисления арифметических выражений, написанной вами при выполнении лабораторной работы №
3. Измените вашу программу так, чтобы каждый ее оператор выполнял только одну арифметическую операцию. Например, вместо
вычисления выражения a = b + c/d – (f + j) одним оператором программы, выполните последовательно t = c/d, g = f + j, a = b + t – g,
где t и g дополнительные переменные, введенные в вашу программу
для хранения промежуточных вычислений.
2. Откройте пункт главного меню Отладка и выберете подпункт
Начать отладку. Как результат, создастся вариант программы, который может работать под управлением отладчика.
3. Нажмите кнопку F10 и начните выполнять вашу программу по
шагам. Убедитесь в том, что в окне редактора кодов появился маркер отладчика, а также в том, что в нижней части экрана открылось
окно локальных переменных Локальные или Видимые (рис. 38).
4. Сделайте активным окно локальных переменных, для чего
в левом нижнем окне интегрированной среды выберите вкладку Локальные.
71
5. Последовательно выполняя команду Шаг с обходом (F10), убедитесь в том, что ваша программа выполняется по шагам. Контролируйте факт изменения значений переменных вашей программы
в окне Локальные (Видимые). Выполните программу до последнего
оператора и остановите отладку командой Остановить отладку.
6. Установите несколько точек останова в программе. Запустите
программу на выполнение с отладкой (F5). После остановки на первой точке останова продолжите отладку до следующей точки в режиме Шаг с обходом (F10) и наблюдайте изменения значений переменных в окне локальных переменных. Дойдя до последней точки
останова, в меню отладка выберите команду Остановить отладку
(Shift+F5), которая прервёт выполнение программы. Убедитесь, что
программа не выполнилась до конца в этом режиме.
7. Ещё раз запустите программу на выполнение с отладкой (F5).
После остановки на первой точке останова продолжите отладку до
следующей точки в режиме Шаг с обходом (F10) и наблюдайте значения переменных в окне локальных переменных. Дойдя до последней точки останова, в меню отладка выберите команду Продолжить
(F5), которая продолжит выполнение программы. Убедитесь, что
программа выполнилась до конца в этом режиме. Отмените точки
все точки останова.
8. Если в вашей программе исходные данные вычислений задавались присвоением значений переменных, то модифицируйте
вашу программу таким образом, чтобы исходные данные (входные
переменные) вводились с клавиатуры.
9. Подготовьте тесты для вашей программы и оформите их как
это показано в табл. 9. Границы диапазонов изменения входных
значений задайте с учетом типа данных уже используемых вами
переменных. Обратите внимание на то, что при выполнении каждой проверки может потребоваться несколько комбинаций входных
данных (несколько тестов).
10. Ещё раз запустите программу на выполнение с отладкой (F5)
и проведите тестирование. Сделайте заключение о результатах тестирования.
Контрольные вопросы
1. Поясните назначение справочной системы
2. Каков смысл ошибок этапа компиляции?
3. Каков смысл ошибок этапа редактирования связей?
4. Каков смысл ошибок этапа выполнения программы?
72
5. Для отыскания каких ошибок предназначен отладчик?
6. Что такое тест?
7. Что означают слова «Результат тестирования отрицательный»?
8. Какие типы ошибок могут встретиться при программировании?
9. Чем отладка отличается от тестирования?
10. Какие возможности для отладки программы предоставляет
интегрированная среда программирования?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, перечень использованных возможностей отладчика, рисунки алгоритмов разработанных программ, текст отлаживаемой программы и таблицу с перечнем тестов и отметками о результатах их выполнения
(ваш вариант табл. 9). Кроме этого, должны быть сформулированы
ваши выводы по результатам выполнения лабораторной работы.
Таблица 9
Разработка вариантов тестовых заданий
Назначение проверки
Проверки работоспособности в рабочем диапазоне
данных
Проверки работоспособности на границах рабочего
диапазона данных
Проверки реакции программы на ввод данных вне
рабочего диапазона
Проверки реакции на ошибку типа входных данных
Проверки исключительных
точек (например, деление на
ноль)
НоЗначения
Ожидаемый
Результат
мер
входных
результат рабо- тестиротеста переменных ты программы
вания
1.1
1.2
1.?
2.1
2.2
2.?
3.1
3.?
4.1
4.?
5.1
5.?
Пример 12. Разработанная программа и ее алгоритм (рис. 37)
#include <iostream>
#include <iomanip>
73
Начало программы
а
б
в
Начальное
присвоение
b=2;c=3;d=6;f=5;j=1
a1 = f + j;
aI = c/d
aI = aI - a1
a1 = f + j
a2 = c/d
aI = static_cast
<int > (c) / d
aD = b + aD
a2 = c* d
a = b + a2
aD = c / d;
aD = b + static_cast
<double> (c) / d
a = b + a2
a = a - a1
aD = static_cast
<double> (c) / d
aD = b - a1;
a = a - a1
a1 = f + j
aI = b + aI
Вывод
результата
а
б
в
Конец программы
Рис. 37. Пример алгоритма программы, используемой
для обучения отладчику
using namespace std;
void main()
{
int a, aI, b, c, d, f, j;
double aD;
int a1, a2;
b = 2; c = 3; d = 6; f = 5; j = 1;
//a=b+c*d – (f+j);
a1 = f + j;
a2 = c*d;
a = b + a2;
a = a – a1; // cout << "rezultat1 " << a << '\n';
// Вычисляется a=b+c/d – (f+j);
74
a1 = f + j;
a2 = c/d;
a = b + a2;
a = a – a1;// cout << "rezultat2 " << a << '\n';
// Вычисляется aD=b + static_cast <double> (c)/d-(f+j);
a1 = f + j;
aI = c / d;
aI = static_cast <int> (c) / d;
aD = c / d;
aD = static_cast <double> (c) / d;
aI = b + aI;
aI = aI – a1;
aD = b + aD;
aD = b + static_cast <double> (c) / d ;
aD = b – a1;
// cout << "rezultat3 " << a << " " << aI << " " << aD << '\n';
cout << endl;
return;
}
Рис. 38. Работа в режиме отладки
75
4. ОПЕРАТОРЫ ЯЗЫКА C++,
ИЗМЕНЯЮЩИЕ ПОСЛЕДОВАТЕЛЬНОСТЬ ВЫПОЛНЕНИЯ
ОПЕРАТОРОВ ПРОГРАММЫ
4.1. Блок операторов и операторы continue и break
Операторы программы могут группироваться в так называемые
блоки. В этом случае в рассмотрение вводится так называемый составной оператор. Символом начала блока является открывающаяся фигурная скобка, а символом конца блока – закрывающаяся
фигурная скобка. Важной особенностью блока является то, что
с точки зрения остальных операторов, находящихся вне блока, он
рассматривается как один оператор. Допускаются вложенные блоки операторов (рис. 39).
Блок операторов
Вложенный блок 1
Вложенный блок 2
оператор1; {оператор2; {оператор3; {оператор4;} } оператор5;}
Рис. 39. Блоки операторов
Операторы continue и break позволяют пропускать часть операторов, входящих в блок. Последовательность выполнения программы с учетом этих операторов показана на рис. 40 и 41.
{оператор1; оператор2; break; оператор3; } оператор4;
Рис. 40. Схема выполнения оператора break
{оператор1; оператор2; continue; оператор3; } оператор4;
Рис. 41. Схема выполнения оператора continue
4.2. Условный оператор if
Пользуясь оператором присваивания можно создавать линейные
программы, выполняющие вычисления и запись данных в ячейки
памяти (рис. 37). Как только выполнится последний такой оператор, закончится и программа в целом.
76
Оператор if предназначен для выбора одной из двух возможных
последовательностей выполнения других операторов. Необходимость ветвления возникает в том случае, когда, в зависимости от
конкретной ситуации, требуется выполнить одну или другую ветви
алгоритма. Сама ситуация задается в виде так называемого условия, в состав которого могут входить константы и идентификаторы,
а также различные операции с ними. Выполнение оператора начинается с вычисления условия, которое может принимать значения
истина или ложь. Синтаксис оператора имеет вид:
if (условие) оператор1 else оператор2
Если условие принимает значение True, выполняется оператор1,
иначе выполняются оператор2. Допускаются вложенные операторы if. Схема и алгоритм выполнения оператора представлены на
рис. 42. Отметим, что оператор3 не имеет отношения к оператору
if и является просто очередным оператором. Он может вообще отсутствовать, если оператор if является последним оператором программы.
True
False
if( условие ) { оператор1 ;} else { оператор2 ;} оператор3;
условие
True
False
оператор1
оператор2
оператор3
Рис. 42. Схема и алгоритм выполнения оператора if
77
Пример 13. Условный оператор с вложенными блоками операторов
if(a>=0)
{b=1; c=0;}
else
{b=0; c=1;}
Существует сокращенная форма оператора if. Если оператор2 пустой, то ключевое слово else можно не указывать. Тем не менее, мы
рекомендуем исходя из соображений надежности программирования всегда пользоваться полной формой оператора. Заменяя в тексте оператор2 пустым оператором.
Пример 14. Проверка допустимости выполнения операции деления
if (b==0) c=1;
else c=a/b;
Пример 15. Печать в зависимости от условия
if (a>b)
cout << "a bolshe b";
else
cout << "a menshe ili ravno b";
Пример 16. Выполнение операций при вычислении условий
if (a>2 &&a <10)
cout << "a bolshe 2, no menshe 10";
else
cout << "a menshe ili ravno 2 ili a bolshe ili ravno 10";
//*********************************
if (a==2 || a==10)
cout << "a ravno 2 ili a ravno 10";
else
cout << "a ne ravno 2 i a ne ravno 10";
Пример 17. Сокращенная форма оператора if
if (a>10)
{
cout << endl;
cout << «**************»;
cout << endl;
cout << "a=" << a;
cout << endl;
}
Лабораторная работа №5. Оператор if
Цель работы: Приобретение навыков использования условного
оператора if
78
Задание
Научитесь писать программы, реализующие одну из двух возможных последовательностей действий, и выполнять их тестирование и отладку.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер своего варианта задания.
3. Разработайте алгоритм и напишите программу с использованием оператора if в соответствии со своим вариантом задания. Исходные данные программы должны вводиться с клавиатуры в процессе
ее выполнения, а результаты вычислений выводиться на дисплей.
4. Подготовьте тесты для вашей программы и оформите их в соответствии с табл. 9.
5. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
Варианты заданий по программированию
с использованием оператора if
1. Ввести действительные числа x, y. Меньшее из этих чисел заменить их полусуммой, a большее – их удвоенным произведением.
2. Ввести действительное число a. Вычислить f(a), если
0 ïðè a ≤ 0,

f (a)= a2 − a ïðè 0 < a ≤ 1,
 2
3
a + a ïðè a > 1.
3. Ввести три действительных числа. Возвести в квадрат положительные числа, остальные числа оставить без изменения.
4. Ввести координаты точки x, y (x, y ≠ 0). Переменной k присвоить номер четверти плоскости, в которой находится точка с координатами x, y.
5. Ввести действительное число a. Вычислить f(a), если
a2 + 4a + 5 ïðè a ≤ 2,
f (a) = 
2
1 / (a + 4a + 5) ïðè a > 2.
6. Ввести целую переменную w. Если значение w не равно нулю,
то поменять знак у w, а если значение w равно нулю, тогда присвоить
w значение 1.
79
7. Ввести число x. Напечатать в порядке возрастания числа 1 +
x2, cos(x), 1 + x3.
8. Ввести два действительных числа. Заменить первое число нулем, если оно меньше или равно второму, и оставить числа без изменений в противном случае.
9. Ввести два числа x, y. Определить, принадлежит ли точка с координатами x, y окружности с радиусом r.
10. Ввести действительное число a. Вычислить f(a), если
0 ïðè a ≤ 0,

=
f (a) a ïðè 0 < a ≤ 1,
 4
a ïðè a > 1.
11. Ввести переменные целого типа a, b, c. Поменять их местами
так, чтобы выполнялось условие a ≥ b ≥ c.
12. Известно, что из четырех введенных целых чисел a1, a2, a3, a4
одно отлично от трех других равных между собой. Присвоить номер
этого числа переменной n.
13. Ввести три действительных числа. Выбрать большее из них
и возвести в квадрат, а из меньшего извлечь квадратный корень,
оставшееся число оставить без изменения.
14. Ввести два действительных числа a, b. Вывести a, если оно
больше b и оба, если это не так.
15. Ввести три действительных числа. Выбрать из них те, которые принадлежат интервалу (1,3).
16. Если сумма трех введенных различных действительных чисел x, y, z, меньше 1, то наименьшее из этих трех чисел заменить
полусуммой двух других; в противном случае заменить меньшее из
x и y полусуммой двух оставшихся значений.
17. Ввести целое число. Определить и вывести на экран, равно ли
это число 10. В противном случае вывести соответствующее сообщение.
18. Ввести целые числа a, b. Определить v = min(a × b, a + b) и вывести на экран значение v.
19. Ввести символ. Определить его и вывести на экран сообщение, введен ли символ ‹с›. В противном случае вывести соответствующее сообщение.
20. Ввести целое число. Если число равно 10, то присвоить ему
значение –1. В противном случае присвоить значение 2.
80
21. Ввести целое число. Если число равно 10, то присвоить ему
значение 5. В противном случае присвоить значение нуль.
22. Ввести a, b, c – разные действительные числа. Определить d
= max(a, b, c).
23. Ввести x, y – целые числа. Определить
max(x, y) ïðè x < 0,
f (a) = 
min(x, y) ïðè x ≥ 0.
24. Ввести два целых числа. Если первое число больше второго
по абсолютной величине, то необходимо уменьшить первое в 5 раз.
Иначе оставить числа без изменения.
25. Ввести действительные числа x и y. Если x и y отрицательны,
то каждое значение заменить его модулем; если отрицательно только одно из них, то оба значения увеличить на 0,5; если оба значения
не отрицательны, то оба значения увеличить в 10 раз.
Контрольные вопросы
1. Что является аргументом оператора if?
2. Какие операции могут быть использованы при составлении
условия?
3. Что является результатом вычисления условия?
4. Как задать последовательность операторов, которая должна
быть выполнена в случае, когда условие ложно?
5. Как можно изменить значение условия?
6. Могут ли в состав условия входить операции?
7. Могут ли в состав условия входить операторы?
8. Как надо проводить тестирование условного оператора?
9. Как можно выполнить отладку условного оператора?
10. Может ли условие менять свое значение в процессе выполнения программы?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунок алгоритма разработанной программы,
текст программы и таблицу с перечнем тестов и отметками о результатах их выполнения (ваш вариант табл. 9). Кроме этого, должны
быть сформулированы ваши выводы по результатам выполнения
лабораторной работы.
81
Пример 18. Разработанная программа и ее алгоритм (рис. 43)
#include <iostream>
#include <iomanip>
using namespace std;
void main ()
{
int a, b;
cout <<"vvedute a, b \n";
cin>>a>>b;
if (a>b)
cout << "a bolshe b"<<'\n';
else
cout << "a menshe ili ravno b"<<'\n';
if (a>2 && a<10)
cout << "a bolshe 2, no menshe 10"<<'\n';
else
cout << "a menshe ili ravno 2 ili a bolshe ili ravno 10"<<'\n';
if (a==2 || a==10)
cout << "a ravno 2 ili a ravno 10"<<'\n';
else
cout << "a ne ravno 2 i a ne ravno 10"<<'\n';
cout<<endl;
}
a
Начало
a > 2 && a < 10
a,b
a>b
Да
Да
а больше 2 ,
но меньше
10
Нет
a меньше
или равно
b
a больше
b
Нет
a > 2 || a < 10
а меньше
или равно 2
или а больше
или равно 10
Нет
Да
а равно 2
или а
равно 10
а не равно 2
или а не
равно 10
a
Конец
Рис. 43. Пример алгоритма программы,
созданной в процессе выполнения лабораторной работы №5
82
4.3. Оператор ветвления switch
Оператор выбора switch представляет собой более развитую конструкцию, чем оператор ветвления if, и позволяет выбрать один из
многих вариантов дальнейшего выполнения программы. Если аргументом оператора if является условие, принимающее только два
возможных значения истина или ложь и обеспечивающее один из
двух вариантов продолжения последовательности действий, то в качестве аргумента оператора switch выступает выражение, результатом вычисления которого оказывается целое число или строка текста (вычисленное выражение). При записи оператора switch в следующих после заголовка строках case в виде константных выражений
предусматриваются различные варианты. Программист может легко вводить новые строки с константных выражений или удалять существующие. Выполняясь, оператор последовательно проверяет все
имеющиеся в его теле строки case. Синтаксис оператора имеет вид:
switch (выражение)
{ case Const1: оператор1;
...;
case ConstN: операторN;
default: операторN+1;}
Если в процессе выполнения программы реальное значение вычисленного выражения совпало с константным, записанным в одной из строк case, то выполняется последовательность операторов,
соответствующая этой строке, после чего проверка совпадения вычисленного выражения с константным продолжается. Последней
выполняется последовательность операторов в строке, отмеченной
ключевым словом default.
Конструкция оператора switch в языках С и C++, описанная
выше, несколько отличается от общепринятой. В других языках
программирования после совпадения вычисляемого выражения
с константным тоже выполняется операторы соответствующей
строки конструкции, однако после этого выполнение оператора
ветвления завершается. Для того, чтобы привести оператор switch
языка С или C++ в соответствие с общепринятыми в других языках программирования, в его конструкцию надо добавить оператор
break. Тогда синтаксис оператора switch будет иметь вид:
switch (аргумент)
{ case Const1: оператор1; break;
...;
case ConstN: операторN; break;
default: операторN+1;}
83
switch(выражение)
{case Const1: оператор1; break;
case Const2: оператор2; break;
...
case ConstN: операторN; break;
default: операторN+1; break;
}
оператор;
Выражение
1
2
Оператор1
Оператор2
3
Оператор3
N
ОператорN
default
ОператорN+1
Оператор
Рис. 44. Схема и алгоритм выполнения оператора switch
Схема оператора и алгоритм выполнения оператора switch изображены на рис. 44. Как и в предыдущем случае, последний оператор на рис. 44, обозначенный просто как оператор, не имеет отношения к конструкции switch и представляет собой следующий
оператор программы.
Пример 19. Программа с использованием оператора switch
(рис. 45)
#include <iostream>
using namespace std;
void main ()
{int day;
cin >> day;
switch (day)
{
case 1: cout << "Priem documentov"; break;
case 2:
case 3:
84
}
cin.get();
cin.get ();
}
case 4: cout << "Rabota s documentani"; break;
case 5: cout << «Vidacha documentov»; break;
case 6:
case 7: cout << "Vihodnie dni "; break;
default: cout << "Oshibka nomera dnya "; break;
Лабораторная работа №6.
Оператор switch
Цель работы: Приобретение навыков использования оператора
ветвления switch.
Задание
Научитесь писать программы, реализующие несколько возможных последовательностей действий, и выполнять их тестирование
и отладку.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер своего варианта задания.
3. Разработайте алгоритм и напишите программу с использованием оператора switch в соответствии со своим вариантом задания.
Исходные данные программы должны вводиться с клавиатуры
в процессе ее выполнения, а результаты вычислений выводиться на
дисплей.
4. Подготовьте тесты для вашей программы и оформите их в соответствии с табл. 9.
5. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
Варианты заданий по программированию
с использованием оператора switch
1. Ввести целое число x. Если введенное число x равно 2, то присвоить x значение –2, если x равно 3, то присвоить x значение –3,
если x равно 4, то присвоить x значение –4, в других случаях присвоить x значение 0.
85
2. Ввести целое число x. Если введенное число x равно 7, то присвоить x значение 17, если x равно 1, то присвоить x значение 11,
если x равно 5, то присвоить x значение 15.
3. Ввести символ. Если введенный символ равен ‹$›, то вывести
доллар, если введенный символ равен 'F', то вывести фунт, если введенный символ равен 'Р', то вывести рубль.
4. Ввести целое число x. Если введенное число x равно 9, то присвоить x значение 99, если x равно 8, то присвоить x значение 88,
если x равно 7, то присвоить x значение 77, если x равно 6, то присвоить x значение 66.
5. Написать программу, которая в зависимости от введенного номера дня недели выводит его название.
6. Написать программу, которая в зависимости от введенного номера месяца выводит его название.
7. Написать программу, которая в зависимости от введенного
номера квартала выводит название месяцев, которые входят в этот
квартал.
8. Написать программу, которая в зависимости от введенного номера месяца выводит количество дней в нем.
9. Написать программу, которая в зависимости от введенной
буквы выводит названия животных, с нее начинающихся.
10. Написать программу, которая в зависимости от введенной
буквы выводит названия улиц, с нее начинающихся.
11. Написать программу, которая в зависимости от введенной
буквы выводит названия городов, с нее начинающихся.
12. Написать программу, которая в зависимости от введенной
буквы выводит названия гостиниц, с нее начинающихся.
13. Написать программу, которая в зависимости от введенной
буквы выводит названия стран, с нее начинающихся.
14. Написать программу, которая в зависимости от введенной
буквы выводит названия континентов, с нее начинающихся.
15. Написать программу, которая в зависимости от введенной
буквы выводит названия времен года, с нее начинающихся.
16. Написать программу, которая в зависимости от введенной
буквы выводит названия шахматных фигур, с нее начинающихся.
17. Написать программу, которая в зависимости от введенной
буквы выводит названия нот, с нее начинающихся.
18. Написать программу, которая в зависимости от введенной
буквы выводит названия падежей, с нее начинающихся.
86
19. Написать программу, которая в зависимости от введенной
буквы выводит названия сторон света, с нее начинающихся (север,
юг, запад, восток).
20. Ввести целую переменную k, принимающую значения 1, 2,
3, 4. Каждое значение переменной k соответствует номеру четверти
плоскости, в которой находится точка с координатами x, y. Вывести
значения x, y в этих четвертях.
21. По первой введенной букве падежа существительного вывести слово «информатика» в соответствующем падеже
22. Ввести целое число k, изменяющееся в диапазоне от 1 до 5,
и вывести фразу: «Сестре k лет», учитывая при этом, что при некоторых значениях k слово лет надо заменить на слова год или года.
23. Ввести целое число k, изменяющееся в диапазоне от 1 до 5, и
вывести фразу: «Мы нашли в лесу k грибов», согласовав окончание
слова гриб с числом k.
24. Написать программу, которая в зависимости от первой введенной буквы времени года выведет фразы: зимой – холодно; весной – тепло; летом – жарко; осенью – прохладно.
25. Написать программу, которая в зависимости от первой введенной буквы названий стран: Австрия, Болгария, Греция, Италия
выведет названия их столиц.
Контрольные вопросы
1. Что является аргументом оператора switch?
2. Как можно составить аргумент оператора switch?
3. Что является результатом вычисления аргумента оператора
switch?
4. Как задать последовательность операторов, которая должна
быть выполнена в случае, когда выражение не совпало ни с одной
из констант?
5. Как можно изменить значение аргумента оператора switch?
6. Могут ли в состав аргумента оператора switch входить операции?
7. Могут ли в состав аргумента оператора switch входить операторы?
8. Как надо проводить тестирование оператора ветвления if?
9. Как можно выполнить проверку выполнения оператора switch
при разных значениях аргумента?
10. Может ли аргумент менять свое значение в процессе выполнения программы?
87
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунок алгоритма разработанной программы
(например, рис. 45), текст программы и таблицу с перечнем тестов
и отметками о результатах их выполнения (ваш вариант таблицы
9). Кроме этого, должны быть сформулированы ваши выводы по результатам выполнения лабораторной работы.
Начало
Ввод номера
дня
day
1
2,3,4
Прием
документов
Работа с
документами
5
Выдача
документов
6,7
Выходные
дни
default
Ошибка
задания
номера дня
Нажмите
клавишу
Конец
Рис. 45. Пример алгоритма программы,
созданной в процессе выполнения лабораторной работы №6
88
4.4. Операторы циклов
Оператор while
При написании программ часто возникает необходимость многократного выполнения определенных действий программы. По своему назначению операторы цикла предназначены как раз для решения именно этой задачи организации автоматического повторения
выполнения неких одиночных операторов или даже целой группы
операторов. Обычно повторяемые операторы называют телом цикла. Как правило, можно сформулировать условие, до каких пор
собственно должно повторяться тело цикла. Это условие является
параметром (аргументом) оператора цикла и называется условием
продолжения цикла.
В языке C++ имеется несколько операторов цикла, предназначенных для автоматизации создания повторяющихся действий
в программе. На рис. 46 показаны схема и алгоритм выполнения
одного из операторов цикла – оператора while. Тело цикла представлено оператором1. Этот оператор и будет выполняться до тех пор,
пока условие, записанное после ключевого слова while, находится
в состоянии истина. На схеме рис. 46 оператор1 заключен в операторные скобки. В принципе, они не являются обязательными в том
случае, когда оператор1 представляет собой только один оператор
языка C++. А вот если требуется повторять выполнение нескольких
True
False
while(условие) {оператор1;} оператор;
True
Оператор1
Условие
False
Оператор
Рис. 46. Схема и алгоритм выполнения оператора while
89
операторов, то использование блока операторов (фигурных скобок)
является принципиально необходимым. Из соображений повышения надежности программирования рекомендуется всегда для указания оператора или операторов тела цикла использовать символы
блока операторов. Как и в предыдущих случаях, последний оператор на рис. 46, обозначенный просто как оператор, не имеет отношения к конструкции while и представляет собой следующий оператор
программы.
Синтаксис оператора while следующий:
while (условие)
{операторы;}
Пример 20.
while
Программа цикла с использованием оператора
int i;
double AA [10];
double ww;
i=0;
while (i<10)
{
cout << "i=" << i <<endl;
i=i+1;
}
В процессе выполнения программа выводит на консоль последовательность чисел, начиная с числа 0. В программе используется
переменная i, которая последовательно увеличивает свое значение
на единицу. Условие продолжения цикла использует текущее значение этой переменной. Когда i становится равным некому заранее заданному числу (в данном случае 10), выполнение цикла прекращается. В таком контексте переменную i очень часто называют
переменной цикла.
Если в состав тела цикла входит оператор continue (рис. 47), то
при его выполнении управление передается в конец блока операторов, а затем снова проверяется условие. В то же время оператор break
(рис. 48) прекращает выполнение цикла без проверки условия.
True
False
while(условие) {оператор1;continue;оператор2;} оператор;
Рис. 47. Схема выполнения оператора while
совместно с оператором continue
90
False
True
while(условие) {оператор1;break;оператор2;} оператор;
Рис. 48. Схема выполнения оператора while
совместно с оператором break
Оператор do while
Оператор do while (рис. 49) предполагают выполнение всех операторов, размещенных в теле цикла после заголовка do. Как и в предыдущем случае, это может быть один оператор или блок операторов. Условие продолжения записывается после ключевого слова
while. Оператор do while продолжает выполнение тела цикла до тех
пор, пока условие продолжения цикла имеет значение True.
Оператор do while – это оператор с постусловием. Термин «постусловие» означает, что проверка условия проводится после выполнения операторов тела цикла. В то же время рассмотренный ранее
оператор while является оператором с предусловием. В отличие от
«постусловия» «предусловие» означает, что проверка условия проводится до выполнения операторов тела цикла. Отсюда вытекает основное отличие этих операторов – если условие продолжения цикла
False
do {оператор 1;} while (условие ); оператор ;
True
Оператор1
True
Условие
False
Оператор
Рис. 49. Схема и алгоритм выполнения оператора do
while
91
с самого начала не выполняется, то операторы тела цикла с предусловием при выполнении программы будут пропущены, в то время как цикл с постусловием заставит выполниться операторы тела
цикла один раз. Синтаксис оператора while следующий:
do
{операторы;} while (условие);
Пример 21. Программа цикла с использованием оператора do
while
int i;
i=0;
do
{
cout << "i=" << i <<endl;
i=i+1;
}while (i<10);
Оператор for
В примерах программ с использованием операторов while и do
while в состав операторов тела цикла, обозначенного на рис. 47 и 48
как Оператор1, пришлось включить оператор присваивания i=i+1.
Его назначением является подсчет количества выполненных циклов
(модификация счетчика цикла). Кроме этого, программа содержала
оператор, задающий начальное значение переменной i=0. Условие
продолжения цикла было задано явно, поэтому цикл должен выполняться строго определенное количество раз. Для упрощения программирования подобных задач в состав операторов языков C и C++
включен специальный оператор for (рис. 50). Он позволяет прямо
в заголовке задать начальное значение аргументу (счетчику) цикла
(выражение 1), указать условие продолжения цикла (выражение 2), и
автоматически модифицировать переменную (счетчик) цикла после
завершения выполнения операторов тела цикла (выражение 3).
Пример 22. Программа цикла с использованием оператора for
int i;
for (i=0; i<10; i++)
{
cout << "i=" << i <<endl;
}
Заголовок и тело цикла не обязательно должны использовать
целые переменные. Оператор for можно запрограммировать и переменными с плавающей точкой, как это показано в следующем примере. Надо только принимать во внимание одну важную особенность переменных с плавающей точкой. Она заключается в том, что
92
False
True
for(выражение1;выражение2;выражение3) {оператор1;} оператор;
выражение1
True
Оператор1
выражение2
False
Оператор
выражение3
Рис. 50. Схема и алгоритм выполнения оператора for
с такими переменными практически невозможно выполнить операцию точного равенства, поскольку отличие значений таких переменных проявляется даже при пренебрежимо малых погрешностях
их представления1. Как следствие, при программировании циклов
для формирования условия их продолжения надо обязательно использовать операцию сравнения, а не равенства.
Пример 23. Программа цикла с оператором for с использованием переменной с плавающей точкой
double ww;
for (ww=3.14; ww<4; ww=ww+0.1)
{
cout << "ww=" << ww << endl;
}
Использование массивов при программировании циклов
Основные достоинства операторов цикла ярче всего проявляются
в сочетании с массивами. Поскольку к элементу массива можно обратиться по имени массива и номеру элемента, а сам номер элемента
может быть задан не только константой, а и обычной переменной,
появляется возможность в общем виде программировать, например, математические выражения.
1 Например, число 0 с плавающей точкой может быть представлено как 0.0 и как
0.1E-308. Операция проверки равенства таких чисел == даст отрицательный результат
93
Допустим нам необходимо рассчитать и сохранить последовательность из N чисел, причем известно первое число последовательности (например, 0), правило вычисления следующего числа последовательности (например, следующее на единицу больше предыдущего). Тогда в программе можно объявить массив из N чисел, задать
переменной – счетчику цикла начальное значение. Далее используя
то обстоятельство, что на каждом шаге выполнения цикла значение
счетчика цикла увеличивается на единицу, можно использовать
счетчик цикла для указания номера элемента массива. Также используя счетчик цикла, можно записать выражение для следующего числа последовательности в общем виде. Наконец, условие продолжения цикла может быть сформировано в таком виде: действия
должны повторяться до тех пор, пока содержимое счетчика цикла
будет меньше N. Ниже представлены примеры программ, реализующих рассмотренную последовательность действий с использованием различных операторов цикла в предположении, что N=10.
Пример 24. Программа цикла с массивом с использованием
оператора while
int i; double AA[10];
i=0;
while (i<10)
{
AA[i] = i+1;
i=i+1;
}
Пример 25. Вариант программы с использованием оператора do
while
int i; double AA[10];
i=0;
do
{
AA[i] = i+1;
i=i+1;
}while (i<10);
Пример 26. Программа цикла с массивом с использованием
оператора for
int i; double AA[10];
for (i=0; i<10; i++)
{
AA[i] = i+1;
}
94
Лабораторная работа №7.
Операторы цикла
Цель работы: Приобретение навыков использования операторов
цикла while, do while и for.
Задание
Научитесь писать программы, реализующие возможность повторения некоторых операторов в зависимости от значения условия, и
выполнять их тестирование и отладку.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер группы заданий и номер
своего варианта задания.
3. Разработайте алгоритм и напишите программу, реализующую повторяющиеся вычисления, с использованием оператора
while в соответствии со своим вариантом задания. Исходные данные программы должны вводиться с клавиатуры в процессе ее выполнения, а результаты вычислений выводиться на дисплей.
4. Подготовьте тесты для вашей программы и оформите их в соответствии с табл. 9.
5. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
6. Модифицируйте алгоритм и саму разработанную вами программу так, чтобы для организации повторяющихся вычислений
использовался оператор do while. Проведите ее тестирование, используя подготовленные вами тесты.
7. Модифицируйте алгоритм и саму разработанную вами программу так, чтобы для организации повторяющихся вычислений
использовался оператор for. Проведите ее тестирование, используя
подготовленные вами тесты.
Варианты заданий по программированию
с использованием операторов циклов (1 группа)
1. Ввести целочисленную квадратную матрицу размером n × n.
Найти номера столбцов, все элементы которых равны нулю.
2. Ввести целочисленную квадратную матрицу размером n × n.
Найти номера столбцов, все элементы которых одинаковы.
95
3. Ввести целочисленную квадратную матрицу размером n × n.
Заменить все отрицательные элементы этой матрицы нулями, а все
положительные единицами.
4. Ввести натуральное число n. Выяснить, сколько положительных элементов содержит матрица A (n × n), если A (i, j) = sin (i + j/2).
5. Ввести натуральное число n. Выяснить, есть ли на главной
диагонали матрицы A (n × n) единичные элементы, если
A (i, j) = i × cos (j/p).
6. Ввести действительную квадратную матрицу размером n × n.
Определить число строк, начинающихся с отрицательного элемента, и сумму элементов в каждой такой строке.
7. Ввести целочисленную квадратную матрицу размером n × n.
Найти индексы всех элементов, имеющих наибольшее значение.
8. Получить таблицу температур по Цельсию от 0 до 100 градусов и их эквивалентов по шкале Фаренгейта, используя для перевода формулу TF=9/5TC+32.
9. Ввести целочисленную квадратную матрицу порядка 5.
В строках с отрицательным элементом на главной диагонали найти
сумму всех элементов строки.
10. Ввести действительную квадратную матрицу размером n × n.
Найти среднее арифметическое максимального и минимального ее
элементов. Предполагается, что эти элементы единственные.
11. Ввести целочисленную квадратную матрицу размером n × n.
Выяснить, верно ли, что максимальное из значений элементов главной диагонали больше, чем минимальное из значений побочной диагонали. Предполагается, что эти элементы единственные.
12. Ввести целочисленную матрицу размером n × m. Поменять
местами строку с максимальным значением со строкой, содержащей элемент с минимальным значением. Предполагается, что эти
элементы единственные.
13. Ввести целые числа x1, x2, x3, x4, x5. Рассчитать значения элементов квадратной матрицы порядка 5
 1

 x1
 x2
 1
 x3
 1
 x4
 1
96
1
x2
1
x3
1
x4
x22
x32
x42
x23
x33
x43
x24
x34
x44
1 

x5 
x52  .

x43 

x54 
14. Ввести целочисленную матрицу A (4 × 4). Получить матрицу
B (4 × 4) путем поэлементного вычитания последнего столбца матрицы A из всех столбцов, кроме последнего.
15. Ввести целочисленную матрицу A (2 × 3). Найти сумму ее элементов по столбцам. Получить матрицу B (2 × 3), элементы первой
строки которой равны элементам первой строки матрицы A, а элементы второй строки равны соответственно суммам элементов по
столбцам матрицы A.
16. Ввести целочисленную матрицу A (3 × 5). Найти среднее
арифметическое каждого из столбцов, имеющих четные номера.
17. Ввести целочисленную матрицу A (3 × 4). Получить вектор B(3),
элементы которого равны соответственно суммам элементов строк.
18. Ввести целочисленную квадратную матрицу размером n × n,
элементы которой не равны нулю. Получить новую матрицу такого
же размера путем деления всех элементов исходной матрицы на ее
максимальный по абсолютной величине элемент. Предполагается,
что этот элемент единственный.
19. Построить действительную матрицу A (i, j), где i = 1,…,3,
j = 1,…,5. Первая строка матрицы задается формулой A (i, j) = 2j + 3,
вторая строка формулой A (i, j) = j – 3/(2 + 1/j), третья строка – сумма двух предыдущих.
20. Ввести натуральное число m. Ввести целые числа a1, a2,…,am
и целочисленную квадратную матрицу размером m × m. Строку с номером i матрицы назовем отмеченной, если ai > 0, и неотмеченной
в противном случае. Надо все элементы, расположенные в отмеченных строках матрицы, преобразовать по правилу: отрицательные
элементы заменить на –1, положительные на 1, а нулевые оставить
без изменения.
Варианты заданий по программированию
с использованием операторов циклов (группа 2)
1. Имеется последовательность X(n), где n = 1, 2, 3,…. Известно,
что X(0) = 1, X(n) = n × X(n – 1) + 1/n. Ввести целое k > 0 и вычислить
X(k).
2. Ввести целое n > 0. Вычислить y = (2 × n–1)! = 1 × 2 × 3 ×…×
(2 × n – 1).
3. Ввести целое n > 1. Вычислить y = 1! + 2! + 3! +…+ n!.
4. Числа Фибоначчи определяются формулами: F(0) = F(1) = 1,
F(n) = F(n – 1) + F(n – 2) при n = 2, 3,…. Определить k-е число Фибоначчи.
97
5. Числа Фибоначчи определяются формулами: F(0) = F(1) = 1,
F(n) = F(n – 1) + F(n – 2) при n = 2, 3,…. Найти первое число Фибоначчи, большее m (m > 1).
6. Ввести целое число x. Вычислить
y = sin( x + 0,1) + sin( x + 0,2) + sin( x + 0,3) + sin( x + 0,4) +…
+sin( x + 0,9)
и распечатать полученное значение.
7. Вести целые числа a, b. Вывести таблицу значений sin(x) и
cos(x) на отрезке [a, b] с шагом 0.1 в следующем виде: x, sin(x), cos(x).
8. Ввести массив из 10 целых чисел. Определить сколько из них
больше своих «соседей», т. е. предыдущего и последующего числа.
9. Ввести массив из 10 целых чисел. Вывести их в обратном порядке.
10. Ввести массив из 10 целых чисел. Найти их среднее арифметическое и вывести.
11. Ввести массив из 10 целых чисел. Определить сколько среди
них отличных от последнего.
12. Ввести массив из 10 целых чисел. Вывести сначала все отрицательные элементы массива, затем все остальные.
13. Ввести массив из 15 целых чисел a1, a2,…,a15. Получить число
отрицательных элементов последовательности a1, a2,…,a10 и число
нулевых членов всей последовательности a1, a2,…,a15.
14. Ввести массив целых чисел и целое число n > 0. Если в массиве есть элемент, значение которого равно n, то получить сумму всех
элементов массива, следующих за ним.
15. Ввести натуральные числа a0, a1, a2,…,a12. Вычислить
f(a0) + f(a1) +…+ f(a12),
где
3x, åñëè x êðàòíî 3,

f (x=
) x − 3x, åñëè x ïðè äåëåíèè íà 3 äàåò îñòàòîê 1,
x / 3.

16. Ввести действительные числа a1, a2,…,a10, b1, b2,…,b10. В последовательностях a1, a2,…,a10 и b1, b2,…,b10 все члены, следующие за
членом с наибольшим значением, заменить на 0,5.
17. Ввести действительные числа a1, a2,…,a10, b1, b2,…,b10. Получить последовательность c1, c2,…,c10, члены которой равны c1 = a1 + b1,
c2 = a2 + b2 и так далее.
98
18. Ввести действительные числа a1, a2,…,a10, b1, b2,…,b10. Получить последовательность c1, c2,…,c10, члены которой равны c1 = a1 –
b1, c2 = a2 – b2 и так далее.
19. Ввести массив целых чисел и целое число n. Если в массиве
есть элемент, значение которого равно n, то вывести на экран номер
этого элемента в массиве.
20. Пусть x1 = 0,3, x2 = –0,3, xi = i + sin(xi-2), где i = 3, 4,… . Определить количество отрицательных и положительных членов в последовательности x1, x2, …, x100.
21. Пусть x1 = y1 = 1; xi = xi-1 + yi-1/i; yi = yi-1 + xi-1/i, где i = 2, 3,… .
Рассчитать значения x8 и y18.
22. Пусть ai = (i – 1)/(i + 1) + sin((i – 1)/(i + 1)), где i = 1, 2, 3, 4,…,
20. Среди чисел a1, a2,…,a10 найти все положительные, среди положительных выбрать наименьшее число.
23. Ввести массив из 10 целых чисел. Определить рассортированы ли они по возрастанию. Вывести на экран соответствующее сообщение.
24. Ввести натуральное число n и последовательность действительных чисел y1, y2,…,yn Рассчитать сумму z1 + z2 +…+ zn, где
yi ïðè 0 < yi < 10,
zi = 
1 â ïðîòèâíîì ñëó÷àå.
25. Ввести действительные числа a1, a2,…,a10 и b1, b2,…,b10 Получить последовательность c1, c2,…,c10, члены которой равны c1 = a1b1,
c2 = a2b2 и так далее.
Контрольные вопросы
1. Что такое массив и как его можно использовать при программировании операций?
2. В чем заключается необходимость применения операторов
цикла в программировании?
3. Чем цикл с предусловием отличается от цикла с постусловием?
4. Что понимается под бесконечным циклом?
5. Как можно заменить оператор цикла последовательной программой?
6. Как указать последовательность операторов, которая должна
выполняться в цикле?
7. Чем цикл for отличается от цикла do?
8. Чем оператор отличается от операции?
99
9. Что понимается под термином «счетчик цикла»?
10. Зачем нужно предусловие цикла?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунок алгоритма разработанной программы
(варианты исполнения алгоритма показаны на рис. 51), текст программы и таблицу с перечнем тестов и отметками о результатах их
выполнения (ваш вариант табл. 9). Кроме этого, должны быть сформулированы ваши выводы по результатам выполнения лабораторной работы.
4.5. Указатели
Указателем называется ячейка памяти, предназначенная для
хранения адреса другой ячейки памяти. Основное назначение указателя – позволить программисту обращаться к любой ячейке памяти ЭВМ находящейся, в том числе, и вне пределов его собственной программы. Если обычные переменные программы доступны
только внутри блока программы, в котором они объявлены (правило области действия переменной), то указатели, будучи определены
в той же пользовательской программе, могут содержать адрес произвольной ячейки памяти. Указатели потребовались Кернигану и
Ритчи для программирования связей между компонентами операционной системы, и они ввели их в свой язык программирования.
Первоначально указатели применялись для решения двух задач
программирования. Во-первых, с помощью указателя можно было
легко получить доступ к фиксированным адресам памяти, например, к регистрам внешних устройств. Во-вторых, с помощью указателей можно легко отслеживать границу между занятой и свободной
в настоящий момент оперативной памятью и, при необходимости, изменять ее, занимая, например, дополнительную память в интересах
конкретной задачи. Указанные действия являются принципиально
необходимыми для написания, например, программы операционной
системы, для чего собственно Керниган и Ритчи и создавали свой
язык. Позднее программисты нашли еще ряд эффективных приемов
программирования с использованием указателей, что прочно ввело
их в ряд разрабатываемых уже после C языков программирования.
В языке C++ размер указателя соответствует 4 байтам памяти.
Это означает, что указатели в языке C++ работают с тридцатидвух100
101
Нет
Да
б
i<10
i=i+1;
cout<<i
i=0
Нет
Конец
i=i+1;
cout<<i
Да
i<10
i=0
Тело цикла
do while
for(i=0;
i<10; i++)
б
Нет
а
i
i++
б
i<10
i++
cout<<i
i
i<10
cout <<i
i=0
do while
(i<10)
а
i=0
Тело цикла While(i<10)
for
Начало
Конец
i
cout << i
i<10
i++
i=0
for (i=0;
i<10; i++
б
Рис. 51. Вариант рисунка алгоритма программы, созданной в процессе выполнения лабораторной работы №7
а
i=i+1;
cout<<i
Да
i<10
i=0
do
While(i<10)
While (i<10)
Тело цикла
while
а
Начало
разрядной шиной адреса памяти. Если разрядность шины адреса
памяти вашей машины меньше, то в этом нет ничего страшного –
просто ряд старших разрядов указателя не будет использоваться.
Для того, чтобы создать указатель, его надо просто объявить
соответствующим оператором языка. Так, например, оператор int
*aaa, *bbb; создает в памяти машины две четырехбайтные ячейки aaa и bbb, являющиеся указателем на переменные типа integer.
Символ '*' звездочка в строке объявления показывает компилятору, что соответствующая переменная является не обычной ячейкой,
а указателем.
Объявление указателя, как и обычной переменной, только резервирует память для его хранения. Для использования указателя
в работе ему надо присвоить определенное значение, которое и будет
представлять собой адрес ячейки памяти, на которую и будет нацелен указатель. Если мы хотим, чтобы указатель работал с ячейкой
памяти нашей программы, то лучше всего воспользоваться операцией взятия адреса, обозначаемой символом '&'.
Так, например, если мы напишем фрагмент программы такого
вида:
int *aaa, c, d[4];
d[0] = 0; d[1] = 1; d[2] = 2; d[3] = 3;
aaa= &d[0];
то мы создадим в памяти машины указатель по имени aaa, предназначенный для работы с переменными типа int. Кроме этого, мы зарезервируем место в памяти еще для пяти ячеек типа int – обычной
ячейки c и четырех ячеек d, образующих массив с ячейками d[0],
d[1], d[2], d[3]. Далее в ячейки массива d после выполнения операторов присваивания будут занесены константы, соответствующие
номеру элемента массива d. Эти действия представляют собой задание начальных значений массива d или, как часто еще говорят, его
инициализацию. А вот последняя строчка рассматриваемого фрагмента программы осуществляет инициализацию указателя aaa.
В результате выполнения этого оператора физический адрес памяти
нулевого элемента массива d будет записан в указатель aaa.
Выполняя операцию взятия адреса '&', мы получаем значения
указателя в момент выполнения программы. Такой прием иногда
называют динамическим связыванием. Очевидно, что от запуска
к запуску программы на выполнение это значение может изменяться, поскольку в перерыве между первым и вторым запуском рассматриваемой программы на нашей машине могло произойти большое количество других событий. Например, могла бы запуститься
102
какая-то другая программа, которая заняла бы для себя оперативную память и, как следствие, отодвинула бы границу свободной памяти. Концепция указателей позволяет программисту не заботиться о конкретных адресах памяти, поскольку используемая технология позволяет осуществлять программирование в общем виде без
привязки к конкретному положению ячейки в памяти.
Информацию, хранящуюся в указателе, можно использовать
для обращения к ячейкам, на которые указатель нацелен. Для реализации этой возможности используется операция ссылки * по
указателю1. Так, например, следующий оператор рассматриваемой
программы c=*aaa; запишет в ячейку c число 0, поскольку именно
оно содержится в элементе массива d[0]. Проверить это обстоятельство мы можем, например, воспользовавшись выводом информации
на консоль с помощью операторов
cout << "Znachenie ukazatelya = " << aaa << endl;
cout << "Ssilka po ukazatlyu = " << *aaa << endl;
cout << "Soderzimoe yacheiki c = " << c << endl;
В языке C++ значение указателю можно задать не только за счет
выполнения операции взятия адреса, но и непосредственно записывая в указатель константу. В этом случае указатель оказывается
нацеленным на определенную ячейку оперативной памяти машины. Подобный прием опасен возможными ошибками программирования, при возникновении которых программа может получить
доступ к ячейкам памяти, хранящим какую-то важную информацию, не имеющую непосредственного отношения к самой программе. Поэтому в синтаксис языка C++ введена специальная защита,
позволяющая записать константу в указатель только с использованием приведения типа. Например, попытка занесения в указатель
ааа константы 0xB8000000 оператором присваивания aaa=0xB8000000;
приведет к возникновению ошибки синтаксиса. В то же время строка программы aaa=(int *) 0xB8000000; синтаксически верна.
Операции new и delete
Одной из новых возможностей языка C++ по сравнению с языком С является используемая в том числе и совместно с указателями операция new. В результате ее выполнения у операционной системы запрашивается и резервируется память необходимого размера.
Например, строка программы int *aaa = new int; резервирует па-
1
Иногда операцию * называют разыменовывание
103
мять для хранения переменной типа int. В данном случае программа на этапе выполнения (динамически) создает новую переменную,
однако за этой переменной не закреплен идентификатор и обращение к ней возможно только по указателю. Если использовать старую статическую технику создания переменной, то аналогичных
результатов можно добиться, записав следующую последовательность кодов:
int *aaa, c;
aaa= &c;
Указание типа переменной после ключевого слова new показывает и требуемое количество выделяемой под хранение переменной
памяти (в данном случае 2 байта).
Динамическое распределение памяти – это современный прием
программирования, связанный с созданием так называемых объектов. В качестве объекта может выступать не только переменная
определенного типа, но и, например, структура данных, созданная
по определенному правилу.
Может оказаться, что у операционной системы не хватает свободной памяти для создания объекта. В этом случае операция new
возвращает значение 0 и записывает его в указатель. Если программа далее попытается выполнить операцию ссылки по указателю,
то возникнет ошибка этапа выполнения программы null pointer
assignment, которая приведет к снятию программы с выполнения.
Поэтому в некоторых случаях целесообразно, например, с помощью
оператора if проверять факт правильности создания переменной.
Операции запроса памяти new соответствует обратная операция
освобождения памяти delete. Если выполнить оператор delete aaa;,
то зарезервированная под объект (в данном случае под переменную
типа int) память освободится. После освобождения памяти мы можем повторно ее занять с помощью указателя aaa. При написании
программы необходимо обеспечить баланс работы операций new и
delete. Если этого баланса нет, например, операция new встретилась
большее число раз, чем операция delete, то возникает так называемая утечка памяти, Она характеризуется тем, что память выделена,
но не может быть использована. Еще большую опасность представляет обратная ситуация, когда количество операций new меньше,
чем операций delete. В этом случае результат работы программы
становится неопределенным. И наконец, отметим, что освободить
память, отведенную под хранение явно (статически) объявленных
переменных нельзя.
104
4.6. Функции
Вы, наверное, уже смогли убедиться в том, что составление программ для ЭВМ требует большого объема трудозатрат. Вполне очевидно, что уже созданные и проверенные программы представляют
собой самостоятельную ценность. Программисты стремятся использовать свои разработки в новых программных проектах и, как
следствие, создают методы, позволяющие относительно несложно
включать ранее разработанные коды в новые программные изделия. Достаточно быстро стало понятно, что обычное механическое
копирование кодов в новую программу чревато серьезными ошибками. Для уменьшения вероятности появления ошибок при использовании ранее созданных программ в языках программирования
высокого уровня была внедрена концепция так называемых функций и процедур. Для ее практической реализации потребовались
существенные доработки в системе команд процессора, результатом
которых явилось появление специальных команд вызова функции
(процедуры) и возврата в точку вызова.
Ключевая идея создания функций и процедур заключалась
в обеспечении возможности многократного обращения к одной и той
же последовательности кодов из разных мест программы. По своей
сути термины функция и процедура в языках высокого уровня взаимозаменяемы. Отличие одного от другого сводится к непринципиальной разнице в способах их оформления в теле программы и, что
более важно, в способах оформления вызова. Некоторые языки программирования, например, С, вообще определяют только функции,
а процедура рассматривается просто как разновидность функции,
которая не возвращает значения в точку своего вызова.
Функцией в языке С называется самостоятельная программа,
предназначенная для решения определенной задачи. Поскольку
любая работоспособная программа попадает под это определение,
можно сделать вывод, что функцией может быть любая последовательность кодов. На самом деле это действительно так. Написанная
нами программа на языке высокого уровня, оформленная в соответствии с правилами языка, оттранслированная и запущенная
на выполнение представляет собой функцию, запускаемую, например, операционной системой. В языке C++ предусмотрено, что операционная система вызывает на выполнение функцию языка C++
по имени main. В данном случае правила оформления такой программы представляют собой не что иное, как правила оформления
функций операционной системы.
105
Самостоятельный интерес представляют собой правила оформления функций, написанных на языке высокого уровня и вызываемых из других программ, написанных также на том же или другом
языке высокого уровня или на ассемблере. Наиболее строго определены правила создания функций в том случае, когда для написания
вызывающей программы и собственно функции используется один
и тот же язык программирования.
Практический смысл использования функций в программировании определяется следующими обстоятельствами. Появляется
возможность разбиения большой программы на отдельные составляющие, разработка которых гораздо проще разработки всей программы. Сокращается объем кодов программы за счет удаления повторяющихся действий и замены их вызовами. Повышается надежность программного обеспечения, поскольку программа использует
уже многократно проверенные последовательности кодов. Все это,
в конечном итоге, ведет к росту производительности труда программиста.
Изложенные соображения играют важную роль при проектировании сложных программ. Так, например, взявшись за выполнение
задачи, программист разбивает ее на набор возможно менее связанных между собой функций или процедур, каждая из которых решает некую самостоятельную задачу. Подобный прием называется
декомпозицией и широко используется на практике. Очевидно, что
для любой более или менее сложной задачи можно найти чрезвычайно большое, если даже не бесконечное количество вариантов
декомпозиции. Поэтому выбор конкретного варианта разбиения задачи во многом определяется используемой методологией ее проектирования, а также опытом и пристрастиями разработчика.
При изучении способов создания функций следует принимать во
внимание следующие положения:
– каждая функция имеет имя; это имя является идентификатором и должно быть тем или иным способом объявлено;
– каждая функция имеет свои коды, которые должны быть
оформлены заданным языком программирования способом; эти
коды называются определением функции;
– для решения задачи функция может потребовать набор аргументов (исходные данные), которые передаются ей в момент вызова;
– функция может возвращать результаты своих вычислений
(возвращаемые данные) в вызывающую программу; возврат значений может, в частности, производиться через список аргументов;
106
– каждая функция должна быть вызвана по имени; если вызов
отсутствует, то функция выполняться не будет.
В языке C для создания и выполнения функции требуется обеспечить выполнение трех обязательных условий:
– функция должна быть определена;
– функция должна быть объявлена;
– функция должна быть вызвана.
Можно выделить два вида функций, которые используются
в программах. С одной стороны, это функции, которые созданы программистом для решения своей собственной задачи. В этом случае
программист самостоятельно создает определение и объявление1
необходимой ему функции и оформляет их в соответствии с правилами языка программирования. С другой стороны, в программах
могут использоваться так называемые библиотечные функции.
Обычно с их помощью выполняются некие часто встречающиеся
действия: некоторые математические вычисления, операции проверки типов, преобразования форматов, обработки строк, работы
со временем и датами и тому подобное. Определения и объявления
библиотечных функций находятся в специальных библиотеках.
Программисту придется побеспокоиться о том, чтобы эти библиотеки были подключены к его проекту. Обычно само подключение
функций происходит на этапе редактирования связей, однако необходимые указания для этого должны быть в тексте программы.
Заметим, что в большинстве случаев программист не имеет доступа к исходным кодам библиотечных функций и пользуется только
описанием их действий (назначением функции) и описанием списка
аргументов. Полный список имеющихся в языке функций можно
получить, воспользовавшись, например, системой помощи, которая
стандартно вызывается нажатием клавиши F1 при запущенной системе программирования. Общим для обоих рассматриваемых видов функций является то, что они должны быть вызваны.
Разработка определения функции
Под определением функции обычно понимают создаваемую
программистом последовательность операторов программы, которая после трансляции может быть вызвана из другой программы.
Структура определения рассмотрена ниже:
тип_возвращаемого_параметра
1
Некоторые авторы объявление функции называют прототипом функции
107
имя_функции
(тип_формального_параметра идентификатор, ..., ...)
{объявление_локальной_переменной1;
...;
оператор1;
оператор2;...;
}
В языке C функции могут возвращать, а могут и не возвращать
параметр (результат) в точку вызова. Если вы создаете определение
функции, которая возвращает в точку вызова значение одного из
возможных в языке типов данных, то вы указываете этот тип данных на месте тип_возвращаемого_параметра. Если вы пишете функцию, которая не возвращает значение в точку вызова (аналог процедуры в других языках программирования), то в качестве типа
данных указывается слово void.
Далее, по крайней мере через один пробел, записывается имя_
функции. Это имя представляет собой идентификатор языка C. Позднее оно будет использовано при вызове функции.
Затем открываются круглые скобки. В этих скобках размещают
так называемые формальные параметры. Последовательно указываются тип_формального_параметра, который также может быть одним
из возможных типов данных, и идентификатор переменной, которая
далее в тексте определения будет использована для указания последовательности действий с переданным в функцию параметром.
Параметры определения функции принято называть формальными по той причине, что в определении функции под них хранение не отводятся ячейки памяти. Имена формальных параметров
используются в определении функции для указания последовательности действий с передаваемыми функции данными.
Формальных параметров может вообще не быть. Тогда в скобках
надо указать слово void. В другом случае этих параметров может
быть некоторое конечное число1. В этом случае каждый из параметров записывается через запятую и должен иметь свой тип данных
и свой идентификатор.
Описанное выше представляет собой не что иное как заголовок
определения функции. Далее в соответствии с синтаксисом язы1 Существует вариант, когда количество параметров функции не ограничивается
на этапе определения функции и задается только этапе ее вызова. С нашей точки зрения этот вариант больше относится к области экзотики или трюкачества. Тем не менее, если вас он интересует, посмотрите его описание, например, в системе помощи.
108
ка должен следовать оператор функции. Трудно себе представить
функцию только с одним оператором, поэтому обычно сразу после
заголовка открываются фигурные скобки, обозначающие начало
блока операторов (см. 4.1).
Последовательность операторов внутри блока как раз и есть последовательность решения определенной задачи, для которой функция и создается. Эта задача может иметь собственные локальные
переменный, которые и объявляются внутри блока. Далее может
существовать просто последовательность операторов языка. Например, написанную ранее на языке C++ и отлаженную программу
можно обычным копированием поместить внутрь блока операторов.
Необходимо учесть при этом только два обстоятельства. Во-первых,
если программа определения должна использовать значения параметров, передаваемых из вызывающей программы, то в тексте
программы используются идентификаторы соответствующих формальных параметров. Во-вторых, результат вычисления функции,
если функция возвращает значение определенного типа, указывается оператором return.
Оператор return обычно последний оператор определения программы. Дале следует закрывающаяся фигурная скобка и определение программы заканчивается. В качестве примера приведем
определения двух программ. Одна их них не возвращает значения
в точку вызова (display), а другая возвращает (summa):
void display (double a)
{
using namespace std;
cout << a << endl;
}
double summa (int a, int b)
{
int temp;
temp=a+b;
return (double) temp;
}
Разработка объявления функции
Если определение функции разработано, то составить на его
основе объявление функции уже совсем не сложно. Объявление
включается в тело вызывающей функции и представляет собой копию заголовка определения, заканчивающуюся символом точка
109
с запятой (конец оператора). Синтаксис объявления предусматривает указание в объявлении функции только типов формальных
параметров, а их имена являются необязательными. Но последнее
не означает, что эти имена запрещены. Поэтому если уж определение функции создано, то проще всего скопировать заголовки
определений в начало программы и закончить их символом ‹;'. Такой скопированный заголовок функции носит название прототип
функции.
Пример 27. Объявление ранее определенных функций
void display (double a); /* Объявление*/
double summa (int a, int b); /* Объявление*/
Вызов функции
Созданная нами функция (определенная и объявленная) может
быть сколь угодно много раз вызвана из любой другой функции,
входящей в проект. Функция, которая не возвращает параметры,
вызывается по имени. Это означает, что в тексте программы записывается оператор, текст которого совпадает с объявленным
именем функции. Если функция возвращает параметры, то ее вызов производится с помощью оператора присваивания. Для этого
в тексте программы записывается оператор присваивания, в левой
части которого указывается объявленная в вызывающей программе переменная, тип которой совпадает с возвращаемым функцией
значением. А справа от символа присваивания записывается имя
функции.
Если вызываемая функция имеет формальные параметры, то
при ее вызове они получают значения переменных, объявленных
в вызывающей программе и указанных в круглых скобках после
имени функции. В этом случае обычно говорят, что в момент вызова
формальные параметры заменяются на фактические.
Под фактическими параметрами функции понимаются объявленные в вызывающей программе переменные того же типа, которым объявлены и формальные параметры. А вот имена этих переменных могут и отличаться. Именно это позволяет вызывать функцию многократно с различными данными и не заботиться о том, что
имена переменных могут случайно совпасть.
Пример 28. Фрагмент программы вызова функции
int rrr=2, zzz=16;
double ttt;
ttt = summa (rrr,zzz);
display (ttt);
110
Возврат значений из функции
через список формальных параметров
В языке C++ передача параметров аргументов производится по
значению. Это означает, что определение функции через формальные параметры получает доступ не к самим ячейкам вызывающей
программы, а к их копиям, хранящимся в специальной области
памяти, организуемой в момент вызова функции. Подобный прием повышает надежность программирования, поскольку защищает
область исходных данных от случайного воздействия.
Встречаются задачи, в которых в точку вызова необходимо возвращать не одно, а несколько значений. Поскольку функция может
вернуть в вызывающую программу только одно значение через оператор присваивания, похожие задачи приходится решать за счет
передачи данных через список формальных параметров по ссылке.
Основным инструментом программиста в этом случае становится
ссылка на фактический параметр, а подобный метод передачи данных в функцию – передачей по ссылке. Для ее реализации необходимо в качестве формального параметра в заголовке цикла задать
не переменную, а указатель на нее.
Если формальный параметр передается по ссылке, то это означает, что вызываемая функция через указатель получает в свое
распоряжение не копию данных, а адрес ячейки памяти, в которой
эти данные находятся. Как следствие, у вызываемой функции появляется возможность изменить содержимое ячейки памяти вызывающей программы, поскольку адрес ее становится известным.
В момент вызова функции формальному параметру ставится в соответствие адрес фактической ячейки памяти вызывающей программы. Если в определении функции записать слева от оператора присваивания ссылку по этому указателю, то в фактическую ячейку
вызываемой программы будет записано то, что указано справа от
оператора присваивания.
Пример 29. Определение функции
void analis (int a, int b, int *max, int *min)
{
if (a>b)
{*max = a; *min = b;}
else
{*max = b; *min = a;}
}
Функция analis имеет четыре аргумента. Два первых – это
обычные ячейки, передача данных через которые осуществляется
111
по значению. Два вторых – это указатели на переменную типа int.
Именно их и планируется использовать для возврата результата вычислений в вызывающую программу. Объявление функции analis
имеет обычный вид и выполнено без указания имен переменных,
что допускается правилами C++:
void analis (int, int, int *, int *);
Фрагмент вызывающей программы показан ниже:
int qq=22, ww=140, ee, rr;
/* Вызов*/
analis(qq, ww, &ee, &rr);
Переменные qq и ww объявлены и инициализированы. Переменные ee и rr просто объявлены в вызывающей функции. В момент
вызова две первых переменных передают свои значения функции.
А вот вместо двух вторых переменных передаются их адреса. С точки зрения механизма передачи параметров функции C++ адреса
этих переменных также передаются по значению и не могут быть
изменены в вызываемой функции. Однако эти значения могут быть
использованы указателями на переменные. При получении управления вызываемой функцией формальные параметры max и min получат смысл адресов ee и rr. Именно по этим адресам произойдет
запись операторами *max=a; *min=b;.
Область действия и время жизни переменной.
Пространства имен
Поскольку программисту разрешено выдумывать собственные идентификаторы, возникает проблема, связанная с опасностью использования одинаковых имен в разных программах. Она
может усугубиться в том случае, когда над одной программой работает коллектив программистов. Действительно, всегда может
существовать отличная от нуля вероятность того, что одно и то
же имя придет в голову разным людям. В этом случае возникает
вопрос: две переменных с одинаковыми идентификаторами имеют в памяти машины одинаковый адрес или нет? Другими словами, это одна и та же ячейка памяти или речь идет о разных
переменных? Еще один интересный вопрос: как долго в памяти
машины будет храниться информация, занесенная в конкретную
ячейку памяти?
Важность правильного ответа на эти вопрос весьма велика.
Очень часто содержимое ячейки памяти может многократно использоваться в программе и хранить некую информацию, доступ к которой может потребоваться из разных точек программы. С другой
112
стороны, вполне вероятна ситуация, когда одним и тем же именем
обозначаются разные переменные. Соответственно возникает необходимость регламентации правил подобного вида. Обычно в таких
случаях говорят об области действия и времени жизни переменной.
В языки программирования высокого уровня искусственно вносится локализация области действия идентификатора внутри программы. Предполагается, что любая программа содержит некий
набор составных частей, называемых обычно модулями. В языке
С++ под модулем обычно понимают функцию. В свою очередь в составе функции могут находиться свои так называемые вложенные
функции, имеющие самостоятельное объявление и определение.
Правило области видимости переменной регламентирует область
программы, в которой на данный идентификатор можно сослаться.
Существуют три области видимости идентификатора: область видимости – файл, область видимости – функция, область видимости –
блок.
Переменная, объявленная вне любой функции (на внешнем
уровне), имеет область видимости файл. Такая переменная «известна» всем функциям от точки её объявления до конца файла.
Переменные, объявления функций и прототипы функций, находящиеся вне функции – все имеют областью видимости файл. Как
правило, переменные, объявленные вне функции, называются глобальными переменными.
Переменные, объявленные в функции, доступны внутри этой
функции. Если функция содержит вложенный блок, то объявленная в этом блоке переменная доступна только внутри этого блока.
Обычно такие переменные называют локальными. Если имя переменной, объявленной в функции, совпадает с именем переменной,
объявленной во вложенном в функцию блоке, то под эти одноименные переменные резервируются разные ячейки памяти.
Правило времени жизни переменной фиксирует тот факт, что
значения локальных переменных не сохраняются в памяти машины и при каждом вызове функции эти значения надо инициализировать заново.
113
Лабораторная работа №8.
Создание и использование функций
Цель работы: приобретение навыков программирования функций языка C++.
Задание
Научитесь писать программы, реализующие возможность создания
функций различного вида и выполнять их тестирование и отладку.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер своего варианта задания.
3. Разработайте алгоритм и напишите программу, реализующую ваше индивидуальное задание.
4. Подготовьте тесты для вашей программы и оформите их в соответствии с таблицей 9. Проведите тестирование программы.
5. Оформите вашу программу в виде функции, возвращающей
значение в точку вызова. Разработайте новый алгоритм программы
с учетом того, что в ней реализуется вызов такой функции.
6. Создайте вызывающую программу, сделайте в ней необходимые объявления переменных и задайте им значения.
7. Разработайте коды объявления вашей функции и внесите их
в вызывающую программу.
8. Разработайте коды вызова вашей функции и внесите их в вызывающую программу.
9. Проведите трассировку вашей программы и функции с использованием команды отладчика Шаг с заходом (А11).
10. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
11. Напишите еще один вариант вашей функции с другим именем так, возврат результатов вычислений вашей функции передавался в вызывающую программу через список формальных параметров. Проведите ее тестирование.
Варианты заданий для программирования с помощью функций
В основной программе ввести два целых числа, передать их значения в функцию. В функции произвести с ними следующие действия:
1. Получить их сумму.
2. Получить их произведение.
114
3. Найти меньшее из них.
4. Найти большее из них.
5. Найти меньшее по абсолютной величине.
6. Найти большее по абсолютной величине.
7. Найти сумму квадратов этих чисел.
8. Получить их полусумму.
9. Получить их полуразность.
10. Наименьшее из них возвести в квадрат.
11. Наименьшее из них возвести в куб.
12. Наибольшее из них возвести в квадрат.
13. Наибольшее из них возвести в куб.
14. Найти корень квадратный суммы этих чисел.
15. Найти корень квадратный наибольшего из них.
16. Найти корень квадратный наименьшего из них.
17. Получить утроенное произведение суммы этих чисел.
18. Получить удвоенное произведение их разности.
19. Получить синус суммы этих чисел.
20. Получить косинус суммы этих чисел.
21. Получить синус разности этих чисел.
22. Получить косинус разности этих чисел.
23. Получить произведение суммы и разности этих чисел.
24. Получить частное от деления суммы на разность этих чисел.
25. Поменять знак у меньшего из этих чисел.
Контрольные вопросы
1. Что является аргументом функции?
2. Где должно быть размещено объявление функции?
3. Что задает определение функции?
4. Как выглядит структура функции?
5. Для чего используется оператор return?
6. В чем различие формальных и фактических параметров?
7. Как можно вызвать функцию?
8. Что понимается под локальными переменными функции?
9. Зачем нужны формальные параметры?
10. Что понимается под «вложенной» функцией?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунок алгоритма разработанной программы
115
Начало
Начало summa ()
Начало display ()
rrr=2, zzz=16
temp =a+b
cout a
Конец summa ()
Конец display ()
cout rrr zzz
summa ()
cout “diplay
(ttt)”
display ()
Конец
Рис. 52. Вариант алгоритма программы,
созданной в процессе выполнения лабораторной работы №8
с учетом разбиения ее на функции (например, рис. 52), текст программы и таблицу с перечнем тестов и отметками о результатах их
выполнения (ваш вариант табл. 9). Кроме этого, должны быть сформулированы ваши выводы по результатам выполнения лабораторной работы.
4.7. Массивы, структуры, классы и объекты
Массивы. Создание и использование динамических массивов
Обычное объявление предусматривает собой инструкцию по
резервированию в памяти машины необходимого количества ячеек, которые на этапе выполнения программы будут закреплены за
116
конкретной простой переменной. Пользуясь этим приемом, программист может не интересоваться конкретным размещением переменных в памяти (конечно, если это ему не интересно по каким-то
особым причинам). При написании программы для обращения
к физическим данными программисту оказывается достаточным
упомянуть только имя переменной в тексте. В результате работы
компилятора и других программ связь переменной с физическим
ячейками памяти обеспечивается автоматически.
Хотя и объявление простых переменных представляет собой
обычную практику, в некоторых случаях, например, при программировании циклов, их использование оказывается неудобным.
Практика программирования широко использует объявление переменных, обращение к которым ведется как по имени, так и по номеру элемента внутри имени. В этом случае можно говорить о создании
переменных табличного типа, когда обращение к данным ведется
по имени и номеру (индексу) внутренней таблицы. Такие переменные обычно называются массивами. По определению массив – последовательно упорядоченные в памяти данные одного типа.
В отличие от простого объявления переменной объявление массива в том или ином виде предусматривает еще и задание диапазона
изменения номера элемента массива. В языке С минимальное значение номера равно нулю. При объявлении массива задается еще количество его элементов, а максимальное значение индекса массива
оказывается на единицу меньшим. Более подробно о создании и использовании массивов рассказано выше (см. п. 2.6).
Массивы удобно использовать при программировании однотипных действий с ячейками памяти. В качестве примера рассмотрим
задачу расчета начисления заработной платы (табл. 10). Первая коТаблица 10
Пример задачи расчета начисления заработной платы
Фамилия, И.О.
Начислено
Налог
К выдаче
Иванов В.Н.
1234
148,08
1085,92
Трофимова Л.А.
1234
148,08
1085,92
Семенова Е.Г.
1000
120
880
Степанов А.Г.
900
108
792
Итого
4368
524,16
3843,84
117
лонка представляет собой список сотрудников, которым начисляется заработная плата. Во второй колонке задаются исходные ставки.
Третья колонка содержит результаты вычисления подоходного налога по ставке 12%. Четвертая колонка представляет собой разницу
между содержимым второй и третьей колонок. Поскольку исходные
данные и результаты промежуточных вычислений должны храниться в памяти ЭВМ, в процессе программирования решения задачи приходится использовать идентификаторы. Заметим, что обычный идентификатор в этом случае не очень удобен. Действительно,
хотя возможно введение в текст программы обычной переменной
вида Nalog_Trofimova, создаваемая программа может быть в этом
случае использована только для расчетов налога, уплачиваемого
именно Л.А. Трофимовой. Если мы хотим запрограммировать вычисления для другого лица, то нам придется вводить другой идентификатор. Подобные действия ведут к изменению текста исходной
программы и крайне нежелательны на практике. Конечно, мы можем ввести идентификаторы обычных переменных вида Nalog _
record_2, однако и в этом случае мы должны будем индивидуально
описать последовательность манипуляций с ячейками памяти для
каждого сотрудника, включенного в список. Для нашего примера
это вполне возможно, но реальный список может состоять, например, из 100 фамилий.
Кроме всего прочего, каждый раз при изменении количества сотрудников мы должны корректировать объявления переменных и,
возможно, делать добавления в текст программы. Программирование существенно упростится, если ввести в рассмотрение массивы
данных, имеющие смысл Nachisleno (4), Nalog (4), K_vidache (4)
и рассматривать их элементы с одинаковыми номерами как записи, относящиеся к сотруднику, имеющему соответствующий
идентификационный номер. На первый взгляд этот способ ничем
существенным не отличается от использования идентификаторов
одиночных переменных с номерами, однако если вспомнить, что
существует возможность обращения к элементу массива с использованием идентификатора другой переменной, то можно рассматриваемую задачу попытаться описать и в общем виде.
Пример 30. Расчет заработной платы
В общем виде выражение для вычисления величины суммы
к выдаче для каждого сотрудника может быть записано как:
K_vidache (i) = Nachisleno (i) – Nalog (i);
118
Если организовать повторения вычислений по этой формуле
столько раз, сколько сотрудников имеется в списке для последовательно изменяющихся значений индекса i, то рассматриваемая задача может быть решена заметно проще, чем в случае объявления
одиночных переменных.
Иногда приходится создавать массивы, размер которых невозможно определить на этапе компиляции программы. В нашем примере нам может быть неизвестно общее число сотрудников, для
которых должна быть начислена зарплата. Конечно, можно объявить массивы с запасом, так, чтобы номер максимального элемента массива был заведомо большим максимально возможного числа
сотрудников, допустим 100 человек. Однако такой прием приводит
к нерациональному распределению памяти.
В языке C++ размер массива явно задается в момент его объявления (смотри подраздел 2.6). Такой прием называется статическим1
связыванием. Действительно, в этом случае размер массива определяется в момент компиляции и компилятор в соответствии с данными ему указаниями резервирует в коде программы необходимое
для размещения массива количество байт памяти. Альтернативой
является метод динамического (на этапе выполнения программы)
использования памяти с помощью указателя. Если в программе
создать указатель, например, pNachisleno, и воспользоваться операцией new (пример 31).
Пример 31. Объявление и использование динамического массива в С++
int i;
//Динамическое объявление массива
double* pNachisleno = NULL; //Объявление указателя
pNachisleno= new double [20]; //Запрос памяти под массив
for (i=0; i<20; i++)
{*(pNachisleno+i) = i;
cout <<*(pNachisleno+i) << endl;
}
delete [] pNachisleno;
pNachisleno = NULL;
Указатели позволяют создавать массивы, размер которых определяется на этапе выполнения. Такие действия обычно называют
динамическим2 связыванием. Для того, чтобы динамически соз-
1
2
В некоторых книгах такое связывание называется ранним
В некоторых книгах такое связывание называется поздним
119
дать массив, можно также воспользоваться операцией new. Например, если нужен массив из 5 элементов, то можно написать следующую строку кода программы: int *massiv = new int[5];.
Обрабатывая код программы и встретив такую строку, компилятор создает в памяти место для хранения указателя massiv. Далее,
на этапе выполнения программы при достижении рассматриваемого оператора генерируется запрос операционной системе на резервирование памяти для создаваемого объекта. В нашем случае таким
объектом является 5 элементов массива, каждый из которых занимает 2 байта памяти (см. табл. 2). Таким образом, операционная система возвращает в указатель massiv адрес свободной в настоящий
момент области памяти размером 10 байт и отмечает в своей таблице
эту память как занятую. Если по каким-то причинам операционная
система не может выделить запрашиваемую память, то в указатель
massiv записывается 01. Таким образом, адрес начального (нулевого) элемента динамически созданного массива хранится в указателе massiv. Обратится к нулевому элементу массива можно записав:
massiv[0] либо *massiv. Для обращения к первому элементу массива
в тексте программы достаточно записать massiv[1] или *(massiv +1),
а для обращения к последнему элементу – massiv[4] или *(massiv +4).
В рассмотренном примере конкретный размер массива 5 задается константой в процессе кодирования программы, а память для
хранения данных отводится во время выполнения. Возникают задачи, у которых требуемый размер массива вообще не может быть
задан на этапе написания кода и определяется только в момент выполнения программы. Тогда можно задать размер динамического
массива через переменную, что иллюстрирует пример 32:
Пример 32. Динамическое объявление массива на этапе выполнения программы
int size;
cin>>size;
int *massiv = new int[size];
В этом случае размер массива полностью задается и размещается
в памяти на этапе выполнения программы.
1 Конечно в современных условиях трудно представить себе ситуацию, когда
программе не хватило 10 байт свободной памяти. Тем не менее, размер создаваемого динамическим связыванием объекта может быть очень велик (например, набор
фотографий высокой четкости). Поэтому для обеспечения надежности программирования целесообразно после создания объекта оператором if проверить содержимое
указателя massiv на нулевое значение.
120
Особенностью рассматриваемой формы использования операции
new [ ] является то, что освобождение выделенной таким образом
памяти реализуется оператором delete особой формы:
delete [ ] massiv;
В пособиях по языку [13] указано, что, используя new и delete,
необходимо придерживаться следующих правил:
– не использовать delete для освобождения той памяти, которая
не была выделена new;
– не использовать delete для освобождения одного и того же блока памяти дважды;
– использовать delete [ ], если для размещения массива применялся new [ ];
– использовать delete (без скобок), если применялся new для размещения отдельного элемента;
– безопасно использовать delete к нулевому указателю.
Как предупреждает С. Прата [13], реакция программы на нарушение указанных правил не определена.
Структуры
Очень часто при программировании возникает необходимость
создания новых типов данных, вид которых определяется конкретной задачей. Так, например, программируя задачу, представленную
в табл. 10, обратим внимание на то обстоятельство, что информация,
размещенная в этой таблице, имеет одинаковую организацию по
строкам. Программируя соответствующую колонку таблицы в виде
массива, программист обязан следить за тем, чтобы номера элементов разных массивов, относящихся к одному сотруднику, не отличались бы один от другого. Из соображений надежности программирования оказывается удобным рассматривать все, относящееся
к одному сотруднику, в виде целой неделимой записи, содержащей
соответственно фамилию, начисленную сумму, рассчитанный налог и сумму к выдаче. На самом деле речь идет о создании нового
типа данных, определенного программистом и включающего в себя
относящиеся к записи поля.
Структура – объединение под одним именем различных компонентов с индивидуальными именами и типами, называемых членами структуры. Структуру надо первоначально определить, задав
в ее составе набор переменных, которые обычно называют данными-членами или полями структуры. Можно сказать, что при определении структуры мы задаем новый тип данных, в нашем случае
121
тип данных ZAPIS (пример 33), предназначенный для хранения одной строки таблицы 10.
Пример 33. Определение структуры, представляющей собой
одну строку записи в табл. 10
//Определение структуры
struct ZAPIS
{
public:
string Familiya;
double Nachisleno;
double Nalog;
double K_vidache;
};
Определение только описывает состав структуры, информация
о которой размещается в общей области программы. Имеющееся
определение можно использовать для создания конкретного экземпляра структуры. Очевидно, что, как и в случае стандартных типов
данных, мы можем создать в программе сколь угодно много экземпляров структуры, отличающихся один от другого своим идентификатором. В программе можно объявить и использовать массивы
структур.
Пример 34. Создание экземпляров структуры, массива структур и пример работы с ними
//Создание двух экземпляров структуры
ZAPIS ex1, ex2;
//Пример работы с членами структуры
ex2.Nachisleno = 1234;
ex2.Nalog =ex2.Nachisleno * 0.12;
ex2.K_vidache=0;
ex1=ex2;
ex1.Familiya = "Иванов В.Н.";
//Массив структур
ZAPIS exmassiv [4];
exmassiv [0] = ex1;
exmassiv [1] = ex2;
exmassiv [0].K_vidache=exmassiv [0].Nachisleno-exmassiv[0].Nalog;
ex1=exmassiv [0];
Для обращения к элементу структуры нужно указать имя экземпляра, а затем через точку имя составляющей, например,
exmassiv [0].K_vidache, exmassiv [0].Nachisleno или exmassiv [0].
Nalog. Поскольку созданные экземпляры структуры занимают в памяти вполне определенное место, работать с ними можно и с помощью указателей. В языке C++ определена использующая указатели
операция непрямого выбора члена. Она обозначается как малень122
кая стрелочка -> и состоит из комбинации знака минус и символа
больше. С ее помощью можно по-другому записать оператор доступа
к члену структуры (пример 35).
Пример 35. Пример работы с элементами структуры с помощью
указателя
ZAPIS* pex;
pex = &ex2;
(*pex).Familiya = «Трофимова Л.А.»;
pex -> K_vidache = exmassiv [1].Nachisleno-exmassiv[1].Nalog;
Объекты и классы
Хотя применение функций и процедур существенно упрощает
создание программ (повышает производительность труда программиста), их использование в сложных программных системах наталкивается на ряд существенных ограничений. По своей сути обычная
функция представляет собой так называемый автомат без памяти.
Ее реакция на входное воздействие однозначно определена в момент
разработки и никак не зависит от текущей ситуации. Как исключение следует отметить функции, вызываемые с атрибутом Static. Эти
функции сохраняют значения своих переменных от вызова к вызову, нарушая правило времени жизни переменной. Написанная с использованием таких переменных функция может иметь существенно
больший набор реакций на входное воздействие, так как в каждом
случае отклик функции зависит не только от входного воздействия,
но и от состояния, в котором функция находилась в момент вызова.
В отличие от обычной функции, такая функция представляет собой
так называемый автомат с памятью, а создание подобного рода функций оказывается куда более сложным делом, поскольку программисту приходиться предусмотреть практически бесконечное число ситуаций, в которых можно ожидать отклик. Исследования в области
надежности программирования показали, что при использовании
функций подобного вида вероятность возникновения ошибки в программе существенно возрастает, в связи с чем во многих руководствах по программированию использование функций с атрибутом
Static было запрещено.
Попытки найти компромисс между потребностями практики
программирования с одной стороны и требованиями обеспечения
надежности программирования с другой привели к созданию еще
одних специфических типов данных, называемых объектами. В современной терминологии конкретные экземпляры структур могут
123
рассматриваться как объекты. Каждый созданный объект идентифицируется по имени созданному в момент его объявления, а обращение к его составляющим производится за счет указания этого
имени и имени составляющей разделенными символом точка (примеры 34 и 35).
Для систематизации объектов и стандартизации принципов работы с ними в языки программирования было введено понятие класса. С точки зрения общепринятой терминологии класс – это некоторое множество объектов, имеющих общие структуру и поведение. А
с точки зрения языка программирования C++ класс – это некий тип
данных схожий со структурой, но имеющий дополнительно определенный набор свойств заданных с помощью специальных функций, называемых функциями-членами класса. Можно сказать, что
объект обладает неким состоянием, определяемым экземпляром
структуры, созданной на основе данных-членов класса, поведением, определяемым набором связанных с классом функций-членов,
и идентичностью, определяемой уникальным в рамках программы
именем объекта.
Обобщая, можно сказать, что объект – это экземпляр некоторого
конкретного класса. То есть «объект – это физическая реализация
в памяти данных-членов и связанных с ним функций-членов класса. Термины «экземпляр класса» и «объект» взаимозаменяемы.
Данные-члены класса описываются тем же способом, как и данные-члены структуры, а для обращения к составляющим объекта
используются та же нотация.
Под поведением обычно понимают реакцию объекта на внешнее
воздействие сводящуюся к изменению его состояния. Поведение
объекта определяется набором связанных с классом функций, которые в C++ называют функциями-членами. Их основное назначение – манипуляции с данными объекта. В результате количество
возможных состояний объекта с известным поведением существенно ограничивается возможностями этих функций и определяется
заранее при создании класса, что позволяет сохранить надежность
программирования на разумном уровне. Наконец характеристика
идентичности представляет собой свойство объекта, отличающее
его от других объектов, и обеспечивается уникальностью имени
объекта.
Класс является более общим понятием, чем структура, за счет
введения характеристики поведения в виде функций-членов. Тем не
менее, сам класс, как и структура, только описывает объект. С дру124
Class C1
Главная программа
class C1
Определения
внешних
функций-членов
класса
начало определения
Объявление данных и
функций - членов класса
Объявления
переменных и
внешних функций
класса
public : ...
protected : ...
private : ...
Определения внутренних
функций - членов класса
Функция - член
класса f1()
Определение
функции
Конец функции f1
…
Функция - член
класса fm(_)
Определения
конструкторов класса
Конструктор без
аргументов C1()
Создание
объекта 1
С1 ob1
Определение
функции
Определение
конструктора
Конец
конструктора C1()
main()
Внешняя
функция – член
класса g1
Определение
деструктора
класса
Конструктор с
аргументами
C1::C1(int a)
Деструктор
~ C1()
Определение
конструктора
Определение
деструктора
Конец
конструктора
C1(a)
Конец
деструктора
Конец функции
g1
Действия с ob1
ob1.f1()
ob1.fm()
…
Создание
объекта 2
С1 ob2(t)
Внешняя
функция – член
класса gn1
Определение
функции
Действия с ob1
ob2.f1()
ob2.fm()
Конец функции
gm
Вызов
деструктора
return 0
Определение
функции
Конец функции
fm
…
class C 1
Конец
main()
конец определения
Рис. 53. Алгоритм программы с использованием класса
гой стороны, структура схожих объектов определяет общий для
них класс. Объявленный объект существует до конца выполнения
программы, однако, при необходимости, для освобождения памяти объект может быть удален специальным оператором языка программирования. Пример алгоритма программы с использованием
технологии классов и объектов представлен на рис. 53.
Функция-член1 определяется в составе определения класса.
Если функция-член определена в другом месте, то она должна иметь
префикс в виде имени класса с разделителем в виде двух символов
1 Иногда, например, в VBA, функции-члены класса называют еще методами
класса
125
двоеточия, называемых операцией разрешения контекста, а составе
определения класса она должна быть объявлена.
По умолчанию экземпляры структур имеют атрибут public. Поэтому к ним может обратиться любая функция программы, что
может являться источником серьезных ошибок. Из соображений
защиты от несанкционированного доступа к членам класса используются модификаторы видимости членов класса. При определении
данных-членов класса можно пользоваться модификаторами видимости составляющих объекта. Если при объявлении членов класса
указан модификатор private, то доступ разрешается только членам
класса. Модификатор public разрешает доступ любым функциям,
а модификатор protected открывает доступ к переменным классам,
производным от исходного.
Ну и, наконец, существует необязательное соглашение о том, что
имя класса начинается с латинского символа C, а имена его переменных с буквы m.
Пример 36. Структура программы определения класса с данными-членами и функциями-членами
// Заголовочные файлы
class С_Name
// Определение класса С_Name
{
private: // Область для объявления внутренних переменных
// Здесь объявляются защищенные элементы класса С_Name
public:
// Область для объявления внешних переменных
// Область для объявления данных-членов класса С_Name
//(переменных класса, которые будет иметь каждый созданный объект класса)
// Область для определения данных-членов класса С_Name (функций и методов класса)
// Область для определения конструкторов класса С_Name
// Область для определения деструктора класса С_Name
};
// Конец определения класса С_Name
// Область для определения внешних функций
int main ()
{
// Начало главной функции
// Создание объектов
// Работа с объектами
126
// Удаление объектов
return 0;
// Конец главной функции
}
Пример 37. Пример создания объекта класса и вызов функциичлена
//Создание объекта класса
C_ZAPIS zapis1;
zapis1.m_Nachisleno=1000;
//Вызов функции-члена
zapis1.m_Nalog= zapis1.Value_Nalog ();
zapis1.m_K_vidache=zapis1.Value_K_vidache ();
Конструкторы
и деструкторы
Как правило, при создании объекта класса приходится выполнять ряд стандартных действий над данными. Обычно это задание
начальных значений переменных класса в создаваемом объекте,
хотя это могут быть и другие действия, например, выбор файла данных, связанных с объектом. Конструктор класса – это специальная
функция, являющаяся членом класса, которая автоматически вызывается при создании объекта класса. Имя конструктора класса
в C++ совпадает с именем класса. Класс может иметь несколько
конструкторов, позволяя создавать объекты разными способами.
По умолчанию запускается так называемый конструктор без аргументов, однако, при необходимости, можно создать в классе другой
конструктор с тем же именем (совпадающим с именем класса). Запуск соответствующего конструктора производится в момент создания объекта, которому, при необходимости, могут быть переданы
некие параметры. В зависимости от их набора и количества будет
запускаться тот или иной конструктор.
После окончания работы с объектом его необходимо удалить. Под
удалением понимается освобождение памяти, занятой объектом.
Деструктор – это функция-член класса, с именем, совпадающим
с именем класса с предшествующим знаком тильды (~). Деструктор
не возвращает значения и не имеет аргументов, поэтому он может
быть только один (пример 39).
Объявление конструктора и деструктора должно производиться
в модуле класса (Пример 38), а определение рассматривается как
внешняя функция.
127
Пример 38. Объявление конструктора и деструктора в модуле
класса
class C_ZAPIS
{public:
……….
C_ZAPIS (); // Объявление конструктора в модуле класса
~C_ZAPIS (); // Объявление деструктора в модуле класса
………..
};
Конструктор класса может не иметь, а может и иметь аргументы.
Используя их, мы можем в момент инициализации объекта присваивать переменным конкретные значения (пример 39).
Пример 39. Конструкторы и деструктор класса
#include <iostream>
using namespace std;
class Base // Определение класса Base
{
private: // Область для объявления внутренних переменных
int value; // Защищенный элемент класса Base
public:
int m_var;
// Область для объявления внешних переменных
//Объявление данных-членов класса Base
void show () //Определение функции-члена базового класса
{cout<<"Base show "<<"\n"<<endl;}
Base () // Определение конструктора класса Base без аргументов,
// используемого по умолчанию
{value = 1; // Используется защищенная переменная value класса Base
cout<<"Base () value="<<value<<"\n"<<endl;}
Base :: Base (int input) // Определение конструктора класса Base,
//использующего аргументы
{value = input; // Используется защищенная переменная value класса Base
cout<<"Base (input) value="<<value<<"\n"<<endl;}
~Base () //Определение деструктора класса Base
{cout<<"Base () destructor\n"<<endl;
}
};
int main ()
{
Base ba;
Base ba1(2);
ba.show ();
128
// Главная функция
// Создание объекта класса Base value = 1
// Создание объекта класса Base value = 2
// Выполняется функция-член класса Base с объектом класса Base
}
ba.m_var=10; // Изменение данных-членов объекта ba класса Base
ba1.m_var=20;// Изменение данных-членов объекта ba1 класса Base
return 0;
// Поскольку в этой программе было создано два объекта (ba и ba1),
//перед ее завершением деструктор будет вызван два раза
Отдельную проблему представляет динамическое распределение
памяти в объектах класса. Если память была занята динамически,
например, как это было сделано в примерах 31 и 32, то выполнение деструктора должно гарантировать ее освобождение, например,
delete [] pNachisleno; pNachisleno = NULL; (пример 31).
Дружественные функции класса
В объектно-ориентированном программировании функции могут обращаться к переменным класса только с помощью функцийчленов этого же класса. В языке С++ существует понятие дружественных функций и классов. «Дружественность» предусматривает
возможность доступа к методам (функциям-членам) другого класса,
работающим с переменными сгенерированных этим классом объектов. Соответственно дружественная функция может обратиться
только к конкретному методу класса, а дружественный класс предоставляет возможность воспользоваться извне всеми методами.
Такой подход снижает надежность программирования, поскольку
повышается вероятность случайного изменения содержимого объекта, но позволяет создавать меньший по объему программный код
и сократить время выполнения программы. Ключевое слово friend
позволяет указать функцию или класс как дружественные.
Для создания дружественной тому или иному классу функции
необходимо в описании класса объявить дружественную функцию
с указанием ключевого слова friend. Если функция дружественна
нескольким классам, то надо добавить это описание во все классы,
к внутренним данным которых производится обращение (примеры
40 и 41). Пример алгоритма программы с использованием дружественной функции представлен на рис. 54.
Пример 40. Общая структура программы с дружественными
функциями класса
// Описание класса A
class A
{public:
//...
129
130
Определения
внутренних
функций –
членов,
конструкторов и
деструктора
Конец
class B
Определения
внутренних
функций –
членов,
конструкторов и
деструктора
Конец
class A
конец определения
class C
Конец
Определения
внутренних
функций –
членов,
конструкторов и
деструктора
Начало
Объявление
дружественного
класса
public: ...
protected: ...
private: friend class A;
...
Объявление
дружественного класса
класса A
class C
начало определения
Рис. 54. Дружественные функции и классы
конец определения
Начало
Начало
конец определения
Объявление
дружественной функций
класса
public: ...
protected: ...
private: friend void A::z();
...
Объявление
дружественной функции z
класса A
class B
начало определения
Объявления
переменных и
внешних функций
класса
public:.void z();
protected: ...
private: ...
Объявление функции z
класса A
class A
начало определения
Конец функции
Определение
функции
Дружественная
функция
int func(Base x,A y)
Определение
дружественной
функции классов
BиA
Конец
main()
Вызов
деструктора
return 0
ba.m_var=10;
a.m_varA=30;
ba.show ();
a.show ();
a.showA ();
Создание
объектов
int res;
Base ba;
A a;
main()
Главная
программа
void z(); // Объявление функции z класса A
};
// Описание класса B
class B
{private:
//...
friend void A::z (); // Объявление функции z класса A как дружественной классу B,
// т.е. из функции z класса A можно получить доступ к внутренним
// переменным класса B
};
// Описание класса C
class C {private:
//...
friend class A; // Объявление класса A как дружественного классу C,
// все функции класса A будут дружественны классу C и
// из любой функции класса A можно получить доступ к // внутренним переменным класса C
};
Пример 41. Создание дружественной функции класса
#include <iostream>
using namespace std;
class A; //Объявление класса A для использования его объекта в других классах
class Base // Базовый класс определение
{
private: int value;
// Защищенный элемент класса Base
public:
int m_var;
//Объявление данных-членов класса Base
void show ()
//Определение функции-члена базового класса
{cout<<"Base show "<<"\n"<<endl;}
Base ()
// Конструктор базового класса
{value = 1;
// Используется защищенная переменная value класса Base
cout<<"Base () value="<<value<<"\n"<<endl;}
friend int func (Base, A); //Объявление дружественной функции класса
};
class A
// Другой класс
{
private:
int valueA;
// Защищенный элемент класса A
public:
int m_varA;
//Объявление данных-членов класса A
void showA ()
//Определение функции-члена класса A
{cout<<"A showA "<<"\n"<<endl;}
A () //Конструктор A инициирует начальное значение в классе A
131
{valueA = 3;
// Используется защищенная переменная valueA класса A
cout<<"A () valueA="<<valueA<<"\n"<<endl;}
friend int func (Base, A); //Объявление дружественной функции класса
};
int func (Base x, A y)
//Определение дружественной функции классов Base и A
{return x .m_var +y.m_varA;}
int main ()
// Главная функция
{
int res;
Base ba;
// Создание объекта класса Base value = 1
A a;
// Создание объекта класса A value = 3
ba.show (); // Выполняется функция-член класса Base с объектом класса Base
a.showA ();
// Выполняется функция-член класса A с объектом класса A
ba.m_var=10;
// Изменение данных-членов объекта класса Base
// Защищенная переменная value класса Base недоступна в объекте класса Base
a.m_varA=30;
// Изменение данных-членов объекта класса A в объекте класса A
// Защищенная переменная value класса Base недоступна в объекте класса A
// Защищенная переменная valueA класса A недоступна в объекте класса A
res=func (ba, a);
return 0;
}
Инкапсуляция, наследование и полиморфизм
Слово инкапсуляция имеет латинские корни и может быть переведено как изоляция, закрытие чего-либо. В объектно-ориентированном программировании под термином инкапсуляция понимается сокрытие внутренней структуры данных и реализации методов
объекта. Другим объектам остается доступен только интерфейс
объекта, через который осуществляется все взаимодействие с ним.
Такой подход позволяет максимально изолировать объект от внешнего воздействия других программ, что обеспечивает надежность
программирования, а модернизация класса в этом случае сводится
к добавлению функций-членов класса, что минимизирует вероятность ошибки.
Под наследованием обычно понимается создание производного
класса на базе другого. Базовый класс – это любой класс, который
используется в качестве основы для определения другого класса.
Производный класс автоматически получает данные базового класса, а также доступ к функциям-членам этого класса. Для того, чтобы
указать, какой класс является производным, а какой базовым, в заголовок определения производного класса включается строка вида:
class A: public Base {}; // Производный класс A наследующий Base
132
Наследующий класс может использовать данные и функции базового класса и дополнять их собственными данными и функциями. Объект базового класса отличается от объекта производного
класса за счет добавления в последний данных, созданных в производном классе. Это позволяет последовательно наращивать (уточнять) структуру классов и, как следствие, создавать итоговый класс
сверху вниз.
Отдельно отметим вопрос видимости данных. Если они объявлены с ключевым словом public, то соответствующие ячейки памяти,
в том числе и в созданных объектах, будут доступны любым программам. Переменные с ключом private доступны только для функций – членов класса. Поэтому воспользоваться этими переменными
в объекте, созданном другой программой, оказывается невозможным. Если переменные объявляются ключевым словом protected,
то они оказываются доступными функциям-членам этого класса,
производного класса, а также дружественным функциям класса.
Пример алгоритма программы с использованием наследования показан на рис. 55.
Пример 42. Наследование класса
#include <iostream>
using namespace std;
class Base
// Базовый класс
{
protected: int value;
// Защищенный элемент класса Base
public:
int m_var;
//Объявление данных-членов класса Base
void show () //Определение функции-члена базового класса
{cout<<"Base show "<<"\n"<<endl;}
Base ()
// Конструктор базового класса
{value = 1;
// Используется защищенная переменная value класса Base
cout<<"Base () value="<<value<<"\n"<<endl;}
Base :: Base (int input) // Конструктор базового класса, использующий аргументы
{value = input; // Используется защищенная переменная value класса Base
cout<<"Base (input) value="<<value<<"\n"<<endl;}
};
class A: public Base // Производный класс A наследующий Base
{
protected:
int valueA; // Защищенный элемент класса A
public:
int m_varA; //Объявление данных-членов класса A
void showA () //Определение функции-члена наследующего класса
{cout<<"A showA "<<"\n"<<endl;}
133
class Base
начало
определения
Объявление
переменных- членов
класса Base
Наследующий
class A: public Base
начало
Объявление
переменных- членов
класса A
Объявление
данных и функцийчленов класса
private: ...
protected: int value;
public: int m_var;
void show () {};
Объявление
данных и функцийчленов класса
private: ...
protected: int valueA;
public: int m_varA;
void showA () {};
Конструктор
без параметров
Base()
Конструктор
без параметров
A () : Base ()
value = 1
valueA = 3
Конец
Конец
Конструктор с
параметрами Base
:: Base (int input)
Конструктор с
параметрами
A (int inputA) : Base
(inputA)
value = input
valueA = inputA
Конец
Конец
class Base
class A
конец
Главная
программа
main()
Создание
объектов
Base ba;
Base ba1(2);
A a; A a1(4);
ba.show ();
a.show ();
a.showA ();
ba.m_var=10;
a.m_var=20;
a.m_varA=30;
Вызов
деструктора
return 0
Конец
main()
конец
Рис. 55. Наследование классов
A () : Base ()
//Конструктор A вызывает конструктор Base
{valueA = 3; // Используется защищенная переменная valueA класса A
cout<<"A () value="<<value<<" valueA="<<valueA<<"\n"<<endl;}
A (int inputA) : Base (inputA) //Конструктор A вызывает конструктор Base c inputA
{valueA = inputA; // Используется защищенная переменная valueA класса A
cout<<"A (input) value="<<value<<" valueA="<<valueA<<"\n"<<endl;
// Используется защищенная переменная value класса Base
134
}
};
int main () // Главная функция
{
Base ba; // Создание объекта класса Base value = 1
Base ba1(2); // Создание объекта класса Base value = 2
A a; // Создание объекта класса A value = 3
// Сначала отработает конструктор Base(), а потом A()
A a1(4); // Создание объекта класса A value = 4
// Сначала отработает конструктор Base(input), а потом A(input)
ba.show (); // Выполняется функция-член класса Base с объектом класса Base
a.show (); // Выполняется функция-член класса Base с объектом класса A
a.showA (); // Выполняется функция-член класса A с объектом класса A
ba.m_var=10; // Изменение данных-членов объекта класса Base
// Защищенная переменная value класса Base недоступна в объекте класса Base
a.m_var=20; // Изменение данных-членов объекта класса Base в объекте класса A
a.m_varA=30; // Изменение данных-членов объекта класса A в объекте класса A
// Защищенная переменная value класса Base недоступна в объекте класса A
// Защищенная переменная valueA класса A недоступна в объекте класса A
return 0;
}
Слово полиморфизм имеет греческое происхождение и может
быть переведено на русский язык как многоформенность. В программировании под полиморфизмом понимают возможность объектов с одинаковой спецификацией иметь различную реализацию
(форму) в процессе выполнения программы. Полиморфизм в С++
реализуется за счет существующей возможности создавать так называемые виртуальные (virtual) функции. В отличие от обычных
функций, коды которых формируются компилятором и размещаются в памяти редактором связей, виртуальная функция является
динамической, то есть она размещается в памяти на этапе выполнения программы.
Виртуальная функция должна быть определена в некотором
классе, который называется базовым, с ключевым словом virtual.
Другой класс, называемый производным, может использовать или
перегружать виртуальные функции. Чтобы функция другого класса вела себя как виртуальная, она должна иметь то же имя, тот же
список параметров и тот же тип возвращаемого параметра, что и
функция, объявленная в базовом классе. Как следствие, у виртуальных функций совпадают объявления, но имеются разные определения, причем конкретное определение выбирается в зависимости от типа объекта. Как результат, виртуальные функции реали135
зуют принцип полиморфизма. Алгоритм программы, реализующей
переопределение функций и полиморфизм, представлен на рис. 56.
Пример 43. Переопределение функции и полиморфизм
#include <iostream>
using namespace std;
class Base // Базовый класс
{protected: // Защищенный элемент класса Base
int value;
public:
virtual void show ()
{cout<<"Base value="<<value<<"\n"<<endl;}
Base () // Конструктор базового класса
{value = 1;}
Base :: Base (int input)// Конструктор базового класса
{value = input;}
};
class A: public Base// Производный класс A
{public:
virtual void show ()
{cout<<"A value="<<value<<"\n"<<endl;}
A () // Конструктор производного класса
{value = 2;}
};
class B: public Base// Производный класс B
{public:
virtual void show ()
{cout<<"B value="<<value<<"\n"<<endl;}
// Используется конструктор базового класса
};
class C: public Base// Производный класс D
{public:
virtual void show ()
{cout<<"C value="<<value<<"\n"<<endl;}
// Используется конструктор с аргументами
C :: C (int input)
{value = input;}
};
int main ()
{
Base ba;
A a;
B b;
C c(3);
136
// Создание объекта класса base value = 1
// Создание объекта класса A value = 2
// Создание объекта класса B value = 1
// Создание объекта класса C value = 3
137
конец
class Base
Конец
value = input
Конструктор с
параметрами Base
:: Base (int input)
Конец
value = 1
Конструктор
без параметров
Base()
Объявление
данных и функцийчленов класса
private: ...
protected: int value;
public: virtual void
show () { };
Объявление
переменных- членов
класса Base
начало
определения
class Base
Конструктор
без параметров
A ()
valueA = 2
Конец
class B
Конструктор
без параметров
A ()
valueA = 2
Конец
class A
конец
class C
Конец
value = input
Конструктор с
параметрами
C :: C (int input)
Объявление
данных и функцийчленов класса
private: ...
protected: ...
public: virtual void
show () {};
Объявление
переменных- членов
класса C
Производный
class C : public Base
начало
Рис. 56. Переопределение функций и полиморфизм
конец
Объявление
данных и функцийчленов класса
private: ...
protected: ...
public: virtual void
show () {};
Объявление
данных и функцийчленов класса
private: ...
protected: ...
public: virtual void
show () {};
конец
Объявление
переменных- членов
класса B
Производный
class B : public Base
начало
Объявление
переменных- членов
класса A
Производный
class A : public Base
начало
Конец
main()
Вызов
деструктора
return 0
ba.show ();
b.show ();
c.show ();
Создание
объектов
Base ba; A a;
B b; C c(3);
main()
Главная
программа
ba.show (); // Выполняется виртуальная функция класса Base
a.show ();// Выполняется виртуальная функция класса A
b.show ();// Выполняется виртуальная функция класса B
c.show ();// Выполняется виртуальная функция класса B
return 0;
}
Лабораторная работа №9.
Создание объектов на основе структур и классов
Цель работы: приобретение навыков работы со структурами,
классами и объектами
Задание
Научитесь писать программы, реализующие создания классов и
объектов и выполнять их тестирование и отладку. Предлагаемые индивидуальные задания предусматривают создание класса, а на его
основе объектов. Необходимо обеспечить наличие в составе класса
данных-членов и функций-членов в соответствии с таблицей 11.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер своего варианта задания.
3. Разработайте алгоритм и напишите программу, реализующую ваше индивидуальное задание (табл. 11). Пример программы
создания и использования класса, написанной для задания 1, представлен в примере 44.
4. Проведите трассировку вашей программы и функции с использованием команды отладчика Шаг с заходом (А11).
5. Подготовьте тесты для вашей программы и оформите их в соответствии с таблицей 9. Проведите тестирование программы.
6. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
7. Реализуйте наследование класса, для чего модернизируйте
созданную программу так, чтобы созданная вами функция-член
являлась бы внутренней функцией-членом наследующего класса.
Контрольные вопросы
1. Зачем нужны массивы?
2. Как можно создать массив?
3. Чем структура отличается от массива?
138
4. Что входит в состав модуля класса?
5. Зачем нужны конструкторы и деструктор класса?
6. Чем отличаются области видимости переменных класса
public, protected и private друг от друга?
7. Чем объект класса отличается от самого класса?
8. Что представляет собой наследование класса?
9. Что представляют собой дружественные функции класса?
10. Что такое полиморфизм и инкапсуляция и как они реализуется?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунок алгоритма разработанной программы,
текст программы и таблицу с перечнем тестов и отметками о результатах их выполнения (ваш вариант таблицы 9). Кроме этого, должны быть сформулированы ваши выводы по результатам выполнения лабораторной работы.
Пример 44. Создание и использование класса
#include <iostream>
using namespace std;
class Complex_int //Определение класса
{//Определение данных-членов класса
public:
int m_real; //Действительная часть комплексного числа в алгебраической форме
int m_image; //Мнимая часть комплексного числа в алгебраической форме
//Объявление внешней функции-члена
Complex_int add_c (Complex_int add);
//Определение функции-членакласса
Complex_int sub_c (Complex_int add)
{
Complex_int sum; // Объявление внутренней переменной
sum.m_real = m_real-add.m_real;
sum.m_image =m_image-add.m_image;
return sum;
}
// Конструктор класса без аргументов
Complex_int ()
{m_real=0;
m_image =0;
}
};// Конец определения класса
//Определение внешней функции-члена Complex_int
139
Complex_int Complex_int :: add_c (Complex_int add)
{
Complex_int tmp;
tmp.m_real=m_real+add.m_real;
tmp.m_image=m_image+add.m_image;
return tmp;
}
int main () // Главная программа
{ Complex_int a, b, d, s;
a.m_real = 2; //Задание начальных значений
a.m_image = 3;
b.m_real=4;
b.m_image=5;
s=a; //Копирование комплексных переменных
d=b;
s=a.add_c (b); //Сложение комплексных переменных внешней
//функцией-членом класса
d=b.sub_c (a); //Вычитание комплексных переменных функцией-членом класса
return 0;
}
Варианты заданий для программирования классов и объектов
Таблица 11
Варианты заданий для создания классов и объектов
Номер
варианта
Данные-члены
1
Комплексное число в алгебраической форме
2
Комплексное число в алгебраической форме
3
Комплексное число в показательной форме
4
Комплексное число в алгебраической и показательной формах
5
Катеты прямоугольного треугольника
140
Функциичлены
Внешние
функции-члены
Вычитание
Сложение
комплексных комплексных
чисел
чисел
Умножение
Деление
комплексных комплексных
чисел
чисел
Умножение
Деление
комплексных комплексных
чисел
чисел
Перевод
Перевод числа
числа из ал- из показательгебраической
ной в алгев показательбраическую
ную форму
форму
Вычисление
Вычисление
гипотенузы
площади
Продолжение табл. 1
Номер
варианта
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Данные-члены
Основания трапеции и высота
Функциичлены
Внешние
функции-члены
Вычисление
площади
Вычисление
площади
Вычисление
периметра
Вычисление
площади
Вычисление
периметра
Вычисление
объема
Вычисление
периметра
Стороны прямоугольника
Вычисление
периметра
Две сопряженных стороны и выВычисление
сота параллелограмма
площади
Диагонали ромба
Вычисление
периметра
Две стороны четырехугольника
Вычисление
площади
Стороны параллелепипеда
Вычисление
площади поверхности
Часы, минуты, секунды, сотые
Арифметиче- Арифметичесекунды
ские опера- ские операции
ции с сотыми
с секундами
секунд
Высота и радиус цилиндра
Объем цилинПлощадь
дра
поверхности
цилиндра
Высота и радиус конуса
Площадь
Объем конуса
поверхности
конуса
Радиусы верхнего и нижнего
Объем конуса
Площадь
оснований усеченного конуса и
поверхности
его высота
конуса
Радиус сферы
Площадь
Объем сферы
сферы
Катет и гипотенуза прямоуголь- Второй катет Площадь треуного треугольника
гольника
Строка текста
Строка текста Количество
без лидируюсимволов
щих и конечв строке
ных пробелов
Катет и гипотенуза прямоугольУгол между Периметр треного треугольника
катетом и
угольника
гипотенузой
Даны два числа
Среднее знаНаибольшее
чение
число
141
Окончание табл. 1
Номер
варианта
Данные-члены
21
Даны три числа
22
Дано натуральное число
Т, которое представляет
длительность прошедшего
времени в секундах.
23
Строка текста
24
Значение гипотенузы и
прилежащего угла
По предложению студента
25
142
Функциичлены
Внешние
функции-члены
Среднее ариф- Среднее геометическое
метрическое
квадратов
модулей чисел
чисел
Вывести
Вывести
данное
данное
значение
значение
длительности в следующей
в минутах.
форме: НН ч
ММ мин SS с.
Определить
Записать
длину строки
строку
в обратном
порядке
Периметр
Катеты
5. НЕКОТОРЫЕ ДРУГИЕ ВОЗМОЖНОСТИ ЯЗЫКА C++
5.1. Строки
Под термином «строка» обычно понимается последовательность
символов, хранящаяся в памяти машины. Поскольку сами ячейки памяти универсальны и хранят только двоичное число, вопрос
о том, что находится в конкретном месте памяти, решается с помощью правил, которые реализует компилятор при обработке операторов объявления.
Предполагается, что строка в памяти машины есть последовательность ячеек памяти, в которые занесены данные символьного
типа. Среди стандартных типов данных языков С и C++ присутствуют и символьные переменные типов char, unsigned char и появившейся только в языке C++ wchar_t. Два первых типа практически
взаимозаменяемы и используют для хранения кода символа восьмиразрядное слово памяти. Если принять во внимание то обстоятельство, что служебные символы и буквы латинского алфавита и
кириллицы кодируются числами в диапазоне от 0 до 255, использование переменных типа unsigned char для программирования
строк является предпочтительным.
Тип данных wchar_t использует для хранения одного символа
шестнадцатиразрядное слово памяти (два последовательных восьмиразрядных). Он предназначен для хранения информации в коде
Юникод (UNICOD). Увеличение разрядности расширило диапазон
кодовых слов (до 65535), что позволяет кодировать, например, иероглифы.
Последовательность кодов в памяти машины может рассматриваться как строка текста. Очевидно, что если мы хотим хранить
в памяти строку, то мы обязаны сообщить об этом компилятору и
позаботиться о выделении под нее необходимого количества ячеек
типов char, unsigned char или wchar_t. Используя приемы классического языка С проще всего это сделать, объявив массив соответствующего типа, например, unsigned char str[10]. Тогда фактически строка представляет собой массив символьных переменных. Именно так и делается в языке С. В этом случае str является
указателем на начало (первый символ) строки. К каждому символу
строки можно обратиться по номеру элемента массива. Например,
при обращении к первому символу строки можно написать str[0],
а к пятому str[4]. Размер хранимой в массиве str строки не должен
превышать 9 символов. Дело в том, что в языке С существует согла143
шение, согласно которому признаком окончания строки в памяти
машины является нулевой символ '\0'. Этот символ может находиться или в последнем элементе массива str[9], или в предыдущих
элементах, если хранимая строка содержит меньше 9 символов.
Пустая строка содержит символ '\0' в нулевом элементе массива
(str[0]=0). Очевидно, что если по каким-то причинам длина строки
превышает девять символов, то места для ее хранения оказывается
недостаточным. Попытка записать такую строку в массив str приведет к возникновению очень опасной ошибки под названием «выход
индекса за границы массива»1.
Сложности реализации хранения строк «в стиле С» побудили
разработчиков компилятора C++ изменить всю концепцию подхода
к решению этой задачи. Сохранив возможность работы со строками, реализованную в языке С, разработчики добавили в библиотеку
языка C++ специальный библиотечный класс string. Для использования возможностей этого класса необходимо в заголовочной части
программы добавить макрорасширение #include <string>. Кроме
этого в программе должна существовать строка using namespace
std;, позволяющая связать класс с стандартными используемыми
в нем именами.
Основное отличие способов использования переменных для хранения строк в языке C++ заключается в том, что такая переменная
может быть объявлена в программе обычным способом. То есть оператор программы string str1; создает в памяти область данных для
хранения строки. Важной особенностью такой переменной является то, что при ее создании реализуется принцип динамического
распределения памяти. Это означает, что первоначально после объявления переменная str1 места в памяти не занимает. А вот при записи в нее текста, например, оператором str1="abcdefg"; система автоматически отведет под ее хранение необходимую память на этапе
выполнения программы.
Проблема русификации консольного ввода-вывода
Далее обсудим вопрос, а какому собственно символу, хранящемуся в памяти, вводимому с клавиатуры и отображаемому на экране, принтере и т. п. соответствует какой код? Восьмиразрядные
коды символов латыни в мире де факто стандартизованы Американским стандартным кодом для обмена информации ASCII (American
1
144
Эта ошибка может не диагностироваться компилятором.
Standard Code for Information Interchange). Именно поэтому отображение символов латыни в любых системах осуществляется стандартно. Младшая группа кодов ASCII (от 0 до 31) используется для
так называемых служебных символов или символов управления
печатью. В диапазоне кодов от 33 до 127 размещены прописные и
строчные символы латыни и специальные символы, а код 32 зарезервирован под символ пробел (‘ ‘) (табл.12).
Таблица 12
ASCII коды символов латыни
32-
48- 0
64- @
80- P
96- `
112- p
33- !
49- 1
65- A
81- Q
97- a
113- q
34- «
50- 2
66- B
82- R
98- b
114- r
35- #
51- 3
67- C
83- S
99- c
115- s
36- $
52- 4
68- D
84- T
100- d
116- t
37- %
53- 5
69- E
85- U
101- e
117- u
38- &
54- 6
70- F
86- V
102- f
118- v
39- ‘
55- 7
71- G
87- W
103- g
119- w
40- (
56- 8
72- H
88- X
104- h
120- x
41- )
57- 9
73- I
89- Y
105- i
121- y
42- *
58- :
74- J
90- Z
106- j
122- z
43- +
59- ;
75- K
91- [
107- k
123- {
44- ,
60- <
76- L
92- \
108- l
124- |
45- -
61- =
77- M
93- ]
109- m
125- }
46- .
62- >
78- N
94- ^
110- n
126- ~
47- /
63- ?
79- O
95- _
111- o
127-
Оставшиеся 127 кодов (от 128 до 255) разработчики ASCII зарезервировали под так называемые национальные кодировки. Ими
предполагалось, что коды символов каждого национального алфавита (греческого, украинского, романского) будут размещены имен145
но в этом диапазоне, причем в каждый момент времени вычислительная установка будет работать с собственным национальным алфавитом, а для его изменения будет производиться загрузка новых
шрифтов1.
К сожалению, оказалось, что применительно к символам кириллицы разными разработчиками аппаратуры и программных
средств использовалась, по крайней мере, два варианта кодировки –
Основная и Альтернативная. Это обстоятельство привело к тому,
что программные средства, используя разные кодировки, связанные со шрифтами кириллицы, по-разному и интерпретируют их
коды символов. К сожалению, интегрированная среда программирования Visual C++ как базовую использует Основную кодировку
(табл. 13), а для организации консольного ввода – вывода Альтернативную (табл. 14).
Различие кодировок символов кириллицы и породило проблему,
с которой мы столкнулись с самого начала при изучении C++. Оператор вывода cout может задавать строку символов, которая выведется на экран:
int num;
num =1;
cout << “Menya zovut Ivanov Ivan.\n Ya uchus na “<<num<<” kurse.”;
Выполнение этой строчки произойдет без всяких изменений выводимого текста и будет иметь вид, показанный на рис. 57
Рис. 57. Консольная печать латынью
1 Операционная система для обеспечения возможностей кодирования различных национальных алфавитов использует так называемые кодовые страницы, имеющие свои индивидуальные номера
146
147
.1
ђ
’
‘
І
406
±
B1
45E
Ў ў
40E
411
412
414
ґ
491
415
µ
B5
490
416
¶
B6
¦
A6
417
·
B7
§
A7
• – –
418
ё
451
Ё
401
Й
419
№
2116
A9
©
.B
.C
40A
.D
.E
.F
41B
»
BB
41C
ј
458
¬
AC
41D
Ѕ
405
AD
45C
40C
41E
ѕ
455
AE
®
45B
40B
41F
ї
457
Ї
407
џ
45F
40F
К Л М Н О П
41A
є
454
AB
203A 45A
2039
Є «
404
459
409
.A
™ љ › њ ќ ћ
2122
2030
.9
€ ‰ Љ ‹ Њ Ќ Ћ Џ
.8
Г Д Е Ж З И
413
і
456
A4
”
‡
.7
440
р
430
а
420
442
т
441
с
в
432
б
431
422
421
у
443
г
433
423
445
446
436
426
447
437
427
е ж з
435
425
448
и
438
428
449
й
439
429
ф х ц ч ш щ
444
д
434
424
44B
44C
43C
42C
44E
о
43E
42E
44F
п
43F
42F
э ю я
44D
43D
42D
л м н
43B
42B
ъ ы ь
44A
к
43A
42A
Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я
410
.6
„ … †
.5
201E 2026 2020 2021 20AC
.4
Ј ¤ Ґ
408
“
ѓ
453
.3
2018 2019 201C 201D 2022 2013 2014
А Б В
B0
°
A0
452
201A
‚
.2
403
Ђ Ѓ
.0
(кодовая страница 1251)
Таблица 13
* Нижняя часть таблицы кодировки (латиница) полностью соответствует кодировке ASCII. Числа под буквами обозначают
шестнадцатеричный код подходящего символа в Юникоде
8.
9.
A.
B.
C.
D.
E.
F.
Кодировка
Windows-1251*
148
D.
E.
F.
C.
B.
8.
9.
A.
412
.2
414
.4
415
.5
416
.6
417
.7
418
.8
419
.9
41A
.A
41B
.B
41C
.C
в
432
б
431
422
421
г
433
423
д
434
424
436
426
437
427
е ж з
435
425
439
429
43A
42A
и й к
438
428
л
43B
42B
м
43C
42C
н
43D
о
43E
42E
п
43F
42F
41F
.F
Э Ю Я
42D
41E
.E
2502 2524 2561 2562 2556 2555 2563 2551
2557 255D 255C 255B 2510
2550 256C 2567
404
Є
451
Ё ё
401
є
454
443
Ї
407
444
ї
457
445
у ф х
442
т
с
441
440
р
2559 2558 2552
2568 2564 2565
45E
°
B0
448
∙
2219
449
·
B7
44A
ь
44C
э
44D
221A 2116
A4
25A0
A0
44F
ю я
44E
2584 258C 2590 2580
√ № ¤ ■
44B
ч ш щ ъ ы
447
Ў ў
40E
ц
446
2553 256B 256A 2518 250C 2588
╨ ╤ ╥ ╙ ╘ ╒ ╓ ╫ ╪ ┘ ┌ █ ▄ ▌ ▐ ▀
2514 2534 252C 251C 2500 253C 255E 255F 255A 2554 2569 2566 2560
└ ┴ ┬ ├ ─ ┼ ╞ ╟ ╚ ╔ ╩ ╦ ╠ ═ ╬ ╧
2591 2592 2593
░ ▒ ▓ │ ┤ ╡ ╢ ╖ ╕ ╣ ║ ╗ ╝ ╜ ╛ ┐
430
а
420
41D
.D
Таблица 14
Г Д Е Ж З И Й К Л М Н О П
413
.3
Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь
411
.1
А Б В
410
.0
Альтернативная кодировка букв кириллицы (кодовая страница 866)
Попытка выполнить оператор cout << "Меня зовут Иванов Иван.
\n Я учусь на "<<num<<" курсе."; дает результат, показанный на
рис. 58.
Рис. 58. Консольная печать кириллицей
Причина этого эффекта кроется как раз в несоответствии кодовой таблицы Windows-1251, используемой при работе интегрированной среды программирования Visual C++ и кодовой страницы
Кириллица – 866, используемой системой для вывода на консоль.
Аналогичные проблемы существуют и при выполнении операций
консольного ввода cin. Очевидно, что разработчики компилятора
из соображений совместимости программного обеспечения самостоятельно устранить его не смогут. Поэтому приходится принимать
специальные меры для обеспечения качественной работы консольного ввода – вывода.
В интернете есть множество полезных советов, предлагающих
способы преодоления рассматриваемого противоречия, например,
указания в коде программы строки setlocale (LC_ALL, «RUS»);. По
разным причинам этот, да и ни один из других рекомендованных
вариантов нас не удовлетворил. Поэтому мы создали собственную
функцию, основывающуюся на чужих идеях, но реализованную
именно нами с учетом совместимости с классом string. Для ее подключения необходимо указать в головном файле дополнительно
#include <string>
using std ::string;
В основе предлагаемого решения лежит создание дополнительных специальных функций, использующих свойства кодовой страницы 866 таблица 14. Применяя их, можно писать программы
в кодировке Windows-1251 используя все возможности интегрированной среды программирования, в том числе и возможности отладчика. А перед выполнением консольной операции вывода cout
149
Начало Code
1251_866
Определение
размера строки s
Проверка всех
элементов строки
i=0; s>i; i++
Нет
cbuf >= 0xF0 &&
cbuf <= 0xFF
Да
cbuf >= 0xC0 &&
cbuf <= 0xFF
Да
Код символа =
Код символа - 16
Код символа =
Код символа - 64
Запись в строку
для вывода
i
Конец Code1251_
866
Рис. 59. Рисунок алгоритма функции Code1251_866
предлагается осуществлять перекодировку текста в коды 866. Текст
функции приведен ниже, а ее алгоритм показан на рис. 59.
Пример 45. Текст функции перекодировки Code1251_866
string Code1251_866 (string buffer)
{ using namespace std;
int s, i;
150
string outbuf ="";
s= buffer.size();
unsigned char cbuf;
for (i=0; s>i; i++)
{ cbuf = buffer[i];
if (cbuf >= 0xC0 && cbuf <= 0xEF)
{cbuf = cbuf – 64;} // 64=0x40
else {};
if (cbuf >= 0xF0 && cbuf <= 0xFF)
{cbuf = cbuf – 16;} // 16=0x10
else{}
outbuf = outbuf += cbuf;
}
return outbuf;
}
Аналогично после выполнения операции cin необходимо выполнить перекодировку из кодов страницы 866 в коды Windows-1251.
Текст функции приведен ниже, а ее алгоритм показан на рис. 60.
Пример 46. Текст функции перекодировки Code866_1251
string Code866_1251 (string buffer)
{using namespace std;
int s, i;
string outbuf="";
s= buffer.size();
unsigned char cbuf;
for (i=0; s>i; i++)
{cbuf = buffer[i];
if (cbuf >= 0x80 && cbuf <= 0xAF)
{cbuf = cbuf + 64; // 64=0x40
}
else
{if (cbuf >= 0xE0 && cbuf <= 0xFF)
{cbuf = cbuf + 16; // 16=0x10
}
}
outbuf = outbuf += cbuf;
}
return outbuf;
};
В качестве примера рассмотрим возможности предлагаемых
функций.
Пример 47. Программа вывода информации на консоль
string Code1251_866 (string buffer);
string Code866_1251 (string buffer);
151
Начало
Code 866_1251
Определение
размера строки s
Проверка всех
элементов строки
i=0; s>i; i++
Нет
cbuf >= 0xE0 &&
cbuf <= 0xFF
Да
cbuf >= BxB0 &&
cbuf <= 0xAF
Да
Нет
Код символа =
Код символа + 16
Код символа =
Код символа + 64
Запись в строку
для вывода
i
Конец
Code 866_1251
Рис. 60. Рисунок алгоритма функции Code866_1251
152
string str1, str2, str3, stdbuf1, stdbuf2;
int num;
num = 1;
str1="Меня зовут Иванов Иван.\n Я учусь на ";
str2=" курсе.";
stdbuf1=Code1251_866(str1);
stdbuf2=Code1251_866(str2);
cout << stdbuf1 << num << stdbuf2<<endl;
Выполнение фрагмента программы примера 47 дает результат,
показанный на рис. 61.
Рис. 61. Консольная печать кириллицей с использованием перекодировки
В то же время выполнение фрагмента программы из примера 48
дает на экране результат рис. 62.
Рис. 62. Консольный вывод кириллицей
Пример 48. Вывод на экран
cout << stdbuf1 << num << stdbuf2<<endl;
str1 = "В каком городе вы живете?\n";
stdbuf1=Code1251_866(str1);
cout << str1;
cout << stdbuf1;
При этом в окне отладчика введенная строка stdbuf отображается неправильно, а после перекодировки str2 – в соответствии с введенным текстом (рис. 63).
Рис. 63. Содержимое исходной и перекодированной строки
в окне отладчика
153
Можно предложить, по крайней мере, два варианта реализации
консольного ввода. Первый вариант представляет собой ввод последовательности символов до стандартного символа ограничителя. Такой ограничитель может заноситься во вводной поток с клавиатуры
клавишами пробел, табуляция или Enter. Сама работа оператора
ввода заканчивается при установлении факта нажатия клавиши
Enter. Во вводимую переменную записывается вся последовательность символов, введенная до символа ограничителя, а оставшаяся
часть текста хранится в буфере ввода. Учитывая проблемы перекодировки, ввод – вывод может быть реализован с помощью последовательности операторов примера 49
Пример 49. Ввод до стандартного ограничителя
str1 = «В каком городе вы живете?\n»;
stdbuf1=Code1251_866(str1);
cout << str1;
cout << stdbuf1;
//Копирование строк
cin >> stdbuf2;
str1 = stdbuf2;
str2 = Code866_1251 (stdbuf2);
cout << stdbuf2<<endl;
В результате ввода с клавиатуры строки "Санкт-Петербург" и нажатия клавиши Enter программа отображает на консоли текст, показанный на рис. 64. Попытка ввести с консоли нескольких слов,
разделенных пробелом, например, текста "Большой привет", приводит к тому, что в переменную str2 попадает только первое слово
строки, которое и отображается (рис. 65).
Рис. 64. Ввод строки с консоли
Рис. 65. Отображение введенной строки
“Большой привет” в окне программы.
154
Второй вариант ввода позволяет разрешить эту проблему указанием заданного программистом символа ограничителя. В рассматриваемом примере таким символом является символ '|'. Воспользовавшись
библиотечной функцией, мы имеем оператор программы пример 50.
Пример 50. Использование ограничителя ввода
getline (cin, str1,'|');
Выполнение этого оператора будет продолжаться до тех пор, пока
во входной поток с клавиатуры не будет введен символ '|', а за ним
будет нажата клавиша Enter. Результат ввода окажется в переменной str1.
Программирование операций со строками
При написании программ, предназначенных для обработки
строк, приходится исходную задачу разбивать на последовательность элементарных операций, реализуемых библиотечными функциями, методами и свойствами. Предлагаемый системами программирования набор таких операций, как правило, избыточен. Поэтому перед программистом встает задача выбора необходимых ему
для решения конкретный задачи функций, свойств и методов, которую он решает исходя из своих предпочтений. В большинстве языков программирования существуют функции или операции ввода –
вывода строк, их копирования, сцепления, сравнения, определения
длины, поиска образца, выделения, вставки и замены подстроки, а
также удаления символов из подстроки. Поэтому разрабатывать алгоритм решения задачи обработки строк целесообразно в терминах
приведенных выше строковых операций.
При работе со строками очень полезен отладчик. Выполняя
программу по шагам и контролируя в окне локальных переменных (Видимые) текущие значения переменных программы, можно
сравнительно быстро добиться необходимого результата. Как и при
проектировании других программ, особую важность при проектировании и написании кодов программ играет тестирование. Напоминаем, что тесты должны разрабатываться совместно с проектированием программы. В задачах обработки строк важную роль играет проверка программы на работоспособность в рабочем диапазоне
данных и на его границах, а также реакции программы на ошибочные действия оператора.
Отдельную проблему в консольных приложениях при обработке строк представляет ввод – вывод символов кириллицы. Очевидно, что эта проблема не имеет универсального решения и в каждом
155
конкретном случае программист может решить ее по-своему. Тем
не менее, ожидать, что эта проблема будет решена разработчиками
Visual C++ не приходится, в связи с чем в дальнейшем представляется целесообразным переход на другие программные приложения.
В языке С существовала целая библиотека стандартных функций, ориентированная на работу со строками. К сожалению, с появлением класса string она устарела, поскольку использует статическую концепцию распределения памяти. Поэтому для работы со
строками в C++ представляется целесообразным использовать набор методов класса string. Рассмотрим некоторые возможности реализации типовых операций со строками.
Копирование строк
Проще всего выполняется оператором присваивания. Результаты работы приведенного ниже фрагмента программы в окне отладчика показаны на рис. 51.
Пример 51. Копирование строк
cin >> stdbuf2;
str1 = stdbuf2;
str2 = Code866_1251 (stdbuf2);
cout << stdbuf2<<endl;
Сцепление строк
Выполняется присоединением одной строки к концу другой. Результаты работы приведенного ниже фрагмента программы в окне
отладчика показаны на рис. 66 и 67.
Рис. 66. Результат копирования и сцепления введенной строки
без преобразования и с преобразованием
Рис. 67. Результат сцепления введенной строки в окне отладчика
156
Пример 52. Сцепление строк
string str3;
str1="Большой";
str2="привет";
str3=str1+" "+str2+".";
Еще один вариант сцепления строк использует метод append
класса string. В результате выполнения следующего фрагмента
программы строка str3 будет содержать текст "Большой привет"
(рис. 68 и 69).
Рис. 68. Результат сцепления строки “Большой привет”
в окне отладчика
Рис. 69. Результат сцепления строки “Большой привет”
в окне программы
Пример 53. Сцепление строк с использованием метода append
str1="Большой";
str2="привет";
str3=str1.append(str2);
Сравнение строк
Для выполнения этой операции можно использовать метод
compare класса string. После выполнения последнего оператора
приведенного ниже фрагмента программы ячейка resultcmp содержит 0. Если добавить пробел в конец строки str2, в результате чего
ее содержимое изменится на "Большой", то результатом работы метода compare будет отрицательное число –1, указывающее на факт от157
сутствия совпадения строк (рис. 70). Это обстоятельство необходимо
учитывать при сравнении символьных строк.
Рис. 70. Демонстрация результатов работы метода сравнения
в режиме отладки
Пример 54. Сравнение строк
int resultcmp1, resultcmp2;
str1="Большой";
str2="Большой";
str3="Большой";// В конец строки добавлен пробел
resultcmp1 = str1.compare(str2);
resultcmp2 = str1.compare(str3);
Определение длины строки
Может быть реализовано через метод size класса string.
Пример 55. Определение длины строки
int strlen;
strlen = str1.size();
Поиск образца
Поиск образца (один или более символов) в исходной строке реализуется свойством find класса string. Оно возвращает номер позиции, после которой находится искомый образец. Второй параметр
свойства задает номер позиции, после которой надо начинать поиск.
Если образец не найден, то свойство возвращает константу –1. После выполнения фрагмента программы, приведенного ниже, ячейка
position1 будет содержать число 5:
Существуют свойство find_first_of, выделяющее первое вхождение любого символа из заданной подстроки в строку. Аналогично свойство find_last_of выделяет последнее вхождение заданных
158
символов. В приведенном ниже примере в ячейках position2 и
position3 появятся соответственно значения 2 и 20 (рис. 71):
Рис. 71. Демонстрация результатов работы методов определения длины
сроки и поиска образца в режиме отладки
Пример 56. Поиск образца
int position1, position2, position3;
str1="Большой";
position1 = str1.find ("о", 3);
//Первое или последнее вхождение
str1="Большой и очень большой";
str2 = "лш";
position2 = str1.find_first_of (str2);
position3 = str1.find_last_of (str2);
Выделение подстроки из исходной строки
Выделение, начиная с некоторой позиции в строке, некоторого
заданного количества символов (подстроки) из исходной строки. После выполнения фрагмента программы, приведенного ниже, строка
stdbuf1 сохранит старое значение, а строка stdbuf2 будет содержать
"льшо" (рис. 72).
Рис. 72. Демонстрация результатов работы методов выделения
и вставки образца в режиме отладки
159
Пример 57. Выделение подстроки из исходной строки
stdbuf1 ="Большой";
stdbuf2 = stdbuf1.substr (2, 4);
Вставка подстроки в исходную строку
Существует возможность методом insert класса string вставить
подстроку в исходную строку. Можно указать позицию в исходной
строке, начиная с которой будет производиться вставка (в нашем
примере с позиции 8). В результате выполнения представленного
ниже фрагмента программы строки str1 и str3 будут содержать
одинаковый текст "Большой и очень большой привет" (рис. 72).
Пример 58. Вставка подстроки в исходную строку
str1="Большой привет";
str2="и очень большой";
str3=str1.insert (8, str2);
Замена подстроки в исходной строке на другую подстроку
С помощью метода replace класса string можно заменить содержимое исходной строки на другую подстроку. При этом надо указать позицию в исходной строке (в рассматриваемом ниже примере
позиция 0) и количество заменяемых символов (в рассматриваемом
ниже примере 7). Результатом выполнения представленного фрагмента программы будет строка "Общий привет" (рис. 73).
Рис. 73. Демонстрация результатов работы методов замены подстроки
в исходной строке на другую подстроку
Пример 59 Замена подстроки в исходной строке на другую подстроку
str1="Большой привет";
str2="Общий";
str1=str1.replace(0, 7, str2);
Удаление символов из строки
Удаление символов строки, начиная с некоторой позиции (в данном случае позиции 4) реализует метод erase класса string. По160
сле выполнения оператора программы строка str1 будет иметь вид
"Общи": Если задать второй параметр свойству erase, то можно удалить заданное количество символов начиная с заданной позиции.
После выполнения приведенного ниже фрагмента программы строка str2 будет иметь вид "Бошой" (рис. 74).
Рис. 74. Демонстрация результатов работы методов
удаления подстроки в исходной строке
Пример 60. Удаление символов из строки
str1.erase(4);
str2="Большой";
str2.erase (2,2);
Удаление начальных и (или) конечных пробелов из строки
Отдельного метода, удаляющего начальные и конечные пробелы
в библиотеке класса String нет. Поэтому для выполнения этой операции приходится воспользоваться комбинацией методов. Так, для
удаления лидирующих пробелов в строке можно выполнить следующую последовательность операторов:
Пример 61. Удаление начальных и (или) конечных пробелов из
строки
str1="
Большой
";
position1 = str1.find_first_not_of (" ");
str1 = str1.erase (0, position1);
//Конечные пробелы могут быть удалены следующим образом:
Рис. 75. Демонстрация результатов работы методов
удаления начальных и конечных пробелов в строке
161
position2 = str1.find_last_not_of (" ");
strlen = str1.size ();
str2 = str1;
str2 = str2.erase (position2+1, strlen-position2);
Результаты работы программы в режиме отладки можно увидеть
на рис. 75.
Лабораторная работа №10.
Обработка символьных строк
Цель работы: приобретение навыков работы со строками
Задание
Научитесь писать программы, реализующие возможности обработки строк, и выполнять их тестирование и отладку.
Порядок выполнения работы
1. Создайте новый проект в новом решении.
2. Согласуйте с преподавателем номер своего варианта задания.
3. Разработайте алгоритм и напишите программу, реализующую ваше индивидуальное задание.
4. Проведите трассировку вашей программы и функции с использованием команд отладчика Шаг с заходом (F11) и Шаг с обходом (А10).
5. Подготовьте тесты для вашей программы и оформите их в соответствии с таблицей 9. Проведите тестирование программы.
6. Проведите тестирование созданной вами программы и, при
обнаружении ошибок, выполните ее отладку.
7. Напишите еще один вариант вашей программы в виде функции, вызываемой из главной программы, и обеспечивающей ввод и
вывод строк с консоли. Проведите ее тестирование.
Варианты заданий по обработке строк
1. Задать строку и определить в ней количество символов 'a' и
слов HELLO.
2. Задать текст и выделить из него отдельные слова предполагая,
то они разделены в тексте символом пробел ' '.
3. Задать две строки, соединить их в одну и определить количество символов 'r' и слов bye.
4. Задать строку и какое-либо слово. Определить, сколько раз
введенное слово входит в данную строку.
162
5. Задать строку. Подсчитать в ней количество символов, код которых меньше 120, и количество слов hello.
6. Задать три строки. Соединить их в одну и определить в ней количество символов 'C' и словосочетаний Turbo C.
7. Задать строку. Подсчитать количество цифр, входящих в первую треть строки, и вывести их количество на экран, а также определить, где больше символов '*' – в первой половине строки или во
второй.
8. Задать строку и подсчитать в ней количество слов ПРИВЕТ и
ПРИВЕТЫ.
9. Задать строку. Определить количество символов пробел ' '
в строке и количество слов ЭВМ.
10. Задать строку символов и определить, содержатся ли в ней
все символы, необходимые для написания слова computer.
11. Задать строку символов и какое-либо слово. Определить, содержатся ли в строке все символы, необходимые для написания
данного слова.
12. Задать строку символов и определить, есть ли в ней заглавные русские буквы и упорядочены ли они по алфавиту.
13. Задать строку символов. Определить количество символов
в строке и занести их в другую строку в обратном порядке.
14. Задать строку символов. Занести в другую строку символы,
встречающиеся в исходной строке больше одного раза.
15. Задать строку символов. Если в строке есть символ '+', то
символы, следующие за ним, заменить на '*'.
16. Задать строку символов. Определить сколько в ней цифр. Занести в другую строку буквы русского алфавита, встречающиеся
в строке.
17. Задать строку символов. Определить имеется ли среди символов пара соседствующих одинаковых. Например, если заданная
строка имеет вид "пррвоугаав", то в другую строку должны быть занесены символы "ра".
18. Задать две строки символов. Соединить их в одну, взяв из
первой только цифры, а из второй первые 3 символа. Занести результат в другую строку.
19. Задать строку символов. Найти в строке последнее вхождение символа "." и все символы, следующие за ним, заменить на "?".
20. Задать две строки символов. Соединить вторую половину
первой строки и первые пять символов второй строки. Занести результат в другую строку.
163
21. Задать текст. Заменить в нем все прописные русские буквы
на соответствующие строчные.
22. Задать строку символов. Занести в другую строку вторую
треть исходной строки, поместив ее в кавычки.
23. Задать текст. Напечатать его, подчеркивая (ставя минусы
в соответствующих позициях следующей строки) все входящие
в него заглавные и строчные русские буквы.
24. Задать текст. Заменить в нем все строчные буквы на соответствующие прописные.
25. Задать текст. Определить в нем факт наличия последовательностей символов пробел и заменить каждую последовательность на
один символ пробел.
Контрольные вопросы
1. Как определить код символа, встречающегося в тексте?
2. Почему консольный вывод отображает символы с ошибками?
3. Что такое сцепление строк и как можно выполнить эту операцию?
4. Почему существует несколько вариантов консольного ввода?
5. В чем опасность начальных и концевых пробелов в строке?
6. Как можно изменить текст в строке?
7. Как можно перевести текст из одного регистра в другой?
8. Как можно заменить символ в строке?
9. Как можно организовать повторение слов в тексте?
10. Как в тексте можно выделить цифры?
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, номер
варианта задания, рисунки алгоритмов разработанных программ,
тексты программ и таблицы с перечнем тестов и отметками о результатах их выполнения (ваш вариант таблицы 9). Кроме этого,
должны быть сформулированы ваши выводы по результатам выполнения лабораторной работы.
5.2. Работа с файлами
Данные в памяти ЭВМ хранятся в виде двоичных чисел. Единственное, что может сделать процессор с данными – это извлечь содержимое некой ячейки памяти, выполнить над ним некоторое заранее оговоренное и выбранное из перечня возможных действие и
164
занести число (результат) назад в память в ту же или другую ячейку. Вполне естественным является следующий вопрос: каким способом числа попали в ячейку памяти первоначально?
Существует всего четыре варианта ответа. Во-первых, эти данные могли остаться в ячейке памяти от предыдущей программы
или, если программа загружается в память сразу после включения машины, в ячейке памяти осталась случайная комбинация
установок триггеров, возникшая после подачи напряжения на
ОЗУ. Часто такие данные называют мусором. Во-вторых, данные
могут быть размещены в ячейке вместе с программой, то есть
сама программа при компиляции предусматривает некое начальное значение в конкретной ячейке памяти. В-третьих, данные
могли попасть в ячейку в результате выполнения некой команды
процессора на запись данных в ОЗУ, например, при выполнении
оператора присваивания. Наконец, в-четвертых, данные могли
быть занесены в ячейку памяти в результате выполнения команды ввода.
На первый взгляд существенных различий между двумя последними вариантами нет. Тем не менее, следует принимать во
внимание следующее обстоятельство: в третьем варианте содержимое ячейки является результатом вполне конкретных действий над данными, которые при необходимости могут быть повторены. В то же время, в четвертом варианте в ячейке памяти
находится число, представляющее собой результат реального физического воздействия на устройство ввода в конкретный момент
времени, которое может быть уникальным и никогда более не повторяющимся.
Файл может быть программой (исполняемыми кодами), исходным текстом, документом, просто хранилищем записей. В C++
отсутствуют операторы для работы с файлами, поэтому все необходимые действия с файлами выполняются с помощью функций, включенных в стандартную библиотеку. Они позволяют
работать с различными физическими устройствами, подключенными к компьютеру. Такими устройствами могут быть диски,
принтеры, коммуникационные каналы и т.д. Все эти устройства
существенно отличаются друг от друга принципом действия, скоростью работы и объемом передаваемой информации. Для унификации работы с устройствами файловая система рассматривает их как единое абстрактное логическое устройство, называемое
потоком.
165
В C++ имеется два типа потоков и, как следствие, два типа файлов: текстовые и бинарные1. Текстовый поток – это последовательность кодов символов (например, ASCII). Содержимое текстового
потока может рассматриваться как совокупность записей (строк),
между которыми существует некие символы разделители. Фрагмент текста (строка) рассматривается как последовательность символов от разделителя до разделителя. В качестве разделителя могут
использоваться символы пробела, табуляции, возврата каретки и
т.п. Как следствие, текстовый поток представляет собой последовательность строк случайной длины и может быть использован для
передачи и хранения собственно текстов. Поэтому для того, чтобы
получить доступ к конкретной строке, надо последовательно прочитать все символы потока с самого начала.
Бинарный поток – это последовательность байтов, каждый из
которых может рассматриваться как самостоятельная запись. В отличие от текстового потока, в бинарном потоке существует возможность обращения к записи по ее номеру в потоке, что существенно
увеличивает скорость обработки данных. Иногда текстовые файлы
называют последовательными, а бинарные – файлами с произвольным доступом.
Работа с файлами обеспечивается функциями специальной библиотеки fstream, поэтому в программе должна быть соответствующая строка заголовка. Перед началом работы программы с файлом
он должен быть открыт. Предполагается, что первоначально файл
(если он существует) хранится на внешнем устройстве, например,
жестком диске. Открытие файла подразумевает установление связи
программы с записями. Для этого операционная система организует специальный буфер (область памяти), в которую и копирует информацию с внешнего устройства.
Для открытия текстового файла, необходимо описать переменную типа ofstream. Через эту переменную ваша программа будет
устанавливать связь с буфером операционной системы, в котором
хранится копия файла. В общем случае оператор открытия потока
будет иметь вид:
file_name.open ("file_name ", mode);
Здесь file_name – переменная, описанная как ofstream, представляющая собой имя файла на диске, mode – режим работы с открываемым файлом. Если указано только имя файла, то он откры1
166
Иногда такие файлы называют еще и двоичными
вается в текущем каталоге вашего решения. Можно указать полное
имя файла, тогда при его записи вместо одного слеша нужно ставить
двойной. Параметр mode (табл. 15) может отсутствовать и в этом случае файл открывается в режиме по умолчанию для данного потока. Полный набор параметров для конкретного файла указывается
при его открытии. Если используется несколько параметров, то они
указываются в поле последовательно и разделяются символом "|".
Ключ двоичного файла указывается дополнительно.
Таблица 15
Константы, используемые для работы с файлами
mode
ios::in
ios::out
Выполняемое действие
Открыть файл в режиме чтения данных, режим по умолчанию для потоков ifstream
Открыть файл в режиме записи данных (при этом
информация о существующем файле уничтожается),
режим по умолчанию для потоков ofstream
ios::app
Открыть файл в режиме записи данных в конец файла
ios::ate
Передвинуться в конец уже открытого файла
ios::trunc
Очистить файл, это же происходит в режиме ios::out
ios::nocreate
Не выполнять операцию открытия файла, если он не
существует
ios::noreplace
Не открывать существующий файл
ios::binary
Ключ бинарного (двоичного) файла
ios::beg
Указатель на начало файла
ios::cur
Указатель на текущей позиции
ios::end
Указатель на конец файла
Если файл уже существует, то при открытии его в режиме
ios::out старый файл удалится, а новый запишется на его место.
Если предполагается внесение изменений в уже существующий
файл, он должен быть открыт в режиме ios::app.
Пример 62 содержит вариант программы, выполняющей действия по созданию, чтению и дополнению уже существующих текстовых файлов. Программа создана как консольное приложение,
поэтому вывод информации производится во всплывающее при
запуске окно. Составляющие программы разделены строками комментария в виде звездочек и иллюстрируют примеры открытия для
чтения и записи не существующего текстового файла, добавление
записей в файл, работу с полным именем файла. Для того, чтобы
167
устранить существующее противоречие между кодировками консольных приложений и собственно Windows, в программе используется описанная выше в примере 45 функция перекодировки символов кириллицы. Отладка программы производилась в пошаговом
режиме отладчика с контролем содержимого ячеек памяти в окне
локальных переменных.
Пример 62. Работа с текстовыми файлами
// Работа с текстовыми файлами
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string Code1251_866 (string buffer);
// Преобразование Windows -> Console
string file_name, o_string_buf, in_string_buf;
// Строковые переменные
// для хранения имен
ofstream oFile;
// Объявление файла потока вывода
ifstream iFile;
// Объявление файла потока ввода
fstream File;// Объявление файла потока ввода вывода
char name_file1[13] = "test_out.txt";
// Имя файла для открытия
// в текущем каталоге
char name_file2[63] = "C:\\Users\\Саша\\Documents\\Лекции\\visual C\\test_out.txt";
// Полное имя файла (обратные слеши сдвоенные)
//*****************************************************************
//Открытие для чтения не существующего текстового файла
file_name="zero.txt"; // Имя файла
iFile.open(file_name, ios::in);
if (iFile)// Файл открывается с флагом 'False'
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
}
else
{ cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n");
}
iFile.close();
//*****************************************************************
// Открытие для записи не существующего файла в текущем каталоге
file_name = (const char*) name_file1;
// Преобразование к типу string
oFile.open (file_name, ios::out);
if (oFile)// Файл открывается и создается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно создан и открыт"+ "\n");
o_string_buf= "Самый большой привет \n";
oFile << o_string_buf; // Собственно запись в файл
cout << Code1251_866 (o_string_buf + "Вот это мы записали в файл\n\n");
168
}
else
{ cout << Code1251_866 ("Ошибка открытия файла "+ file_name + "\n");
}
oFile.close();// Закрытие файла. Файл создан и сохранится на диске
//*****************************************************************
// Открытие для дозаписи существующего текстового файла в текущем каталоге
file_name = (const char*) name_file1;
// Преобразование к типу string
File.open (file_name, ios::in);
if (File) //Файл открывается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
//in_string_buf="";
while (! File.eof ())
{
File >> in_string_buf; // Чтение из файла
cout << Code1251_866 (in_string_buf) << " ";
}
cout << Code1251_866 ("Это было записано в файле "+ file_name + "\n");
File.close(); // Файл открывался для чтения, поэтому сейчас он закрывается
File.open(file_name, ios::app); //Файл открывается для дозаписи
o_string_buf= "\nУспехов вам в изучении С++ \n";
File << o_string_buf; // Собственно запись в файл
cout << Code1251_866 (o_string_buf);
cout << Code1251_866 ("А вот это мы дописали в файл "+ file_name + "\n");
}
else
{cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n");
}
File.close();
std::remove(name_file1);
// Удаление созданного файла
//*****************************************************************
// Открытие для записи не существующего файла с полным именем
file_name = (const char*) name_file2;
// Преобразование к типу string
oFile.open(file_name, ios::out);
if (oFile) //Файл открывается и создается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно создан и открыт"+ "\n");
o_string_buf= "Это тестовый файл, созданный с полным именем \n";
oFile << o_string_buf; // Собственно запись в файл
cout << Code1251_866 (o_string_buf + "Вот это мы записали в файл\n\n");
}
else
{ cout << Code1251_866 ("Ошибка открытия файла "+ file_name + "\n");
}
oFile.close();// Закрытие файла. Файл создан и сохранится на диске
std::remove(name_file2);
169
return 0;
}
//*****************************************************************
// Преобразование из кодировки Windows в кодировку Console
string Code1251_866 (string buffer)
{ using namespace std;
int s, i;
string outbuf="";
s= buffer.size ();
unsigned char cbuf;
for (i=0; s>i; i++)
{ cbuf = buffer[i];
if (cbuf >= 0xC0 && cbuf <= 0xEF)
{cbuf = cbuf – 64;} // 64=0x40
else {};
if (cbuf >= 0xF0 && cbuf <= 0xFF)
{cbuf = cbuf – 16;} // 16=0x10
else{}
outbuf = outbuf += cbuf;
}
return outbuf;
}
Работу с бинарными файлами иллюстрирует пример 63. В целом
работа с такими файлами существенно не отличается от работы
с текстовыми. Точно так же существующий имя файла представляет собой переменную, содержимое которой меняется после каждого
обращения. Соответственно, для работы бинарный файл тоже должен быть создан и открыт. Бинарные файлы позволяют обращаться
к записи в файле по ее номеру. Такая возможность обеспечивается
за счет создания регулярной структуры записей определенного формата, которую легко обеспечить, например, за счет типов данных,
определяемых пользователем. Запись и чтение информации из файла производится с помощью функций write() и read() побайтно с использованием переменных типа char.
Указатель записи бинарного файла последовательно нумерует
все байты записей. Может оказаться, что одна запись в файле содержит несколько байт. В этом случае расчет значения указателя начала следующей записи производится как добавление к значению
указателя числе, соответствующего размеру реальной записи.
Начальное положение указателя записи в файле зависит от ключа открытия файла (табл. 15). Для задания текущего положения
указателей чтения и записи бинарного файла используется функции seekg() (указатель чтения) и seekp() (указатель записи). Теку170
щее положение указателя можно узнать с помощью функций tellg()
и tellp(). Положение указателей можно задавать относительно начала или конца файла (ключи ios::beg, ios::end). Этом случае указатель имеет абсолютный номер записи в файле. Если установлен
ключ ios::cur, то задается смещение записи по отношению к текущей.
Пример 63. Работа с бинарными файлами
// Работа с бинарными файлами
#include <stdio.h>
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string Code1251_866 (string buffer);
// Преобразование Windows -> Console
string file_name, o_string_buf, in_string_buf;
// Строковые переменные для
// хранения имен
ofstream oFile;
// Объявление файла потока вывода
ifstream iFile;
// Объявление файла потока ввода
fstream File;// Объявление файла потока ввода вывода
char name_file1[13]="test_out.bin"; // Имя файла для открытия в текущем каталоге
char name_file2[63]="C:\\Users\\Саша\\Documents\\Лекции\\visual C\\test_out.bin";
// Полное имя файла (обратные слеши сдвоенные)
int i, n, size, pos;
long buf;// Буфер и указатель позиции в файле произвольного доступа
//*****************************************************************
//Открытие для чтения не существующего в текущем каталоге бинарного файла
file_name="zero.bin"; // Имя файла
iFile.open (file_name, ios::binary|ios::in);// Файл потока ввода открывается на ввод
// с ключом ios::binary
if (iFile)// Файл открывается с флагом 'False"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
}
else
{ cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n");
}
iFile.close();
//*****************************************************************
// Открытие для записи не существующего в текущем каталоге бинарного файла
size=20; // Задается длина символьной записи
char *massiv = new char[size];
// Массив для хранения записываемой переменной
file_name = (const char*) name_file1;// Преобразование имени файла к типу string
oFile.open (file_name, ios::binary|ios::out);
// Файл потока вывода открывается на вывод с ключом ios::binary
171
if (oFile)// Файл открывается и создается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно создан и открыт"+ "\n");
buf=100;
n=4;
for (i=0; i<n; i++)
{
buf = buf+i;
_itoa_s (buf, massiv, size,10);
// Преобразование
// к символьному виду
oFile.write (massiv, size);
// Запись в символьном виде
cout << buf << Code1251_866 (" Вот это мы записали в файл\n");
}
}
else
{ cout << Code1251_866 ("Ошибка открытия файла "+ file_name + "\n");
}
oFile.close ();// Закрытие файла. Файл создан и сохранится на диске
//*****************************************************************
// Открытие для дозаписи существующего в текущем каталоге бинарного файла
file_name = (const char*) name_file1;
// Преобразование имени файла к типу
// string
iFile.open (file_name, ios::binary|ios::in );
// Файл потока ввода открывается на ввод с ключом ios::binary
if (iFile) //Файл открывается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
buf =0;
while (! iFile.eof ())
{
iFile.read (massiv, size);
// Чтение из файла
buf=atoi (massiv);
// Преобразование к числовому виду
if (! iFile.eof())
{cout << buf << Code1251_866 (in_string_buf) << "\n";
}
else {};
}
cout << Code1251_866 ("Это было записано в файле "+ file_name + "\n");
iFile.close();// Файл открывался для чтения, поэтому сейчас он закрывается
oFile.open (file_name, ios::binary|ios::app);
//Файл потока ввода – вывода открывается для дозаписи с ключом ios::binary
buf=200;
n=4;
for (i=0; i<n; i++)
{
buf=buf+i;
_itoa_s (buf, massiv, size, 10);
oFile.write (massiv, size);
cout << buf << Code1251_866 (" Вот это мы записали в файл\n");
}
172
cout << Code1251_866 (" А вот это мы дописали в файл "+ file_name + "\n");
}
else
{cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n");
}
oFile.close ();
//*****************************************************************
// Открытие для чтения существующего в текущем каталоге бинарного файла
file_name = (const char*) name_file1;
// Преобразование имени файла к типу
// string
iFile.open (file_name, ios::binary|ios::in);// Файл потока ввода открывается на ввод
// с ключом ios::binary
if (iFile) //Файл открывается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
buf = 0;
while (! iFile.eof())
{
iFile.read (massiv, size);
// Чтение из файла
buf = atoi (massiv);
// Преобразование к числовому виду
if (! iFile.eof())
{cout << buf << Code1251_866 (in_string_buf) << "\n";
}
else {};
}
cout << Code1251_866 ("Это было записано в файле "+ file_name + "\n");
iFile.close();// Файл открывался для чтения, поэтому сейчас он закрывается
}
else
{cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n");
}
oFile.close ();
//*****************************************************************
// Открытие для чтения и записи существующего в текущем каталоге бинарного файла
file_name = (const char*) name_file1;
// Преобразование имени файла к типу
// string
File.open (file_name, ios::binary|ios::in|ios::out);
// Файл потока ввода открывается на ввод с ключом ios::binary
if (File) //Файл открывается с флагом "True"
{ cout << Code1251_866 ("Файл "+ file_name + " успешно найден и открыт"+ "\n");
//*******************************************
File.read (massiv, size);// Чтение из файла с нулевой позиции
buf=atoi(massiv);
// Преобразование к числовому виду
pos = static_cast <int> (File.tellg ()/size); // Номер текущей записи
if (! File.eof ())
{cout << Code1251_866 ("Значение ") << buf <<Code1251_866 (" в позиции ") << pos << "\n";}
else {};
173
//*******************************************
pos = 4;
File.seekg (pos* size, ios::beg); // Переход к записи 4 от начала файла
pos = static_cast <int>(File.tellg ()/size); // Номер текущей записи ввода
File.read (massiv, size);// Чтение из файла из 4 записи
buf=atoi(massiv);
// Преобразование к числовому виду
if (! File.eof())
{cout << Code1251_866 ("Значение ") << buf <<Code1251_866 (" в позиции ") << pos << "\n";}
else {};
//*******************************************
pos=4;
buf=333333333;
File.seekp (pos*size, ios::beg); // Переход к записи 4 от начала файла
_ltoa_s (buf, massiv, size,10);
// Преобразование к символьному виду
File.write (massiv, size);
// Запись в символьном виде
File.seekg (pos*size, ios::beg); // Переход к записи 4 от начала файла
File.read(massiv, size);
// Чтение 4 записи файла
buf=atoi(massiv);
// Преобразование к числовому виду
if (! File.eof())
{cout << Code1251_866 ("Значение ") << buf <<Code1251_866 (" в позиции ") << pos << "\n";}
else {};
//*******************************************
pos = static_cast <int>(File.tellg ()/size); // Номер текущей записи ввода 5
cout << Code1251_866 ("Номер позиции ") << pos << "\n";
File.seekg(size, ios::cur);
// Переход к следующей записи
pos=static_cast <int>(File.tellg ()/size); // Номер текущей записи ввода 6
cout << Code1251_866 ("Номер позиции ") << pos << "\n";
File.read(massiv, size);
// Чтение 6 записи файла
buf=atoi(massiv);
// Преобразование к числовому виду
if (! File.eof())
{cout << Code1251_866 ("Значение ") << buf <<Code1251_866 (" в позиции ") << pos << "\n";}
else {};
//*******************************************
cout << Code1251_866 ("Это было записано в файле "+ file_name + "\n");
File.close (); //
}
//*****************************************************************
else
{cout << Code1251_866 ("Ошибка открытия файла "+ file_name+ "\n" );
}
oFile.close ();
std::remove (name_file1);
// Удаление созданного файла
delete [] massiv;
// Удаление динамического массива
return 0;
174
}
//*****************************************************************
// Преобразование из кодировки Windows в кодировку Console
string Code1251_866 (string buffer)
{ using namespace std;
int s, i;
string outbuf="";
s= buffer.size ();
unsigned char cbuf;
for (i=0; s>i; i++)
{ cbuf = buffer[i];
if (cbuf >= 0xC0 && cbuf <= 0xEF)
{cbuf = cbuf – 64;} // 64=0x40
else {};
if (cbuf >= 0xF0 && cbuf <= 0xFF)
{cbuf = cbuf – 16;} // 16=0x10
else{}
outbuf = outbuf += cbuf;
}
return outbuf;
}
В процессе работы с файлом изменение его содержимого происходит в буфере операционной системы, а не на диске. Поэтому для
того, чтобы эти изменения сохранилась, например, на диске, необходимо выполнить операцию закрытия файла, иначе сделанные
в файле изменения будут потеряны. Закрытие файла происходит
после выполнения функции close. Кроме этого, все открытые файлы автоматически закрываются после завершения работы программы. Это обстоятельство приходится принимать во внимание, например, при отладке программы, когда при обнаружении программной
ошибки трассировка программы завершается принудительно до достижения ее последнего оператора.
175
6. СОЗДАНИЕ ПРИЛОЖЕНИЙ ДЛЯ WINDOWS
6.1. Терминология
Термин «программа» появился в обращении задолго до появления первых ЭВМ. Под программой обычно понимали (и понимают)
последовательность действий, ведущих к достижению некой цели.
Для того, чтобы уточнить назначение программы и ее специфику,
программисты очень быстро начали говорить «компьютерная программа» или «программа для ЭВМ». Такая программа обычно содержала последовательность кодов процессора или операторов языка высокого уровня, выполнение которых обеспечивало достижение
цели, как правило, заключающейся в получении результатов вычислений. Усложнение задач, решаемых на вычислительных установках, привело к необходимости хранения совместно с программой наборов данных, используемых при вычислениях. Эти наборы
стали размещать вместе с программами в самостоятельных папках
(каталогах). Такой прием позволял легко переносить программы
с диска на диск и с машины на машину без опасений потерять связанные с программой данные.
Развитие и усложнение операционных систем привело к усложнению процедуры запуска программы на выполнение. Сейчас
программа может быть запущена под управлением операционной
системы только в том случае, если операционной системе известно
о ее существовании. Стандартный процесс информирования операционной системы о появлении новой программы называется установкой приложения, а работающая под управлением операционной
системы программа – приложением. Оно представляет собой не
только набор исполняемых кодов и данных собственно программы,
но и совокупность дополнительных кодов и данных, обеспечивающих связь с операционной системой. Как следствие, для создания
приложений требуются специальные технологии, зависящие от
операционной системы, под управлением которой предполагается
использовать приложение, и реализованные в инструментальных
системах программирования.
Для создания приложений для Windows система программирования Visual C++ предлагает, по крайней мере, три основные технологии – применявшуюся ранее технологию и использующую библиотечный класс windows.h технологию API (Application Programming
Interface), технологию на основе использования библиотеки MFC
(Microsoft Foundation Classes) и технологию, основанную на исполь176
зовании библиотеки Windows Forms, предназначенную для построения оконных приложений, выполняемых так называемой средой
.NET Framework. Она в свою очередь состоит из двух элементов:
общеязыковой исполняющей среды (Common Language Runtime –
CLR), в которой выполняются программы, и набора библиотек.
Можно сказать, что CLR представляет собой некую стандартную
программную составляющую операционной системы, которая имеет собственный входной язык и предназначена для выполнения программ, подготовленных на самых разнообразных языках программирования. Обычно этот язык называют CLI (Common Language
Infrastructure). Компилятор C++ переводит исходную программу
на язык CLI, а далее подключаются необходимые библиотеки и результат переводится компилятором CLI в машинный код [10].
6.2. Создание приложения Windows MFC
Разрабатывая свой язык, Керниган и Ритчи предположили, что
программа, создаваемая на языке С, всегда имеет одну точку входа, в которую при запуске передается управление от операционной
системы. Для обозначения этой точки они ввели понятие главной
программы (main). Программируя, например, консольные приложения, модуль main() надо было явно включать в состав программы,
а сам модуль мог возвращать (int main()) или не возвращать (void
main()) результат работы программы в операционную систему.
Особенностью приложения Windows MFC является то, что
модуль главной программы приложения имеет другое имя int
WinMain(). Он вызывается операционной системой в момент запуска
программы и аналогичен по назначению функции main () языка C.
Данная функция присутствует в каждой программе, написанной
под Windows.
Поскольку операционная система Windows является многозадачной, может оказаться, что одновременно на компьютере выполняется несколько программ с одинаковым именем int WinMain(). Ранее эта проблема разрешалась за счет включения функции WinMain()
в программу приложения и передаче так называемого дескриптора
приложения, однозначно идентифицирующего программу. Сейчас
подключение обеспечивается за счет использования технологии
классов и объектов, которые рассматривают каждую выполняемую
программу как объект класса CWinApp. Функция WinMain() входит
в состав класса и скрыта от пользователя внутри библиотеки MFC.
Получить доступ к ней программист не может, и, следовательно, не
177
может изменить её код. Тогда возникает вопрос о том, как создать
приложение, отличающееся одно от другого? Для достижения этой
цели используется объект theApp – глобальная переменная, сгенерированная до выполнения функции WinMain() при помощи конструктора и имеющая тип класса CMFCApp. Через него и передаются
всё необходимые данные в конкретную главную функцию.
Алгоритм работы функции WinMain() условно можно разбить на
три основных этапа (более наглядно этапы работы главной функции
представлены на рис. 76). Первый этап – это вызов функции theApp.
InitInstance(). Ее главной задачей является создание рабочего окна
для конкретного приложения с заданными пользователем параметрами. Если функция возвращает значение TRUE, то окно приложения создано и далее вызывается собственно приложение как функция theApp.Run(), а если FALSE, то окно удаляется (вызывается функция theApp.ExitInstance ()).
На втором этапе управление передается функции theApp.Run().
Она обрабатывает события программы, т. е. выбирает сообщения из
очереди и отправляет их главному окну. Коды функции int WinMain()
скрыты от программиста, а вот текст функции инициализации
InitInstance объекта theApp доступен. Поэтому для дальнейшего
программирования приложения используется именно эта функция.
theApp.ExitInstance
Деинициализация
приложения
WinMain
theApp.InitInstance
theApp.Run
нет
Начало цикла
да
Конец
Run
Обработка очереди
событий
Конец цикла
ExitInstance
Конец
Конец
Рис. 76. Общий алгоритм работы приложения MFS
178
Третий этап – удаление объекта. Функция theApp.ExitInstance()
занимается деинициализацией приложения и освобождением всех
занятых ресурсов. Её основная цель – правильно завершить приложение. После его завершения происходит разрушение всех глобальных объектов с помощью деструктора класса CMFCApp.
Рассмотрим фрагмент функции InitInstance() подробнее. Интересующая нас часть кода данной функции отображена на рис. 77.
Рис. 77. Главная часть кода функции InitInstance()
Как уже отмечалось, в этой функции можно программировать
все действия, которые будет производиться с нашим окном. Часть
кода, расположенная перед главной частью, отвечает за подключение необходимых классов и параметров, которые обеспечивают
стандартную инициализацию приложения, поэтому изменять эту
часть кода не следует. Для того чтобы быстро найти любую функцию класса, в том числе InitInstance(), необходимо во вкладке Class
View выбрать класс CApp_MFS_LRApp и в нижнем окне выбрать одну
из отобразившихся функций.
Рассмотрим главную часть кода, отражающую принцип работы
данной функции:
BOOL CApp_MFS_LRApp::InitInstance()
{
……………………………………………
CApp_MFS_LRDlg dlg; /* Создаётся объект dlg класса CApp_MFS_LRDlg. В этот момент происходит создание диалога, вызов его конструктора, но на экране он ещё не отображается */
m_pMainWnd = &dlg; // Эта переменная является указателем на главное окно программы
179
INT_PTR nResponse = dlg.DoModal(); /* Данная функция загружает шаблон диалога и отображает его на экране, она закончит своё выполнение, когда пользователь закроет окно. Функция
DoModal() возвращает код клавиши и записывает его в переменную nResponse */
if (nResponse == IDOK) {………} // Нажатие клавиши ОК
else if (nResponse == IDCANCEL){………} // Нажатие клавиши CANCEL
else if (nResponse == -1) {……….} /* Сообщение об ошибке при создании окна. В этой части кода
происходит проверка соответствия значений переменной nResponse и констант IDOK, IDCANCEL
и -1. Т.е., если пользователь нажал на клавишу ОК, то будет выполняться код с условием
nResponse == IDOK */
………………………………….
return FALSE;
}
Вполне естественно предположить, что каждое приложение
для Windows имеет, по крайней мере, одно окно. Описание класса
этого окна имеется в модуле созданного и поименованного нами
в процессе построения решения класса CMFCDlg. В тексте функции
InitInstance есть объявление объекта dlg, представляющего собой
после выполнения инициализации экземпляр окна диалога.
После создания объекта dlg его можно отобразить на экране. Существует два варианта отображения диалогов – модальный и немодальный. Модальный диалог располагается поверх всех уже созданных окон программы и блокирует доступ ним. Например, в режиме
модального диалога выдаются сообщения об ошибках и выполнение других программ блокируется, ожидая реакции оператора. Немодальные окна не мешают обращению к другим открытым окнам
программы. Для непосредственного открытия окна можно воспользоваться свойствами dlg.DoModal() или dlg.Create(), которые возвращают код нажатых клавиш закрытия окна.
6.3. Пример создания оконного приложения
на примере программы калькулятор
Общая постановка задачи на программирование
В качестве примера приведем последовательность и результаты
разработки приложения для Windows, реализующей простейший
калькулятор. Рассмотрим один из вариантов проектирования калькулятора на основе метода MFC. Общий алгоритм разрабатываемой
программы представлен на рис. 78. Предполагается, что в открывшемся окне имеется возможность задать операнды, выбрать математическую операцию (в примере предполагается только операция
сложения) и в отдельном окне получить результат вычислений. Ра180
181
Вычисления
Выполнение
операции
вычитания
Отображение
результата
вычитания
Выполнение
операции
сложения
Отображение
результата
сложения
Конец
Отображение
результата
умножения
Выполнение
операции
умножения
Выбор
операции
умножения
Рис. 78. Общий алгоритм разрабатываемого приложения
Ввод второго
слагаемого
Завершение
работы
Выбор режима
Выбор
операции
вычитания
Ввод первого
слагаемого
Выбор режима
Задание
режима
вычислений
Выбор
операции
сложения
Задание
режима работы
приложения
Начало
Начало
вычислений
Отображение
результата
деления
Выполнение
операции деления
Деление на
ноль?
Выбор
операции
деления
Сообщение
Конец вычислений
Завершение
вычислений
бота с калькулятором может продолжаться до тех пор, пока пользователь не закончит работу с приложением выбрав соответствующий
режим.
Пример 64. Создание проекта приложения
Прежде чем приступить к разработке диалога, необходимо на
компьютере создать проект приложения. Для этого следует запустить интегрированную среду программирования Visual C++,
дважды щелкнув ее пиктограмму на рабочем столе. В начальном
окне интегрированной среды программирования Visual Studio, открывающемся после запуска, надо выбрать в меню пункт ФАЙЛ –
Создать–Проект. Далее в левой части диалогового окна Создать
проект надо выбрать Тип проекта язык Visual C++ MFC, в средней
части окна выбрать Приложение MFC. Внизу диалогового окна
в поле Имя вводится ваше имя проекта, в поле Расположение – диск
и папка для сохранения создающегося проекта. Тогда в поле Имя
решения автоматически появится имя, аналогичное имени проекта
(рис. 79). После нажатия кнопки ОК запустится мастер приложения
MFC. В первом окне Мастера приложений MFC ознакомьтесь с текущими параметрами проекта (рис. 80) и нажмите кнопку Далее.
Рис. 79. Окно создания нового проекта
182
Во втором окне Мастера приложений MFC (Тип приложения)
укажите На основе диалоговых окон, а в стиле проекта Использовать MFC в статической библиотеке и нажмите на кнопку Далее
(рис. 81). Согласитесь с начальными настройками третьего окна мастера (Свойства интерфейса пользователя) и нажмите кнопку Далее
(рис. 82). Также согласитесь с настройками четвертого окна (Дополнительные параметры) (рис. 83).
В последнем (пятом) окне мастера сохраните или переименуйте
имена классов, создаваемых в приложении, по своему усмотрению
(рис. 84). На этом настройка заканчивается и для продолжения работы надо нажать кнопку Готово. Как результат настройки, окно
интегрированной среды программирования Visual Studio после работы мастера MFC примет приблизительно вид, изображенный на
рис. 85.
Выполните команду Вид – Классы и включите вкладку Окно
классов (если, конечно, она не была включена). Убедитесь в наличии
в структуре классов, созданных в процессе настройки (возможно переименованных) нами, классов CApp_MFS_LRApp (создаваемого при-
Рис. 80. Начальное окно диалога при создании приложения MFC
183
Рис. 81. Настройка окна Тип приложения при создании приложения MFC
Рис. 82. Настройка окна Свойства интерфейса пользователя
при создании приложения MFC
184
Рис. 83. Настройка окна Дополнительные параметры
при создании приложения MFC
Рис. 84. Установка имен классов приложения
185
Рис. 85. Вариант окна интегрированной среды программирования
Visual Studio после работы мастера MFC
Рис. 86. Результаты компиляции и запуска программы на выполнение
186
Рис. 87. Фрагмент текста свойства InitInstance
ложения) и CApp_MFS_LRDlg (диалогов в приложении), а также класса CAboutDlg, предназначенного для задания начальных сведений о
программе. Выполните команду Запуск без отладки и постройте решение (рис. 85). Убедитесь, что при успешной компиляции на этапе
выполнения программы создается диалоговое окно (рис. 86), Проверьте, что окно закрывается после нажатия кнопок ОК, Отмена и
кнопки закрытия окна (Крест). В тексте функции InitInstance ()
(рис. 87) присутствуют операторы создания объекта dlg и создания
окна в модальном режиме dlg.DoModal().
Разработка дизайна оконного приложения
и элементы управления
При работе любого оконного приложения используются внедренные туда элементы управления. Элемент управления – это часть
окна, позволяющая организовать удобный интерфейс для связи
пользователя с исходным кодом (программой). Их можно добавить
при помощи вкладки Панель элементов и разместить в пределах
окна. При помощи вкладки Свойства можно менять различные параметры элементов управления. Самым важным свойством является идентификатор (ID), позволяющий идентифицировать объект
(элемент управления) в данном диалоге. Если его название слиш187
Рис. 88. Help-система
ком длинное для пользователя, то его можно заменить. Описание
остальных свойств доступны в Help-системе, вызываемой клавишей
F1. Пример использования Help-системы отображён на рис. 88.
В соответствии с алгоритмом разрабатываемого приложения
(рис. 78) внедрим в разрабатываемое окно элементы Статический
текст для отображения трех надписей: первое число, второе число,
Рис. 89. Проектируемое окно с установленными элементами управления
188
результат. Для каждой из них в меню Свойства соответствующей
надписи изменим атрибут Подпись. Добавим также три поля ввода
соответственно для двух вводимых чисел и результата. У поля Результат выберем свойство Только чтение, для того чтобы пользователь не мог изменять в нём данные. Также добавим кнопку «+». Результат конструирования окна приложения представлен на рис. 89.
Работа со значениями, хранящимися в элементах управления.
Мастер добавления переменной-члена
Некоторые элементы управления, такие как EditControl, могут
хранить в себе данные, которые ранее ввел пользователь. Соответственно, необходимо периодически получать тот текст, который
хранится в поле ввода или наоборот записывать текст в это поле
ввода. Сделать это можно с помощью переменных в исходном коде
программы, связанных с элементами управления. Для того, чтобы
связать переменную программы с элементом управления, необходимо выбрать данный элемент управления, нажать на него правой
кнопкой мыши и выбрать пункт меню Добавить переменную (Add
Variable). Отроется мастер добавления переменной-члена (рис. 90).
В этом окне доступны две категории: Control и Value. Через пере-
Рис. 90. Мастер добавления переменной-члена
189
Рис. 91. Мастер добавления переменной члена,
настроенный для программы калькулятор
менную категории Control можно полностью управлять элементом
управления, то есть, изменять его размер, позицию, отображать на
экране, скрывать и т. д. Через переменную категории Value можно
управлять только значением, которое хранится в элементе управления, то есть, что касается поля ввода, помещать текст в поле ввода
или считывать его в переменную. Тип переменной по умолчанию
предлагается выбрать CString. Это класс библиотеки MFC, предназначенный для работы со строками.
Применительно к калькулятору: чтобы отобразить текст в поле
ввода, необходимо связать с ним переменную. Для этого необходимо кликнуть правой кнопкой мыши по элементу управления пункт
добавить переменную. Выбрать категорию Value, тип double, имя
Nomber1 (рис. 91). Аналогично надо поступить и с другими элементами связав с ними переменные Nomber2 и Result.
Обработчик событий
Обработчик событий создаёт функцию в исходном коде, в которой можно указать действия, которые должны выполняться при
каждом нажатии на элемент управления. Чтобы вызвать обработчик событий необходимо кликнуть правой кнопкой мыши по эле190
Рис. 92. Обработчик событий
менту управления и выбрать в раскрывающемся списке пункт Add
Event Handler.
Рассмотрим параметры обработчика. В поле Тип Сообщения
перечислены типы событий, относящиеся к определённому элементу управления. В поле Имя функции – обработчика можно указать имя создаваемой функции. В поле Список классов выбирается
класс, в котором будет производиться обработка события.
После установки всех параметров обработчик событий автоматически создаст функцию в классе, который выберет пользователь.
Пример обработчика событий отображён на рис. 92.
Применительно к разрабатываемому нами калькулятору для
того, чтобы определить действие, которое необходимо выполнить
при нажатии на кнопку «+», необходимо добавить обработчик событий. Назовем создаваемую функцию Summa. Автоматически откроется код созданной функции. Сначала необходимо, чтобы значения, введенные в поле ввода, попали в переменные. Для этого
нужно вызвать специальную функцию UpdateData (). Если она вызывается с аргументом TRUE, то она копирует данные из элементов
управления в связанные с ними переменные, а если с аргументом
FALSE, то выполняет обратное действие (копирует данные из пере191
Рис. 93. Окно, выполняющее сложение двух чисел
менных в связанные с ними элементы управления). Следовательно,
в данном случае необходимо выполнить вызов UpdateData (TRUE).
Далее запишем необходимое действие, а именно: Result = Nomber1 +
Nomber2. После этого снова необходимо вызвать функцию UpdateData
(FALSE) и запустить приложение на выполнение (рис. 93).
Для определения операции деления необходимо добавить ещё
одну кнопку «/» и вызвать обработчик событий. Назовём эту функцию Divide. При делении необходимо проверить, не является ли второе число равным нулю. Запишем:
UpdateData (TRUE);
if (Nomber2 == 0)
{
MessageBox (L"Деление на ноль невозможно",
L"Ошибка", MB_OK | MB_ICONERROR);
}
else
{
Result = Nomber1 / Nomber2;
UpdateData (FALSE);
}
Тогда, если второе число окажется равным нулю, в окно будет
выведено сообщение об ошибке (рис. 94). MessageBox – функция по
192
созданию окна, как правило, небольшого размера, которое содержит заранее определённое информационное сообщение и определённые кнопки для выбора действий.
Рис. 94. Сообщение об ошибке при делении на 0
Синтаксис данной функции:
MessageBox (<Text>, <Caption>, <Flags>);
Параметры функции: <Text> – текст, отображаемый в сообщении; <Caption> – заголовок сообщения; <Flags> – комбинация флагов, перечисляющая кнопки и значки (предупреждение, вопрос,
ошибка и др.), которые будут отображены в окне MessageBox. Флаги
разделяются через знак |.
Аналогично добавим еще две кнопки: вычитание и умножение.
Итоговый вид приложения представлен на рис. 95
Рис. 95. Итоговый вид приложения
193
Карта сообщений
Для того, чтобы закончить программирование приложения,
остается указать библиотеке MFC имя функции, которую нужно
вызвать при клике на определённый элемент управления (событие). У каждого класса окна есть так называемая карта сообщений
(рис. 96) представленная в виде макроса. Располагается она почти
в начале .cpp файла. Begin_message_map – это начало карты сообщений класса CMainDlg, End_message_map – конец карты сообщений. Карта сообщений – это единственное место, которое позволяет
связать функции с элементами управления и с событиями.
Рис. 96. Карта сообщений
Лабораторная работа №11.
Создание приложения для Windows
с использованием библиотеки MFC
Цель работы: Обучение работе в среде Visual Studio для создания и редактирования пользовательских приложений C++ на основе библиотеки MFC.
Задание
Научитесь создавать один из возможных видов диалоговых приложений для Windows.
Порядок выполнения работы
1. Начните разработку оконного приложения. Для этого создайте проект приложения, затем выберите вкладку Вид – Ресурсы и откройте окно ресурсов и раскройте ветвь App_mfs_LR для ознаком194
ления с шаблонами используемых окон диалога IDD_ABOUTBOX и
IDD_APP_MFS_LR_DIALOG. Начните редактировать окно диалога
IDD_MFC_DIALOG.
2. Создайте пустой проект приложения MFC и назовите его
Recod.
3. Откройте в окне ресурсов папку Recod.rc, затем папку Dialog
и выберите пункт IDD_RECORD_DIALOG. Откроется макет проектируемого приложения.
4. Поместите на макет 6 полей (EditControl) и с помощью элемента управления Статический текст (Static text) дайте полям следующие названия: Фамилия, Имя, Отчество, Ставка руб./час, Отработанное время, Зарплата – для статического текста зарплата установите режим только для чтения.
5. Добавьте 2 кнопки и назовите их: Рассчитать и Отчёт. Примерный вариант оформления оконного приложения представлен на
рис. 97.
Рис. 97. Шаблон задания
6. Свяжите с каждым полем переменную с помощью мастера добавления переменной-члена;
7. Добавьте обработчик событий для кнопки Рассчитать. Напишите программу, производящую вычисление зарплаты для поля
Зарплата.
195
8. Добавьте обработчик событий для кнопки Отчёт. Напишите
программу, выводящую на экран сообщение, в котором должны
быть отображены следующие данные: Фамилия, Имя, Отчество и
Зарплата;
9. Примите во внимание, что функция MessageBox работает только со строковым форматом CString, пример преобразования типа
double в CString:
CString str;
str.Format (L”%3.2f”, pay);
где pay – переменная типа double, str – переменная типа CString.
Пример сообщения представлен на рис. 98.
Рис. 98. Пример работы функции MessageBox ()
10. Проведите тестирование вашей программы и убедитесь в её
работоспособности.
Контрольные вопросы
1. Какие существуют технологии создания приложений под
Windows?
2. Назовите основные этапы работы приложения Windows MFC.
3. Каково назначение функции WinMain ()?
4. Для чего нужна функция DoModal ()? Какие значения возвращает функция DoModal ()?
5. Как можно изменить название объекта?
6. В чём разница между категориями Value и Control в мастере
добавления переменной-члена?
7. Что нужно сделать, чтобы данные из поля ввода попали в переменную?
8. Опишите синтаксис функции MessageBox.
9. Для чего нужна карта сообщений?
10. Как задать действия, выполняющиеся при клике на элемент
управления?
196
Отчет о работе
Подготовьте отчет о выполненной лабораторной работе. Он должен содержать титульный лист, формулировку цели работы, текст
программы и результаты её выполнения, выводы, которые можно
сделать по результатам выполненной работы. Вариант титульного
листа отчета приведен на сайте [12].
Список рекомендованной литературы
1. Керниган, Б. Язык программирования Си. 2-е изд./ Б. Керниган, Д. Ритчи. М.:: Вильямс, 2007, 304 с..
2. Antique Software: Turbo C version 2.01. [В Интернете]. Available:
http://edn.embarcadero.com/article/20841. [Дата обращения: 22. 01. 2018].
3. Касаткин, А. И. Профессиональное программирование на языке Си:
От Turbo C++ к Borland C++ / А. И. Касаткин, А. Н. Вальвачев. Под общей
ред. А. И. Касаткина. Минск: Вышэйшая школа. 1992. 240 с.
4. Киммел, П. Borland C++ 5 / П. Киммел. Под ред. И. И. Дериева, СПб.:
BHV. 1997. 976 с.
5. Хейлсберг, А. Язык программирования C#. Классика Computers
Science. 4-е издание / А. Хейлсберг, М. Торгерсен, С. Вилтамут, П. Голд,
СПб.: Питер, 2012, 784 с.
6. Уотсон, К. Visual C# 2010: полный курс / К. Уотсон, К. Нейгел, Я. Х.
Педерсен, Д. Рид и С. Морган. М.: Диалектика, 2010. 960 с.
7. Страуструп, Б. Язык программирования C++. Пер. с англ. 3-е изд. / Б.
Страуструп, СПб.; М.: Невский диалект – Бином. 1999. 991 с.
8. Шилдт, Г. Полный справочник по C++ / Г. Шилдт. М.: Вильямс.
2011. 800 с.
9. Майо, Д. Самоучитель Microsoft Visual Studio 2010 / Д. Майо. СПб.::
БХВ-Петербург. 2010. 464 с.
10. Хортон, А. Visual C++ 2008: базовый курс. Пер. с англ. / А. Хортон.
М.:: ООО «И.Д. Вильямс», 2009. 1280 с.
11. Интегрированная среда разработки Visual Studio. [В Интернете].
Available: https://msdn.microsoft.com/ru-ru/library/dn762121.aspx. [Дата
обращения: 22. 01. 2018].
12. Титульные листы для оформления практики, а также курсовых,
контрольных и лабораторных работ. [В Интернете]. Available: http://guap.
ru/guap/standart/titl_newpage2.shtml. [Дата обращения: 22. 01. 2018].
13. Прата, С. Язык программирования C++. Лекции и упражнения. 5-е
изд. Пер. с англ. / С. Прата. М.: ООО «И.Д. Вильямс». 2007. 1184 с.
197
Предметный указатель
ASCII, 39, 154
арифметические операции
Borland C++, 9
с дробными числами, 53
break, 76, 83, 90
арифметические операции с целыми
числами, 52
C Sharp, 7
C классический, 5
C#, 7
CMFCApp, 177
continue, 76, 90
delete, 38, 103, 104
do while, 90
for, 92
friend, 129
fstream, 166
бинарные файлы, 170
бинарный поток, 166
блок операторов, 41
виртуальная функция, 135
данные-члены, 123, 124
декомпозиция, 106
деструктор класса, 127
дополнительный код, 29
дружественная функция, 129
if, 77
запись и чтение информации
из файла, 167
MessageBox, 192
идентификатор, 31, 32
Microsoft Visual C++, 10
идентичность объекта, 124
new, 38, 103, 104, 114
инкапсуляция, 132
printf, 44, 47
private, 133
интегрированная среда программирования, 14
protected, 133
карта сообщений, 194
public, 133, 136
класс, 124
return, 109
комментарий, 41
scanf, 44, 47
компилятор, 4, 7
string, 144
консольное приложение, 19
switch, 83
консольное приложение CLR, 12
theApp.ExitInstance(), 178
консольное приложение Win32, 12
theApp.InitInstance(), 178
константа, 33
theApp.Run(), 178
конструктор без аргументов, 127
Turbo Vision, 6
конструктор класса, 127
virtual, 135
мантисса числа, 30
Visual Studio, 10
массив, 35, 128
wchar_t, 143
двухмерный, 37
while, 89
динамический, 120
WinMain(), 177
объявление, 3, 120
198
модификатор видимости, 133
представление числа двоичное, 27
наследование, 132
представление числа с плавающей
точкой, 31
нормализация дробного числа, 53
нормализованное представление числа,
30, 53
обозначения в алгоритмах, 42
обработчик событий, 191
объект, 124
объявление переменной, 32
оператор, 39
оператор цикла, 89
представление числа шестнадцатеричное, 27
приоритет операции, 38
прототип функции, 107
символы управляющие, 34
строка, 143
структура, 121
текстовый поток, 166
операционная система, 3
текущее положение указателя файла,
170
операция, 39
тестирование, 66
взятия адреса, 102
тип данных, 28
запроса памяти, 103
точка останова, 69
освобождения памяти, 104
трассировка, 70
разрешения контекста, 125
триггер, 25
ссылки по указателю, 102
указатель, 110
операция <<, 38
утечка памяти, 104
операция >>, 38
файл, 165
организация памяти ЭВМ, 26
фактические параметры функции, 110
отладка, 68
отладчик, 69
формальные параметры функции, 108,
110
ошибка
функция, 105
этапа выполнения программы, 104
объявление, 109
ошибка синтаксическая, 22
определение, 107
переменная, 36
функция-член, 125
полиморфизм, 47, 132
экземпляр класса, 124
пользовательское пространство
имен, 21
экспоненциальное представление
числа, 26
порядок числа, 30
199
СОДЕРЖАНИЕ
1. Интегрированная среда программирования Visual C++
и основные приемы работы с ней................................................... 3
1.1. Общие сведения о языке программирования C
и его диалектах................................................................
3
1.2. Разновидности программ, создаваемых с помощью
интегрированной среды программирования Visual C++.......... 11
1.3. Начальный запуск интегрированной среды
программирования Visual Studio C++.................................. 13
1.4. Создание проекта консольного приложения Win32................ 15
Лабораторная работа №1. Начальный запуск и использование
консольного приложения Win32......................................... 19
2. Операции и последовательно выполняющиеся операторы
языка C++................................................................................. 2.1. Выполнение команд процессором........................................
2.2. Организация информации в памяти и типы данных...............
2.3. Кодирование информации в памяти.....................................
2.4. Идентификаторы и объявление переменных.........................
2.5. Константы языка C++........................................................
2.6. Создание и использование массивов данных..........................
2.7. Операции языка C++.........................................................
2.8. Понятие оператора............................................................
2.9. Оператор присваивания.....................................................
2.10. Ввод с клавиатуры и вывод на экран в языке С......................
2.11. Ввод с клавиатуры и вывод на экран в Visual C++..................
Лабораторная работа №2. Базовые операции
ввода-вывода C++............................................................. 2.12. Выполнение арифметических операций
и приведение данных..........................................................
3. Методы подготовки программы
к выполнению, тестирования и отладки......................................... 3.1. Общая схема прохождения задачи.......................................
3.2. Ошибки этапов подготовки программы к выполнению. ..........
3.3. Ошибки этапа выполнения, автоматически определяемые
процессором.....................................................................
Лабораторная работа №3. Операции в базовой арифметике
и преобразование типов данных.......................................... 3.4. Задача тестирования..........................................................
3.5. Отладка и программные средства отладки............................
Лабораторная работа №4. Тестирование
и отладка программы......................................................... 25
25
26
29
31
33
34
37
39
41
43
47
51
53
57
57
59
60
61
65
68
71
4. Операторы языка C++, изменяющие последовательность
выполнения операторов программы............................................... 76
4.1. Блок операторов и операторы continue и break....................... 76
4.2. Условный оператор if......................................................... 76
Лабораторная работа №5. Оператор if.................................. 78
200
4.3. Оператор ветвления switch.................................................
Лабораторная работа №6. Оператор switch............................ 4.4. Операторы циклов.............................................................
Оператор while.................................................................. Лабораторная работа №7. Операторы цикла.......................... 4.5. Указатели........................................................................
4.6. Функции..........................................................................
Лабораторная работа №8. Создание и использование
функций.......................................................................... 4.7. Массивы, структуры, классы и объекты...............................
Лабораторная работа №9. Создание объектов на основе
структур и классов............................................................ 83
85
89
89
95
100
105
114
116
138
5. Некоторые другие возможности языка C++................................. 5.1. Строки.............................................................................
Проблема русификации консольного ввода-вывода................ Лабораторная работа №10. Обработка символьных строк........ 5.2. Работа с файлами..............................................................
143
143
144
162
164
6. Создание приложений для Windows........................................... 6.1. Терминология. .................................................................
6.2. Создание приложения Windows MFC...................................
6.3. Пример создания оконного приложения
на примере программы калькулятор....................................
Общая постановка задачи на программирование.................... Лабораторная работа №11. Создание приложения
для Windows с использованием библиотеки MFC................... 176
176
177
180
180
194
Список рекомендованной литературы............................................ 197
Предметный указатель................................................................ 198
201
Учебное издание
Будагов Артур Суренович,
Васильева Виктория Сергеевна,
Галкина Анна Сергеевна и др.
ПРОГРАММИРОВАНИЕ НА VISUAL C++
Учебно-методическое пособие
Под общей редакцией А. Г. Степанова
Публикуется в авторской редакции
Компьютерная верстка А. Н. Колешко
Сдано в набор 30.06.18. Подписано к печати 01.10.18. Формат 60 × 84 1/16.
Усл. печ. л. 11,7. Тираж 50 экз. Заказ № 409.
Редакционно-издательский центр ГУАП
190000, Санкт-Петербург, Б. Морская ул., 67
Документ
Категория
Без категории
Просмотров
0
Размер файла
5 789 Кб
Теги
budagov
1/--страниц
Пожаловаться на содержимое документа