МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ
ФЕДЕРАЦИИ
Федеральное государственное бюджетное образовательное учреждение
высшего профессионального образования
«ТОМСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ СИСТЕМ
УПРАВЛЕНИЯ И РАДИОЭЛЕКТРОНИКИ»
(ТУСУР)
Кафедра телевидения и управления
(ТУ)
УТВЕРЖДАЮ
Заведующий кафедрой ТУ, профессор
_________________И.Н. Пустынский
«______»___________________2012 г.
ИСПОЛЬЗОВАНИЕ ИНТЕГРИРОВАННОЙ СРЕДЫ РАЗРАБОТКИ
MICROSOFT VISUAL STUDIO 2005
Учебное методическое пособие
РАЗРАБОТАЛ
_________ И.В. Гальчук
«______»_________2012 г.
2012
2
Гальчук И.В. Использование интегрированной среды разработки
microsoft visual studio 2005: Учебное пособие. – Томск: кафедра ТУ, ТУСУР,
2012. – 124 с.
Пособие
предназначено
для
студентов
специальностей
радиотехнического профиля, желающих приобрести базовые навыки в
разработке программного обеспечения с помощью интегрированной среды
разработки Microsoft Visual Studio 2005.
© Гальчук И.В., 2012
©Кафедра телевидения и управления, ТУСУР, 2012
3
СОДЕРЖАНИЕ
ВВЕДЕНИЕ
5
1. Среда разработки Microsoft Visual Studio 2005
7
1.1. Запуск и начало работы
7
1.2. Дополнительные настройки
12
1.3. Получение справочной информации
14
2. Этапы создания программы
20
2.1. Создание проекта
22
2.1.1. Проект консольной программы
25
2.1.2. Проект программы-библиотеки
33
2.1.3. Работа с несколькими проектами
40
2.2. Компиляция, сборка и запуск
42
2.2.1. Компиляция
42
2.2.2. Сборка (компоновка)
44
2.2.3. Запуск программы
46
2.3. Отладка программы
46
2.3.1. Разновидности ошибок
47
2.3.2. Запуск программы в режиме отладки
48
2.3.3. Поиск и навигация по тексту программы
51
2.4. Сохранение настроек программы
3. Создание программы с оконным интерфейсом
54
59
3.1. Интерфейс SDI
59
3.2. Интерфейс MDI
67
3.3. Интерфейс на базе диалогового окна
69
4. Язык программирования C++
74
4.1. Стандарты
74
4.2. Типы данных, константы и переменные
75
4
4.3. Операторы, область видимости, пространство имён
79
4.4. Порядок выполнения: циклы, условия переходы
87
4.4.1. Циклы (for, while)
87
4.4.2. Условия (if, else, switch, ?:)
91
4.4.3. Переходы (goto, return)
94
4.5. Функции и статические переменные
95
4.6. Массивы и указатели, выделение памяти
100
4.7. Структуры, файлы и перечисления
104
4.8. Объектно-ориентированное программирование
109
4.8.1. Три основных понятия
109
4.8.2. Классы и объекты
111
4.9. Директивы препроцессора
114
4.10. Комментарии в программах
115
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
117
5
ВВЕДЕНИЕ
Процесс разработки почти любой программы можно представить в
виде определённой последовательности этапов: написание исходного текста
программы, компиляция, сборка, отладка, оптимизация. При этом, с
увеличением размера программы и усложнения реализуемых в ней
алгоритмов, процесс разработки может оказаться очень трудоёмким.
В первом разделе пособия рассматривается интегрированная среда
разработки Microsoft Visual Studio 2005, которая позволяет значительно
облегчить указанный выше процесс. Приводится описание внешнего вида
главного окна среды после её первого запуска. Рассматриваются некоторые
дополнительные настройки, позволяющие более комфортно работать в среде.
Приводится описание справочной библиотеки Microsoft Developer Network
(MSDN Library), которая содержит справочную информацию по среде и её
возможностям, а также информацию по различным дополнительным
программным библиотекам и технологиям программирования.
Второй раздел пособия посвящён описанию этапов создания программ
средствами среды Visual Studio на примере консольной программы, которая
инвертирует чёрно-белое 8-битное изображение, хранящееся в файле в
формате BMP. Кроме того, рассматривается процесс создания программыбиблиотеки для обеспечения возможности многократного использования
одного и того же кода в других программах. Описывается процесс
компиляции, сборки и откладки (поиска и устранения ошибок) программ. В
конце раздела описывается механизм хранения раздельных настроек одной и
той
же
программы
при
её
настройке
и
эксплуатации
разными
пользователями.
В третьем разделе рассматриваются различные оконные интерфейсы
(средства взаимодействия с пользователем) программ: SDI (Single Document
6
Interface – однодокументный интерфейс), MDI (Multiple Document Interface –
многодокументный интерфейс) и на базе диалогового окна.
Четвёртый раздел носит справочный характер и кратко описывает
возможности и правила использования языка программирования C++.
Данный раздел в первую очередь следует прочитать тем, кто не очень хорошо
знаком с этим языком и указанной библиотекой или что-то подзабыл.
В списке использованных источников приведены не только печатные
издания, использованные для написания данного пособия, но и электронные
ресурсы – адреса сайтов в сети Интернет, нормально функционировавших на
момент написания данного пособия.
Следует отметить, что для изучения среды Microsoft Visual Studio 2005
во многих случаях подходят книги, написанные по предыдущим версиям
среды, в частности 2003 и даже 98.
И последнее – изложение материала пособия предполагает, что на
компьютере установлена и используется интегрированная среда разработки
Microsoft Visual Studio 2005 редакции Professional Edition или лучше (в
принципе любой редакции кроме Express Edition, которая является
бесплатной, но сильно ограниченной по возможностям). Кроме того
предполагается, что среда имеет англоязычный интерфейс и используется в
операционной системе Microsoft Windows XP, имеющей русскоязычный
интерфейс. Также предполагается, что помимо среды разработки на
компьютере установлена справочная библиотека разработчика MSDN Library,
имеющая англоязычный интерфейс.
Требования пособия к компьютеру соответствуют аналогичным
требованиям интегрированной среды разработки Microsoft Visual Studio 2005
Professional Edition и справочной библиотеки разработчика MSDN Library.
7
1. СРЕДА РАЗРАБОТКИ MICROSOFT VISUAL STUDIO 2005
В данном разделе описывается внешний вид среды после первого
запуска. Рассматриваются некоторые полезные настройки, позволяющие
более комфортно работать в среде. Описываются приёмы получения
дополнительной информации с помощью справочной библиотеки MSDN
Library.
1.1. Запуск и начало работы
Запустить среду можно с помощью главного системного меню
операционной системы Microsoft Windows. В этом меню следует выбрать
подменю "Microsoft Visual Studio 2005", в котором выбрать пункт "Microsoft
Visual Studio 2005" (указанный пункт имеет характерный значок в виде
разноцветного знака бесконечности). Следует отметить, что если конкретный
пользователь компьютера запускает среду в первый раз, то может появиться
диалоговое окно "Choose Default Environment Settings", которое имеет список
вариантов настроек среды по умолчанию. В этом списке следует выбрать
пункт "Visual C++ Development Settings" и нажать кнопку "Start Visual
Studio". Далее следует немного подождать (на время показа диалога с
индикатором), пока среда выполнит и сохранит выбранные настройки.
При условии выполнения описанных выше действий внешний вид
среды после первого запуска будет таким, как показано на рис. 1.1. Что же
содержит появившееся окно среды? Как с ним работать? С чего начать?
Прежде всего, кратко ознакомимся с содержимым окна и узнаем назначение
его элементов.
В самом верху окно содержит панель с набором меню ("File", …,
"Help"), позволяющих целиком и полностью управлять средой. Например,
чтобы завершить работу и закрыть окно, следует выбрать пункт "Exit" в меню
8
"File". В дальнейшем подобные действия будем описывать, например, так:
9
Рис. 1.1. – Внешний вид среды Visual Studio после первого запуска
10
"Для создания нового проекта следует выбрать пункт меню "File / New /
Project…". Это означает, что в меню "File" следует выбрать пункт "New",
который также представляет собой меню (в названии есть значок
треугольника), а затем уже в новом меню вы брать пункт "Project…" (знак
многоточия указывает, что при выборе такого пункта появится диалоговое
окно).
Ниже меню расположена панель, содержащая различные элементы
(кнопки, выпадающие списки и др.). К уже имеющейся панели можно
добавить другие с помощью пунктов меню "View / Toolbars" (по умолчанию
там выбрана лишь панель "Standard"). Элементы этих панелей дублируют
различные пункты набора меню. Ведь проще нажать кнопку с изображением,
например, открытой папки (третья слева), чем выбрать её эквивалент – пункт
меню "File / Open / File…". Маленькая подсказка: если задержать курсор
мыши над любым элементом панели (см. рис. 1.2 слева), то возле курсора
появится
всплывающая
подсказка
с
названием
элемента,
т.е.
соответствующего ему пункта меню.
Больше всего места по умолчанию занимает область редактирования и
просмотра, в котором пока что отображается стартовая страница (Start Page).
Наибольший интерес на этой странице представляет область Recent Projects –
в ней в дальнейшем будут перечислены 4 проекта (см. раздел 2), с которыми
работал пользователь в последнее время и которые можно открыть щелчком
мыши (на имени нужного проекта). Открыть другой проект, которого нет в
списке, можно щелчком мыши на надписи "Project…", что напротив надписи
"Open:" (аналогичные действия со второй надписью приводят к созданию
нового проекта). Область "Getting Started" позволяет вызвать окно со
справочной
информацией
при
щелчках
левой
клавишей
мыши
на
соответствующих надписях.
Левая боковая панель имеет три "ярлыка": Solution Explorer, Class
11
View, Property Manager. Маленькая подсказка: если где-либо в среде надпись
показана не полностью (вместо остальной части видно многоточие), то можно
навести курсор мыши на эту надпись и подождать всплывающей подсказки,
которая будет содержать полный вариант надписи (см. рис. 1.2 справа).
Рис. 1.2. – Примеры всплывающих подсказок.
Щелчок левой кнопкой мыши на любом из этих ярлыков отображает на
панели соответствующее окно. В большинстве случаев нужны только два
первых окна. Окно "Solution Explorer" показывает все программы (проекты), с
которыми в настоящее время работает пользователь. Окно "Class View"
перечисляет переменные, функции, классы, определения и пр., которые
используются в разрабатываемой или исследуемой программе.
Каждое подобное окно в своём правом верхнем углу содержит три
кнопки с изображениями (слева направо): треугольника, кнопки и крестика.
На практике используются две последних кнопки. Кнопка с изображением
кнопки позволяет постоянно отображать соответствующее окно, при этом
изображение кнопки ориентировано вертикально. Нажав на эту кнопку
(изображение будет ориентировано горизонтально), можно установить режим
автоматического скрытия окна – будет виден только его ярлык. Для показа
окна достаточно навести на ярлык курсор мыши. Как только курсор покинет
пределы окна, оно автоматически скроется. Кнопка с крестиком закроет окно.
Не следует пугаться, если какое-либо подобное окно было закрыто. Снова
увидеть их (хотя таким же образом можно скрыть ненужные окна) можно
12
нажатием соответствующих кнопок на упомянутой выше панели Standard (см.
рис. 1.3) или с помощью меню "View" и меню "View / Other Windows".
Рис. 1.3. – Кнопки для показа и скрытия окон
Правая боковая панель по умолчанию свёрнута, видны только ярлыки
окон. Интерес представляет только окно "Toolbox", в котором перечислены
все объекты, позволяющие создать оконный интерфейс программы.
Нижняя панель по умолчанию содержит следующие три окна, но нам
нужны только два: "Code Definition Window" и "Output". Первое окно
показывает объявление или определение объекта программы, на котором
установлен курсор в окне редактирования исходного текста программы (см.
п. 2.1.1). Второе окно выводит различную статистику, в том числе
информацию об ошибках при компиляции и сборке программы (см. п. 2.2).
В процессе создания программ могут автоматически появляться и
другие панели и окна. Любую из описанных выше панелей можно
"перетащить" в любое другое более удобное место в пределах окна среды.
Для этого нужно навести курсор мыши на заголовок нужной панели (там, где
её название и три описанных выше кнопки) нажать левую клавишу мыши и,
не отпуская клавиши, переместить мышь. При этом переместится и панель, а
на фоне окна среды, в его центре и по краям появятся полупрозрачные
прямоугольные значки. Если теперь просто отпустить клавишу мыши, то
панель будет отображаться отдельно от окна среды и может быть перемещено
за его пределы. Если, не отпуская клавиши мыши, навести её курсор на один
из появившихся полупрозрачных значков, то часть окна среды будет
13
затемнена для показа возможного расположения панели. Если расположение
нравится, следует отпустить клавишу мыши. Аналогично можно переместить
кнопочную панель, только "хватать" её следует за левый край – "корешок",
обозначенный четырьмя точками.
Итак, среда запущена, а её внешний вид приведён в порядок. Что
дальше? Выполним некоторые полезные настройки и начнём работу (см.
ниже).
1.2. Дополнительные настройки
Прежде всего, обратим внимание на SysTray (область на панели задач,
в которой обычно можно увидеть время и прочие значки). Если в этой
области есть соответствующий значок бесконечности (см. рис. 1.4), то
щёлкнем на нём левой кнопкой мыши. При этом появится диалоговое окно
"Customer Experience Improvement Program", в котором следует отметить (в
самом низу) пункт "No, I would not like to participate" и нажать кнопку "OK".
Рис. 1.4. – Значок Visual Studio (крайний слева) в области SysTray
Теперь воспользуемся пунктом меню "Tools / Options…". В левом
списке появившегося диалогового окна "Options" выбираем пункт "Fonts and
Colors", и теперь в правой части окна можем настроить шрифты и цвета для
отображения исходного текста программы, сообщений об ошибках и пр.
Причём верхнее поле "Show settings for:" позволяет указать, для какого окна
выполняются настройки (по умолчанию "Text Editor", т.е. окно области
редактирования и просмотра исходного кода программ). Для сохранения
14
настроек следует нажать кнопку "OK", а для их отмены "Cancel". Кнопка "Use
Defaults" позволяет вернуться к настройкам по умолчанию.
Рис. 1.5. – Настройки отображения справочной информации
Теперь щёлкнем левой кнопкой мыши на знаке плюса слева от надписи
"Help" (см. рис. 1.5) – при этом появятся дополнительные пункты, из которых
выберем
пункт
"Online".
Это
настройки
отображения
справочной
информации (MSDN Library). В правой части окна вверху выберем вариант
"Try local only, not online", а в нижнем списке снимем все отметки кроме
пункта "Local Help".
Далее слева выбираем пункт "Startup", а справа в поле "At startup:"
выбираем вариант "Show empty environment", а также снимаем отметку
"Download content every:". Теперь при запуске среда не будет показывать
"Start Page" в области просмотра. Это не страшно, так как соответствующую
справочную информацию можно получить с помощью меню среды "Help", а
15
создать новый или открыть имеющийся проект можно с помощью пунктов
меню среды "File / New / Project…" и "File / Open / Project/Solution…"
соответственно.
Итак, среда готова к работе, но прежде следует научиться получать
нужную справочную информацию средствами среды и изучить возможности
справочной библиотеки MSDN Library.
1.3. Получение справочной информации
Кроме интегрированной среды разработки Visual Studio компания
Microsoft создала большую справочную библиотеку MSDN (Microsoft
Developer Network) Library, которая прекрасно дополняет среду Visual Studio.
16
Рис. 1.6. – Используем версию справки, установленную на компьютере
При первом обращении к библиотеке из среды разработки может
появиться диалоговое окно "Online Help Settings" (см. рис. 1.6), в котором
следует выбрать пункт "Do not use online Help" и нажать кнопку "OK". Т.е.
будет использоваться справочная библиотека, установленная на компьютере,
в противном случае поиск справочной информации производился бы сначала
на сайте компании Microsoft и только потом на компьютере.
Справочную информацию можно получить тремя способами. Вопервых, если диалоговое окно в своём правом верхнем углу имеет кнопку со
знаком вопроса, то можно нажать на эту кнопку и в появившемся окне
увидеть справочную информацию по содержимому окна.
17
Во-вторых, можно выделить нужный объект и нажать на клавиатуре
клавишу F1. Так, например, можно выделить (просто навести курсор мыши)
на нужную кнопку (на кнопочной панели вверху окна среды) или выделить
нужный пункт меню и нажать F1. Можно в исходном тексте программы
щёлкнуть левой кнопкой мыши на нужном слове либо выделить несколько
слов (поиск фразы, а не слова) и также нажать F1.
В-третьих, можно выбрать пункт меню "Help / Dynamic Help". При
этом появится окно с оперативной справкой. Теперь, щёлкая мышью на
нужных объектах (например, словах в исходном тексте программы), можно
тут же увидеть в упомянутом окне ссылки на темы справки, в которых, так
или иначе, упоминается выбранный объект. Теперь остаётся щёлкнуть по
нужной ссылке и получить непосредственно информацию.
Какой бы способ мы не выбрали, в любом случае появится окно
библиотеки MSDN Library, пример которого показан на рис. 1.7 (это окно
получено нажатием кнопки со знаком вопроса в диалоговом окне, показанном
на рис. 1.5).
Основную часть окна занимает область просмотра, можно увидеть
запрошенную справочную информацию. При этом название справочного
документа можно увидеть чуть выше области просмотра. Если открыто
несколько справок, то будет и несколько ярлыков. Щёлкая по ним мышью
можно увидеть соответствующие справки.
В левой части окна расположена панель с тремя ярлыками,
позволяющими увидеть соответствующие окна:

Окно "Contents" показывает в виде дерева содержание всей
библиотеки. Щёлкая мышью на знаках плюса, можно увидеть
дополнительные пункты. Как правило, щелчок мышью на самом
пункте приводит к отображению соответствующей справочной
информации в области просмотра.
18

Окно "Index" показывает список всех ключевых слов, которые есть
в MSDN Library. Поясним, что каждая справка имеет набор
ключевых слов, отражающих тематику справки и, тем самым,
облегчающих её поиск. Как и в предыдущем окне, щелчок мышью
на самом пункте приводит к отображению соответствующей
справочной информации в области просмотра.

Окно "Help Favorites" позволяет увидеть сделанные пользователем
закладки, щёлкая мышью на которых можно сразу увидеть нужную
справку. Чтобы сделать закладку, необходимо в области просмотра
получить искомую справочную информацию и на кнопочной
панели вверху окна библиотеки нажать кнопку с изображением
листа бумаги и знака плюса либо щёлкнуть правой кнопкой мыши
по области просмотра и в появившемся меню выбрать пункт "Add
to Help Favorites".
Описанные три окна можно также получить с помощью пунктов меню
"Help" как окна библиотеки, так и окна среды Visual Studio. Кроме того,
кнопочная панель окна библиотеки также имеет кнопки для показа этих окон.
19
Рис. 1.7. – Окно MSDN Library с примером справки
20
Упомянутые меню (и кнопочная панель) содержат ещё два полезных
пункта (кнопки):

"How Do I" – показывает в области просмотра список тем,
объединяющих в себе статьи "Как сделать что-то" (например, "Как
написать консольное приложение"). Впрочем есть статьи и другого
типа, например описание языка программирования C++ , а также
описания разных полезных библиотек.

"Search" – позволяет провести поиск нужной информации по
заданному слову или набору слов. На рис. 1.8 показан фрагмент
окна библиотеки с результатами поиска по слову "OpenGL". То
есть в поле поиска нужно ввести искомое слово или фразу и нажать
кнопку "Search" (три поля чуть ниже используются для фильтрации
результатов поиска и по умолчанию настроены на приемлемом
уровне). В маленьком окошке справа будет показано количество
результатов поиска, а в основной области список ссылок (с
краткими комментариями) на сами результаты. Чтобы увидеть
нужный результат поиска необходимо дважды щёлкнуть левой
кнопкой мыши на соответствующей ссылке. Сортировка ссылок
производится с помощью выпадающего списка, расположенного
правее надписи "Sort by:". Ещё правее расположен значок, щелчок
мышью на котором дополнительно сортирует ссылки по алфавиту
в прямом и обратном порядке (от A до Z или от Z до A).
Следует отметить, что окно справочной библиотеки можно вызвать и
без необходимости запуска среды Visual Studio. Для этого нужно
использовать пункт главного меню операционной системы Windows
"Microsoft Developer Network / MSDN Library" (в названии пункта также
могут быть указаны месяц и год выпуска библиотеки).
21
Рис. 1.8. – Результаты поиска справки по слову "OpenGL"
22
2. ЭТАПЫ СОЗДАНИЯ ПРОГРАММЫ
Процесс создания любого программного обеспечения обычно состоит
из шести этапов: проектирование, написание исходного кода, компиляция,
сборка, отладка, оптимизация.
Этап проектирования выполняется без помощи компьютера. На данном
этапе необходимо задать вопрос о том, что должна делать программа, т.е.
какие действия она должна предпринимать в той или иной ситуации и
записать их в виде списка на обычном листе бумаги. Затем каждый пункт
этого списка разбить на список более простых действий и т.д. Продолжать до
тех пор, пока программная реализация каждого пункта полученного в итоге
списка не станет совершенно очевидной. Например, нам нужно создать
программу, которая инвертирует изображение, хранящееся в виде BMPфайла. Итак, у нас есть одно действие – инверсия изображения. Однако для
его выполнения нам нужно само изображение, а также необходимо куда-то
сохранить результат инверсии. То есть уже три действия, каждое из которых
также можно разбить на последовательность более простых действий. В итоге
получим следующую последовательность:

Открыть (в режиме чтения) BMP-файл с исходным изображением.

Считать из открытого файла в память компьютера служебную
информацию об изображении (его размеры и пр.).

Считать из этого же файла в память компьютера данные об
элементах самого изображения.

Последовательно перебрать все элементы изображения, заменяя
каждый из них его же инвертированным вариантом.

Создать новый BMP-файл (с другим именем) для записи
инвертированного изображения.

Открыть (в режиме записи) созданный и пока что пустой BMP-
23
файл.

Записать служебную информацию о старом изображении (т.к. эту
информацию мы не изменяем) из памяти компьютера в открытый
файл.

Записать результаты инвертирования из памяти компьютера в тот
же BMP-файл.

Закрыть оба открытых файла.
Любое из этих действий достаточно легко реализовать средствами
языка программирования C++, поэтому на такой последовательности можно
остановиться и перейти к следующему этапу.
На
втором
этапе
полученная
последовательность
действий
описывается средствами какого-либо языка программирования (например,
C++). То есть пишется исходный текст (также называемый исходным кодом
или сырцом) программы. Также на данном этапе создаётся оконный
интерфейс и выполняется его перевод на другие языки (например, русский,
т.к. по умолчанию среда Visual Studio назначает английский язык для
интерфейса), добавляются изображения (например, для кнопок), звуки и
шрифты, а также файлы со справочной информацией по работе программы.
Среда Visual Studio имеет прекрасный редактор для написания исходного
кода. В п. 2.1 и 2.3.4 возможности редактора будут рассмотрены более
подробно.
На
третьем
этапе
выполняется
компиляция
исходного
текста
создаваемой программы. То есть выполняется его перевод с языка
программирования в объектный код. Если компилятор обнаружит в
программе ошибки, то придётся вернуться на предыдущий этап, найти
причины всех обнаруженных ошибок и устранить их.
На четвёртом этапе происходит сборка программы, т.е. получаем
готовый к использованию файл. Если сборщик (также называемый линкером)
24
обнаружит в программе ошибки, то придётся вернуться на второй этап, найти
причины всех обнаруженных ошибок и устранить их.
Даже если программа была благополучно собрана, в ней всё ещё могут
быть ошибки, которые могут быть обнаружены в дальнейшем при запуске
программы. В этом случае переходим к пятому этапу и используем
возможности среды Visual Studio для тестирования (отладки) программы. С
помощью
отладчика
можно
достаточно
быстро
найти
причины
обнаруженных ошибок.
Если предъявляются повышенные требования к быстродействию или
размеру программы, то переходим к шестому этапу и оптимизируем её
средствами среды и языка программирования, на котором создаётся
программа.
2.1. Создание проекта
25
Рис. 2.1. – Окно выбора имени, шаблона и расположения проекта
Как
было
описано
выше,
для
создания
программы
может
потребоваться множество файлов различного формата. Для удобства
управления этой совокупностью файлов среда вводит понятие программного
проекта и использует служебные файлы, хранящие его настройки. Таким
образом, для перехода ко второму этапу необходимо создать проект.
Для создания проекта используем пункт меню " File / New / Project…"
и воспользуемся услугами "волшебника" AppWizard (Application Wizard).
Этот инструмент с помощью пары диалоговых окон поможет нам создать
каркас проекта нужного нам типа.
В появившемся диалоговом окне "New Project" выполним следующие
действия (см. рис. 2.1):

В списке "Project types:" выделим пункт "Visual C++".
26

В списке "Templates:" выберем нужный шаблон проекта (см.
далее).

В текстовом поле "Name:" введём имя проекта. Фактически это имя
программы, т.е. файла, который получим после сборки проекта.

В поле "Location:" укажем расположение проекта. Для этого можно
воспользоваться кнопкой "Browse…", в появившемся диалоге
зайти в нужную папку и нажать кнопку "Open". В результате, в
указанной папке, будет создана папка с именем, заданным в
предыдущем поле, а уже в этой папке будут находиться файлы
проекта.

Если нужно работать с несколькими проектами (см. п. 2.1.3), то
отметим поле "Create directory for solution" (см. рис. 2.1), а в поле
"Solution Name:" введём имя папки, которая будет создана в папке,
указанной в поле "Location". В полученной папке, в свою очередь,
будет создана папка, которая указана в поле "Name" и в которой
будут располагаться файлы проекта.
Теперь следует нажать кнопку "OK". При этом появится диалоговое
окно с предварительными настройками создаваемого проекта. На рис. 2.2
показан вариант такого окна для проекта консольной программы (см. п.
2.1.1). Выбирая в его левой части какой-либо пункт, в правой части увидим
соответствующую группу настроек.
27
Рис. 2.2. – Окно предварительной настройки проекта
Далее возможны два варианта. Во-первых, если уже есть исходный код
программы, но нет проекта, то в правой части окна можно отметить поле
"Empty project" для создания пустого проекта (без файлов) и нажать кнопку
"Finish". Затем поместить имеющиеся файлы в папку проекта. Для этого
следует в окне "Solution Explorer" щёлкнуть правой кнопкой мыши на имени
проекта и в появившемся меню выбрать пункт "Add / Existing Item…". В
появившемся диалоговом окне выбрать нужные файлы и нажать на кнопку
"Add". Ну и не следует забывать сохранять состояние проекта после
добавления или удаления файлов. Для этого используем пункт меню "File /
Save (имя проекта)".
Во-вторых, можно сразу нажать кнопку "Finish", не отметив поле
"Empty project". В этом случае будут создан
минимальный набор
28
необходимых файлов. Созданный таким образом проект можно уже сейчас
скомпилировать, собрать и запустить (см. п. 2.2). Правда ничего толкового он
не сделает. Ведь это ещё только заготовка.
2.1.1. Проект консольной программы
Примером консольной программы может служить широко известный
файловый менеджер FAR (более подробную информацию по этой программе
можно найти на сайте http://www.rarlab.com). Подобные программы не имеют
"настоящего" оконного интерфейса, но для его имитации могут использовать
псевдографику (т.е. для отображения графики используются буквы и прочие
символы). Впрочем, как правило, такие программы ограничены интерфейсом
командной строки. В русской версии операционной системы Microsoft
Windows для запуска консольных программ можно воспользоваться пунктом
главного системного меню "Кнопка ПУСК / Все программы / Стандартные /
Командная строка" или выбрать пункт меню "Кнопка ПУСК / Выполнить" и в
появившемся диалоговом окне ввести "cmd" и нажать кнопку "OK" (или на
клавиатуре клавишу "Enter"). При этом откроется окно (так называемая
консоль), из которого можно запускать консольные программы, используя
соответствующие команды (список команд можно получить по команде help).
Также для этой цели можно использовать упомянутый выше файловый
менеджер FAR.
В качестве примера рассмотрим процесс создания проекта программы
инвертирования чёрно-белого изображения, имеющего 256 градаций серого
цвета и хранящегося в файле в графическом формате BMP. Назовём
программу "bmpinv". Этап проектирования этой программы был рассмотрен
выше в начале раздела 2. Для создания проекта воспользуемся настройками с
рисунков 2.1 и 2.2. То есть в списке "Templates" окна "New Project" выберем
29
шаблон "Win32 Console Application", в поле "Name" укажем имя нашей
программы, отметим поле "Create directory for solution", а в поле "Solution
Name" введём имя "Graph" (без кавычек). Во втором окне ("Win32 Application
Wizard") сразу жмём кнопку "Finish". Таким образом, получим по указанному
в поле "Location" пути папку Graph, в которой будет папка bmpinv. В области
просмотра и редактирования появится содержимое файла bmpinv.cpp, а в окне
Solution Explorer появится список всех файлов проекта bmpinv (см. рис. 2.3).
Следует отметить, что это окно показывает не только список файлов
конкретного проекта, но и список проектов, входящих в открытое "решение"
– Solution (см. п. 2.1.3). Под кнопочной панелью Standard появится ещё одна
кнопочная панель "Text Editor", которую рассмотрим подробнее.
Первые четыре кнопки позволяют ускорить набор исходного текста
программы. Например, завершить недописанное слово, показать в виде
всплывающей подсказки список аргументов функции, полей структуры или
методов класса и т.д. Впрочем, среда, как правило, автоматически производит
эти
действия
при
отсутствии
ошибок
в
части
коде
выше
места
редактирования. Например, при вставке открывающей круглой скобки после
имени функции появляется список её аргументов, а набираемый в текущий
момент времени аргумент показывается "жирным" шрифтом. Если вариантов
списка аргументов несколько, то в этой же подсказке присутствуют числа –
номер варианта и количество вариантов, а также значки треугольных стрелок,
щёлкая мышью на которых можно перебирать варианты. Ещё один способ
перебора вариантов – использование курсорных клавиш "вверх" и "вниз" на
клавиатуре, то есть клавиш "↑" и "↓". Другой пример: набираем имя какойлибо структуры и ставим точку, чтобы ввести имя поля структуры. При этом
появляется список всех полей указанной структуры. Выбираем теми же
курсорными клавишами нужное имя в списке и нажимаем клавишу "Enter".
Следует
отметить,
что
если
автоматика
не
срабатывает,
то
для
30
принудительного вызова подсказки (или завершения набираемого слова)
следует использовать указанные первые четыре кнопки панели "Text Editor".
Впрочем,
во
многих
случаях
можно
воспользоваться
клавиатурной
комбинацией Ctrl+Space (нажать на клавиатуре клавишу Ctrl и, не отпуская
её, нажать клавишу "Пробел").
Рис. 2.3. – Часть окна среды с открытым проектом bmpinv
Чтобы применить на практике материал п.1.3, щёлкнем мышью на
слове "return" в окне с открытым файлом "bmp.cpp" и на клавиатуре нажмём
клавишу F1. В результате получим окно со справочной информацией по
этому оператору (см. п. 4.4.3 и 4.5). Полученное окно содержит:

Краткую информацию о назначении данного оператора.

Пример записи использования этого оператора в общем виде, т.е. с
использованием условных обозначений.

Раздел "Remarks", который расшифровывает использованные в
записи условные обозначения.
31

Раздел "Example", который показывает пример практического
использования данного оператора.

Раздел "See Also", который перечисляет ссылки на темы, имеющие
какое-либо отношение к полученной справке.
Следует отметить, что разделы могут быть скрыты или, наоборот,
показаны щелчком мыши на прямоугольнике (слева от названий разделов) со
знаком минуса или плюса соответственно. Аналогичным образом скрываются
и отображаются элементы списка в окне "Solution Explorer", а также функции,
блоки комментариев и пр. области в окне с исходным текстом программы (в
данном примере весь исходный текст располагается в файле bmpinv.cpp).
Ещё несколько замечаний. Наведём курсор мыши на слово "_TCHAR"
и получим "всплывающую" подсказку по этому слову. В данном случае это
шаблон и мы увидим, как он был определён, т.е. "typedef wchar_t _TCHAR;".
Теперь щёлкнем мышью по этому же слову и ниже, в окне "Code
Definition Window", увидим часть содержимого файла "tchar.h" (название
файла можно увидеть в скобках в заголовке этого окна), в котором будет
выделена строка с указанным определением. Таким же образом можно
просматривать объявления и определения констант, переменных, функций и
др.
Итак, у нас есть каркас консольной программы, код которой
сосредоточен в файле bmpinv.cpp. Также у нас есть ещё два файла: stdafx.h и
stdafx.cpp. Эти файлы появились благодаря отметке поля "Precompiled
Header" (см. рис. 2.2). Поясним, что для использования различных функций,
специфичных типов данных и пр. в программу необходимо включить
директивой
препроцессора
(см.
п.
4.9)
"#include"
соответствующие
заголовочные файлы (h-файлы), системные или свои. При этом имена
системных заголовочных файлов ограничиваются треугольными скобками
32
"<>", а имена своих заголовочных файлов двойными кавычками (разные
средства
ограничения
указывают препроцессору место
расположения
включаемых файлов – системная папка среды или папка создаваемой
программы). При разработке программы нужные заголовочные файлы
перечисляют в файле stdafx.h, который и включают в свою программу (файл
stfafx.cpp, как правило, не изменяют). В данном случае stdafx.h содержит
включения двух системных файлов: stdio.h и tchar.h. С одной стороны
системные заголовочные файлы (и соответствующие им системные cppфайлы) имеют достаточно большой размер и поэтому долго (относительно
общего времени компиляции всей программы) компилируются, а с другой
стороны подобные файлы редко модифицируются (или не изменяются
вообще). Поэтому кроме самих заголовочных файлов в распоряжении среды
имеются их прекомпилированные версии. Отметка упомянутого выше поля
(см. рис. 2.2) указывает компилятору не компилировать перечисленные в
stdafx.h файлы, а использовать их прекомпилированные версии. При этом
сокращается общее время компиляции программы.
Теперь, имея некоторое представление о возможностях встроенного
редактора среды Microsoft Visual Studio 2005, можно набрать исходный код
программы bmpinv. Полный вариант кода (содержимого файла bmpinv.cpp)
приведён на листинге 2.1. Замечание: перед компиляцией и сборкой следует
изменить свойства данного проекта (см. п. 2.2) – в строке "Character Set"
группы "Project Defaults" раздела "General" выбрать значение "Not Set".
Следует отметить, что листинг не содержит комментариев (см. п. 4.10)
только потому, что далее идет пояснение приведённого кода. Однако на
практике следует всегда осмысленно и подробно комментировать исходный
код программы. Как показывает практика, время, затрачиваемое на изучение
или вспоминание плохо прокомментированного кода средней сложности, во
много раз превышает время, затрачиваемое на составление "хороших"
33
комментариев и в дальнейшем на изучение полученного кода. Подробнее об
оформлении кода можно узнать из описания стандарта языка C++ (см. п. 4.1).
Листинг 2.1. – Программа bmpinv (файл bmpinv.cpp)
#include "stdafx.h"
int main(int argc, char* argv[])
{
FILE *p_src;
unsigned long buff, file_size, adr_data, cnt;
p_src = fopen(argv[1], "rb+");
fseek(p_src, 0, 0);
buff = 0;
fread(&buff, 2, 1, p_src);
if (buff != 19778)
{
printf("This is not BMP file...\n");
return 1;
}
fseek(p_src, 2, 0);
fread(&file_size, 4, 1, p_src);
printf("FileSize = %ld\n", file_size);
fseek(p_src, 10, 0);
fread(&adr_data, 4, 1, p_src);
printf("Origin of image data: %ld\n", adr_data);
buff = 0;
for (cnt = adr_data; cnt < file_size; cnt++)
{
fseek(p_src, cnt, 0);
fread(&buff, 1, 1, p_src);
buff = 255 - buff;
fseek(p_src, cnt, 0);
fwrite(&buff, 1, 1, p_src);
}
fclose(p_src);
return 0;
}
Итак, у нас есть функция main (см. п. 4.5), которой с помощью массива
(см. п. 4.6) argv можно передать аргументы – в данном случае имя bmp-файла
для инверсии. То есть в первой ячейке массива будет находиться указанное
имя ( в нулевой ячейке имя самой программы, т.е. bmpinv.exe). Далее с
помощью функции fopen открываем файл с заданным именем в режиме
бинарного чтения и записи и присваиваем указателю p_src (от англ. pointer to
34
source – указатель на исходные данные) адрес начала файла. На всякий
случай принудительно устанавливаем указатель p_src на начало открытого
файла. Затем очищаем переменную buff – буфер для хранения считанных из
файла данных. Функцией fread считываем в переменную buff первые два
байта файла для проверки, что файл имеет BMP формат. Если не так, то с
помощью функции printf выводим в окне консоли предупредительное
сообщение и завершаем работу программы (возвращая ненулевое значение,
сигнализируем о принудительном завершении работы программы из-за
ошибки или по какой-либо другой причине). Далее функцией fseek
пропускаем первые два байта файла и считываем следующие четыре байта
для определения размера файла (в байтах). Полученный размер показываем в
консольном окне. Затем смещаемся на 10-ю ячейку (байт) файла и считываем
четыре байта (10 – 13), содержащих адрес ячейки файла, с которой
располагаются непосредственно данные изображения. Согласно третьей
версии BMP формата для 8-битных изображений первые 1024 байта (с 0-го по
1023-й) содержат служебную информацию (идентификатор BMP формата,
размер файла, ширину и высоту (в пикселях) изображения и т.д.) в том числе
палитру из 256 элементов (цветов или градаций серого), а уж с 1024-го байта
хранятся данные самого изображения. То есть, считав указанные выше
четыре байта в переменную adr_src (от англ. address of source – адрес
исходных данных), получим значение 1024. Затем в цикле выполняем
следующие действия: считываем данные изображения по одному элементу
(пикселю) за раз (т.е. за одну итерацию цикла); производим инверсию
элемента вычитанием его значения из величины 255 (максимально возможное
значение для 8-битного изображения, в данном случае соответствует белому
цвету); для упрощения программы записываем (функцией fwrite) результат
инверсии в ту же ячейку того же файла, откуда брали исходное значение;
перемещаемся к следующей ячейке файла с помощью переменной счётчика
35
cnt и функции fseek. И так до конца файла. Затем закрываем файл и
завершаем
работу
программы,
возвращая
нулевое
значение
для
информирования о благополучном завершении программы.
При компиляции и сборке приведённого кода могут быть сообщения
(warning) о функции "fopen" – можно проигнорировать. Для запуска
программы (см. п. 2.2) нужно открыть консоль (см. выше) или запустить FAR,
в папку с файлом bmpinv.exe скопировать "серый" bmp-файл и запустить
программу, набрав "bmpinv.exe имя bmp-файла", например, "bmpinv.exe
lena.bmp". Затем можно открыть указанный bmp-файл и убедиться, что он
"инвертировался".
Повторный
запуск
приведёт
к
повторному
инвертированию, то есть возврату изображения в исходное состояние.
Пример исходного чёрно-белого 8-битного (256 градаций серого цвета,
то есть градаций яркости) изображения "Лена" (lena.bmp) и результат его
инверсии приведены на рис. 2.4.
Рис. 2.4. – Изображение "Лена" и его инверсия
2.1.2. Проект программы-библиотеки
36
Как правило программное обеспечение не создаётся с абсолютного
нуля. После этапа проектирования разработчик использует литературные
источники и сеть Интернет в поисках чужих разработок, которые можно
легально использовать для создания своего программного продукта. Кроме
того свой же код пишется таким образом, чтобы в перспективе его можно
было повторно использовать в других своих разработках.
В п. 2.1.1 был приведён пример программы, инвертирующей bmpизображение. Очевидно, что весь код функции main можно было бы
оформить в виде отдельной функции, которой всего лишь нужно передать
имя файла с исходным изображением. Эту функцию можно также описать в
другом CPP-файле (или заголовочном H-файле). Таким же образом можно
писать и другие подобные варианты обработки изображений (изменение
яркости, бинаризация и пр.). В дальнейшем при создании другой программы
обработки изображений эти функции можно вписать в соответствующий
файл проекта (где они вызываются) или, что проще, добавить (H)CPP-файлы
с описаниями функций в проект (см. п. 2.1). У такого способа есть три
существенных недостатка.
Во-первых,
при
изменении
какой
либо
функции
придётся
перекомпилировать и пересобрать весь программный код разрабатываемой
программы. Например, мы продали кому-то свою программу. Однако
покупатель жалуется, что программа работает с ошибками. А что если мы
продали много экземпляров этой программы и к тому же использовали эти
"ошибочные" варианты функций в других своих разработках, которые также
кому-то продали? Ответ – нужно "исправить" функции и собрать заново ВСЕ
наши разработки, в которых используются эти функции!
Во-вторых, подключаемые к проекту файлы могут содержать описания
множества функций, из которых для конкретной разработки могут быть
необходимы лишь некоторые. Однако в процессах компиляции и сборки
37
будут участвовать все описания функций, что не только увеличит время
указанных процессов, но и увеличит размер EXE-файла разрабатываемой
программы.
В-третьих, при продаже файлов с описаниями функций какой-либо
компании по разработке программного обеспечения исходные тексты этих
функций становятся доступными (известными) этой компании. В случае
разработки "открытого" программного обеспечения такой доступ как раз
должен быть предоставлен. Однако что делать, если передаваемый
(продаваемый) исходный код содержит "ноу-хау" или другие секреты,
которые нужно сохранить от "чужих" глаз и вместе с тем предоставить
возможность покупателю использовать их в своих разработках?
От этих недостатков свободен метод использования библиотек
функций. Т.е. все описания функций оформляются в виде отдельной
программы, в результате компиляции и сборки которой получаем библиотеку
– файл с расширением DLL. В дальнейшем при разработке какой-либо своей
программы достаточно сослаться на этот DLL-файл и указать (перечислить),
какие функции нужны. Такой способ имеет очевидные достоинства и
используется в том числе операционной системой Microsoft Windows.
Во-первых,
при
изменении
любой
из
функций,
производится
компиляция и сборка только библиотеки. Главное, чтобы тип и порядок
аргументов функции и тип возвращаемого ею значения не изменились.
Программы, использующие библиотеку, собирать заново не нужно!
Во-вторых, в разрабатываемую программу не включаются описания
используемых функций, а только ссылки на них. Это может способствовать
значительному уменьшению конечного кода программы по сравнению с
предыдущим методом.
В-третьих, можно продать библиотеку (DLL-файл) и приложить к ней
список входящих в её состав функций с описанием их аргументов и
38
возвращаемых значений. Содержимое самих функций покупателю будет
недоступно, но, руководствуясь прилагаемым списком, покупатель сможет
использовать купленную библиотеку в своих разработках. Правда при
распространении
своих
разработок
ему
придётся
распространять
и
библиотеку, но это будет только способствовать рекламе авторов библиотеки.
Рассмотрим процесс создания и использования библиотеки на примере
программы bmpinv из п. 2.1.1. Итак, у нас есть рабочее пространство
(Solution) "Graph", в котором пока расположен только один проект – bmpinv.
Поясним, что для открытия проекта следует воспользоваться пунктом меню
"File / Open / Project/Solution…", в появившемся диалоговом окне "Open
Project" найти и выделить файл "Graph.sln" и нажать кнопку "Open".
ВНИМАНИЕ! Если расширения у файлов отсутствуют, то вызвать
"Панель Управления" операционной системы Windows, выбрать пункт
"Свойства папки", в появившемся диалоге щёлкнуть на ярлыке "Вид" и в
списке "Дополнительные параметры" снять отметку с пункта "Скрывать
расширения для зарегистрированных типов файлов"!
Итак проект открыт и мы видим то, часть чего показана на рис. 2.3. В
открытое рабочее пространство Graph добавим новый проект библиотеки,
которую назовём img_inv. Для этого наведём указатель мыши на строку
"Solution 'Graph' (1 project)" и щёлкаем правой кнопкой мыши, а в
появившемся меню выбираем "Add / New Project…". В появившемся окне
"Add New Project" (оно почти идентично окну создания нового проекта из п.
2.1) слева выбираем "Visual C++", а справа выбираем "Win32 Project". В поле
"Name" введём img_inv и нажмём кнопку "OK". В появившемся окне "Win32
Application Wizard – img_inv" слева выберем "Application Settings", а справа
отметим "DLL" и нажмём кнопку "Finish". На рис. 2.5 показана часть окна
среды Visual Studio после добавления проекта библиотеки img_inv.
Следует отметить, что в данном случае этих настроек достаточно.
39
Однако при необходимости использования библиотек MFC и ATL следует
отметить соответствующие поля. Более подробно об этих библиотеках можно
узнать в [8 – 17] (возможности библиотеки MFC частично рассматриваются в
разделе 3).
Рис. 2.5. – Проекты bmpinv и img_inv в рабочей области Graph
Прежде всего уберём в настройках проекта использование UNICODE
(см. п. 2.1.1). Для этого вызываем окно настроек свойств проекта (см. п. 2.2) и
в строке "Character Set" группы "Project Defaults" раздела "General" выбираем
значение "Not Set". Также в окне "Solution Explorer" наводим указатель мыши
на имя проекта bmpinv, щёлкаем правой кнопкой мыши и выбираем пункт
меню "Project Dependencies…". В появившемся одноимённом диалоговом
окне на вкладе "Dependecies" в списке "Depends on" отмечаем поле "img_inv",
40
а на вкладке "Build Order" в списке "Projects build in this order" сперва указано
имя img_inv, а потом bmpinv. Нажмём кнопку "OK". Первая настройка
указывает среде (её сборщику и компилятору), что проект bmpinv использует
проект img_inv, т.е. зависит от него. Вторая настройка указывает среде, что
сперва нужно собрать проект библиотеки (img_inv), а уже потом все проекты,
которые от неё зависят (в данном случае только проект bmpinv). Благодаря
этим настройкам собрать оба проекта можно одной командой. Если теперь
собрать рабочую область (см. п. 2.2), то в папке "Debug" папки рабочей
области "Graph" появятся два файла: bmpinv.exe и img_inv.dll. В дальнейшем
для нормальной работы программы bmpinv либо оба этих файла должны
находиться в одной папке, либо файл img_inv.dll можно поместить в любую
папку, указанную в переменной среды PATH (об этой переменной и способах
изменения её значения можно узнать из книг посвящённых описанию правил
эксплуатации операционной системы Microsoft Windows XP). На листингах
2.2 и 2.3 представлены полные тексты файлов bmpinv.cpp (его содержание
уже не соответствует листингу 2.1) и img_inv.cpp.
Листинг 2.2. – Программа bmpinv (файл bmpinv.cpp)
#include "stdafx.h"
extern "C" _declspec(dllimport)
int img_inv(char *file_name);
int main(int argc, char* argv[])
{
int result = img_inv(argv[1]);
if (result != 0)
{
printf("ERROR!\n");
return 1;
}
printf("Processing was done!\n");
return 0;
}
41
Листинг 2.3. – Библиотека img_inv (файл img_inv.cpp)
#include "stdafx.h"
#include <stdio.h>
#ifdef _MANAGED
#pragma managed(push, off)
#endif
extern "C" _declspec(dllexport)
int img_inv(char *file_name)
{
FILE *p_src;
unsigned long buff, file_size, adr_data, cnt;
p_src = fopen(file_name, "rb+");
fseek(p_src, 0, 0);
buff = 0;
fread(&buff, 2, 1, p_src);
if (buff != 19778)
{
printf("This is not BMP file...\n");
return 1;
}
fseek(p_src, 2, 0);
fread(&file_size, 4, 1, p_src);
printf("FileSize = %ld\n", file_size);
fseek(p_src, 10, 0);
fread(&adr_data, 4, 1, p_src);
printf("Origin of image data: %ld\n", adr_data);
buff = 0;
for (cnt = adr_data; cnt < file_size; cnt++)
{
fseek(p_src, cnt, 0);
fread(&buff, 1, 1, p_src);
buff = 255 - buff;
fseek(p_src, cnt, 0);
fwrite(&buff, 1, 1, p_src);
}
fclose(p_src);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
#ifdef _MANAGED
#pragma managed(pop)
#endif
42
Если теперь собрать рабочую область и запустить программу bmpinv,
то можно убедиться, что результат её работы идентичен результату,
полученному в п. 2.1.1.
Таким образом наша основная программа (bmpinv) вызывает функцию
img_inv, описанную в одноимённой библиотеке, и передаёт ей имя файла,
полученное в свою очередь через аргумент argv функции main.
В дальнейшем при создании другой программы, которой понадобится
наша библиотека, необходимо будет выполнить следующие шаги:

В окне "Solution Explorer" щёлкнуть правой кнопкой мыши на
имени рабочего пространства и выбрать пункт меню "Add / Existing
Project…".

В
появившемся
диалоговом
окне
найти
и
выбрать
файл
"img_inv.vcproj". Подтвердить выбор, нажав кнопку "OK".

В
свойствах
проекта
разрабатываемой
программы
указать
зависимость от проекта библиотеки и проверить порядок сборки
(см. выше).

В начале того CPP-файла, в котором вызывается библиотечная
функция,
после
строк
"#include"
объявить
используемую
библиотечную функцию, прописав следующие строки:
extern "C" _declspec(dllimport)
int img_inv(char *file_name);
Потом мы можем даже изменять структуру функции и собирать
библиотеку отдельно от программы, а старую версию DLL-файла (который
находился вместе с EXE-файлом программы) заменить новой версией. При
последующем запуске программа ничего "не заметит" и будет работать попрежнему.
В качестве самостоятельного упражнения можно порекомендовать
добавить
в
библиотеку
функции,
например,
регулировки
яркости
43
изображения или его бинаризации (ограничения по уровням яркости), а
программу bmpinv модифицировать таким образом, чтобы она позволяла
пользователю задавать нужный вид обработки при своём запуске или в
процессе работы.
2.1.3. Работа с несколькими проектами
В п. 2.1.2 мы работали с двумя проектами "одновременно", причём
один зависел от другого. Можно привести и другие примеры, когда
приходится работать с гораздо большим количеством проектов и при этом
желательно их всех держать под рукой. И не обязательно, что они имеют
какие-либо взаимные зависимости.
Например, мы берём чужую программу с целью изучить её и затем
создать уже свою программу на основе полученных знаний. В обычном
режиме мы открываем чужой проект и собираем его, а потом запускаем.
Затем что-то меняем в тексте программы и опять собираем, и запускаем. Это
и есть один из методов изучить принципы работы программы. Закончив, мы
закрываем проект и создаём новый – свой проект. Пишем код своей
программы, но понимаем, что забыли что-то испытать в чужой программе и
нужно снова обратиться к чужому проекту. Тогда закрываем свой проект,
открываем чужой и проводим испытания. Затем опять закрываем чужой
проект, открываем свой проект и так далее. Конечно это чудовищно не
рационально. Но среда Visual Studio уже на протяжении нескольких версий
предлагает использовать рабочие области, которые раньше так и назывались
(от английского "workspace"), однако в версиях 2003 и 2005 вместо термина
"рабочая область" принято использовать термин "решение" (от английского
"solution"). Впрочем смысл и назначение не поменялись, поэтому будем в
равной степени использовать оба термина.
44
Итак, с одной стороны solution – это способ упростить работу с
несколькими проектами. С другой (физической) стороны это папка с именем
заданным в поле Solution диалогового окна создания нового проекта. Все
настройки
рабочей
области
(например
количество
проектов
и
их
расположение) хранятся в файле с именем, как у указанной папки, и
расширением SLN. Папки проектов, как правило, также хранятся в этой папке
(на самом деле в ней обязательно находится папка только одного проекта – на
основе которого была создана рабочая область, остальные же проекты могут
храниться в любом другом месте). Кроме того в папке рабочей области есть
папки Debug и Release (см. п. 2.2.2), в которых находятся конечные
результаты сборки проектов, входящих в состав solution.
Использование solution имеет очевидные преимущества:

Можно открывать и изменять файлы одного проекта, не закрывая
файлов других проектов.

Можно производить отдельную "независимую" сборку любого из
проектов, не закрывая другие проекты.

Можно собрать все проекты одной командой (см. п.2.2).

Конечные результаты сборки всех проектов располагаются в одном
и том же месте, что упрощает их испытание и отладку (см. п. 2.3).
Однако следует отметить один важный момент. Обратите внимание на
окно "Solution Explorer" – можно заметить, что имя одного из проектов
выделено жирным шрифтом. Это "текущий" или "активный" проект (оба
названия одинаково часто используются. Именно он будет запущен
командами меню "Debug" и именно в него будут добавляться новые файлы с
помощью команд меню "Project", и т.д. Чтобы сделать другой проект
активным, следует щёлкнуть правой кнопкой мыши на имени нужного
проекта (в окне "Solution Explorer") и выбрать пункт меню "Set as StartUp
Project". Впрочем, собирать проекты и добавлять в них файлы можно и без
45
изменения "активности" – достаточно щёлкнуть правой кнопкой мыши на
имени
нужного
проекта
(в
окне
"Solution
Explorer")
и
выбрать
соответствующий пункт меню. Но запускать и отлаживать можно всё-таки
ТОЛЬКО АКТИВНЫЙ проект!
Можно задать настройки solution (например, какой проект по
умолчанию будет активным при открытии solution). Для этого щёлкаем
правой кнопкой на его имени в окне "Solution Explorer" и выбираем в
появившемся меню пункт "Properties". В появившемся диалоговом окне слева
выбираем нужный пункт, при этом справа будут показаны возможные
настройки, которые и меняем.
2.2. Компиляция, сборка и запуск
2.2.1. Компиляция
Для того чтобы получить программу из исходного кода, её необходимо
скомпилировать и собрать. Компиляция – это процесс перевода исходного
текста программы в так называемый "объектный" код (т.е. перевод
программного кода с языка программирования C++ на язык машинных
команд. Файлы с объектным кодом имеют расширение OBJ. В дальнейшем
эти файлы нужны для сборки программы.
На этапе компиляции выявляются все "синтаксические" ошибки (см. п.
2.3.1). Таким образом для проверки правильности синтаксиса исходного кода
программы можно ограничиться компиляцией её файлов – это во-первых
экономит время, а во-вторых позволяет сосредоточиться именно на ошибках
такого рода.
Для
компиляции
нужного
CPP-файла
(H-файлы
отдельно
компилировать не нужно) следует выделить его имя в окне "Solution Explorer"
46
и либо щёлкнуть на этом имени правой кнопкой мыши и в появившемся
меню выбрать пункт "Compile", либо использовать пункт основного меню
среды "Build / Compile". Во втором случае обратите внимание на то, что
пункт "Compile" имеет пояснительную надпись "Ctrl+F7" – это так
называемые горячие клавиши. В данном случае для компиляции выбранного
файла можно нажать на клавиатуре клавишу "Ctrl" и, не отпуская её, нажать
клавишу "F7". Можно задать и свои горячие клавиши. Для этого следует
вызвать окно настроек среды Visual Studio (см. п. 1.2, рис. 1.5), в левом
списке выбрать пункт "Keyboard", а справа щёлкнуть мышью в поле "Press
shortcut keys", нажать нужную комбинацию клавиш и нажать кнопку "Assign".
В процессе компиляции вся статистическая информация (а также
результат компиляции) отображается в окне "Output", которое автоматически
становится активным в нижней панели среды Visual Studio. Если панель
скрыта (настроено автоматическое скрытие), то она будет показана.
47
Рис. 2.6. – Свойства проекта img_inv (см. п. 2.1.2)
Перед компиляцией можно настроить её правила "по умолчанию" для
всех файлов проекта. Для этого следует выделить имя проекта в окне
"Solution Explorer"и либо щёлкнуть правой кнопкой мыши на имени проекта
и в появившемся меню выбрать "Properties", либо использовать пункт меню
"Project / Properties" (можно также использовать горячие клавиши, но это уже
для самостоятельного рассмотрения). В появившемся диалоговом окне (рис.
2.6) в левом списке следует выбрать пункт "C/C++" или нажать значок плюса
слева от указанного пункта и выбрать нужный подпункт, при этом правая
часть окна будет отображать список возможных настроек выбранного пункта.
Для отмены сделанных изменений следует нажать кнопку "Отмена", а для
подтверждения – кнопку "OK". Аналогичным образом можно задать
индивидуальные настройки для каждого конкретного файла.
2.2.2. Сборка (компоновка)
Полученный объектный код ещё нельзя запускать – сначала весь код
нужно
"собрать"
(скомпоновать).
При
этом
формируется
структура
исполняемого или библиотечного файла [6], предназначенного для запуска и
использования в операционной системе Microsoft Windows.
В процессе сборки в определённых секциях этого файла размещается
объектный
код
всех
скомпилированных
файлов
проекта,
а
также
прописываются ссылки на функции, которые находятся во внешних
библиотеках (см. п. 2.1.2). В случае сборки библиотеки получаем файл с
именем проекта и расширением DLL, а в случае исполняемого файла
получаем расширение EXE. Так в результате сборки проектов из п. 2.1.2
получим два файла img_inv.dll и bmpinv.exe. Следует отметить, что процесс
сборки автоматически включает в себя этап компилирования, поэтому вместо
48
последовательности "компиляция/сборка" можно сразу собирать программу
на основе только её исходного кода – все необходимые файлы будут
скомпилированы автоматически.
Правила сборки можно задать только для всего проекта сразу. Для
этого вызываем диалоговое окно, показанное на рис. 2.6 и в левом списке
выбираем пункт "Linker" или любой из его подпунктов.
Для сборки проекта следует выбрать его имя в окне "Solution Explorer"
и либо щёлкнуть правой кнопкой мыши на имени проекта и в появившемся
меню выбрать "Build" или "Rebuild", либо выбрать соответствующие пункты
меню "Build" (в основном меню среды). Если выбрать пункт "Build", то будет
проведена "ускоренная" сборка. При этом будут перекомпилированы только
те файлы проекта, которые были изменены после последней сборки, что
может существенно уменьшить время сборки всей программы в целом. Если
выбрать пункт "Rebuild", то ВСЕ файлы проекта будут перекомпилированы и
собраны независимо от наличия в них указанных выше изменений.
Меню "Build" также содержит пункты "Build Solution", "Rebuild
Solution" (которые имеют горячие клавиши) для сборки всех проектов в
рабочей области сразу. При этом соблюдается порядок сборки проектов (см.
п. 2.1.2).
Пункты "Clean Solution" и "Clean" предназначены соответственно для
очистки папок рабочей области и конкретного проекта от результатов
компиляции и сборки.
Конфигурации "Debug" и "Release"
Кнопочная панель Standard (см. п. 1.1) содержит поле – выпадающий
список "Solution Configurations", в котором по умолчанию можно выбрать две
конфигурации сборки: Debug и Release.
Конфигурация Debug предназначена для сборки отладочной версии
49
программы, т.е. в исполняемый файл включается отладочная информация,
благодаря которой отладчик среды Visual Studio может запустить программу
в режиме отладки (см. п. 2.3). Поэтому данная версия программы получается
медленнее Release-версии и имеет гораздо больший размер.
Конфигурация Release предназначена для окончательной сборки
программы, когда все ошибки выявлены и исправлены, а программа отлажена
и
работает
без
нареканий.
Из
программы
исключается
отладочная
информация, т.е. собранную программу нельзя будет отладить средствами
среды Visual Studio. Однако при этом программа будет иметь гораздо
меньший размер и быстрее запускаться и выполняться.
После выбора конфигурации рекомендуется пересобрать программу
командами "Rebuild" или "Rebuild Solution"!
2.2.3. Запуск программы
Полученный в результате сборки исполняемый (EXE) файл можно
запустить средствами операционной системы Microsoft Windows (например с
помощью "Проводника"), либо непосредственно из среды Visual Studio (это
не касается консольных программ, имеющих аргументы – например
программы bmpinv, рассмотренной в п. 2.1.1 – 2.1.3).
Для запуска программы следует выбрать пункт меню "Debug / Start
Without Debugging" или комбинацией горячих клавиш "Ctrl+F5".
2.3. Отладка программы
При создании любой программы могут быть допущены ошибки – по
незнанию или по невнимательности. К этому следует относиться спокойно,
т.к. исправление ошибок, как и их сознательное допущение – это лучший
50
способ научиться писать хорошие программы!
После компиляции и сборки программы следует внимательно изучить
содержимое окна "Output", если его предпоследняя строка не содержит такого
выражения "0 error(s), 0 warning(s)" (т.е. ноль ошибок и ноль замечаний). При
выявлении ошибок исполняемый файл создан не будет, а вот при замечаниях
исполняемый файл создаётся и его можно будет использовать, но тем не
менее следует внимательно изучать замечания, т.к. они могут указывать на
ошибки, которые проявят себя не сразу, а в дальнейшем при эксплуатации
программы.
2.3.1. Разновидности ошибок
Ошибки бывают синтаксические, логические и ошибки этапа сборки.
Первые (синтаксические ошибки) очень легко выявить на этапе компиляции,
т.к. компилятор отслеживает все ошибки подобного рода и после компиляции
выдаёт не объектные файлы, а список ошибок в окне "Output" в нижней
панели среды Visual Studio. Щёлкнув дважды левой кнопкой мыши на строке
с описанием ошибки, мы попадём в район её происхождения (ошибка не
обязательно может быть в указанной компилятором строке – она может быть
и в строках, расположенных выше). Можно также воспользоваться горячими
клавишами "F4" и "Shift+F4" (перемещение на следующую и предыдущую
ошибки соответственно). Примерами синтаксических ошибок могут служить:
пропуск оператора или знака "точки с запятой", пропуск скобки или вставка
лишней скобки и т.д.
Ошибки этапа сборки также относительно легко определить, т.к. они
автоматически выявляются сборщиком (компоновщиком). Примером ошибки
такого рода является отсутствие библиотеки с нужной функцией –
попробуйте из рабочего пространства в п. 2.1.2 удалить проект библиотеки
51
img_inv и собрать всё рабочее пространство!
Логические ошибки самые "неприятные" – их обнаружить труднее
всего. Например где-то в теле программы вместо операции сложения
используется операция вычитания или вместо типа float используется тип int.
Проект благополучно компилируется и собирается, т.к. ни компилятор, ни
сборщик не смогут обнаружить такую ошибку! Единственный способ
обнаружения этих ошибок это интенсивная эксплуатация программы при
различных её настройках (если таковые имеются) – по результатам работы
программы можно понять, что допущена логическая ошибка, если автор
программы знает, каким должен быть "правильный" результат. Впрочем
результат может быть и правильным, но при определённых условиях
программа может аварийно завершить свою работу в самый неподходящий
для пользователя момент. Например, мы в цикле производим суммирование
каких-то величин и храним сумму в переменной типа short без проверки
переполнения. Но максимальное значение переменной такого типа равно
32767. Т.е. если при определённых условиях сумма превысит указанное
значение, то либо программа приведёт сумму к типу short и мы получим
неверный результат, либо аварийно завершит свою работу.
Итак, мы знаем, что программа содержит логическую ошибку. Как
найти её в исходном тексте программы? Есть два способа: первый – добавить
в текст сигнальные строки; второй использовать отладочные средства
интегрированной среды разработки Microsoft Visual Studio 2005. Первый
способ можно использовать в достаточно простых программах и заключается
он в вызове функций printf (в консольных программах) и MessageBox (в
программах с оконных интерфейсом) или аналогичных им, с помощью
которых можно увидеть значения конкретных переменных в конкретном
месте программного кода.
52
2.3.2. Запуск программы в режиме отладки
Прежде всего следует расставить "точки останова" (Breakpoints). Для
этого следует щёлкнуть левой кнопкой мыши по серой полосе (вдоль левого
края окна с исходным текстом программы) левее тех строк, в которых нужно
остановиться. Либо переместить курсор в нужную строку и нажать клавишу
F9. При этом в соответствующих местах этой серой полосы появятся круглые
зачки красного цвета. Чтобы убрать точки останова, следует повторно
щёлкнуть левой кнопкой мыши по этим красным значкам. Например у нас в
программе есть две строки:
int R = func();
V = R + S;
Чтобы узнать значение переменной R (т.е. фактически результат,
возвращённый функцией func), следует расположить точку останова во
второй строке.
Для запуска программы в режиме отладки следует использовать пункт
меню "Debug / Start Debugging" или горячей клавишей "F5". При этом
запустится специальная программа – отладчик среды Visual Studio, который в
свою очередь запустит нашу программу, но будет прерывать её выполнение
при достижении точек останова.
При достижении каждой точки останова в специальных окнах
выводится
различная
отладочная
информация.
Любые
необходимы
отладочные окна можно вызвать с помощью пунктов меню "Debug /
Windows". По умолчанию нижняя панель окна среды будет разделена
пополам. В левой части появятся окна: Autos, Locals, Threads, Modules,
Watch1. В правой части появятся окна: Call Stack, Breakpoints, Output.
Окно Autos автоматически показывает список всех переменных,
структур и пр., используемых программой. Окно Locals автоматически
53
показывает список всех ЛОКАЛЬНЫХ переменных, структур и пр.,
используемых программой и видимых с точки останова (подробнее об
областях видимости см. в п. 4.3). В окнах Watch1 – 4 (по умолчанию
отображается только первое окно, остальные можно вызвать через меню)
можно указать данные для просмотра – для этого нужно щёлкнуть левой
кнопкой мыши на ячейке столбца Name, ввести нужно имя и нажать клавишу
Enter. При достижении очередной точки останова все описанные окна
обновят своё содержимое. Окно Breakpoints содержит список точек останова.
В этом окне щелчком левой кнопки мыши можно снять метку с ненужных
точек останова (слева будут пустые квадратики), тогда программа в них не
будет останавливаться.
Чтобы выйти из режима отладки следует выбрать пункт меню "Debug /
Stop Debugging" или нажать горячие клавиши "Shift+F5".
Вместо точек останова можно использовать режим "пошаговой
отладки". Для запуска программы в режиме следует выбрать пункт меню
"Debug / Step Into" или "Debug / Step Over" (горячие клавиши F11 и F10
соответственно). При этом мы окажемся на первой строке нашей программы.
Используя указанные команды можно "шагать" по строкам программы, при
этом на каждом шаге будет обновляться содержимое описанных выше
отладочных окон. Следует отметить, что если строка программы содержит
вызов какой-либо функции, то команда "Step Into" перенесёт нас на первую
строку этой функции, а команда "Step Over" вызовет эту функцию, дождётся
возврата из неё и переместит нас к следующей строке программы.
Следует отметить, что отладчик среды Visual Studio прекрасно
подходит не только для выявления ошибок, но и для изучения заведомо
"исправного" программного кода. Так во время остановки (на точке останова
или шаге отладки) можно в описанных выше окнах Autos, Locals и Watch1–4
изменить значения изучаемых данных и продолжить отладку программы,
54
отслеживая последствия внесённых изменений. Для того чтобы изменить
значение какой-то величины, нужно в соответствующей ячейке столбца Value
дважды щёлкнуть левой кнопкой мыши, ввести нужное значение и нажать
клавишу Enter.
2.3.3. Поиск и навигация по тексту программы
При изучении, создании и отладке программ приходится достаточно
интенсивно перемещаться по их исходному тексту. При этом можно
воспользоваться следующими советами.
Откройте любой CPP-файл или H-файл проекта. Вверху окна
редактирования открытого файла можно видеть два выпадающих списка. В
левом списке можно выбрать любое пространство имён из описанных в этом
файле. Затем в правом списке можно выбрать любую функцию из описанных
в выбранном пространстве имён. При этом попадём на заголовок выбранной
функции. Пункт "(Global Scope)" в левом списке соответствует всему файлу
за исключением пространств имён, т.е. в правом списке будут доступны
функции, не входящие ни в одно из описанных в файле пространств имён.
Щёлкните правой кнопкой мыши на имени любой функции и в
появившемся меню выберите "Go To Definition" или "Go To Declaration". В
первом случае мы попадём на первую строку описания выбранной функции.
Во втором случае попадём на строку объявления функции. Например возьмём
проект из п. 2.1.2 и проделаем описанную операцию с функцией img_inv.
Команда "Go To Definition" перенесёт нас в файл img_inv.cpp на первую
строку описания этой функции. А команда "Go To Declaration" перенесёт нас
в файл bmpinv.cpp на строку, где эта функция объявлена (выше функции
main). Аналогично можно поступать с переменными, структурами и другими
видами данных (но для них оба пункта будут работать одинаково).
55
В левой панели среды Visual Studio щёлкаем на ярлыке "Class View" и
видим одноимённое окно, разделённое на две части. В верхней части
перечислены категории данных, используемых программой: глобальные
данные, определения и макросы (см. п. 4.9 о директиве #define), классы и
пространства имён. Выделяем нужную категорию и в нижней части окна
видим все данные соответствующей категории. Например, выбрав категорию
"Global Functions and Variables", увидим в нижней части окна функцию main
(если у нас проект консольной программы, например bmpinv из п. 2.1.1). Для
перемещения к описанию или объявлению нужного объекта в тексте
программы следует щёлкнуть на его имени в нижней части окна правой
кнопкой мыши и в появившемся меню использовать "Go To Definition" или
"Go To Declaration".
На кнопочной панели Standard (см. п. 1.1) есть поле "Find" и кнопка
"Find in Files" (см. рис. 2.7 справа налево). Открываем нужный файл, в
указанное поле вводим искомое слово (или его часть) и нажимаем на
клавиатуре клавишу Enter. Искомое слово (или его часть) будет показано
выделенным цветом в окне редактора. Для продолжения поиска следует
повторно нажать клавишу Enter. Чтобы вернуться к предыдущему результату
поиска (так называемый "поиск в обратном направлении"), следует нажать
комбинацию двух клавиш "Shift+Enter".
Выше был поиск в открытом файле. Чтобы провести поиск в
нескольких файлах, следует нажать кнопку "Find". В диалоге (см. рис. 2.8) в
поле "Find what" ввести искомое слово (или его часть). В поле "Look in"
указать зону поиска – выбрать из списка ("Entire Solution" означает поиск по
файлам всех проектов рабочей области) или указать конкретную папку (для
этого надо нажать кнопку со знаком многоточия). Во втором случае отмечаем
поле "Include sub-folders" для поиска в папках, находящихся в указанной
папке. Отмечаем поле "Match case", если надо различать прописные и
56
строчные буквы. Отмечаем поле "Match whole word", если искомый текст
является отдельным словом, а не его частью. В поле "Look at these file types"
указываем расширения файлов, в которых нужно искать (выбираем из списка
или вводим сами). Нажимаем кнопку "Find All" для начала поиска. В нижней
панели среды появится окно "Find Results" с результатами поиска (это окно
также можно вызвать через меню "View / Find Results"). Результаты поиска
представляют собой ссылки на строки файлов, в которых найден искомый
текст. Дважды щёлкнем левой кнопкой мыши на нужном результате, при
этом в редакторе откроется соответствующий файл и мы окажемся на
соответствующей строке.
Рис. 2.7. – Средства поиска
57
Рис. 2.8. – Диалоговое окно для поиска в файлах
Следует отметить, что описанные выше поля "Find" и "Find what"
имеют историю, которую можно посмотреть, нажав кнопку с изображением
треугольника в правой части указанных полей. Также следует отметить что
диалоговое окно поиска можно вызвать комбинацией горячих клавиш
"Ctrl+F".
2.4. Сохранение настроек программы
Многие
современные
программы
предоставляют
возможность
сохранять своё состояние и настройки (размеры, положение и цвет окна, язык
58
интерфейса, список открытых файлов и пр.). При этом каждый пользователь
компьютера
может
сохранять
свои
настройки
программы,
которые
автоматически будут использованы программой при её последующих
запусках. Для хранения настроек принято использовать INI файлы (имеют
расширение ini) или реестр операционной системы Microsoft Windows.
INI файлы
Это по сути обычные текстовые файлы, которые можно просмотреть и
изменить любым текстовым редактором. Содержимое такого файла состоит
из секций, ключей и значений ключей. Секции предназначены для
группировки близких по смыслу ключей. Имя каждой секции заключено в
квадратных скобках для отличия от одноимённых ключей. После имени
секции располагается список ключей и их значений. По сути, ключ – это
какой-то параметр (настройка) программы.
Рассмотрим консольную программу test_ini, состоящую из одного
файла (test_ini.cpp), содержимое которого приведено на листинге 2.4. Чтобы
проверить эту программу в действии, создадим проект test_ini, как описано в
п. 2.1.1, но в диалоговом окне настроек (см. рис. 2.2) отметим поле "Empty
project". Затем в свойствах проекта (см. рис. 2.6) в поле "Character Set"
раздела "General" установим значение "Not Set", а в поле "Use of MFC" того
же раздела установим значение "Use MFC in a Shared DLL". Далее добавим в
проект (см. п. 2.1) новый файл test_ini.cpp (см. листинг 2.4). В данном случае
мы используем библиотеку MFC (Microsoft Foundation Classes), которая
содержит функции для работы с INI файлами. С помощью последней
настройки мы эту библиотеку подключаем динамически (такой же подход
был использован в п. 2.1.2) – это позволяет уменьшить размер исполняемого
файла программы, но тогда нужно обеспечить наличие в операционной
системе соответствующей библиотеки (DLL файла). В противном случае
59
(если в указанном выше поле выбрать "Use MFC in a Static DLL"), т.е. при
статическом подключении библиотеки, её необходимые функции будут
встроены в исполняемый файл программы, что увеличит его размер, но
позволит использовать программу даже при отсутствии в системе самой
библиотеки.
Листинг 2.4. – Программа test_ini (файл test_ini.cpp)
#include <windows.h> // Описания функций записи и чтения INI файлов.
#include <iostream> // Описание пространства имён std.
using namespace std; // Здесь есть описание потока cout.
int main(int argc, char *argv[])
{
char *name;
char file[] = ".\\test_ini.ini"; // Имя INI файла.
char sect[] = "Test_1"; // Имя секции.
char key[] = "Name"; // Имя ключа.
char def[] = "Unknown"; // Значение ключа Name (по умолчанию).
if (argc < 2) // Если запустили программу без аргументов.
name = def; // Используем значение ключа по умолчанию.
else
name = argv[1]; // Иначе используем аргумент программы.
cout << "Hello, " << name << '!' << endl; // Приветствие 1.
WritePrivateProfileSection(sect, "", file); // Пишем секцию.
// Пишем ключ и его значение в INI файл.
WritePrivateProfileString(sect, key, name, file);
char name1[100]; // Буфер для чтения значения ключа.
// Читаем значение указанного ключа заданной секции.
GetPrivateProfileString(sect, key, def, name1, 100, file);
cout << "From INI-file:" << endl;
cout << "Hello, " << name1 << '!' << endl; // Приветствие 2.
return 0;
}
Если при запуске программы указать ей своё имя, то она
поздоровается, используя указанное имя, в противном случае использует имя
"Unknown" ("незнакомец"). Кроме того программа в СВОЕЙ папке создаст
файл test_ini.ini, в который запишет секцию "Test_1" и ключ "Name".
Значением ключа будет указанное программе имя (или "Unknown"). Далее
программа произведёт обратную операцию – прочитает значение ключа
"Name" в секции "Test_1" и снова поздоровается, используя в качестве имени
60
прочитанное значение. Вот примеры вызова программы и её сообщений:
test_ini.exe Vladimir
Hello, Vladimir!
From INI-file:
Hello, Vladimir!
test_ini.exe
Hello, Unknown!
From INI-file:
Hello, Unknown!
В качестве самостоятельной работы предлагается модернизировать
программу – пусть она при отсутствии аргументов использует имя из INI
файла, а если этого файла ещё нет или ключ не задан, то только тогда
использует имя "Unknown".
Отметим несколько моментов. Во-первых, в программе используется
поток cout для вывода сообщений в консольном окне (см. "файлы" в п. 4.7).
Во-вторых, если указать функциям записи имя уже существующего ключа, то
значение этого ключа будет "молча" перезаписано новым значением! Втретьих, чтобы каждый пользователь мог хранить свои настройки программы
в отдельном INI файле следует можно использовать переменные среды
операционной системы Microsoft Windows и функции для работы с ними.
Список переменных среды можно получить в консольном окне командой
"set". Напомним, что консольное окно можно открыть, если на панели задач
операционной системы Windows нажать кнопку "Пуск" и в меню выбрать
"Выполнить…", а в появившемся диалоговом окне ввести "cmd" и нажать
кнопку "OK".
Реестр операционной системы
Подробно о реестре можно узнать из книг по операционной системе
Microsoft
Windows.
Главное
то,
что
реестр
является
хранилищем
всевозможных настроек всевозможных программ, в том числе и настроек
самой операционной системы. Для просмотра и редактирования реестра
61
можно использовать программу regedit. Способ запуска этой программы
аналогичен способу открытия консольного окна (см. выше). Окно редактора
реестра (см. рис. 2.9) содержит: слева список секций (разделов, которые
также могут иметь подразделы) в виде дерева, а справа список ключей и их
значений для выбранного слева раздела (подраздела).
Листинг 2.5. – Программа test_regidtry (файл test_registry.cpp)
#include <atlbase.h> // Описание класса CRegKey.
#include <iostream> // Описание пространства имён std.
using namespace std; // Описание потока cout.
int main(int argc, char *argv[])
{
CRegKey reg; // Объект - раздел реестра.
char sect[] = "Software\\TUSUR\\test_registry"; // Имя раздела.
char key[] = "Name"; // Имя ключа.
char *name; // Указатель на значение ключа.
char def[] = "Unknown"; // Значение ключа по умолчанию.
if (argc < 2) // Если программа запущена без аргументов.
name = def; // Используем значение по умолчанию.
else
name = argv[1]; // Иначе используем аргумент программы.
cout << "Hello, " << name << '!' << endl; // Приветствие 1.
reg.Create(HKEY_CURRENT_USER, sect); // Создать раздел в реестре.
reg.SetStringValue(key, name); // Записать ключ и его значение.
char name1[100]; // Буфер для чтения значения ключа реестра.
ULONG size[100]; // Нужен для QueryStringValue.
reg.QueryStringValue(key, name1, size); // Чтение значения ключа.
cout << "From registry:" << endl;
cout << "Hello, " << name1 << '!' << endl; // Приветствие 2.
reg.Close(); // Работа с реестром закончена.
return 0;
}
Рассмотрим консольную программу test_registry, состоящую из одного
файла (test_registry.cpp), содержимое которого приведено на листинге 2.5. Эта
программа делает то же самое что и программа test_ini, но для записи и
чтения имени использует реестр. Создание проекта и задание его свойств, а
также запуск выполняются также как для предыдущей программы.
62
Рис. 2.9. – Окно редактора реестра regedit
В программе используется класс CregKey, предназначенный для
Работы с реестром и описанный в заголовочном файле "atlbase.h". Сначала
нужно создать раздел реестра и его подразделы методом Create указанного
класса. Если раздел уже существует, то его нужно открыть методом Open.
Метод SetStringValue записывает в созданный раздел ключ с заданным
именем и значением, а метод QueryStringValue считывает значение ключа.
Отметим, что здесь значение ключа пишем и читаем в виде строки. Для
записи и чтения значений других типов есть соответствующие методы (см.
MSDN Library). Программа test_registry создаёт в реестре раздел TUSUR и
сразу же подраздел test_registry, в котором создаёт ключ "Name" и записывает
его значение (см. рис. 2.9). Это общепринятая практика – создавать раздел с
именем организации-разработчика и подраздел с именем самой программы.
63
3. СОЗДАНИЕ ПРОГРАММЫ С ОКОННЫМ ИНТЕРФЕЙСОМ
Интегрированная среда разработки Microsoft Visual Studio 2005
является типичным примером программы, имеющей оконный интерфейс.
Любое окно имеет заголовок и рамку. Заголовок содержит имя программы и
её логотип (иконку), а также три системные кнопки, позволяющие: свернуть
окно в кнопку на панели задач (или в системный трей – область на панели
задач возле часов), развернуть окно на весь экран и закрыть окно. Рамка окна
предназначена для изменения его размеров. Остальная часть окна называется
клиентской областью окна или рабочей областью программы (см. п. 3.2).
Некоторые окна также имеют меню, панели управления (иногда называемые
"кнопочными" панелями, хотя могут содержать не только кнопки) и строку
состояния (расположена вдоль нижнего края окна и нужна для показа
различной информации, например в строке состояния окна среды Visual
Studio при редактировании какого-либо файла можно увидеть номер строки и
столбца, в которых находится текстовый курсор).
Ниже рассматриваются три разновидности оконного интерфейса: SDI,
MDI и на базе диалогового окна.
3.1. Интерфейс SDI
Расшифровывается
как
"Single
Document
Interface"
–
"однодокументный интерфейс". Это означает, что программа может работать
только с одним документом. Если мы открываем или создаём новый
документ, то старый документ будет автоматически закрыт! Примерами
программ с таким интерфейсом являются текстовый редактор "Блокнот" и
графический редактор "Paint", входящие в состав операционной системы
Microsoft Windows. В данном случае под термином "документ" мы
подразумеваем содержимое открываемого (или создаваемого заново) файла –
64
такой интерпретации термина будем придерживаться и далее, хотя следует
отметить,
что
указанный
термин
подразумевает
более
широкую
интерпретацию.
Для создания SDI программы начнём новый проект (см. п. 2.1) с
именем "ex_sdi", а в качестве типа проекта выберем "MFC Application". В
следующем окне "MFC Application Wizard – ex_sdi" выберем слева раздел
"Application Type", а справа в одноимённой группе переключателей выберем
"Single Document". Отметим, что группа переключателей "Use of MFC"
соответствует одноимённой опции, рассмотренной в п. 2.4.
Далее слева выберем подраздел "Document Template Strings", а справа в
поле "File extension" укажем "exsdi" ("фирменное" расширение файлов,
означающее что эти файлы являются документами нашей программы). В поле
"Main frame caption" укажем "Example of SDI" (это "название" программы,
которое увидим в заголовке её окна, – не следует путать название программы
и имя её исполняемого файла – например программа "Блокнот" имеет
исполняемый
файл
"notepad.exe").
Из
остальных
настроек
раздела
наибольший интерес представляют два поля: "Doc type name" и "Filter name".
В первом задаётся имя, которое присваивается по умолчанию каждому
новому документу. Во втором поле задаём имя фильтра, которое
используется в диалоге открытия файлов для фильтрации отображаемых
файлов, т.е. будут показаны только файлы, имеющие расширение, заданное
выше.
Больше никаких настроек ни в этом, ни в других разделах менять не
будем, но всё же рассмотрим некоторые другие настройки.
Раздел (слева) "User Interface Features". Поле "Thick frame" позволяет
использовать рамку окна для изменения его размеров. Поля "Minimize box" и
"Maximize box" разрешают/запрещают использование соответствующих
системных кнопок в верхнем правом углу окна программы. Поля "Minimized"
65
и "Maximized" управляют состоянием окна программы при её запуске
(свёрнуто в кнопку на панели задач или развёрнуто на весь экран
соответственно). Поле "System menu" разрешает системное меню окна
программы – это меню можно вызвать щёлчком левой кнопкой мыши на
иконке программы в заголовке её окна или правым щелчком мыши по самому
заголовку (или комбинацией горячих клавиш "Alt+Пробел"). Поле "Initial
status bar" добавляет в окно строку состояния, а поле "Standard docking"
добавляет панель управления.
Раздел "Advances features". Поле "Context-sensitive Help" добавляет в
программу каркас справочного файла (см. далее) в формате HTML Help (этот
же формат имеет MSDN Library). Поле "Number of files on recent file list"
добавит в меню "File" список последних файлов, с которыми работала
программа (в поле задаётся количество файлов в списке – если выбрать ноль,
то списка не будет). Поле "Printing and print preview" добавляет в меню "File"
соответствующие пункты для просмотра документа перед его печатью и для
непосредственно самой печати документа. Поле "Common Control Manifest"
создаёт служебный файл, благодаря которому программа будет использовать
темы операционной системы Microsoft Windows XP.
Очень важный раздел "Generated Classes". Здесь видим список классов,
используемых в программе (см. далее), а также имена файлов, в которых эти
классы описаны. Из всех представленных в списке классов только для класса
"вида" можно указать базовый класс (по умолчанию задан "CView") – если
например в качестве базового класса выбрать "CScrollView", то унаследуем
возможность "прокрутки" документа, если он не будет целиком умещаться в
окне, иначе эту возможность придётся реализовывать самостоятельно.
Подробнее
обо
всех
этих
рассмотренных
и
нерассмотренных
настройках можно узнать в [8–17], а также в MSDN Library (если нажать
кнопку со знаком вопроса в правом верхнем углу этого диалогового окна).
66
Теперь нажимаем кнопку "Finish", собираем и запускаем программу – в
результате получим окно показанное на рис. 3.1.
Рис. 3.1. – Окно программы ex_sdi
Таким образом мы получили каркас реально функционирующей
программы с SDI интерфейсом. Это именно каркас, т.к. хотя программа
показывает диалоги печати и предпечатного просмотра, а также диалоги
открытия и сохранения файлов при нажатии соответствующих кнопок панели
управления или выборе соответствующих пунктов меню, она ничего не
открывает, не сохраняет и не печатает, т.к. тела соответствующих функций не
прописаны! Это всего лишь каркас, который следует наполнить нужным
функционалом. Однако сначала нужно разобраться в структуре созданного
проекта программы и понять принцип её работы. А для этого кратко
рассмотрим два важных момента: карты сообщений и архитектуру
"Документ/Вид".
Архитектура "Документ/Вид" ("Document/View") отделяет данные от
методов их отображения. Программы с интерфейсами SDI и MDI используют
эту архитектуру. В этих программах присутствуют два класса: класс
"документа", который описывает непосредственно данные, а также методы их
67
чтения и записи; класс "вида", который описывает методы отображения
данных. Такое разделение позволяет одни и те же данные отображать
разными способами – например набор целочисленных данных можно
представить в виде таблицы чисел, а можно отобразить графически в виде
диаграммы.
Карты сообщений – это по сути список перехватываемых программой
сообщений и функций, обрабатывающих эти сообщения. Сообщения – это
уведомления, которые операционная система рассылает программам при
возникновении каких-либо событий – например при перемещении мыши или
нажатии клавиш на клавиатуре и т.д. Система уведомляет программы обо
всех событиях, но каждая программа может реагировать ("перехватывать")
только некоторые виды сообщений, а другие игнорировать. При перехвате
очередного
сообщения
программа
вызывает
для
его
обработки
соответствующую функцию (согласно карте сообщений). Каждая карта
сообщений принадлежит какому-нибудь классу программы (см. далее),
поэтому в качестве функций обработки сообщений используются методы
класса-"хозяина".
Любая программа с оконным интерфейсом после своего запуска
входит в бесконечный цикл, в котором она перехватывает и обрабатывает
сообщения до тех пор пока не придёт сообщение о завершении работы
программы. Когда мы щёлкаем мышью на кнопке или выбираем пункт меню,
т.е. работаем с элементами окна программы, то при этом генерируются
соответствующие события, о которых операционная система уведомляет в
первую очередь программу, которой принадлежит это окно. Таким образом,
если мы в окне программы ex_sdi выберем пункт меню "File / Exit", то эта
программа получит сообщение о завершении работы, согласно которому она
закроет своё окно и прекратит свою работу.
68
Структура проекта
Согласно содержимому окна "Class View" (см. п. 1.1) в состав проекта
входят следующие пять классов:

CAboutDlg – класс, описывающий диалоговое окно "About ex_sdi",
которое появляется при выборе пункта меню "Help / About
ex_sdi…" или при нажатии кнопки с изображением знака вопроса
на панели управления.

Cex_sdiApp – класс, описывающий саму программу.

Cex_sdiDoc – класс документа (см. выше).

Cex_sdiView – класс вида (см. выше).

CMainFrame – класс, описывающий главное окно программы.
Если посмотреть на окно "Solution Explorer", то видно что проект
состоит из множества файлов. Общепринято разделять описание класс на два
файла: заголовочный H-файл и CPP-файл, т.е. файл реализации. В H-файле
описывается сам класс, а в CPP-файле описываются тела методов класса.
Таким образом файлы ex_sdi.h и ex_sdi.cpp описывают класс программы
(правда, в них также описывается и класс CAboutDlg, но это исключение).
Файлы ex_sdiDoc.h и ex_sdiDoc.cpp описывают класс документа; файлы
ex_sdiView.h и ex_sdiView.cpp описывают класс вида; файлы MainFrm.h и
MainFrm.cpp описывают классглавного окна программы.
Файлы группы "Resource files" и файл Resource.h описывают ресурсы
программы. Для просмотра списка ресурсов программы следует открыть окно
"Resource View", воспользовавшись пунктом меню "View / Resource View".
Иконки, курсоры, звуковые файлы, диалоги, таблицы строк и т.д. – это далеко
не весь список возможных ресурсов программы. В процессе сборки
программы все её ресурсы встраиваются в тело исполняемого файла
программы. Из этого окна видно, что диалоговое окно "About ex_sdi" также
является ресурсом. Для редактирования любого ресурса следует дважды
69
щёлкнуть левой кнопкой мыши на его имени, при этом откроется
соответствующий редактор ресурсов. При редактировании ресурсов полезно
открыть окно "Properties", воспользовавшись пунктом меню "View / Other
Windows / Properties Window" (о работе с этим окном см. п. 3.3).
Осталось ещё два файла: ReadMe.txt и ex_sdi.reg. Первый содержит
краткие пояснения к файлам проекта, а второй содержит различную
информацию о программе – эта информация при сборке программы
записывается в реестр (см. п. 2.4).
Описание работы программы
Просмотрев все файлы вы не найдёте функции main или похожей, т.к.
она сокрыта в недрах библиотеки MFC. Достаточно сказать, что при запуске
программы объявляется объект класса ex_sdiApp и выполняется его метод
InitInstance Этот метод в данном случае выполняет следующие действия:

устанавливает внешний облик главного окна программы;

записывает в реестр информацию из файла ex_sdi.reg;

загружает список "последних" файлов, с которыми работала
программа;

формирует
так
называемый
"шаблон
документа"
(здесь
CSingleDocTemplate), в котором связывает классы документа и
вида с классом и меню главного окна программы – это необходимо
для обеспечения взаимодействия между ними (например, когда мы
открываем файл, то его имя появляется в заголовке главного окна,
а содержимое автоматически отображается в клиентской области);

т.к. программу можно запустить из консольного окна, используя
имя её исполняемого файла, то проверяется наличие аргументов
(строка "ParseCommandLine(cmdInfo);");

отображается и обновляется главное окно программы (методы
70
"ShowWindow"
и
"UpdateWindow")
–
после
показа
окна
устанавливается его режим отображения (свёрнуто в панель задач
или развёрнуто на весь экран), поэтому необходимо обновление,
при котором окно перерисовывается в уже установленном режиме;

метод
"DragAcceptFiles"
добавляет
программе
возможность
открывать файлы, "перетаскивая" их иконки в окно программы.
Следует отметить, что благодаря описанному выше шаблону,
программа знает, какой класс вида использовать для отображения документа
(если используется несколько классов вида). Любая подобная программа
содержит список таких шаблонов (в данном случае в списке только один
шаблон).
Строка
"AddDocTemplate(pDocTemplate);"
добавляет
сформированный шаблон в список. В конкретный момент времени программа
работает только с одним "активным" шаблоном. Выбирая в списке шаблон с
помощью специальных функций мы автоматически делаем его активным.
При установке внешнего облика главного окна программы вызывается
метод "PreCreateWindow" класса вида. Также вызывается одноимённый метод
класса главного окна программы. Когда же окно создаётся, то генерируется
сообщение "WM_CREATE", для обработки которого используется метод
"OnCreate" класса главного окна программы. Этот метод добавляет в главное
окно программы строку состояния и панель управления.
После выполнения метода InitInstance программа переходит в
бесконечный цикл (см. выше) и начинает перехватывать и обрабатывать
сообщения. Если изучить описанные в файлах карты сообщений, то можно
увидеть, что программа перехватывает сообщения от кнопок панели задач и
пунктов меню. Причём для обработки сообщений о печати и предпечатном
просмотре используются методы класса вида, а для обработки остальных
сообщений – методы класса программы.
Отображение документа производится в методе OnDraw класса вида
71
(Cex_sdiView). При создании или открытии документа объявляется объект
класса документа. Этот объект хранит данные созданного (открытого)
документа. Указанный метод OnDraw получает указатель на объявленный
объект документа. С помощью этого указателя можно обращаться к данным
документа и отображать их. Метод OnDraw вызывается при каждой
перерисовке окна – его перемещении, изменении его размеров и т.д.
Подробнее о возможностях интерфейса SDI, а также примеры SDIпрограмм можно найти в [8 – 17].
3.2. Интерфейс MDI
Расшифровывается
как
"Multiple
Document
Interface"
–
"многодокументный интерфейс". Чтобы увидеть этот интерфейс в действии,
используйте в среде Visual Studio пункт меню "Tools / Options". В
появившемся диалоговом окне "Options" выберем слева раздел "General", а
справа поле "Multiple documents" и нажмём кнопку "OK". Попробуйте
открыть несколько файлов – их содержимое будет показано в отдельных, так
называемых "дочерних" окнах. Т.е. MDI-программа имеет не только главное
окно, но и дочерние, которыми можно манипулировать как и другими
"обычными" окнами, но только в пределах рабочей области главного окна
программы. Под рабочей областью подразумевается часть клиентской
области окна за вычетом панели управления и строки состояния.
72
Рис. 3.2. – Программа ex_mdi
Создадим проект MDI-программы под именем "ex_mdi". Процесс
создания аналогичен процессу, описанному в предыдущем пункте, однако
опишем отличия, которые имеют место при выборе настроек в диалоговом
окне "MFC Application Wizard – ex_mdi":

в разделе "Application Type" следует отметить поле "Multiple
documents";

в разделе "Documents Template Strings" в поле "File extension"
ввести "exmdi", а в поле "Main frame caption" ввести "Example of
MDI";

в разделе "User Interface Features" теперь доступна группа полей
"Child
frame
использование
styles",
которые
системных
разрешают
кнопок (см.
в
п.
и
запрещают
3.1 описание
аналогичных полей для главного окна программы), а также
устанавливают состояние дочерних окон при их открытии
(свёрнуто или развёрнуто на всю рабочую область).
73
Нажимаем кнопку "Finish", собираем и запускаем программу. Внешний
вид программы с двумя дочерними окнами показан на рис. 3.2.
Если изучить структуру проекта, то можно увидеть новый класс
"CChildFrame" – класс дочернего окна, описанный соответственно в файлах
ChildFrm.h и ChildFrm.cpp. В ресурсах мы теперь имеем два меню и две
иконки – для главного окна и дочерних окон соответственно.
Опишем основные отличия принципа работы данной программы от
предыдущей:

метод InitInstance
в шаблоне
документа
связывает классы
документа и вида с классом и меню дочернего окна;

при
создании
дочернего
PreCreateWindow,
который
окна
вызывается
позволяет
его
изменить
метод
параметры
дочернего окна перед его показом;

методы класса документа и методы класса вида вызываются для
"активного" дочернего окна программы (т.е. окна с которым
работает пользователь).
3.3. Интерфейс на базе диалогового окна
В качестве примера программы, использующей такой интерфейс,
можно привести программу "Калькулятор", которая входит в состав
операционной
системы
Microsoft
Windows.
Следует
отметить,
что
программы, использующие интерфейс на базе диалогового окна не реализуют
архитектуру "Документ/Вид".
Создадим проект программы под именем "ex_dialog". Тип программы
тот же – "MFC Application". В диалоговом окне "MFC Application Wizard –
ex_dialog" укажем следующие настройки:

в разделе "Application Type" отметим поле "Dialog based";
74

в разделе "User Interface Features" отметим поле "Minimize box"
(чтобы окно можно было свернуть), а в поле "Dialog title" введём
"Example of Dialog" – название заголовка окна программы.
Нажмём кнопку "Finish", соберём и запустим программу. В результате
увидим диалоговое окно с двумя кнопками и надписью посередине окна (см.
рис. 3.3). Это окно можно свернуть в кнопку на панели задач с помощью
соответствующей системной кнопки в правом верхнем углу окна. Кроме того
окно имеет системное меню, которое можно вызвать щелчком левой кнопкой
мыши на иконке в заголовке окна или щёлкнуть правой кнопкой мыши на
самом заголовке. В этом меню есть пункт "About ex_dialog…", который
показывает диалоговое окно с информацией о версии программы и авторских
правах на неё.
Если обратить внимание на окна "Solution Explorer" и "Class View", то
можно увидеть, что в проекте используются всего три класса – помимо
упомянутого выше класса CAboutDlg, это классы Cex_dialogApp (класс
программы) и Cex_dialogDlg (класс, описывающий диалоговое окно – главное
окно программы), которые описаны в соответствующих четырёх файлах.
75
Рис. 3.3. – Главное окно программы ex_dialog
При запуске программы объявляется объект "theApp" – объект класса
программы, а затем вызывается метод InitInstance этого объекта. Этот метод
создаёт объект "dlg" – объект класса главного диалогового окна и сохраняет в
специальной переменной m_pMainWind адрес на созданный объект. С
помощью этой переменной при необходимости можно получить доступ к
главному диалоговому окну (например, чтобы программно свернуть окно или
изменить его цвет). Далее главное окно программы отображается методом
DoModal и программа фактически переходит в бесконечный цикл перехвата и
обработки сообщений. Когда главное диалоговое окно будет закрыто,
произойдёт возврат в метод InitInstance, который в свою очередь вернёт
значение FALSE, что будет означать для программы завершение работы.
Следует отметить, что при закрытии диалогового окна кнопками "OK"
76
и "Cancel" метод DoModal вернёт соответственно значения IDOK и
IDCANCEL, определенные в системном заголовочном файле "WinUser.h".
При создании диалогового окна вызывается метод OnInitDialog
соответствующего
класса
(см.
файл
ex_dialogDlg.cpp),
в
котором
устанавливаются параметры и внешний вид окна. Для отображения окна и его
последующей
перерисовки
(при
получении
сообщения
WM_PAINT)
используется метод-обработчик "OnPaint" (аналог метода OnDraw). Следует
отметить, что уже имеющийся в этом методе код используется для
корректной прорисовки иконки программы, если диалоговое окно свёрнуто в
кнопку на панели задач. Изображение же самой иконки хранится в ресурсах и
загружается в конструкторе диалогового окна. Таким образом в обычном
состоянии окна для его перерисовки в методе OnPaint используется ветвь
"else".
Внесём изменения в проект: добавим на диалоговое окно кнопку, при
нажатии на которую будет "выскакивать" окошко с приветственным
сообщением. Для этого откроем окно "Resource View" с помощью пункта
меню "View / Resource View", а также откроем окна "Toolbox" и "Properties" с
помощью пунктов меню "View / Toolbox" и "View / Other Windows / Properties
Window" соответственно. Также для редактирования и тестирования
диалогового окна может быть полезна панель "Dialog Editor", которую можно
отобразить с помощью пункта меню "View / Toolbars / Dialog Editor".
В окне "Resource View" раскроем группу "Dialog" и дважды щёлкнем
левой кнопкой мыши на имени IDD_EX_DIALOG_DIALOG, при этом
соответствующее изображение диалога откроется в области редактирования.
В окне Toolbox щёлкнем левой кнопкой мыши на элементе "Button", а затем
щёлкнем левой кнопкой мыши на нашем диалоге – появится кнопка с
надписью "Button1", имеющая по краям восемь тёмных квадратиков, с
помощью которых можно изменять размеры кнопки. Откроем окно
77
"Properties", которое отображает свойства выделенного объекта (в данном
случае новой кнопки). Поле "Caption" содержит текст надписи, которая
отображается на кнопке (пояснения о значении полей выводятся внизу окна
"Properties"). Изменим текст на "Hello!" – для этого введём этот текст в поле
Caption и нажмём клавишу Enter – надпись на кнопке также изменится.
Каждый объект (окно, меню, диалог, иконка и т.д.) имеет свой
уникальный (в рамках программы) идентификатор, по которому можно
определить объект или даже обратиться к нему (например, чтобы программно
нажать на кнопку). В данном случае новая кнопка имеет мало значащий
идентификатор IDC_BUTTON1 (см. поле ID). Изменим его на IDC_HELLO.
Кстати, по идентификатору можно выделить нужный объект, даже если его
не видно (например нужная кнопка расположена под другой кнопкой – в этом
случае выделить нужную кнопку щелчком мыши не удастся) – для этого в
верхней части окна Properties есть поле со списком всех идентификаторов.
Выбираем нужный и соответствующий объект будет выделен, а его свойства
отобразятся в окне Properties.
Кроме свойств объекта окно Properties отображает список событий,
которые может генерировать объект. Чтобы увидеть список событий,
генерируемых новой кнопкой, нажмём в верхней части окна кнопку с
изображением жёлтой молнии (эта кнопка называется "Control Events"). В
данном случае нас интересует событие BN_CLICKED (если на имени
события щёлкнуть левой кнопкой мыши, то внизу окна увидим краткую
справочную информацию), которое генерируется при нажатии на кнопку
(нажать на кнопку можно щелчком левой кнопки мыши или выделением
кнопки с помощью клавиши "Tab" с последующим нажатием клавиши
"Пробел"). Итак, выберем мышью это событие и нажмём в правом поле
появившуюся кнопку с изображением обращённого вниз треугольника, а в
появившемся списке выберем пункт "<Add> OnBnClickedHello". При этом в
78
файле ex_dialogDlg.cpp появится пустое тело метода OnBnClickedHello,
который принадлежит классу Cex_dialogDlg. В облати редактирования
автоматически откроется этот файл, а текстовый курсор будет установлен на
заголовок вставленного метода. Пропишем в тело метода следующую строку:
MessageBox(L"Hello, Unknown!", L"Message");
Рис. 3.4. – Результат нажатия на кнопку IDC_HELLO
Соберём и запустим программу, а затем нажмём (см. выше) на кнопку
с надписью "Hello!" – результат показан на рис. 3.4. Подробнее о диалоговых
окнах, а также об элементах интерфейса (кнопках, списках переключателях и
др.) и о работе с ними можно узнать в [8 – 17].
79
4. ЯЗЫК ПРОГРАММИРОВАНИЯ C++
Из всех объектно-ориентированных языков C++ является наиболее
популярным и, соответственно, широко распространённым. Язык C++
является расширенной версией языка C, которая включила в себя реализацию
механизма объектно-ориентированного программирования. Язык C, в свою
очередь, появился в начале 70-х гг. XX-го века. Он был разработан для
создания операционной системы UNIX. Так как операционная система сама
по себе является очень сложным многофункциональным программным
продуктом, то это в какой-то степени и определило универсальность языка C
для
решения
различных
программных
задач.
Язык
C++
самый
низкоуровневый из подобных языков, то есть близкий к машинным кодам,
что делает его достаточно мощным и гибким и позволяет создавать быстрый
программный код различного назначения. "Ниже" языка С++ расположен
только Ассемблер [5 – 7, 19], а далее идёт уже программирование
непосредственно в машинных кодах центрального процессора (CPU)
компьютера.
4.1. Стандарты
Одним из лучших способов научиться программированию является
чтение хорошо написанных программ. Однако до некоторых пор язык C
развивался без каких-то ограничений. Каждая компания, выпускающая
компилятор
этого
языка,
снабжала
его
своими
специфичными
возможностями (библиотеками, версиями заголовочных файлов и т.д.). Это
конечно же затрудняло изучение программного кода, написанного для разных
компиляторов.
Тем не менее организация ANSI/ISO (ANSI является аббревиатурой
Американского
Национального
Института
Стандартов,
а
ISO
–
80
Международной организации Стандартов) разработала документы известные
под названиями "Стандартный C"
и "Стандартный C++"
[20, 21].
Стандартный C++ включает в себя много дополнительных возможностей,
например стандартную библиотеку шаблонов (Standard Template Library –
STL) [1, 4, 12]. Следует отметить, что компания Microsoft только в среде
разработки Visual Studio версии 2005 (официально это восьмая версия)
реализовало соответствие указанным стандартам.
4.2. Типы данных, константы и переменные
Любые данные имеют свои значения (в виде чисел.), которые
программа хранит в ячейках памяти. Каждая ячейка памяти имеет свой адрес
(от нулевого и до максимально возможного для конкретной аппаратной
конфигурации). Запуск программы заключается в том, что операционная
система выделяет часть оперативной памяти компьютера для программы и
загружает программу в эту выделенную память. При этом невозможно
гарантировать,
что
загружаемые
данные
будут
расположены
по
определённым адресам. Поэтому каждой такой ячейке программист как бы
присваивает имя, с которым и работает, а переводом имени в адрес
занимаются инструменты компиляции и сборки программы.
Если в процессе работы программа может записывать данные в ячейку
или считывать данные из неё, то такую ячейку памяти называют
"переменной". Под значением переменной подразумевается содержимое
соответствующей ячейки памяти. Современные компьютеры являются
цифровыми устройствами, поэтому в ячейках памяти хранятся только
числовые значения, которые в свою очередь могут представлять, например
коды символов какого-нибудь алфавита или логические величины (имеют
значения TRUE и FALSE, т.е. "истина" и "ложь"), или непосредственно
81
числовые величины. То есть речь идёт о типе хранящихся в ячейке данных
или, что то же самое о типе соответствующей переменной (см. ниже). Для
использования
переменной
в
программе
её
(переменную)
нужно
предварительно объявить, указав тип и имя, а затем инициализировать
(присвоить какое-то начальное значение) оператором присваивания "=".
Впрочем, объявление и инициализацию можно объединить. Примеры:
int V1, V2, V3 = 28;
V1 = 0;
V2 = 44;
Здесь две переменных V1 и V2 сначала объявляются, а потом
инициализируются.
Третья
переменная
сразу
V3
объявляется
и
инициализируется.
Если в процессе работы программа может только читать данные из
ячейки, то такую ячейку называют "константой". Имя, тип и значения
определяются так же как для переменной. Однако при объявлении
используется ключевое слово "const" (указывается перед типом константы) и
одновременно
производится
инициализация.
В
дальнейшем
изменять
значение константы нельзя (да и не получится). Пример:
int V1 = -4; // Объявление и инициализация переменной.
const int C1 = 3; // Объявление и инициализация константы.
V1 = 5; // Изменили значение переменной.
C1 = 2; // Изменять константу НЕЛЬЗЯ!
V1 = C1 + 7; // Так можно – использовали константу "по чтению".
Как уже было сказано, программа может работать с данными
различного типа, для хранения которых может потребоваться более одной
ячейки памяти. Например каждая ячейка может хранить один байт
информации, т.е. числовые значения в диапазоне от 0 до 255 включительно
или символы алфавита, числовые значения кодов которых находятся в этом
диапазоне. Для хранения больших чисел или текстовых строк (т.е.
последовательности символов) нужно несколько ячеек. Поэтому язык C++
82
предоставляет набор ключевых слов для обозначения различных типов
данных. Также следует отметить, что числовые данные могут иметь как
положительные, так и отрицательные значения (т.е. знаковые и беззнаковые
типы данных). Если переменная должна принимать только положительные
значения, то при её объявлении следует дополнительно использовать
ключевое слово "unsigned" (беззнаковый). В таблице 4.1 указаны ключевые
слова, диапазоны возможных значений и размеры (в байтах или, что то же
самое, в ячейках памяти) соответствующих типов данных. В скобках указаны
размеры в битах. Точность представления типов float и double составляет 7 и
15 знаков соответственно.
Таблица 4.1. – Типы данных в языке C++
Ключ. слово
Размер
Диапазон
bool
1 (8)
false (т.е. ноль) … true (т.е. не ноль)
char
1 (8)
-128 … 127
unsigned char
1 (8)
0 … 255
short
2 (16)
-32 768 … 32 767
unsigned short
2 (16)
0 … 65 535
int
4 (32)
-2 147 483 648 … 2 147 483 647
unsigned int
4 (32)
0 … 4 294 967 295
long
4 (32)
-2 147 483 648 … 2 147 483 647
unsigned long
4 (32)
0 … 4 294 967 295
long long
8 (64)
± 9 223 372 036 854 775 807
unsigned long long
8 (64)
0 … 18 446 744 073 709 551 615
float
4 (32)
3,4 * 10-38 … 3,4 * 1038
double
8 (64)
1,7 * 10-308 … 1,7 * 10308
Следует отметить, что при использовании специфичных библиотек,
83
можно встретить и другие типы данных, которые по сути являются
"обвёртками" для типов, приведённых в таблице 4.1. Например в библиотеке
MFC (см. раздел 3) есть тип BYTE, соответствующий unsigned char, и тип
WORD,
соответствующий
unsigned
short.
Можно
и
самостоятельно
определить подобные обвёртки с помощью ключевого слова typedef,
например:
typedef
typedef
s_int16
u_int16
short s_int16;
unsigned short u_int16;
V1 = -32154;
V2 = 65133;
Заметим, что при использовании данных смешанных типов в какомлибо математическом выражении происходит их неявное преобразование.
Существует иерархия типов данных, перечислим их в порядке от типа с
самым высоким приоритетом до типа с самым низким приоритетом:
double -> float -> long -> int -> short -> char
Рассмотрим пример:
int V1 = 4;
float F1 = 7.5;
double D1 = V1 * F1;
Здесь переменная V1 неявно (т.е. компилятором) приводится к типу
float и перемножается с переменной F1, а результат перемножения, имеющий
тип float, приводится к типу double и записывается в переменную D1.
Однако не стоит полностью надеяться на компилятор. Есть ситуации, в
которых требуется явно указать, к какому типу преобразовывать. Например:
short S1 = 25500;
S1 = (S1 * 10) / 10;
short S2 = 25500;
S2 = ((int)S2 * 10) / 10;
short S3 = 25500;
S3 = (static_cast<int>(S3) * 10) / 10;
Здесь в первом случае произойдёт переполнение, т.к. результат
84
умножения равен 255000, а согласно таблице 4.1 тип short может хранить
максимум 32767. Поэтому в переменной окончательное значение S1 будет
равно 2563 (почему именно такое значение, предлагаю разобраться
самостоятельно). Во втором случае всё в порядке, т.к. используется явное
преобразование типа переменной S2. Такой вид записи преобразования
можно встретить очень часто, но это устаревший вид записи. Современный
вид записи используется в третьем случае, где тоже всё в порядке. Последний
вид записи значительно упрощает поиск подобных преобразований в
программном коде.
Напоследок следует описать часто совершаемую новичками ошибку.
При выполнении целочисленных вычислений в случае получения нецелого
результат учитывается только целая часть. В случае простых выражений это
очевидно, но наиболее часто ошибка совершается, когда выражение
достаточно сложное и его вычисление выполняется по частям. Например,
приведённые ниже два представления одной и той же дробной величины
тождественны с математической точки зрения (слева и справа получим 1,5):
3/2 ≡ 1/2 + 1/2 + 1/2 .
Однако с учётом целочисленной арифметики слева получим 1, а справа
получим 0. То есть выражение уже не будет тождественным.
Например
нормировки
для
в
цифровой
сохранения
обработки
изображений
есть
понятие
неизменным
среднего
уровня
яркости
изображения. Нормировку можно проводить двояко. Либо обработать
изображение ненормированной матрицей и затем нормировать результат,
либо
обрабатывать
матрицей
с
предварительно
нормированными
коэффициентами. Во втором случае, если не учитывать сказанное выше, то
вместо обработанного изображения можно получить чёрный фон (нулевой
уровень яркости).
85
4.3. Операторы, область видимости, пространство имён
Сперва несколько определений. Оператор это условное обозначение
для одной команды процессора компьютера или совокупности команд,
выполняющих какое-то действие над данными. Данные, "используемые"
оператором, называются операндами.
Различают унарные и бинарные операторы. У первых один операнд у
вторых два. Также все операторы можно разделить на четыре группы по типу
выполняемых операций: арифметические операции, операции отношения,
логические операции и битовые операции.
Арифметические операции задаются следующими операторами: +
(сложение), – (вычитание), * (умножение), / (деление), % (остаток от
деления). Последний оператор нельзя применять к данным вещественного
(нецелого) типа. Запись в общем виде и примеры использования:
результат = операнд1 оператор операнд2;
R = V1 + V2;
R = V1 % V2;
Все перечисленные операторы бинарные и в частном случае могут
быть записаны в другой, более краткой форме:
запись V1 += V2 эквивалентна записи V1 = V1 + V2.
Особняком в этой группе стоят унарные операторы инкремента и
декремента: ++ и -- (два знака минуса). Примеры:
запись V++ эквивалентна записи V = V + 1 (или V += 1),
запись V-- эквивалентна записи V = V - 1 (или V -= 1).
Операции отношения обозначаются операторами: > (операция
"БОЛЬШЕ"),
>=
(операция
"БОЛЬШЕ
или
РАВНО"),
<
(операция
"МЕНЬШЕ"), <= (операция "МЕНЬШЕ или РАВНО"), == (операция
"РАВНО"), != (операция "НЕ РАВНО"). Операции выполняются слева
86
направо. Результат операции имеет логическое значение 0 (false – "ЛОЖЬ")
или не ноль (true – "ИСТИНА").
Логические операции обозначаются операторами: && (логическое
"И", т.е. "AND"), || (логическое "ИЛИ", т.е. "OR"), ! (логическая операция
"НЕ", т.е. "NOT"). Операторы && и || являются бинарными, а оператор !
унарный. Выражения, использующие логические операции и операции
отношения, возвращают 0 (false) для ложного значения результата и 1 (true)
для истинного. В таблице 4.2 приведены значения результата логического
выражения для разных состояний его операндов:
Таблица 4.2. – Определение результата логического выражения
x
y
x && y
x || y
!x
0
0
0
0
1
0
1
0
1
1
1
0
0
1
0
1
1
1
1
0
Битовые операции можно применять к переменным, имеющим тип
bool, char, short, int, long. К типам с "плавающей запятой" (float, double) и
более сложным типам эти операции применять нельзя. Битовые операции
обозначаются операторами: & (поразрядная логическая операция "И", т.е.
"AND"), | (поразрядная логическая операция "ИЛИ", т.е. "OR"), ~
(поразрядная логическая операция "НЕ", т.е. "NOT"), ^ (поразрядная
логическая операция "Исключающее ИЛИ", т.е. "XOR"), << (поразрядный
сдвиг влево), >> (поразрядный сдвиг вправо). Битовые логические операции
имеют смысл и используются аналогично логическим операциям, описанным
выше. Однако здесь уже производится сравнение не значений переменных в
целом, а сравнение состояний их соответствующих разрядов (битов). Т.к.
каждый бит может иметь только два состояния (0 или 1), то здесь уже
87
соблюдается точное соответствие – false соответствует 0, а true соответствует
1. В таблице 4.3 приведены значения результатов выполнения битовых
логических операций при соответствующих состояниях отдельных разрядов
их операндов.
Битовая операция сдвига влево означает, что значение конкретного
разряда переменной присваивается более старшему разряду. При этом
значение самого старшего (седьмого) разряда теряется, а значение самого
младшего (нулевого) разряда всегда будет равно нулю. Такая операция
эквивалентна умножению на два. Битовая операция сдвига вправо действует
аналогично, только здесь уже значение конкретного разряда переменной
присваивается более младшему разряду. При этом значение самого младшего
(нулевого) разряда теряется, а вот значение самого старшего (седьмого)
разряда переменной зависит от её знака! Таким образом, если выполняется
сдвиг отрицательной переменной, то её старший разряд равен 1 и при сдвиге
вправо он также останется равным 1. Если вправо сдвигается положительная
переменная, то после сдвига старший разряд обнуляется. Битовая операция
сдвига вправо эквивалентна делению на два.
Таблица 4.3. – Значения битовых логических операций
x
y
~x
x&y
x|y
x^y
0
0
1
0
0
0
0
1
1
0
1
1
1
0
0
0
1
1
1
1
0
1
1
0
Есть ещё операторы, которые не входят ни в одну из описанных выше
групп. Наиболее часто используемые операторы перечислены в таблице 4.4.
Таблица 4.4. – Прочие часто используемые операторы
88
Обозначение
Описание
=
Присваивание
[]
Выделение элемента массива (см. п. 4.6)
. (точка)
Выделение элемента записи (см. п. 4.7 и 4.8.2)
–>
Выделение элемента записи (см. п. 4.7 и 4.8.2)
::
Выделение элемента класса или пространства имён (см.
(два двоеточия)
ниже, а также п. 4.8.2)
В отличие от других операторов оператор присваивания выполняется
справа налево, поэтому могут иметь место выражения правильные с
программной точки зрения, но абсурдные с математической, например:
V = V + 25;
здесь, благодаря обратному порядку выполнения операции, значение
переменной увеличивается на 25 (почему такая запись абсурдна с точки
зрения математики, следует разобраться самостоятельно).
Из редко применяемых и поэтому не вошедших в таблицу 4.4
операторов следует отметить единственный существующий в языке C++
тернарный оператор "?:", который подробно описывается в п. 4.4.2.
Кроме описанных выше операторов есть ещё один очень важный
оператор, обозначаемый двумя фигурными скобками {}. Левая скобка
называется "открывающей", а правая – "закрывающей". Этот оператор
предназначен для выполнения следующих действий: ограничения области
видимости данных и описания тел различных программных объектов
(функций, структур, классов, перечислений, условных и циклических
составных операторов). Можно сказать, что любые переменные и константы,
объявленные внутри фигурных скобок, не видны (и поэтому не могут быть
использованы) за пределами этих скобок.
ЗАМЕЧАНИЕ! При формировании сложных выражений, состоящих из
89
двух и более операций, следует учитывать, что различные операции имеют
разный приоритет выполнения. Так из школьного курса математики известно,
что операции умножения и деления имеют более высокий приоритет, чем
операции сложения и вычитания, и поэтому выполняются первыми. Поэтому
даже при соблюдении приоритета рекомендуется явно указывать в
выражении порядок выполнения операций и использовать для этого круглые
скобки. Более подробно о приоритете выполнения операций можно узнать в
[1 – 3, 20, 21]. В приведённом ниже примере операция сдвига имеет более
НИЗКИЙ приоритет, чем операция сложения, поэтому скобки не нужны, но с
ними выражение выглядит более наглядно:
V2 = (V1 + 4) >> 1;
Область видимости данных – это часть программного кода, в
пределах которой эти данные могут быть использованы. С учётом сказанного
различают глобальную и локальную области видимости (или данные
называют соответственно глобальными и локальными). Упрощённо можно
считать, что глобальными являются данные, объявленные вне пределов
каких-либо скобок, иначе данные являются локальными и их область
видимости ограничена соответствующими скобками. Глобальные данные
доступны из любого места программы в пределах файла, в котором они
объявлены.
В
других файлах программы
доступ
к таким
данным
осуществляется с помощью ключевого слова extern. На листинге 4.1 приведён
пример программы, объявляющей и использующей глобальные и локальные
переменные.
Листинг 4.1. – Глобальные и локальные переменные
#include <stdio.h>
extern short G1;
int G2 = 43;
90
int main(int argc, char* argv[])
{
int L1 = 0, L2 = 9;
{
short S1 = 4;
L1 = S1 * 2;
// Ошибки нет!
L1 = L1 + G2;
}
L1 = L1 - S1; // Ошибка! Переменная S1 уже не видна.
L2 = L2 + G1;
return 0;
}
В
приведённом
листинге
область
видимости
переменной
S1
ограничена второй парой фигурных скобок и её использование за их
пределами невозможно. Область видимости переменных L1 и L2 ограничена
телом функции main, которое ограничено первой парой фигурных скобок.
Переменная G2 описана в текущем файле и видна из любого его места.
Переменная G1 описана в другом файле программы, но благодаря ключевому
слову extern, также видна в текущем файле.
Пространства имён.
Не секрет, что программа может состоять из нескольких файлов. При
этом есть вероятность, что в разных файлах данные могут быть описаны
одинаковыми именами, а это приведёт к ошибке при сборке программы. Для
исключения подобных ошибок используются пространства имён. Сначала
следует описать пространство и описать в нём нужные элементы – объекты
(функции, классы и пр.) и данные. Далее можно обратиться к элементам
пространства несколькими способами. На листинге 4.2 приведён пример
описания двух пространств имён с описанием их элементов (переменных и
функций), а на листинге 4.3 показаны два примера обращения к элементам
этих пространств.
Листинг 4.2. – Описание пространств имён
// Первое пространство
namespace ns_A;
{
91
short S = 25;
// Перменная.
int MUL2(int arg) // Функция.
{
return (arg * 2);
}
}
// Второе пространство
namespace ns_B;
{
short S = 25;
// Переменная.
int MUL2(int arg) // Функция.
{
return (arg * 2);
}
}
Листинг 4.3. – Обращение к пространствам имён
// Где-то в своей программе...
// Вариант 1
short V = ns_A::S; // Переменная S пространства ns_A.
/* Далее используется функция MUL2 из пространства ns_B
с переменной S из пространства ns_A в качестве аргумента. */
int L = ns_B::MUL2(ns_A::S);
// Вариант 2
using namespace ns_B;
short V = S;
int L = MUL2(S);
Т.е. для описания пространства имён следует использовать ключевое
слово namespace с указанием имени пространства. Элементы пространства
описываются внутри фигурных скобок. Для доступа к элементам в первом
случае используется имя нужного пространства имён и оператор "::", а во
втором случае используется сочетание ключевых слов "using namespace" с
указанием имени нужного пространства.
Нужно помнить, что объявление "using namespace" имеет область
видимости, ограниченную фигурными скобками, внутри которых оно сделано
(если объявление сделано за пределами любых фигурных скобок, то областью
видимости является весь файл). Также во втором случае следует помнить, что
после указанного объявления нельзя использовать имена локальных данных
сходные с именами элементов используемого пространства, а аналогичные
92
имена функций и глобальных данных нельзя использовать также и до этого
объявления.
Если при описании пространства не указать его имени, то элементы
этого пространства будут "глобальными" в пределах файла, содержащего это
описание. То есть любые функции в этом файле могут обращаться к
элементам этого так называемого "неименованного" пространства. Причём
для доступа к элементам не нужно использовать оператор "::" (т.к. у
пространства нет имени) или ключевые слова using namespace.
4.4. Порядок выполнения: циклы, условия, переходы
Любая программа выполняется последовательно "сверху вниз", т.е. от
точки входа и до конца программы (для консольных программ точкой входа
является начало функции main). Однако часто появляется необходимость (в
случае программ с оконным интерфейсом это всегда необходимо) изменить
"стандартную" последовательность выполнения программы. Для этого в
языке C++ используются циклы, условия и переходы.
4.4.1. Циклы (for, while)
Что такое цикл? Это многократное повторение каких-либо действий.
Любой цикл имеет заголовок и тело. Заголовок обязательно содержит
ключевое слово и условие выполнения тела цикла. Также заголовок может
содержать операции по инициализации и изменению счётчика цикла. Условие
и манипуляции со счётчиком описываются внутри круглых скобок. Тело
цикла
представляет
собой
часть
программного
кода,
ограниченную
фигурными скобками. В приведённом ниже примере заголовком цикла
является первая строка, а его телом является вторая строка.
93
Итак пример: у нас есть массив (см. п. 4.6) ar_N из 10 тысяч целых
чисел и мы хотим вывести в консольное окно содержимое каждой ячейки
этого массива. Можно набрать текст программы, содержащей 10 тысяч строк
с вызовами функции printf, а можно организовать цикл:
for (int cnt = 0; cnt < 10000; cnt++)
{ printf("ar_N[%d] = %d\n", cnt, ar_N[cnt]); }
Цикл for
Всего 2 строки вместо 10 тысяч! В данном случае начало цикла (его
заголовок) обозначается ключевым словом for. Итак, заголовок цикла
содержит указанное ключевое слово, а также имеет три поля, которые
ограничены круглыми скобками и следуют друг за другом в строго
определённом порядке: сперва поле инициализации счётчика, затем поле
условия и напоследок поле изменения счётчика. Как только программа в
процессе работы встречает заголовок цикла она инициализирует счётчик,
затем проверяет условие и, если условие удовлетворяется, выполняет тело
цикла. Затем изменяет значение переменной – счётчика цикла и снова
проверяет условие. Если условие удовлетворяется, то программа повторно
выполняет тело цикла (повторная инициализация данных не производится –
они хранят значения, полученные при предыдущем выполнении тела цикла).
И так до тех пор пока указанное условие имеет истинное значение (т.е. true).
Если условие не удовлетворяется, т.е. имеет ложное значение (false), то
программа начинает выполнять код расположенный после цикла. Любое из
этих полей или даже все три поля могут быть не указаны (программа просто
их пропустит), но разделительные знаки "точки с запятой" всегда должны
быть указаны. Вот два примера:
for ( ; V2 != 25; ) {}
for ( ; ; ) {}
Здесь тела циклов для краткости не указаны. В первом случае указано
94
только поле условия. Этот цикл будет выполняться до тех пор пока значение
переменной V2 не станет равным 25. Заметим, что значение для этой
переменной
вычисляется
где-то
в
теле
цикла,
иначе
цикл
станет
бесконечным, т.е. программа никогда самостоятельно не завершит свою
работу! Во втором случае имеем бесконечный цикл (почему бесконечный,
предлагаю разобраться самостоятельно), для выхода из которого следует
воспользоваться переходами (см. далее, а также п. 4.4.3).
Цикл while
Этот цикл имеет две формы реализации while(){} и do{}while(), пример
которых приведён ниже. В первом случае используется ключевое слово while,
а в круглых скобках указывается только условие выполнения тела цикла. Во
втором случае используется ключевое слово do, которое означает начало тела
цикла. Затем идёт само тело цикла и только потом указывается ключевое
слово while с указанием условия в круглых скобках. Разница в том, что во
втором случае программа сразу выполнит тело цикла, а уже потом проверит
условие!
//Первая форма
while (V < 34) // Заголовок цикла.
{
V++;
// Тело цикла.
}
// Вторая форма
do // Первая половина заголовка цикла.
{
V++;
// Тело цикла.
}
while (V <34); // Вторая половина заголовка цикла.
Здесь
можно
задать
два
интересных
вопроса,
касающихся
приведённого выше примера: сколько раз в каждом случае программа успеет
выполнить тело цикла до проверки его условия и чему в каждом случае будет
равно значение переменной V на момент проверки условия, если до начала
95
цикла её значение было равно 28? Предлагаю самостоятельно ответить на эти
вопросы.
Нетрудно
заметить,
что
счётчик
цикла
в
таких
случаях
не
предусмотрен, однако его можно реализовать самостоятельно. Кроме того
следует отметить, что пропуск условия (т.е. пустые круглые скобки) в любой
из этих форм приведёт к бесконечному циклу.
Переходы break и continue
Итак, цикл выполняется пока его условие имеет истинное значение
(например при V=28 условие V<34 имеет истинное значение, а условие
V>=34 имеет ложное значение). Однако бывает, что нужно немедленно
прекратить выполнение цикла. В этом случае следует воспользоваться
ключевыми словами break или continue. В первом случае, встретив ключевое
слово break, программа переходит к выполнению ближайшей строки
программного кода, расположенной после цикла. Во втором случае, встретив
ключевое слово continue, программа не выполняет оставшуюся часть тела
цикла, а изменяет счётчик (если это цикл for) и проверяет условие. Следует
помнить, что все данные, объявленные в теле цикла, перестают быть
видимыми! Ниже приведён пример цикла с использованием обоих переходов
(используемое
в
примере
ключевое
слово
if,
предназначенное
для
обозначения условных конструкций, описывается в п. 4.4.2).
for (short cnt = 1; ; cnt++) // Есть счётчик, но нет условия.
{
if (cnt == 5)
{
break; // Прервать цикл, если счётчик равен 5.
}
if (cnt == 3)
{
continue; // Вернуться к заголовку, если cnt равен 3.
}
printf("cnt = %d\n", cnt); // Показ значений cnt.
}
printf("Loop is done!\n"); // Сообщение о завершении цикла.
96
Этот цикл был бы бесконечен из-за отсутствия условия, однако,
благодаря ключевому слову break, программа прерывает выполнение цикла
при значении cnt равном 5 и перейдёт на последнюю строку примера (в
которой функция printf выведет в консольном окне сообщение "Loop is
done!"). Но до этого, при значении cnt равном 3, программа пропускает вызов
функции printf из-за ключевого слова continue, изменяет счётчик (cnt++) и
опять начинает выполнение тела цикла.
Таким образом, в результате выполнения цикла получим в консольном
окне следующие сообщения:
cnt = 1
cnt = 2
cnt = 4
Loop is done!
4.4.2. Условия (if, else, switch, ?:)
Условие есть выражение, использующее логические операции и
формирующее логический результат: true (истина, т.е. ненулевое значение)
или false (ложь, т.е. ноль). В языке C++
есть ключевые слова, которые
совместно с условиями можно использовать для формирования условных
конструкций: if, else, switch, case, default, break. Кроме того есть тернарный
оператор "?:". По сути цикл это также условная конструкция, которая может
выполняться многократно. Т.е. любая условная конструкция имеет заголовок,
но в отличии от цикла тел может быть несколько. Кроме того, заголовок
обязательно содержит условие (и больше ничего не содержит)!
Конструкции if и if/else
В простейшем случае используется ключевое слово if с указанием
условия (или условий) в круглых скобках, а затем в фигурных скобках
описывается тело конструкции, т.е. что должна сделать программа при
97
истинном значении условия. Таким образом имеем два варианта работы
программы – тело конструкции выполняется или не выполняется в
зависимости от условия.
Такой двухвариантный режим работы программы можно реализовать
по-другому: при истинном значении условия выполняется первое тело, а при
ложном – второе тело. Такая реализация имеет смысл, если программа
должна выполнить какие-то общие операции независимо от значения условия
– тогда в обоих телах останутся только специфичные операции. Данная
реализация выполняется добавлением к рассмотренной выше конструкции if
ключевого слова else и описания второго тела (также в фигурных скобках).
Такие составные конструкции if/else могут объединяться в многоступенчатые
условные конструкции, каждая ступень которых имеет условие. На листинге
4.4 приведён пример условных конструкций для всех трёх вариантов.
Листинг 4.4. – Условные конструкции if и if/else
// Вариант 1: конструкция if
/* Поздравить с наступающим новым годом, если переменная day (день)
равна 31, а переменная month (месяц) равна 12. */
if ((day == 31) && (month == 12))
{
printf("Happy The Coming New Year!\n");
}
// Вариант 2: конструкция if/else
/* Поздравить с наступающим новым годом, если переменная day (день)
равна 31, а переменная month (месяц) равна 12. Иначе просто
поздороваться. */
if ((day == 31) && (month == 12))
{
printf("Happy The Coming New Year!\n");
}
else
{
printf("HELLO!\n");
}
// Вариант 3: конструкция if/else if/else
/* Поздравить с наступающим новым годом, если переменная day (день)
равна 31 и переменная month (месяц) равна 12. Иначе, если day
равна 7 и month равна 1, поздравить с православным рождеством.
98
В противном случае просто поздороваться. */
if ((day == 31) && (month == 12))
{
printf("Happy The Coming New Year!\n");
}
else if ((day == 7) && (month == 1))
{
printf("Merry Christmas!\n");
}
else
{
printf("HELLO!\n");
}
Оператор ?:
Вместо конструкции if/else можно использовать тернарный оператор
"?:", имеющий три операнда: условие; первое выражение, вычисляемое при
истинном значении условия; второе выражение, вычисляемое при ложном
значении условия. Таким образом, результатом использования оператора
является значение одного из указанных выражений. Ниже в приведённом
примере этот оператор используется для определения максимальной и
минимальной величин из двух предложенных значений:
int A = 5, B = 7, Max, Min;
Max = (A < B)? B: A; // Определение максимума.
Min = (A < B)? A: B; // Определение минимума.
Ключевые слова switch, case, default break
Если для условий используется только одно выражение, то указанные
ключевые слова помогут создать конструкцию более компактную, чем if/else.
Ниже приведён пример такой конструкции. Следует отметить, что вариант
default не является обязательным и может отсутствовать. Кроме того если
убрать ключевые слова break, то программа "просмотрит" всю конструкцию,
даже после выполнения нужного варианта. Варианты могут содержать более
одной строки программного кода, но и в этом случае их не нужно
ограничивать фигурными скобками (разве что для ограничения области
видимости). Благодаря ключевому слову break в каждом варианте, программа
99
после выполнения нужного варианта не просматривает оставшуюся часть
конструкции, а переходит к строке кода после варианта "default" или, если нет
этого варианта, после последнего варианта "case" (в примере это строка
"color++;").
int color = 2; //Заданное значение для определения цвета.
switch (color) // Условие.
{
case 0: // Если color равна 0, то это чёрный цвет.
printf("BLACK\n");
break;
case 1: // Если color равна 1, то это красный цвет.
printf("RED\n");
break;
case 2: // Если color равна 2, то это зелёный цвет.
printf("GREEN\n");
break;
case 3: // Если color равна 3, то это синий цвет.
printf("BLUE\n");
break;
default: // Для остальных значений – это неизвестный цвет.
printf("Unknown!\n");
}
color++; // Сюда выполняется переход по словам break.
4.4.3. Переходы (goto, return)
Что такое переход? Это изменение порядка выполнения программы.
Т.е. стандартный порядок заключается в последовательном выполнении строк
программного кода. Если программа встречает строку с соответствующим
ключевым словом (см. п. 4.4.1 и 4.4.2), то вместо выполнения следующей
строки она ищет строку, содержащую цель перехода (например заголовок
цикла), и продолжает работу уже с этой строки.
Кроме break и continue есть ещё одно ключевое слово – return, которое
позволяет выполнить переход. Это ключевое слово, как и рассмотренные
ранее, не используется отдельно, а является неотъемлемой частью функций
(см. п. 4.5). Проще говоря, любая функция возвращает значение с помощью
этого ключевого слова, многочисленные примеры использования которого
100
можно найти в листингах разделов 2 и 3. Цель перехода с помощью return –
место, откуда была вызвана функция.
Таким образом, все рассмотренные выше ключевые слова (break,
continue, return) выполняют безусловные переходы, однако цели этих
переходов предопределены и изменить это никак нельзя. Т.е. такие переходы
не позволяют перейти в "произвольное" место программы (под произвольным
местом мы подразумеваем пределы описания тела функции, в которой нужно
сделать
переход,
т.е.
за
пределы
функции
перейти
нельзя).
Хотя
необходимость в таком переходе возникает нечасто, а создание программного
кода, требующего таких переходов, считается "дурным" тоном, в языке C++
есть ключевое слово goto, используемое именно для выполнения безусловных
переходов в произвольное место функции.
Сначала следует отметить цель – место перехода, т.е. поставить метку.
Метка располагается обязательно в начале строки и состоит из произвольного
имени (о правилах именования см. в [20, 21]) и символа двоеточия. Для
выполнения перехода следует указать ключевое слово goto и имя метки, к
которой осуществляется переход. Вот пример с подробными комментариями:
goto place2;
printf("Start\n");
place2:
printf("Finish\n");
//
//
//
//
Выполнить переход.
Эта строка будет пропущена.
МЕТКА.
На эту строку будет совершён переход.
4.5. Функции и статические переменные
Функция есть основа, на которой строится любая программа на языке
C++. Это часть кода, объединённая фигурными скобками под конкретным
именем, и её можно использовать независимо от остального кода программы
Любая программа имеет в своём составе как минимум одну функцию – main
(в случае консольной программы) или WinMain (в случае программы с
оконным интерфейсом для операционной среды Microsoft Windows). Эти две
101
функции присутствуют обязательно – с них программа начинает свою работу.
Функции могут быть самостоятельными или входить в состав классов (см. п.
4.8.2 и 4.8.3). На листинге 4.5 представлен пример программы, выводящей в
консольном окне сообщение "Hello World!". В программе присутствует
описание только функции main, но для вывода сообщения используется
стандартная функция printf, описание которой содержится в заголовочном
файле "stdio.h". Так как мы вставили содержимое этого файла в программу с
помощью директивы препроцессора (см. п. 4.9) "#include", то в принципе
можно сказать, что программа также содержит описание функции printf.
Листинг 4.5. – Пример консольной программы
#include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello World!\n");
return 0;
}
Здесь "main" это имя соответствующей функции. Переменные,
описанные в круглых скобках, являются её аргументами. Посредством
аргументов можно передать в функцию нужные данные. В данном случае в
функцию printf передаётся строка "Hello World!" в качестве аргумента.
Ключевое слово int перед именем функции указывает тип возвращаемого
функцией значения. Функция возвращает значение с помощью оператора
return (см. п. 4.4.3). Механизм возвращения функцией значения используется
с одной стороны для получения результата работы функции, а с другой
стороны для индикации режима завершения работы функции. Во втором
случае ноль означает, что всё в порядке, а ненулевое значение означает, что в
процессе работы функции произошла ошибка. Так листинг 2.1 содержит
вызов функции fopen для открытия файла (см. п. 4.7). Однако файл может
отсутствовать или быть занят, при этом функция вернёт ненулевое значение
102
различное для каждого случая. Вообще при разработке программного
обеспечения и в частности достаточно сложных функций принято к
функциям прилагать описания возвращаемых значений. Если функция ничего
не возвращает, то следует указывать ключевое слово void перед именем
функции. Следует отметить, что с помощью оператора return можно выйти из
любого места функции.
Далее в фигурных скобках описывается тело функции, т.е. что она
должна делать. В данном случае тело функции main содержит всего две
строки – в первой вызывается функция printf, а во второй возвращается
нулевое значение.
Описав функцию, можно её вызвать, указав имя и аргументы. Встретив
имя функции, программа переходит на её первую строку, а встретив в теле
вызванной функции оператор return, возвращается к той строке, из которой
эта функция была вызвана.
Функцию можно также вызвать изнутри какого-либо выражения, при
этом возвращаемое функцией значение используется для вычисления
выражения. На листинге 2.1 можно увидеть такую строку:
p_src = fopen(argv[1], "rb+");
Здесь возвращаемое функцией fopen значение является указателем (см.
п. 4.6) на начало открытого файла и присваивается соответствующей
переменной (указателю).
Аргументы передавать в функцию можно по значению или по ссылке
(адресу). В первом случае в функцию передаются не сами данные, а их копия.
Т.е. в процессе выполнения функции программа может изменить лишь копию
данных, а сами данные не изменяются. Во втором случае передаётся адрес
данных, т.е. адрес ячейки памяти, где эти данные хранятся. Таким образом
передаётся только адрес – сами данные не передаются и их копии не
создаётся, что экономит память компьютера и время (которое было бы
103
затрачено на создание копии). В процессе выполнения функции программа
может изменять содержимое указанной ячейки памяти, т.е. непосредственно
сами данные. Так как оператор return может вернуть только одно значение, а
аргументов у функции может быть сколь угодно много, то для возврата
функцией множества значений используется передача аргументов по ссылке,
а оператор return используется для индикации режима завершения
выполнения функции.
Для получения адреса данных используется оператор "&" – оператор
взятия адреса. Для обеспечения возможности передачи в функцию данных по
ссылке при описании функции в качестве соответствующих аргументов
используются указатели (см. п. 4.6). Ниже приведён пример описания и
вызова функции с передачей первого аргумента по значению а второго по
ссылке:
int my_function(short A1, short *A2, short *A3)
{
A1++; // Увеличение КОПИИ переменной V1 на единицу.
(*A2)++; // Увеличение переменной V2 на единицу.
(*P1)++; // Увеличение переменной V1 на единицу.
return 0;
}
short V1 = 5, V2 = 24, *P1;
P1 = &V1; // Указатель на переменную V1.
my_function(V1, &V2, P1);
Примечание: третий аргумент показывает различия синтаксиса при
передаче переменной по ссылке или указателя (см. п. 4.6) на переменную.
Следует отметить, что вызывать функцию можно только тогда, когда её
описание находится в пределах видимости от места вызова.
Статические данные
Рассмотрим ещё один пример описания и вызова функции:
// Описание функции.
int func(int A1)
104
{
static int Sum = 0; // Статическая переменная.
int V = 0;
V++;
Sum = Sum + A1;
return Sum; // Возврат нового значения Sum.
}
// Вызов функции.
int N[5] = {2, -5, 1, 0, 7};
int R = 0;
for(int cnt = 0; cnt < 5; cnt++)
{
R = func(N[cnt]);
printf("Sum = %d\n", R);
}
При выполнении цикла получим в консольном окне пять сообщений:
Sum
Sum
Sum
Sum
Sum
=
=
=
=
=
2
–3
–2;
–2
5
Sum
Sum
Sum
Sum
Sum
=
=
=
=
=
2
–5
1
0
7
Первый столбец соответствует приведённому выше примеру, а второй
столбец сообщений мы получим, если уберём слово static при объявлении
переменной Sum. Т.е для объявления статической переменной используется
указанное ключевое слово static.
В чём же разница между обычными и статическими переменными?
Почему получаем разные результаты для приведённого выше примера? Всё
просто. При каждом вызове функции выполняется инициализация её данных
(переменных и пр.). Если же данные статические (не только переменные
могут иметь такой статус), то их инициализация выполняется только при
ПЕРВОМ вызове функции (и не важно, откуда она была первый раз вызвана).
Таким образом в приведённом выше примере строка с объявлением
переменной Sum будет выполнена только один раз. При следующем вызове
функции эта строка будет пропущена. В то же время переменная V1 будет
инициализироваться нулём при каждом вызове функции и поэтому при
выполнении строки "V1" всегда получим 1. Однако если мы закомментируем
105
(см. п. 4.10) строку инициализации этой переменной, то при сборке
программы получим ошибку об отсутствии объявления этой переменной.
Почему же в одном случае мы можем пропустить инициализацию, а в другом
нет? Дело в том что при очередном завершении работы функции все обычные
данные удаляются из памяти программы, а значения статических данных не
удаляются, хотя сами данные за пределами функции не видны. Таким
образом при очередном вызове функции func мы используем значение
переменной Sum, полученное при предыдущем вызове этой функции.
Ключевое слово static также используется для ограничения области
видимости глобальных данных. В п. 4.2 было сказано, что глобальные
данные, объявленные в одном файле программы, видны и из других её
файлов, в которых для обращения к этим данным нужно использовать
ключевое слово extern, например:
short G1 = 8; // Объявление в файле file1.cpp.
extern short G1; // Объявление в файле file2.cpp.
Ключевое слово static при объявлении G1 ограничивает область её
видимости пределами файла file1.cpp, т.е. из других файлов программы
обратиться к этой переменной станет невозможно – она не будет видна. Это
может быть очень полезно для избегания конфликта из-за одинаковых имён
глобальных данных, объявленных в разных файлах одной программы.
4.6. Массивы и указатели, выделение памяти
Указатели
Указатели это переменные целого типа (int для 32-разрядных
операционных систем), содержащие адреса ячеек памяти. При объявлении
казателя используется оператор "*" – оператор разыменования. В дальнейшем
при обращении к указателю использование только его имени означает
106
получение адреса ячейки памяти, а использование имени совместно с
оператором "*" означает получение содержимого этой ячейки. Пример:
short V1 = 5;
int *P = &V1; // Указатель на переменную V1.
(*P)++;
// Теперь переменная V1 равна 6.
P++; // Теперь указатель указывает на следующую ячейку памяти.
Почувствуйте разницу! Очень много ошибок связано с отсутствием
или неуместным наличием оператора "*" при использовании указателей.
ВАЖНО! Следует помнить, что перед первым использованием
указателя его надо инициализировать, причём не произвольным значением, а
обеспечить чтобы он указывал на реальные данные. Кроме того, если
указатель, например, указывает на переменную, то при её удалении из памяти
( например при выходе за пределы области видимости этой переменной)
сначала нужно указателю присвоить нулевое значение.
Массивы
Массив это последовательность ячеек в памяти, занятых данными
одного типа. При описании массива указывается его тип (т.е. тип данных),
имя массива и его размеры в квадратных скобках. Следует помнить, что
нумерация элементов массива начинается с нуля, т.е. если объявлен массив из
10 элементов, то они имеют номера (индексы) от 0 до 9. В примере ниже
трёхмерный массив следует понимать так – это две прямоугольных матрицы,
каждая из которых состоит из 5 строк и 3 столбцов. В памяти такой массив
хранится следующим образом: сперва элементы первой строки первой
матрицы, затем элементы второй строки первой матрицы и так до последней
строки первой матрицы, потом элементы первой строки второй матрицы и
т.д.
int N1[5]; // Объявление пустого массива из 5 элементов.
// Объявление массива с одновременной инициализацией.
int N2[4] = {3, 11, -24, 0};
107
// Присваивание значений отдельным элементам массива.
N1[0] = -7; // Нумерация элементов массива начинается с нуля!
N1[4] = 12; // Последний элемент массива N1.
N1[5] = 3; // ОШИБКА – Выход за пределы массива!
// Объявление трёхмерного массива.
int N3[2][5][3];
Чтобы
обратиться
к
конкретному
элементу
массива
следует
использовать операторы "[ ]" с указанием номера (индекса) элемента по
каждой размерности. В данном случае N3[1][2][1] – это элемент массива N3,
расположенный во втором столбце и третьей строке второй матрицы.
Обратиться к элементу массива можно и с помощью указателей, тем более,
что имя массива является указателем на его начало (первый столбец первой
строки первой матрицы и т.д.). Однако следует помнить, что имя массива
является указателем-константой и поэтому менять его значение нельзя! Ниже
приведён пример последовательного обнуления всех элементов массива, а
также запись величины 34 в элемент N3[1][2][1] – всё с помощью указателей.
int N3[2][5][3]; // Объявление массива.
for (int cnt = 0; cnt < (2*5*3); cnt++)
{
*(N3 + cnt) = 0; // Обнуление текущего элемента массива.
}
*((N3 + 3*5) + 1*2) = 34; // Запись величины 34 в N3[1][2][1].
Кроме имени массива его размеры также являются константами, т.е.
должны быть указаны константными выражениями.
Инициализация массива с помощью перечисления значений его
элементов в фигурных скобках может быть проведена только при объявлении
массива! Вот пример двух вариантов инициализации двумерного массива:
// Первый вариант.
short N[2][3] = { 1, 7, 4,
2, 0, 8 };
// Второй вариант (более читабельный).
short N[2][3] = { {1, 7, 4},
// Первая строка.
{2, 0, 8} }; // Вторая строка.
Выделение памяти
108
Выше было описано статическое выделение памяти для массива. Т.е.
при запуске программа сразу резервирует память под массив, даже если он
пуст (значения элементов не заданы или равны нулю). Так для размещения в
памяти приведённого в примере массива N3 будет автоматически выделено
120 байт. Однако при использовании больших объёмов информации,
например при обработке изображений или видеопотока, статическое
выделение памяти может занимать много времени, да и памяти может просто
не хватить! Кроме того требуемые размеры массива могут определяться лишь
в процессе работы программы, а не на этапе её сборки. Поэтому в таких
случаях используется динамическое выделение памяти, т.е. выделение по
мере
необходимости.
Также
используется
динамическое
удаление
выделенной памяти (её освобождение) когда в ней отпадает необходимость.
Для динамического выделения и удаления применяются ключевые слова
(операторы) new и delete. Ниже приведён пример использования обоих
операторов. Заметим, что в операторе new и при объявлении указателя ptr
используется один и тот же тип данных.
Следует помнить, что даже после освобождения выделенной памяти
указатель будет продолжать указывать на адрес блока освобождённой
памяти, по которому может располагаться "мусор" или данные другой
программы (в этом случае программа может завершить работу с сообщением
об ошибке совместного доступа к участку памяти)!
char *ptr;
// Указатель для управления памятью.
// R – требуемый размер массива (определили где-то в программе).
ptr = new char [R]; // Выделили память под массив.
// Теперь ptr указывает на блок выделенной памяти.
*(ptr + 4) = 50;
// Изменяем содержимое пятой ячейки.
delete [] ptr;
// Освобождаем выделенную память.
При выделении памяти для многомерного массива, размер требуемой
памяти определяется произведением значений размеров всех измерений
массива. Так при выделении памяти для массива размерами [5][20][11]
109
значение переменной R в приведённом выше примере будет равно
1100=5*20*11.
Массивы могут содержать не только данные основных типов (см. п.
4.1), но и структуры (см. п. 4.7). Вот пример:
struct point // Описание структуры типа point ("точка").
{
int x; // Координата X.
int y; // Координата Y.
};
// Для описания вектора используем массив из двух структур
// типа point (для описания вектора нужны две точки).
point Vector[2];
// Инициализируем координаты обеих точек.
Vector[0].x = 0;
Vector[0].y = 0;
Vector[1].x = 12;
Vector[1].y = 9;
Строки
Строка это массив символов – элементов типа char. Объявить и
инициализировать такой массив можно двумя способами:
// Вариант 1.
char str1[5]; // Строка из пяти символов.
str1 = "Hello"; // Инициализация.
// Вариант 2.
// Создаётся массив по длине строковой константы "Hello".
char str[] = "Hello";
В первом случае, если в массиве не хватит места для размещения всех
символов строковой константы, то либо программа завершит свою работу с
сообщением об ошибке "Переполнение буфера", либо строка будет обрезана,
т.е. её не поместившиеся в массив символы "потеряются". Массив строк
объявляется следующим образом:
char имя_ массива[число_строк][длина_строки]
Здесь подразумевается длина самой длинной строки. Впрочем, как и
110
выше, можно пропустить указание размеров, совместив объявление массива с
его инициализацией.
Также для работы со строками язык C++ включает в себя стандартный
класс string, о котором можно узнать достаточно подробно в [1]. Следует
отметить, что этот класс упрощает выполнение таких задач, как копирование
строк или поиск в них отдельных символов, сравнение строк и многое другое.
4.7. Структуры, файлы и перечисления
Структуры предназначены для группирования данных, имеющих
разные типы, но общий смысл. Данные в составе структуры называются её
элементами или полями. Ниже приведён пример описания структуры worker,
которая может быть использована в базе данных отдела кадров какой-нибудь
фирмы для описания её сотрудников.
struct worker // Структура "Сотрудник".
{
char first_name[50];
// Имя сотрудника.
char last_name[100];
// Фамилия сотрудника.
short age;
// Возраст.
bool married = false;
// Женат (замужем)?
}; // Не забудьте точку с запятой!
Как видно, для описания структуры нужно указать ключевое слово
struct, затем имя типа (или вида) структуры. Поля структуры описываются и
инициализируются внутри фигурных скобок. Итак, мы описали структурный
тип данных, имеющий имя worker. Теперь для использования этого типа,
например при приходе в фирму нового сотрудника, нужно объявить
экземпляр структуры этого типа и задать экземпляру имя. По сути делаем то
же самое, что и при объявлении переменной и пр. Объявив экземпляр, мы
можем получить доступ к его полям и изменить их. Например в фирме уже
есть 19 сотрудников, описания которых записаны в структурах W1 – W19
типа worker. В фирму пришли ещё два сотрудника, поэтому объявим еще две
111
структуры W20 и W21 и заполним их описаниями новых сотрудников:
worker W20, W21; // Структуры для описания новых сотрудников.
W20.first_name = "Ирина";
// Имя.
W20.last_name = "Потапова"; // Фамилия.
W20.age = 22;
// Возраст 22 года.
W20.married = true;
// Замужем!
W21.first_name = "Михаил";
W21.last_name = "Громов";
W21.age = 20;
// Имя.
// Фамилия.
// Возраст 20 лет.
Обратите внимание на отсутствие описания поля married для второго
сотрудника! В описании типа структуры этому полю присвоено значение
false ("холост"). Если мы явно не изменим указанное поле, то оно так и
останется равным заданному значению. Инициализация полей структуры на
этапе описания её типа называется заданием значений "по умолчанию". При
объявлении экземпляра структуры его поля заполняются значениями по
умолчанию (если конечно они заданы). Таким образом в данном примере
косвенно описывается, что Михаил Громов не женат!
Файлы
Большинству программ требуется сохранять данные в файлы (и
считывать данные из них), находящиеся на жёстком диске компьютера, на
дискете, CD, DVD и т.д. Для этого язык C++ имеет в своём составе 5
стандартных
функций:
fopen,
fread,
fwrite,
fseek,
fclose.
Пример
использования всех этих функций можно увидеть на листинге 2.1 (см. п.
2.1.1). Эти функции остались со времён языка C и при разработке программ
на языке C++ использование этих функций хоть и не возбраняется, но не
рекомендуется. Вместо указанных функций, а также для сохранения и
считывания сложных типов данных (например классов) в программах на
языке C++ следует использовать потоки. Однако не следует в одной
программе смешивать указанные функции и потоки. Достаточно подробно
потоки описаны в [1]. Кроме того при разработке программ для
112
операционной системы Microsoft Windows можно использовать библиотеку
MFC (Microsoft Foundation Classes), которая предоставляет класс CFile,
обладающий богатыми возможностями для работы с файлами.
На
листинге
4.6
представлен
вариант
программы
bmpinv,
рассмотренной в п. 2.1.1. Этот вариант использует потоки вместо функций
printf, fopen, fread, fwrite, fseek, fclose. В данном случае используется два
потока: cout и file. Сравните данный листинг с листингом 2.1. Сперва мы
подключаем стандартные заголовочные файлы (fstream для работы с
файлами, а iostream для вывода сообщений в консольном окне). Эти
заголовочные файлы содержат всё, что нам может понадобиться, но
используют пространства имён. Поэтому, чтобы воспользоваться, например,
потоком вывода cout (вместо функции printf), используем пространство имён
"std". Далее объявляем объект класса fstream (о классах см. п. 4.8.2) для
работы с файлами. Затем объявляем переменные: buff – буфер для чтения
служебной информации об изображении и чтения/записи данных самого
изображения; file_size – для определения размера файла (чтобы потом
определить условие выполнения цикла); adr_data – адрес начала данных
изображения в файле; cnt – счётчик цикла. Далее открываем заданный файл в
режиме двоичного чтения и записи. Устанавливаем файловый указатель на
начало файла. И так далее (см. описание программы bmpinv в п. 2.1.1).
Следует отметить, что на листинге представлен полный текст
программы, т.е. всё содержимое файла bmpinv.cpp! Теперь можно удалить все
прочие файлы из проекта (щёлкаем правоё кнопкой мыши на их именах в
окне "Solution Explorer" и в появляющемся меню выбираем Remove, далее в
диалоговом окне также жмём кнопку Remove). Собираем программу и
запускаем, как описано в п. 2.1.1.
Листинг 4.6. – Программа bmpinv (использование потоков)
#include <fstream>
113
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
fstream file;
unsigned long buff = 0, file_size, adr_data, cnt;
file.open(argv[1], ios::in | ios::out | ios::binary);
file.seekg(0);
file.read(reinterpret_cast<char*>(&buff), 2);
if (buff != 19778)
{
cout << "This is not BMP file..." << endl;
file.close();
return 1;
}
file.seekg(2);
file.read(reinterpret_cast<char*>(&file_size), 4);
cout << "File size = " << file_size << endl;
file.seekg(10);
file.read(reinterpret_cast<char*>(&adr_data), 4);
cout << "Origin of image data: " << adr_data << endl;
buff = 0;
for (cnt = adr_data; cnt < file_size; cnt++)
{
file.seekg(cnt);
file.read(reinterpret_cast<char*>(&buff), 1);
buff = 255 - buff;
file.seekg(cnt);
file.write(reinterpret_cast<char*>(&buff), 1);
}
file.close();
cout << "Processing was done!" << endl;
return 0;
}
Несколько замечаний. Данный вариант программы bmpinv работает
несколько медленнее предыдущего варианта. А если сравнить размеры
получаемых программ, то данный вариант программы (подразумеваем Debugверсию сборки) получается более чем в три раза больше предыдущего
варианта. В принципе здесь нет нужды в каких-то сложных файловых
операциях, т.к. данные имеют простую структуру. Поэтому особых
преимуществ от применения потоков не видно. Другое дело, если
понадобится сохранять и считывать данные сложных типов (например
классы) да ещё с форматированием (например сохранять данные столбцами
114
определённой ширины) – с потоками это можно будет сделать значительно
проще (см. [1]).
Перечисления
Кроме
структур
существует
ещё
один
способ
создания
пользовательских (т.е. задаваемых автором программы) типов данных,
называемый
перечислением.
На
листинге
4.7
пример
программы,
использующей перечисление Summer.
Листинг 4.7. – Пример использования перечислений
#include <iostream>
using namespace std;
enum Summer { June, July, August };
int main()
{
Summer month1, month2;
month1 = June;
month1++;
month2 = August;
int diff = month2 - month1;
bool T = (month1 < month2);
return 0;
}
При описании перечисления используется ключевое слово enum. Затем
указывается имя перечисления (по сути имя нового типа данных). Потом в
фигурных скобках через запятую указываются элементы перечисления. Этого
не видно, но каждому элементу присваивается номер, начиная с нуля! Т.е.
при использовании элементов перечисления на самом деле используются их
номера, т.е. целочисленные константы. Именно благодаря этому возможно
применение операций отношения и арифметических операций, как показано
на листинге, в отношении элементов перечисления.
При объявлении экземпляров (объектов) перечисления заданного типа
используется имя типа (в данном случае Summer), после которого задаются
115
имена объектов. Далее объявленные объекты могут принимать любое
значение, соответствующее элементу перечисления использованного типа (в
данном случае только June, July и August). Предлагаю самостоятельно
разобраться какие значения имеют переменные на приведённом листинге
после выполнения каждой строки программы.
Перечисления широко используются в технологии программирования
COM и соответственно в широко известной библиотеке DirectX (которая
основана на COM). Кроме того перечисления широко используются
производителями компьютерной техники для идентификации своих изделий
и установки соответствующих драйверов.
4.8. Объектно-ориентированное программирование
4.8.1. Три основных понятия
В реальном мире нам приходится иметь дело с реальными объектами,
такими, например, как люди, машины и т.д. Такие объекты нельзя отнести ни
к данным, ни к функциям, т.к. эти объекты представляют собой совокупность
свойств и поведений. Примерами свойств, например, для людей могут
являться цвет глаз, наличие бороды, место работы и т.д. Таким образом
свойства объектов соответствуют данным в программах, т.е. они могут иметь
значения (например глаза имеют зелёный цвет). Поведение – это реакция
объекта в ответ на внешнее воздействие. Например, если на улице станет
холодно,
то
мы
оденемся
потеплее.
Поведение
реального
объекта
соответствует функции в программе, т.е. внешнее воздействие соответствует
вызову функции, а реакция на это воздействие соответствует выполнению
функции. Таким образом для программного описания объекта реального мира
необходима совокупность данных и функций! Именно в этом – объединении
116
данных
и
функций
состоит
основополагающая
идея
объектно-
ориентированного программирования. При этом данные объекта принято
называть его "полями", а функции объекта – его "методами".
Рассмотрим
три
ключевых
понятия
объектно-ориентированного
программирования: инкапсуляцию, полиморфизм и наследование.
Инкапсуляция (или сокрытие). Все данные (поля) объекта не имеют
прямого доступа извне, что защищает их от случайного изменения. Чтобы
получить к ним доступ, следует использовать соответствующие методы
объекта. В этом случае говорят, что данные "инкапсулированы" (сокрыты).
Инкапсуляция
упрощает
дальнейшую
модернизацию
классов
и
их
наследование.
Полиморфизм (или перегрузка) – это возможность реализовать
разные варианты одной и той же функции. При этом варианты функции будут
иметь одно и то же имя, но могут отличаться типом возвращаемого значения,
типами и количеством аргументов, а также реализациями тел функции. В
этом случае такая функция называется перегруженной. В качестве примера
можно привести функцию seekg (см. листиг 4.6 в п. 4.7), вот две возможные
формы её вызова:
seekg(10); // Указатель на 10-й байт от начала файла.
seekg(10, ios::beg); // Эквивалентно предыдущей строке.
seekg(-10, ios::end); // На 10-й байт от конца файла.
В первом случае вызывается вариант функции, в котором заданный
адрес всегда определяется относительно начала файла. Во втором случае
вызывается вариант этой же функции, в котором сперва по второму
аргументу определяется, относительно чего определять заданный адрес, и
только потом происходит непосредственное определение заданного адреса.
Наследование. В реальном мире одни объекты происходят из других
объектов, которые принято называть "родителями" или "предками". При этом
новые объекты имеют или, как говорят, "наследуют" свойства и черты
117
поведения своих родителей. В то же время эти новые объекты имеют свои
собственные
свойства
и
собственные
черты
поведения.
Например
автомобиль-амфибия – он имеет колёса и может ехать по суше, как и его
предок – обычный автомобиль. Но в отличие от своего предка автомобильамфибия имеет специфическую конструкцию кузова и способен плавать!
4.8.2. Классы и объекты
Классы это подобны структурам, но в отличие от них могут содержать
функции. Таким образом, класс – это сложный (составной) тип данных.
Объект – это экземпляр класса. Можно также сказать, что класс это тип
объекта. Например, автомобили и мотоциклы являются объектами класса
"наземный транспорт". По сути класс является формой, определяющей, какие
данные и функции будут включены в объект этого класса.
Классы могут наследовать поля и методы других классов, добавляя к
ним свои поля и методы, или изменяя унаследованные поля и методы (см.
раздел 3). Класс-родитель называют "базовым" классом, а класс-наследник
называют "производным" классом.
На листинге 4.8 приведён пример описания базового и производного
классов и объявления их объектов. Прокомментируем этот листинг.
Допустим, что мы хотим строить двумерные графики и для этого нам нужно
работать с точками, каждая из которых имеет две координаты X и Y. Поэтому
мы создаём класс Point ("точка"), который имеет два поля для хранения
координат и четыре метода – два для чтения координат и два для их
изменения. Допустим, что в дальнейшем мы захотим строить трёхмерные
графики, т.е. нам нужно будет работать с точками, имеющими три
координаты X, Y и Z. Поэтому создаём новый класс Point3D, производный от
первого класса, и добавляем необходимые поля и методы. Отметим, что если
118
класс является производным от нескольких классов, то имена всех базовых
классов указываются через запятую.
Листинг 4.8. – Классы Point и Point3D
class Point // Описание класса Point.
{
private:
int X; // Координата X.
int Y; // Координата Y.
public:
void Point() {}; // Конструктор объекта класса Point.
int get_X() { return X }; // Метод для получения значения поля X.
int get_Y() { return Y }; // Метод для получения значения поля Y.
void set_X(int a) { X = a; }; // Метод для изменения поля X.
void ~Point() {}; // Деструктор объекта класса Point.
};
// Описание метода set_Y класса Point для изменения поля Y.
void Point::set_Y(int b)
{
Y = b;
}
// Описание класса Point3D (указан базовый класс).
class Point3D:public Point
{
private:
int Z; // Координата Z.
public:
void Point3D() {}; // Конструктор объекта класса Point.
int get_Z() { return Z }; // Метод для получения значения поля Z.
void set_Z(int c) { Z = c; }; // Метод для изменения поля Z.
void ~Point3D() {}; // Деструктор объекта класса Point.
};
// Объявление объекта класса Point3D.
Point3D cur_point; // Объект для хранения координат текущей точки.
// Получение и изменение полей объекта класса Point3D.
cur_point.set_X(25); // Координата X текущей точки теперь равна 25.
int x = cur_point.get_X(); // Получение координаты X текущей точки.
Т.к. методы простые, то их тела размещаем внутри описаний классов,
но тело одного метода (set_Y) для наглядности описываем за пределами
описаний классов.
Ключевые слова private и public (есть ещё protected) – это
модификаторы доступа полей и методов. Область действия модификатора
119
простирается до следующего модификатора или до закрывающей фигурной
скобки. Если модификатор не указан (так делать не рекомендуется), то это
соответствует "присутствию" модификатора private.
Модификатор public означает, что поля и методы доступны за
пределами класса. Именно поэтому в примере мы смогли вызвать методы
get_X и set_X. Модификатор private означает, что поля и методы не доступны
за пределами класса и использовать их можно только внутри класса, т.е. в
методах и полях своего же класса (или производного от него). Модификатор
protected означает, что поля и методы недоступны за пределами класса, но
доступны в производных классах. Подробнее о модификаторах доступа
можно узнать в [1, 2].
Следует отметить, что в приведённом выше примере модификатор
public в строке "class Point3D:public Point" имеет несколько другое
значение. Это уже модификатор наследования и в данном случае
использование public позволяет классу Point3D унаследовать поля и методы
класса Point без изменения их модификаторов доступа. В случае, если класс
является производным от нескольких классов, то имена всех базовых классов
указываются через запятую, но модификатор наследования по прежнему
указывается один – общий для всех базовых классов. Подробнее о
модификаторах наследования можно узнать в [2].
Заметим, что public-полей в классе должно быть как можно меньше,
т.к. это противоречит принципу инкапсуляции.
4.9. Директивы препроцессора
Препроцессор
–
это
специальная
часть
компилятора,
которая
обрабатывает (выполняет) соответствующие директивы перед началом
компиляции программного кода (см. п. 2.2). Любая директива препроцессора
120
начинается с символа # (произносится "шарп"). Если создать проект, как
описано в п. 2.1.1, и посмотреть содержимое файла "stdafx.h", то можно
увидеть наиболее часто используемые директивы, которые мы кратко
рассмотрим на примере вымышленного файла "example.h".
#include <windows.h> // Вставить сюда текст файла windows.h.
#include "myprog.h" // Вставить сюда текст файла myprog.h.
#pragma once // Вставлять файл example.h только один раз.
#define DBG // Определить идентификатор DBG.
#define WIDTH 24 // Определить идентификатор WIDTH.
#ifdef DBG // Если определён DBG, то компилировать сл. строку.
int V = 100;
#else // Если DBG НЕ ОПРЕДЕЛЁН, то компилировать сл. строку.
int V = 50;
#endif // Конец директивы #ifdef.
int W = WIDTH; // Теперь переменная W = 24.
#undef DBG // Теперь идентификатор DBG не определён.
Итак, директива "#include" имеет имя файла в качестве аргумента и
заменяется содержимым указанного файла. Если имя файла в угловых
скобках, то это стандартный файл из поставки компилятора. Если имя файла
в двойных кавычках, то это файл программы и находится он там же, где и все
файлы с исходным кодом программы. Директива "#pragma" позволяет
управлять настройками компилятора и сборщика, при этом управление
производится с помощью указания соответствующих аргументов. В данном
случае аргумент "once" указывает компилятору, что файл "example.h" может
вставляться в другие файлы программы не более чем по одному разу. Это
исключает ошибки, связанные с многократным определением одних и тех же
данных и функций. Директива "#define" определяет идентификатор с
заданным именем, а директива "#undef", указывает компилятору, что
идентификатор не определён. С помощью директив #ifdef, #else, #endif
можно указать компилятору, какие части программы компилировать, если
указанный идентификатор определён или не определён. Если директива
121
#define кроме идентификатора имеет второй аргумент (константу или
выражение), то этот аргумент будет подставлен во все места программы
(кроме условных директив и директивы #undef) вместо идентификатора.
4.10. Комментарии в программах
Комментарии
это
средство
описания
работы
программы
непосредственно в её исходном тексте. Они могут значительно облегчить
изучение чужого программного кода (например при коллективной разработке
программы) или чтение своего же кода спустя много времени (когда можно
совершенно забыть, что означает вот эта переменная, или зачем вызывается
вон та функция и т.д.).
В
языке
C++
используются
однострочные
и
многострочные
комментарии. Первые начинаются символом двойной косой черты "//", а
заканчиваются концом строки, т.е. между указанным символом и концом
строки не должно быть исполняемого программного кода, иначе программа
этот код не будет выполнять. Много строчные комментарии начинаются
двойным символом "/*", а заканчиваются обратным двойным символом "*/".
Также между этими символами не должно быть исполняемого программного
кода, иначе программа этот код будет считать комментарием.
Как правило текст любой программы можно разбить на логические
блоки, например: блок инициализации переменных, блок вызова функций и
т.д. Перед каждым таким блоком следует размещать комментарий с
описанием, что должна делать программа в этом блоке. Если в самом блоке
объявляются новые данные или используются хитрые программные трюки,
которыми так славится язык C++, то следует перед соответствующими
строками или в их конце также размещать комментарии.
Комментарии бывают плохие и хорошие. Плохие комментарии не
122
несут полезной информации и даже могут запутать. Хорошие комментарии
не только кратко описывают действия программы, но и их смысл. Вот
примеры плохого и хорошего комментариев:
// Примеры плохих комментариев.
int cnt = 2; // Переменной cnt присваивается значение 2.
cnt++; // Увеличение переменной cnt на 1.
char fn[100]; // Массив из 100 символов.
// Примеры хороших комментариев.
int cnt = 2; // Начальное значение счётчика цикла.
cnt++; // Увеличение счётчика цикла.
char fn[100]; // Массив для хранения имени выбранного файла.
Интегрированная среда разработки Microsoft Visual Studio 2005
предоставляет следующую интересную возможность. Непосредственно (т.е.
не разделяя пустыми строками) перед описанием, например, функции следует
указать комментарий – описание, назначение, аргументы и т.д. В
дальнейшем, при использовании механизма автоматического завершения
имени функции (по нажатию клавиш "Ctrl" и "Пробел") и при показе
соответствующего списка (см. п. 2.1.1), выберем в появившемся списке имя
прокомментированной функции и во всплывающей подсказе увидим
указанный комментарий. Очевидно, что такой комментарий не только
облегчает
последующее
модернизацию.
изучение
программного
кода,
но
и
его
123
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1.
Лафоре Р.
Объектно-ориентированное
программирование
в
С++.
Классика Computer Science: [пер. с англ.]. – Изд. 4-е. – СПб.: Питер,
2003. – 928 с. – ISBN 5-94723-302-9.
2.
Скляров В.А. Программирование на языках Си и Си++: учеб. пособие. –
Изд. 2-е, перераб. и доп. – М.: Высш. шк., 1999. – 288 с. – ISBN 5-06003486-0.
3.
Карпов Б., Баранова Т. C++: специальный справочник. – СПб.: Питер,
2001. – 480 с.: ил. – ISBN 5-272-00076-5.
4.
Мейерс С. Эффективное использование STL. Библиотека программиста.
– СПб.: Питер, 2002. – 224 с.: ил. – ISBN 5-94723-382-7.
5.
Магда Ю.С. Использование ассемблера для оптимизации программ на
С++. – СПб.: БХВ-Петербург, 2004. – 496 с. – ISBN 5-94157-414-2.
6.
Зубков С.В. Assembler для DOS, Windows и UNIX. – М.: ДМК Пресс,
2000. – 608 с.: ил. – ISBN 5-94074-003-0.
7.
Юров В. Assembler: специальный справочник. – СПб.: Питер, 2001. –
496 с.: ил. – ISBN 5-272-00119-2.
8.
Марк Луис. Visual C++ 6. – М.: Лаборатория Базовых Знаний, 1999. –
720 с.: ил. – (Справочник) – ISBN 5-93208-022-1.
9.
Холзнер С. Visual C++ 6: учебный курс. – СПб.: Питер, 2000. – 576 с.:
ил. – ISBN 5-8046-0053-2.
10.
Поляков А.Ю. Методы и алгоритмы компьютерной графики в примерах
на Visual C++. – СПб.: БХВ-Петербург, 2002. – 416 с.: ил. – ISBN 594157-136-4.
124
11.
Поляков А.Ю., Брусенцев В.А. Программирование графики: GDI+ и
DirectX. – СПб.:БХВ-Петербург,2005. – 368 с.: ил. – ISBN 5-94157-504-1.
12.
Черносвитов А. Visual C++ 7: учебный курс. – СПб.: Питер, 2001. – 528
с.: ил. – ISBN 5-272-00217-2.
13.
Круглински Д., Уингоу С., Шеферд Дж. Программирование на Microsoft
Visual C++ 6.0 для профессионалов / Пер. с англ. – СПб.: Питер; М.:
Издательско-торговый дом «Русская редакция», 2002. – 864 с.: ил. –
ISBN 5-272-00385-3.
14.
Мешков А.В., Тихомиров Ю.В. Visual C++ и MFC: Пер. с англ. – 2-е изд.
перераб. и доп. – СПб.: БХВ – Санкт-Петербург, 2000. – 1040 с.: ил. –
ISBN 5-8206-0073-8.
15.
Олафсен Юджин, Скрайбнер Кенн, Уайт К. Дэвид и др. MFC и Visual
C++ 6. Энциклопедия программиста: Пер. с англ. – СПб.: ООО
«ДиаСофтЮП», 2003. – 992 с. – ISBN 5-93772-064-4.
16.
http://www.firststeps.ru – Сайт "Первые Шаги".
17.
http://rsdn.ru
–
Проект
русскоязычного
аналога
on-line
версии
справочной библиотеки MSDN Library.
18.
http://sourceforge.net – Огромная база исходных текстов (более 20 тысяч)
различного по тематике программного обеспечения.
19.
http://www.wasm.ru – Сайт, посвящённый Ассемблеру!
20.
ISO/IEC International Standard 9899: Programming Languages – C, Second
Edition, 1999. Второй (текущий) официальный стандарт для языка C.
21.
ISO/IEC International Standard 14882: Programming Languages – C++,
2003. Второй (текущий) официальный стандарт для языка C++.
Download