Вы находитесь на странице: 1из 289

ba

ynd
MBc
iha
el H
ym a
n
obArnson

Hungry Minds™
HUNGRYMINDS,INC.
Bes-tSe
nilgfiooks•Dg
ita
i lDownolads•eB
-ooks•AnswerNew
t okrs•e-Newsele
trs•Ba
rndedWebSe
tis-e-Learnnig
NewYork,NY•Ce l vea
l nd,OH•Indianapolis,IN
Майкл Хаймен
Боб Арнсон

ДИАЛЕКТИКА

Москва • Санкт-Петербург • Киев


2002
ББК32.973.26-018.2.75
Х15
УДК 681.3.07

Компьютерное издательство ''Диалектика"

Зав. редакцией В.В. Александров

Перевод с английского и редакция П.А. Минько

1
По общим вопросам обращайтесь в издательство "Диалектика'
по адресу: infoldialcktika.com. http://vvww.dialektika.coni

Хаймен, Майкл, Арнсон, Боб.


Х15 Visual C++.NET для "чайников". : Пер. с англ. — М. : Издательский дом "Вильяме".
2002. — 288 с. : ил. — Парал. тит. англ.

ISBN 5-8459-0326-2 (рус.)

Итак, вы решили серьезно взяться за Visual C++ .NHT. Это хорошая идея, ведь вы в
действительности убиваете сразу трех зайцев: в ваших руках оказывается мощный, полезный
и широко распространенный инструмент. С языком С+-1- можно сделать очень многое. С его
помощью созданы такие продукты, как Hxeel и Access. 'Jror язык также применяется при
разработке управленческих информационных систем и систем целевого назначения,
используемых для анализа деятельности предприятий и принятия решений в сфере
управления бизнесом. И. конечно же, целые армии хакеров и не только хакеров используют
C++ для создания инструментов, утилит, игр и шедевров мультимедиа. Знания, которые вы
получите, изучив язык C++ .NHT. позволят создавать не просто приложения, а приложения,
работающие в разных операционных системах. Возможности этого языка практически не
ограничены, и вы сами в этом убедитесь, прочитав эту книгу.
Книга предназначена для начинающих программистов.
ББК 32.973.26-018.2.75

Все названия программных продуктов являются зарешстрнрованнымиторголыми чарками соответствующих фирм.


Никакая чисть настоящею издания ни в каких целях не может быть воспрошведена в какой бы го ни было
форме и какими бы ю ни было средствами, будь ю электронные или механические, включая
фотокопирование и запись на магнитный носитель, если на эю fiei письменного разрешения издательства
Hungry Minds. Inc.

Copyright © 2002 by Dialektika Computer Publishing.


Original English language edition copyright £> 2002 by Hungry Minds, Inc.

All rights reserved including the right of reproduction in whole or in part in any form.
This edition published by arrangement with the original publisher. Hungry Minds, Inc.
For Dummies and Dummies Man are trademarks under exclusive license to Hungry Mindi, Inc. Used by permission.

ISBN 5-8459-0326-2 (рус ) О Компьютерное изд-во "Диалектика". 2002


ISBN 0-7645-0868-7 (англ ) V: Munarv Minds, Inc.. 2002
Оглавление
Введение 14
Глава 1. Что представляет собой пакет Visual C++ .NET 19
Глава 2. Что такое программа 25
Глава 3. Приступаем к созданию программ 35
Глава 4. Принятие решений — дело серьезное 49
Глава 5. Хороший редактор — что еще нужно бывалому программисту? 55
Глава 6. Компиляция программ, или Первые трудности 65
Глава 7. Типы данных — это серьезно 71
Глава 8. Использование переменных 81
Глава 9. Структуры данных 85
Глава 10. Выразите свои желания 89
Глава 11. Ключевые слова — ключ к диалогу с компьютером 105
Глава 12. Внимание! Повышенная функциональность 117
Глава 13. Указатели 129
Глава 14. Масса информации? Используйте массивы! 155
Глава 15. Пришел, увидел, применил 165
Глава 16. Через тернии к... работающей программе 171
Глава 17. Смотрите на мир объективно 189
Глава 18. Конструкторы и деструкторы 209
Глава 19. Наследование 219
Глава 20. Исключительные ситуации 237
Глава 21. Потоки данных 249
Глава 22. Создаем пользовательский интерфейс 255
Глава 23. Десять синтаксических ошибок 265
Глава 24. Вторая десятка синтаксических ошибок 271
Глава 25. Десять функций .NET 277
Предметный указатель 280
Содержание
Введение 14
Часть I. Первое знакомство с Visual C++ .NET 17
Глава 1. Что представляет собой пакет Visual C++ .NET 19
Инструменты Visual C++ 19
Компилятор, запускающий тысячи программ 20
Отладчик программ 20
Интегрированная среда разработки программ 21
Библиотеки - хранилища электронных инструментов 21
Эти разумные утилиты 22
Помощь, которая всегда рядом 22
Не знаете с чего начать - просмотрите примеры программ 22
Управляемые и неуправляемые программы 23
Глава 2. Что такое программа 25
Введение в программирование 25
Главная функция программы 26
Стандартные подпрограммы 27
Для чего создаются программы 28
А теперь немного теории 31
Что такое объект и с чем его едят 31
Инкапсуляция 32
Наследование 32
Полиморфизм 33
Глава 3. Приступаем к созданию программ 35
Зачем нужны исходные файлы 35
Как все это выглядит на практике 36
С чего начинается выполнение программы 38
Как организовать диалог с пользователем 39
Не скупитесь на комментарии 40
Исходный код программы HelloWorld 41
Пару штрихов к программе Hello World 42
Забудем о . NET 42
Отображение информации на экране 43
Печать с новой строки 44
Обратная связь: получение ответа 45
Использование библиотечных функций 45
Итак, займемся делом 46
Глава 4. Принятие решений - дело серьезное 49
Правильное решение может сделать вас счастливым 49
Файлы проектов сделают вашу жизнь проще 50

6 Содержание
Решения и проекты 50
Построение программ 50
Определение параметров нового проекта 50
Добавление файлов к проекту 52
Что может окно Solution Explorer 53
Глава 5. Хороший редактор — что еще нужно бывалому программисту? 55
Коды существуют для того, чтобы их редактировать 55
Приемы редактирования 57
Коды бывают разные - черные, белые, красные 59
Помощь, которая всегда рядом 60
Навигация по просторам программы 60
Сокрытие и отображение кодов 61
Поиск и автозамена 62
Глава 6. Компиляция программ, или Первые трудности 65
Начать компилировать программу очень просто 65
Синтаксические ошибки: неужели их может быть так много?! 66
Предупреждения 67
Почему компилятор не исправляет ошибки самостоятельно 68
Компилировать можно по-разному 68
Часть II. Все, что вы хотели знать о C + + , но о чем боялись спросить 69
Глава 7. Типы данных - это серьезно 71
Строгие и нестрогие языки программирования 71
Объявление переменных 72
Наиболее часто используемые типы данных 72
Реже используемые типы данных 73
Обеспечение типовой безопасности 74
Функции преобразования типов 75
Константы - то, что никогда не меняется 76
Использование констант в кодах программы 77
Константы и борьба с ошибками 78
Строки как один из наиболее важных типов данных 78
Глава 8. Использование переменных 81
Именование переменных 81
Определение переменных 83
Инициализация переменных 83
Как сделать имя информативным 84
Глава 9. Структуры данных 85
Объявление структур 85
Использование этих загадочных структур 86
Использование одних структур для создания других 86
Структуры на практике 87

Содержание 7
Глава 10. Выразите свои желания 89
Можно ли "выражаться"? 89
Простые операторы 89
Более сложные операторы 90
Оператор ++ 91
Оператор >> 91
Оператор « 92
Истина и ложь в логических выражениях 92
Оператор присвоения 94
Все об операторах 96
Работа с битами 97
Условный оператор 98
Приоритет операторов 99
Примеры работы операторов 101
Математические функции 101
Старый формат математических функций 103
Глава 11. Ключевые слова — ключ к диалогу с компьютером 105
Великолепная тройка: ключевые слова if, for и while 106
Условный оператор 106
Оператор for 109
Пример использования цикла for 109
Повторение ради повторения 110
Вычисление факториала 110
Оператор while 112
Ключевые слова switch и do 112
Оператор switch 113
Оператор do 114
Глава 12. Внимание! Повышенная функциональность 117
Некоторые вводные замечания 117
Создание функций 118
Использование аргументов 119
Функции, которые возвращают результат 120
И снова вернемся к факториалам 122
Рекурсия: спасибо мне, что есть я у меня 124
Если тип аргументов не определен . . . 127
Значения, установленные по умолчанию 128
Глава 13. Указатели 129
Почему указатели 129
Указатели и переменные 130
Что вы! Указатели - это очень сложно 131
Информация и ее адрес 131
Безымянные данные 131

8 Содержание
Связанный список - размер не ограничен 132
Использование указателей в C++ 133
Дайте указателю адрес 134
Как получить значение, на которое ссылается указатель 134
Пример программы, использующей указатели 134
Изменение значения, на которое ссылается указатель 136
Изменение значений в структурах данных 136
Использование стрелки 136
Динамическое выделение памяти 136
Знимание - графика 137
Все цвета радуги 139
Перья для рисования 141
Кисточка для раскраски 142
Шрифты 142
Займемся рисованием 142
Связанные списки и графика 144
Как эта программа работает 144
Код программы 146
Вопросы безопасности 149
Освобождение памяти 149
Общее нарушение защиты 150
Генеральная уборка 151
Кое-что о строках 152
Подведем итог 153
Глава 14. Масса информации? Используйте массивы! 155
Массивы: познакомимся поближе 155
Это же "элементарно", Ватсон 156
Инициализация массива 157
Многомерные массивы 158
Класс ArrayList 159
Класс Stack 161
Перечислимые типы 162
Безопасность при использовании перечислимых типов 162
Одно маленькое "но" 163
Глава 15. Пришел, увидел, применил 165
Немного теории 165
Почему это так важно 166
Правила определения области видимости 169
Глава 16. Через тернии к... работающей программе 171
Синтаксические и логические ошибки 171
Процесс отладки программы 172
Отладчик плюс редактор - и все в наших руках 173

Содержание 9
Остановись, мгновение! 173
Шаг за шагом: движение к цели 174
Посмотрим, что получилось 175
Не нравится значение - измените его 176
Торопитесь? Нет проблем! 177
Очень торопитесь? 177
Генеральная уборка 178
Итак, приступим 178
Так где же ошибка? 180
Что теперь? 183
Но в программе по-прежнему есть ошибка 184
Устранение ошибки 184
Последние штрихи 185
Часть III. Экскурс в объектно-ориентированное программирование 187
Глава 17. Смотрите на мир объективно 189
Что такое классы и с чем их едят 189
Разберемся в деталях 190
Члены данных 190
Функции-члены 190
Объявление классов 191
Ограничение доступа 191
Защищенный доступ 192
Определение функций-членов 192
Что делать с готовыми классами? 193
Доступ к элементам класса 193
Имена, используемые функциями-членами 194
Немного практики 194
Управление памятью в неуправляемых программах 198
Функции доступа 203
Общие рекомендации 207
Глава 18. Конструкторы и деструкторы 209
Работа "до" и "после" 209
Подготовительный этап 209
Много конструкторов - много возможностей 210
Открытые и закрытые конструкторы 212
Заметание следов 212
Когда объект больше не нужен 212
Не забывайте также освобождать динамически выделяемую память 214
Классы внутри классов 215
Чтение кодов объектно-ориентированных программ 216
Глава 19. Наследование 219
Что происходит при наследовании кодов 219

10 Содержание
Наследование открытых, закрытых и защищенных элементов 221
Перегрузка функций-членов 221
Родительские связи 222
А теперь немного практики 222
Как процесс наследования отображается на конструкторах и деструкторах 224
Универсальный указатель 224
Защищенное и закрытое наследование 225
Виртуальная реальность 226
Тест на виртуальность 227
Декларация ваших виртуальных намерений 227
Когда без этого не обойтись 228
Абстрагирование от деталей 231
Искусство абстрагирования 232
Глава 20. Исключительные ситуации 237
Как это было раньше 237
Новый усовершенствованный способ обработки ошибок 238
Вот как это выглядит на практике 239
Гибкость - основное свойство механизма обработки исключений 241
Определение собственных исключительных ситуаций 241
Поговорим о синтаксисе 242
Все это хорошо, но несколько запутанно 245
Наследование классов, описывающих исключения 246
Пять правил исключительного благополучия 247
Глава 2 1 . Потоки данных 249
.NET-классы I/O 249
Записали - прочитали 250
Повторное использование потоковых переменных 251
Записали - прочитали 252
Специальные команды, погружаемые в потоки данных 253
Формат передаваемых данных 253
Кое-что о работе с файлами 254
Глава 22. Создаем пользовательский интерфейс 255
Основной набор инструментов 255
Формы 257
Наследование форм 257
Настройка формы 258
Открытие формы 258
Обработка событий 259
Объекты, которые умеют реагировать 259
Хорошие менеджеры умеют делегировать свои обязанности 260
Объявление и определение обрабатывающей событие функции 261
Добавление делегата 261

Содержание 11
Часть IV. Горячие десятки 263
Глава 23. Десять синтаксических ошибок 265
Подключаемый файл не может быть найден 265
Пропущена точка с запятой 266
Не подключен заголовочный файл 266
Не обновлено объявление класса 267
Использование названия класса вместо названия объекта 267
После объявления класса не поставлена точка с запятой 268
В определении класса пропущено слово public: 268
Неправильно набранные имена переменных 268
Использование точки вместо стрелки и наоборот 269
Пропущена закрывающая фигурная скобка 269
Глава 24. Вторая десятка синтаксических ошибок 271
Конструкторы, которые не требуют аргументов 271
Незакрытые комментарии 272
Несоответствие типов данных 272
То, что работало в С, может не работать в C++ 273
Использование ключевого слова void 273
Конструкторы для производных классов 274
Точка с запятой в конце строки #define 274
Отсутствие свободного места на диске 274
Так в чем же проблема 275
Глава 25. Десять функций .NET 277
Console: :WriteLlne 277
Console::ReadLJne 277
lnt32::Parse 277
Application::Run 278
Graphics->DrawLine 278
Color: ;FromArgb 278
Graphics->DrawString 278
lmage::FromFile 279
Form:;OnMouseMove 279
Controls->Add 279
Предметный указатель 280

12 Содержание
OSа£*но/га)с
Майкл Хаймен (Michael Hyman)— руководитель технического отдела компании
DataChannel. Ранее занимался вопросами медиатехнологий, XML и Internet. Майкл написал
множество книг по компьютерной тематике, среди которых Dynamic HTML For Dummies, Visual
J++ For Dummies и PC Roadkiil. Он имеет ученую степень по электронной инженерии и компь-
ютерным наукам, полученную в Принстонском университете. В свободное время Майкл зани-
мается серфингом, ходит в спортзал и стирает пеленки.
Боб Арнсон (Bob Arnson)— разработчик программного обеспечения в крупной компью-
терной компании. Боб написал несколько других книг no Visual Basic, Visual C++ и Borland C++.
Также его статьи публиковались в журналах VB Tech Journal и VC++ Professional.

Майкл Хаймен посвящает эту книгу Мириам Бэс (Miriam Beth) и Габриеле Миа (Gabrielle Mia).
Боб Арнсон посвящает эту книгу Марио (Mario).

Особая благодарность моей жене Саре, которая неделями терпела мои ночные писатель-
ские бдения и. как следствие, мой уставший вид и сонные глаза. Также спасибо моим доче-
рям, которые иногда отвлекали меня от компьютера и возвращали на землю. Спасибо читате-
лям, просто за то, что они интересуются такими вопросами, как программирование на Visual
C++. А также спасибо всем людям, которые приняли участие в создании этой книги.
Майкл Хаймен

Спасибо моим друзьям из Нью-Гемпшира и Мичигана за моральную помощь и поддерж-


ку, которая так нужна была мне в мои нелегкие писательские будни. Спасибо моим коллегам
по издательству, которые внесли свою посильную лепту в создание этой книги и благодаря
которым она имеет такое прекрасное оформление. Также хочу выразить благодарность ком-
пании Microsoft за новые технологические решения, появившиеся вместе со средой .NET.

Боб Арнсон;
Введение
Итак, вы решили серьезно взяться за C++ .NET. Это хорошая идея, поскольку вы убиваете
сразу трех зайцев: в ваших руках оказывается мощный, полезный и широко распространен-
ный инструмент. С языком C++ можно сделать очень многое. С его помощью созданы такие
продукты, как Excel и Access. Этот язык также применяется при разработке управленческих
информационных систем и систем целевого назначения, используемых для анализа деятель-
ности предприятий и принятия решений в сфере управления бизнесом. И, конечно же. целые
армии хакеров и не только хакеров используют C++ для создания инструментов, утилит, игр
н шедевров мультимедиа.
Знания, которые вы получите, изучив язык C++ .NET, позволят вам создавать не просто
приложения, а приложения, работающие в разных операционных системах. Возможности
этого языка практически не ограничены, и вы скоро сами в этом убедитесь.

Об э/ной книге
Скажем сразу, книга должна вам понравиться. Здесь не нужно читать главы одну за дру-
гой от начала и до конца, Если вы уже знакомы с Visual Studio, пропустите главы, посвяшен-
ные ей. Просмотрите содержание книги и. если найдете что-то для себя незнакомое, просто
начните читать с этого места. Если же во время чтения вы вдруг уснете — не волнуйтесь, это
тоже бывает. Здоровый сон еше никому не повредил.
Тем, кто еще ничего не знает о C++, следует начинать чтение с самого начала, чтобы узнать
не только о C++, но и о Visual Studio .NET. Хотим сразу предупредить, что язык О + vie так
прост, как это может показаться вначале. Придется немного напрячь умственные способности,
чтобы осилить его. Вы столкнетесь с множеством правил, приемов, концепций и терминов.
Кроме того, в отличие от обычных языков программирования, О-+ реализует концепцию объ-
ектно-ориентированного программирования, что вообще является нишей не для слабых умов.
Вдобавок к сложностям C++ вас ожидает знакомство с массой инструментов Visual Studio
ЛЕТ. В целом приложение Visual C++, имеющие 120 Мбайт надстроек, библиотек и тому
подобных вещей, может внушать непосвященным благоговейный ужас. Вначале даже трудно
решить, с какого конца к нему подойти.
Но не все так страшно, когда у вас в руках эта книга. Она дает общее представление как о
С4--1-, так и о Visual C++. На вас не обрушится лавина технических деталей и терминов, вме-
сто этого вы познакомитесь с наиболее важной информацией и общими концепциями. При-
чем это будет сделано в стиле, понятном любому нормальному человеку.
Эта книга не раскрывает всех возможностей языка C++. поскольку для этого понадоби-
лось бы добавить в нее еше несколько сот страниц или набрать весь текст очень мелким
шрифтом. Но зато вы получите достаточно знаний для того, чтобы начать писать на языке
С—*- свои собственные программы.

вы utOMeffie не
Можете не читать высказывания жены Михаэля, поскольку она просто злилась на то, что
ее муж уделяет больше внимания книге, чем ей. Можете не читать газеты, которые пишут о
политике. Не читайте электронную почту, если она приходит вместе с фотоснимком извест-
ной русской теннисистки. Если не хотите, не читайте текст, отмеченный в книге как техниче-
ские подробности, поскольку эти вещи, по сути, являются второстепенными и могут вызвать
интерес разве что у заядлых любителей покопаться в деталях.

Введение 14
UcxoqHbienfteqnocMUtcu
Чтобы прочитать и понять эту книгу, не обязательно иметь опыт программирования. Од-
нако, если раньше вы уже пытались что-то программировать, например создавали макросы
для электронных таблиц или баз данных, вы будете чувствовать себя во время чтения книги
намного комфортнее тех, кто ранее с программированием вообще не сталкивался.
Если вы уже освоили BASIC, COBOL, Pascal или даже С, научиться программировать на
C++ для вас не составит большого труда. (Но если вы уже профессионал по C++, то, вероят-
но, купили не ту книгу-)
Вне зависимости от вашего программистского прошлого, мы все же предполагаем, что вы
знаете, как запускаются приложения Windows и что представляют собой файлы и программы,
Также мы предполагаем, что на вашем компьютере установлены .NET и Visual Studio .NET
(если вы, конечно, хотите сами попробовать набрать приведенные в книге коды и проверить,
как они работают).

Мак организована э*па книга


Книга состоит из четырех частей. В первой части дается краткий обзор Visual C++. Вы
познакомитесь с основными возможностями этого языка программирования и научитесь ими
пользоваться.
Во второй части дается обзор основ программирования на C++ (обратите внимание, что
многие рассмотренные положения будут справедливы также и для языка С). Здесь же вы уз-
наете, что такое программы .NET и как они создаются.
В третьей части вы окунетесь в мир объектно-ориентированного программирования и
расширите свои познания о среде .NET.
В четвертой, заключительной, части, вы найдете практические советы, позволяющие ре-
шить многие проблемы, наиболее часто возникающие у начинающих программистов.

используемые € книге
Пиктограммы— это маленькие картинки, акцентирующие ваше внимание на некоторых
важных вещах. Вот, что они обозначают.

Этой пиктограммой обозначаются технические подробности, которые, впрочем,


читать не обязательно.

Внимательно относитесь к информации, отмеченной такой пиктограммой! Обяза-


тельно постарайтесь ее запомнить. Это будет несложно, поскольку информация
дается в максимально сжатом и лаконичном виде. Если же вы ее проигнорируете,
потом об этом можете пожалеть.

Этой пиктограммой отмечены различные полезные советы, которые могут облег-


чить вашу программистскую жизнь и избавить от ненужных проблем.

15 Введение
Такой пиктограммой отмечено все, что касается неуправляемых программ, т.е.
!„•;_• 1 программ, написанных на старом добром C++, которым разработчики среды
.NET дали такое пренебрежительное определение.

дальше?
На Гавайи, Тайвань, Фиджи. Но только не туда, куда посылала Михаэля эго жена, оби-
женная тем, что на какое-то время он посвятил себя написанию этой книги.

Введение 16
Часть I

Первое знакомство
cVisualC++.NET

Л1Л

НЕ о VUUAI 6++ .
/3 э&ой
Здесь дается краткий обзор Visual C++, начиная с установочного пакета
и заканчивая описанием его отдельных инструментов.
Вы узнаете, как создаются программы, и сможете получить обшее
представление об основных принципах объектно-ориентированного
программирования. Также вы увидите, как можно использовать мастера
Visual C++ для создания программ .NET. Здесь же вы познакомитесь с
такими важными компонентами окружения Visual C++, как компилятор,
редактор кодоь и средство Solution Explorer.
Глава1

Что представляет собой пакет


Visual C++ .NET
В3tfU>u иаве...
> Что такое Visual C++
> Обзор возможностей Visual C++
> Разница между управляемыми и неуправляемыми программами

к, вы решили стать программистом на языке C++. Это действительно хорошая


идея, Освоив этот язык, вы дополните свое резюме, сможете создавать потрясающие
приложения и находить общий язык с множеством очаровательных людей. Но главное— вы
сможете сказать, что владеете самым великолепным и мощным языком программирования из
всех когда-либо созданных.

Мнс/и/и/асен/ныVisualC++
Не все качественные приложения занимают много места.
Увидев впервые установочный комплект Visual C++, вы, наверное, решите, что чего-то в
нем не хватает,— настолько он небольшой и компактный. Казалось бы, приложение, зани-
мающее множество мегабайт памяти, должно иметь более внушительный вид. Однако такое
впечатление создается потому, что наиболее массивная часть почти каждого установочного
пакета— руководство пользователя — поставляется не в распечатанном, а в электронном ви-
де. (Таким образом не только экономится место, но и спасается несколько деревьев.)
Открыв установочный пакет Visual C++, вы найдете в нем компакт-диск с программным
обеспечением. На нем содержится множество инструментов, участвующих в создании про-
грамм на языке C++:
| V компиляторы;
f V отладчики;
I S интегрированная среда разработки;
I •/ системы создания приложений;
;. S библиотеки;
i S утилиты Windows;
i S общие утилиты;
\{ S библиотека оперативной документации;
1 S примеры программ.

Далее в этой главе дается краткое описание этих инструментов с тем, чтобы вы имели о
них обшее представление и знали, для чего устанавливаете.

Глава 1. Что представляет собой пакет Visual C++ . NET 19


Компилятор, запускающий тысячи программ
Компилятор преобразует коды из вида, понятного программистам [исходные коды), к ви-
ду, воспринимаемому компьютером [выполняемые коды). В приложении Visual O + есть два
компилятора:

S строковый компилятор;
•/ интегрированная среда разработки.

Когда вы будете изучать описание примеров, приведенных в книге, используйте интегри-


рованный компилятор, поскольку его пользовательский интерфейс проще и удобнее.

Что представляет собой строковый компилятор


Строковый компилятор- это компилятор, не имеющий графического пользовательского интерфейса. Он
быстр, но не очень дружелюбен по отношению к пользователю. Чтобы обратиться к нему, нужно набрать
специального вида компактную инструкцию, подобную этой:
c l /FR /WX foo.cpp
В данном примере первый элемент является именем компилятора, который вы хотите использовать, вто-
рой и третий представляют собой команды, сообщающие компилятору, что нужно делать, и наконец по-
следний - это имя файла, который должен быть откомпилирован. (Использовать строковый компилятор
вместо интегрированной среды разработки могут только те программисты, которые знают, например, что
команда /FR применяется для отображения окна просмотра информации, а команда /wx обозначает,
что все предупреждения нужно интерпретировать как ошибки. Те, для кого приведенная выше строка яв-
ляется просто набором букв, использовать строковый компилятор не могут.)
В старые добрые времена известны были только строковые компиляторы. Те, кто занимается программировани-
ем довольно длительное время, используют в основном строковые компиляторы, во-первых, по привычке, а во-
вторых, поскольку они предоставляют возможность дополнительного использования множества замечательных
инструментов. Те же, кто только начинает программировать на Visual C++, предпочитают интегрированную сре-
ду разработки программ, поскольку она намного понятнее и проше в использовании.

Отладчик программ
Если вы написали программу, состоящую более чем из двух или трех строк, у вас навер-
няка возникнут проблемы при се компиляции. Если же этого не произойдет, то вы либо вели-
кий программист, либо скопировали коды программы из этой книги.
Все возникающие при запуске программы проблемы можно разделить на две категории:
синтаксические ошибки и логические ошибки. Синтаксические ошибки случаются тогда, ко-
гда вы что-то неправильно набираете, забываете указать информацию, которая нужна компи-
лятору, или некорректно использ\сте команду. Компилятор сам находит синтаксические
ошибки и указывает строки, где такие ошибки встречаются. Вам придется исправить все най-
денные синтаксические ошибки, иначе компилятор не сможет преобразовать набранные вами
коды в выполняемые.
Логические ошибки случаются тогда, когда неправильно разработана или реализована са-
ма программа. Например, вы забыли набрать коды для обработки какой-то информации или
даете команду отобразить на экране не те данные, которые необходимы. Программы, содер-
жащие логические ошибки, процесс компиляции проходят успешно, однако выполняются не
так, как вы хотите.
Рассмотрим это на примере. Допустим, вы создали программу, отслеживающую количе-
ство денег на вашем счете. Но при написании ее кодов забыли набрать команду, прибавляю-

20 Часть I. Первое знакомство с Visual C++ .NET


щую суммы, вносимые на счет. Или наоборот: забыли набрать команду, вычитающую из об-
шей суммы деньги, снимаемые со счета. Тогда сколько бы денег вы со счета не снимапи, со-
стояние счета не изменялось бы (этакий вечный источник наличных). Это пример логической
ошибки. Вы забыли набрать один оператор, и в результате получилась хотя и симпатичная,
но работающая неправильно программа.
Исправить логические ошибки намного сложнее, чем синтаксические. Сложность заклю-
чается в их поиске и локализации места возникновения. Помочь справиться с этой проблемой
призван отладчик программы. Отладчик позволяет строка за строкой проследить за выпол-
нением программы и просмотреть значения обрабатываемых при этом данных. Таким обра-
зом, обнаружив, что какая-то переменная приняла неверное значение, вы сразу же определите
место возникновения ошибки.

Интегрированная среда разработки программ


Пакет Visual C++ включает в себя интегрированную среду разработки (devent.exe), объе-
диняющую множество разнообразных инструментов, значительно упрощающих процесс соз-
дания программ. Если вы используете среду разработки Visual C++. нет необходимости изу-
чать и запоминать принципы работы каждого инструмента в отдельности.
Среда разработки Visual C++ состоит из таких основных компонентов:
IS редакторы, позволяющие набирать и модифицировать исходные коды программы;
1S компилятор, выполняющий компиляцию кодов программы (на этом этапе отсей-
| ваются и исправляются все синтаксические ошибки);
1V отладчик, помогающий исправить логические ошибки и заставить программу рабо-
\ тать так, как вы хотите;
вS диспетчер проектов, позволяющий легко создавать выполняемые подпрограммы
| (файлы с расширением DLL и LIB);
I/ обозреватель, позволяющий отследить связи между различными объектами объ-
1 ектно-ориентированных программ;
IS Visual-инструменты (мастера), с помощью которых можно легко создавать Win-
^ dows-приложения;
\ -/ списки свойств, которые помогают контролировать отображение и поведение объ-
| ектов пользовательского интерфейса создаваемой программы.

Чтобы воспользоваться возможностями любого из этих компонентов, достаточно просто


выбрать команду из раскрывающегося меню и задать настройки в диалоговом окне. Это зна-
чительно упрощает процесс реализации сложных проектов, поскольку нет необходимости
изучать II применять множество не совсем понятных командных строк. Читая эту книгу, вы
будете иметь дело только со средой разработки Visual C++ и освоите при этом большое ко-
личество различных приемов программирования.

Библиотеки — хранилища электронных инструментов


Библиотеки — это наборы заранее созданных функций и классов, которые могут быть ис-
пользованы для решения многих типичных задач. Приложение Visual С+-1- имеет несколько
библиотек. Они могут заметно облегчить процесс создания программ, поскольку позволяют
использовать готовые варианты вместо разработки собственных решений. Среда .NET, no
существ), является одной большой библиотекой. Библиотеки выполнения (обозначаемые аб-
бревиатурой RLT — runtime libraries) содержат множество полезных функций, выполняющих
математические вычисления, обработку текстовых значений и многие другие задачи. Файла-
ми библиотек RLT являются libc.lib, libcmt.lib и msvcrt.lib.

Глава 1. ЧТО представляет собой пакет Visual C++ .NET 21


О чем говорят названия файлов библиотек
Со временем вы обратите внимание на то, что одни и те же библиотеки реализованы в нескольких вари-
антах. Каждый вариант соответствует разным наборам опций, поддерживаемых библиотекой и обозна-
чаемых буквой (или буквами), которая добавляется в начале или в конце названия файла библиотеки.
Например, название mfc70d.lib обозначает, что это файл библиотеки MFC, выполненной в варианте для
отладчика (d - debuggable version).
Вот список букв, используемых для обозначения версий библиотек;
•/ D Debuggable (Отладка);
•/ МТ Multithreacl (Многопоточные процессы);
s О OLE (Технология связывания и внедрения объектов);
s S Static (Статические объекты);
s U Unicode (Уникоды).

Эти разумные утилиты


Visual C++ содержит набор утилит, позволяющих проследить за выполнением .NET- и
Windows-программ. Обычно только опытные программисты пользуются услугами утилит,
наиболее важной среди которых является Spy+-. Во время выполнения программы она ото-
бражает на экране информацию о классах, применяемых этой программой. Вы можете ис-
пользовать ее для отслеживания не только своих, но и чужых программ. Другие утилиты соз-
даны для упрощения процесса программирования. В основном они рассчитаны на опытных
программистов и предназначены для использования совместно с инструментами командной
строки. Наиболее часто используемой среди них является утилита NMAKE. Но вы можете
также найти для себя парочку полезных утилит, не требующих умения работать с командной
строкой. Например, утилита W1NDIFF может сравнивать между собой содержимое двух фай-
лов и находить в них отличия. Утилита ZOOM1N позволяет изменять размеры отображаемых
объектов таким образом, чтобы они выглядели на экране именно так, как вы хотите.

Помощь, которая всегда рядом


Все. что есть в пакете Visual C++ (компиляторы, отладчики, среда разработки, библиотеки.
\тилиты), должно сопровождаться документацией. Однако, чтобы не делать установочный па-
кет слишком громоздким, компания Microsoft поставляет документацию в электронном виде.
Причем эта документация организована в виде оперативной справочной системы, позволяющей
быстро находить нужную информацию и читать ее, не выходя из среды разработки программ.
Справочная система может быть открыта также в отдельном окне. В самом крайнем случае вы
можете лаже подключить дополнительный монитор к своей системе и постоянно иметь перед
глазами справку об инструментах и возможностях, используемых в данный момент.

Не знаете с чего начать — просмотрите примеры программ


Пакет Visual C++ включает в себя множество примеров готовых программ, воспользо-
вавшись которыми вы сможете быстрее и проще создавать собственные программы. Некото-
рые из них демонстрируют определенные приемы программирования, другие являются гото-
выми вариантами решения каких-то задач, например обработки текстовой информации. Са-
мое лучшее в этих программах то, что их коды можно копировать и вставлять в коды своих
программ. Это сохранит ваше время и силы и позволит сосредоточить внимание на решении
более специфических проблем.

22 Часть I. Первое знакомство с Visual C++. NET


Управляемые и
Visual C++ позволяет создавать программы как для среды Windows, так и для среды Micro-
soft .NET. В этой книге основное внимание уделяется вопросам разработки приложений для
среды .NET, но будет также рассмотрено и создание приложений для Windows. Среда .NET,
разработанная компанией Microsoft, предназначена для создания программ, работающих в Inter-
net. Она включает в себя язык CLR (Common Language Runtime) и библиотеку классов.
CLR является основой среды .NET-— все работает под его управлением. CLR следит за
выполнением кодов и обеспечивает предоставление программам всех сервисов нижнего
уровня, таких, например, как управление памятью. Библиотека классов является пртожени-
ем среды .NET и содержит в себе набор готовых классов, которые вы можете использовать
при создании объектно-ориентированных программ. (О том, что такое классы и для чего они
нужны, вы узнаете чуть позже.)
Чтобы работать в среде .NET, программа должна быть изначально созданной для этой це-
ли. Это подразумевает использование при ее создании классов .NET, но главное — она долж-
на быть откомпилирована для работы в этой среде. Visual C++ может удовлетворить оба тре-
бования. Коды программы, которая может работать в среде .NET, называются управляемыми.

Коды программ, написанных для работы в среде Windows и не предназначенных


,_,,.( | для использования в среде .NET, принято называть неуправляемыми.
it., К--А

Если вы не используете при разработке программы систему создания приложений, снабдить


программу хорошим пользовательским интерфейсом будет отнюдь не просто. Пользователям
очень нравятся программы с качественным интерфейсом, однако программистам приходится
потратить немало времени и усилий, чтобы создать его. Например, чтобы создать небольшую
программу для Windows, содержащую несколько пунктов меню и отображающую на экране со-
общение "Hello World!", потребуется набрать от двух до четырех тысяч строк с кодами.
(Программисты называют между собой такие программы Hello World. По тому, насколько легко
или сложно создать программу Hello World, определяют, насколько проста и успешна та или
иная система программирования.) Это действительно так, если не использовать при создании
программы библиотеку классов среды .NET. Только программисты, помешанные на командах
нижнего уровня, занимаются подобными глупостями, все же остальные используют среду раз-
работки .МЕТ. И сложность даже не в том, что нужно набирать эти четыре тысячи строк, а в
том. что нужно знать и помнить тысячи команд, используемых для управления средой Windows.
Это, пожалуй, не та информация, которой стоит засорять голову.
Большинство программ на порядок сложнее, чем Hello World, и при их создании прихо-
дится решить множество задач, прежде чем они заработают так, как нужно. Например, к их
числу относятся такие задачи:

IS определение алгоритма получения сообщений Windows;


х S определение комбинаций клавиш, нажатием которых будут вызываться эти сообщения;
| S определение, в какой части программы будут храниться полученные сообщения;
; S определение, какие другие части программы выполняются в данный момент;
., S регистрация имен разных частей программы.

Обратите внимание, что ни одно из этих действий не имеет никакого отношения к выводу
чего-либо на экран.

Глава 1. Что представляет собой пакет Visual C++ . NET 23


Системы создания приложений позволяют автоматически решать эти и многие другие за-
дачи. Например, приступив к созданию программы, вы можете воспользоваться классом Sys-
tem.Windows.Forms.Application, содержащим в себе все колы, необходимые для начала напи-
сания программы. В частности, этот класс содержит коды, необходимые для создания окон.
Эти и подобные им классы автоматически вставляют в коды вашей программы типичные
фрагменты, что дает возможность сосредоточиться на решении более важных задач.
Visual C++ содержит также другие системы создания приложений, например библиотеку
MFC (Microsoft Foundation Classes), включающую в себя классы C++. используемые при соз-
дании Windows-программ. Библиотека ATL {Active Template Library) содержит шаблоны C++,
используемые при создании объектов для модели COM (Component Object Model). Однако
системы ATL и СОМ настолько сложны, что их использование вызывает трудности даже у
хакеров со стажем, а потому в данной книге рассматриваться не будут.

24 Часть I. Первое знакомство с Visual C++ .NET


Глава2

Что такое программа


Вэ/Лой
> Основы программирования
> Выражения, переменные, комментарии и библиотеки
> Чтение программ
> Основы объектно-ориентированного программирования

м< этой главе дается краткий оозор основных этапов создания программ: проектирова-
'•^ ния, написания, компиляции и отладки. Кроме того, вам придется "проглотить" не-
много теории, касающейся объектно-ориентированного программирования. Вас также ожи-
дает встреча с настоящей живой работающей .NET-программой.

/Звес/ениевп
Проектированием программ называется этап, на котором принимается решение о том,
что же именно программа должна делать. Для этого нужно четко определить проблему или
круг проблем, которые должны быть решены, и выработать стратегию их решения.
Написание программ — это этап, во время которого вы садитесь и набираете коды своей
программы. Обычно вы набираете инструкции языков программирования высокого уровня.
Например, одной строкой можно сказать компьютеру, что нужно вывести что-то на экран. В
этом и следующих разделах вы познакомитесь с множеством команд языка C++, с помощью
которых можно заставить компьютер делать что-нибудь полезное.
Далее программу нужно откомпилировать. На этом этапе Visual C++ .NET преобразует
коды C++ высокого уровня, понятные вам, в коды низкого уровня, понятные компьютеру. В
процессе проектирования и написания программы обычно разбиваются на несколько отдель-
ных файлов (поскольку это удобно, а иногда и полезно). В процессе компиляции программы
эти файлы объединяются в приложение. После того как программа откомпилирована, ком-
пьютер знает, как ее нужно выполнять.
Теперь можно запустить программу на выполнение. Если она не примитивна и состоит бо-
лее чем из нескольких строк, вполне вероятно наличие в ней ошибок. Поскольку ошибки не
всегда бывают явными, необходимо тщательно протестировать программу, чтобы убедиться
в том. что она работает именно так, как было задумано.
Процесс поиска и исправления ошибок называется отладкой программы. Для поиска
ошибок обычно используются услуги отладчика. Когда ошибка найдена, вы снова возвращае-
тесь к этапу редактирования кодов программы для ее исправления.
Все программы состоят из команд, объясняющих компьютеру, что нужно делать и как
обрабатывать имеющиеся данные. Почти все действия программ сводятся к получению, об-
работке и отображению (или сохранению) данных. Даже, например, программы видеоигр за-
няты в основном только этим. Они получают данные, следя за нажатием клавиш, движениями

Глава 2. Что такое программа 25


мыши или джойстика, обрабатывают данные, принимая решения, что нужно делать дальше, и
затем отображают результат (например, на экране отображается следующая комната лаби-
ринта и с потолка прыгают два монстра с пушками в лапах).

Языки высокого и низкого уровня


Чтобы понять разницу между языком программирования высокого уровня и языком программирования
низкого уровня, сравните между собой выражение, набранное в C++ (язык высокого уровня}, и эквива-
лентный ему код, набранный на языке ассемблера (низкий уровень}.
Код C++:
а = 3*а - Ь*2 + 1;
Эквивалентный ему код ассемблера:
mov eax, DWORD PTR _a$[ebp]
lea eax, DWORD PTR[eax+eax*2]
mov ecx, DWORD PTR _b$[ebp]
1
add ecx, ecx
sub eax, ecx
inc eax
mov DWORD PTR _a$[ebp], eax
В действительности коды ассемблера также относятся к более высокому уровню, чем коды, которые ком-
пьютер может воспринимать непосредственно (без компиляции}. Компьютер может понимать только ма-
шинный язык, представляющий любые инструкции и команды в числовом виде.
Вот как выглядит фрагмент кодов машинного языка:
8Ь 45 fc
8d 04 40
3b 4d f8
03 c9
2d cl
40
89 45 fc
Теперь вы можете сравнить коды языков высокого и низкого уровня и решить, язык какого уровня вы хо-
тели бы использовать для написания программ.

Тлавяал (рунщил n/toi/tauut&t


Выполнение всех программ, написанных на языке О + , начинается с функции, именуемой
main. При запуске программы прежде всего выполняется первое выражение функции main.
Выражение — это строка кодов, представляющая собой отдельную инструкцию для компью-
тера. (Функция состоит из группы выражений, собранных вместе для решения определенной
задачи. Более подробно функции будут рассмотрены в следующей главе.) Затем поочередно
выполняются все остальные выражения — по одному за раз.
Инструкции некоторых выражений выполняются только в тех случаях, если справедливо
какое-то условие. Такие выражения называются условными операторами. Так, строка кодов
может содержать, например, следующую информацию для компьютера: "Если пользователь
щелкнет на кнопке Печать, выведи открытый документ на печать'".

26 Часть I. Первое знакомство с Visual C++ .NET


Переменные используются для представления данных в программе. Например, если
нужно запомнить имя пользователя, можно создать переменную Имя. Затем в любой мо-
мент, когда потребуется имя пользователя, можно просто сослаться на значение перемен-
ной Имя. В процессе выполнения программы значения переменных могут изменяться. На-
пример, можно присвоить переменной Имя значение Мартин, а потом другим выражением
присвоить этой же переменной значение Степан. Но само по себе значение переменной
никогда не меняется — в любом случае вы должны написать какое-нибудь выражение, ме-
няющее одно значение на другое.
Комментарии используются для описания того, что происходит в процессе выполне-
ния программы. Вы можете добавлять их для расшифровки целей, с которыми пишутся
те или иные фрагменты кодов, для фиксирования каких-то мыслей и идей, для описания
решаемых задач. Добавляя комментарии, вы упрощаете чтение кодов вашей программы
пользователями. Для вас комментарии также могут быть очень полезны. Если через не-
которое время вы захотите внести изменения в уже набранные коды, вам, скорее всего,
трудно будет вспомнить, для чего используется та или иная переменная и зачем нужно
было создавать ту или иную функцию. В таких случаях, пожалуй, комментарии могут
быть единственным средством, которое поможет вам вспомнить, что же именно вы хо-
тели сделать, набирая эти коды. (Кроме того, в комментариях программы вы можете вы-
сказать все, что наболело. Компьютеру ведь все равно, а вы избавитесь от лишнего гру-
за.) При преобразовании кодов C++ в машинные коды компилятор игнорирует строки,
являющиеся комментариями, и просто пропускает их.
На рис. 2.1 показана небольшая программа, в которой используются функция main, вы-
ражения, переменные и комментарии.

#using <mscorlib.dll>

int nMylnt; -• Переменная

int main (void) Начало функции main

//Получение значения от пользователя Комментарий


nMylnt = GetNumberf); -* - Выражение
if (nMylnt > 0) — — — Условный оператор
return nMylnt;
return -1;

Рис. 2.1. Основные компоненты, из которых состоит программа

CtnattqafitnMyte noqnfiotfiaMMU
Коды общих для большинства программ алгоритмов хранятся в компоненте среды
.NET, называемом CLR (Common Language Runtime). Например, почти все программы
отображают какие-то значения на экране. Возможно, это покажется вам странным, но
для выполнения такого простого действия компьютеру нужно сделать множество шагов.
Вместо того чтобы писать целые наборы кодов для всех этих шагов каждый раз, когда
нужно что-то вывести на экран, вы можете просто воспользоваться готовой подпро-
граммой, созданной для среды .NET.

Глава 2. Что такое программа 27


Когда вы пишете неуправляемые программы (те, которые создаются с ис-
) пользованием старой версии С-+), подпрограммы, реализующие типичные
-/ алгоритмы, содержатся в так называемых библиотеках. Чем они отличаются
от CLR? По сути, ничем. И тс и другие являются библиотеками. Разница
только в названии. CLR может быть использован при создании программ на
языках Visual Basic, О- и Visual С"! - (при этом сами библиотеки для разных
языков будут несколько отличаться). Хотя CLR, по существу, является тем же
набором библиотек, его возможности значительно шире возможностей стан-
дартных библиотек языка О -t.
Г:сть два типа библиотек: статические и динамические. Если вы используете при
создании своей программы процедуры статической библиотеки, коды этих про-
цедур непосредственно копируются в текст вашей программы, увеличивая таким
образом ее размер. Если же вы используете динамические библиотеки
(называемые также DLL), сами процедуры не копируются в вашу программу, а
вызываются в процессе ее выполнения.

н/гог/гаммы
Любая программа создается для решения каких-то задач. При этом, перед тем как на-
чать писать колы, нужно разделить задачу на несколько отдельных логических частей и за-
тем создать процедуры, каждая из которых будет решать проблемы своей части. Поначалу
перспектива разделения общей задачи на несколько более мелких может показаться до-
вольно сложной, однако по мере приобретения опыта программирования вы научитесь де-
лать по быстро и почти автоматически. (Любые хорошие компьютерные курсы уделяют
лому вопросу большое внимание.)
В качестве примера рассмотрим вариант создания программы для решения обычной зада-
чи. Предположим, вам нужно вычислить площадь квадрата. (Зачем? Какая разница. Может, у
вас вечеринка и вы хотите чем-то удивить своих друзей.) Вше со школы вы помните, что
площадь квадрата вычисляется как умножение его стороны на саму себя.
В контексте создания программы эта задача может быть разбита на три логические
подзадачи.
1. Определение длины стороны квадрата.
2. Вычисление площади квадрата путем умножения стороны на саму себя.
3. Отображение полученного результата.
Теперь, когда общая проблема разбита на отдельные подзадачи, каждую из них следует
преобразовать в коды программы, чтобы компьютер знал, какие задачи ему нужно решать.
Например, на языке C++- .NET эти коды могут выглядеть так:
/ , 3^".^ г.сг. '.'оа
/ /'Ъы'-глг.лен.'ле площади квадрата при условии, что известна
/ ' }\:iVM7i О~С СТОрОНЫ

vr: :"!"i-jde "s tdaf x . h"

#.:sinc <irscorlib.dl]>

28 Часть I. Первое знакомство с Visual C++ .NET


using namespace System;

//С этой строки начинается выполнение программы


tifdef _UNICODE
int wmain(void)
ttelse
int main{void)
#endif

String *pszSize;
int nSize;
int nArea;

//Запрос от пользователя значения длины стороны квадрата


Console::WriteLine(Ь"Чему равна длина стороны квадрата?"]

//Получение ответа
DszSiire = Console :: ReadLine

//Преобразование полученного значения в число


nSize = nSize.Parse(pszSize);

//Вычисление площади квадрата


nArea = nSize*nSize;

//Отображение результата
//Обратите внимание, что для этого вначале нужно
//преобразовать полученное значение в строку
Conso]е::WriteLine(L"Площадь квадрата составляет {0}
единиц.", nArea.ToString()};

//Ожидание, пока пользователь не остановит


//выпслнение программы
Console: :WriteLine (L/'Нажмите Enter, чтобь: завершить
выполнение программы");
Console::ReadLine();
return 0;
}
Итак, вы только что узнали, как выглядят коды программы. Но как разобраться, для чего
нужна каждая отдельная строка? Для этого сначала познакомьтесь с общими принципами
чтения кодов программы (если вы их еще не знаете).
1. Начинайте читать с самого начала.
2. Читайте по одной строке.
3. Старайтесь понять, для чего нужна каждая строка.
4. Если вы чего-то не понимаете, переходите к следующей строке.

Глава 2. Что такое программа 29


Эти же принципы используются при работе компилятора, за исключением разве что чет-
вертого пункта.
Далее описывается, как вы могли бы интерпретировать коды приведенной выше про-
граммы. Итак, обратимся к первым двум строкам:

//SquareArea
//Вычисление плошади квадрата при условии, что известна
//длина его стороны
Увидев их, вы можете подумать: "Ну, в этих строках нет ничего сложного. В них говорит-
ся, что эта программа вычисляет площадь квадрата исходя из длины его стороны". (Две ко-
сые черты в начале строк указывают, что эти строки являются комментариями.)

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;


"А это уже что-то совсем непонятное. Наверное, о том, для чего все
это нужно, я узнаю в следующих г л а э а х " . (Совершенно правильный ход
мыслей.)
# i f d e f ^UNICODE
i n t wmain(void)
ttelse
i n t main(void)
#endif
''Опять ЧТО-ТО непонятное. Пожалуй, сейчас это можно пропустить".
String *pszSize;
i n t nSize;
i n t nArea;
"Зачем нужны зти строки я точно не знаю, однако слова s i z e (длина)
и area (площадь) уже как-то созвучны с решаемой задачей".
//Запрос от пользователя значения длины стороны квадрата
C o n s o l e : : W r i t e L i n e ( Ь " Ч е ы у равна длина стороны квадрата?"!;

//Получение ответа
pszSize = Console::ReadLine(};

//Преобразование полученного значения в число


nSize = n S i z e . P a r s e ( p s z S i z e ) ;

'"Все это выглядит немного странно, но в целом можно понять, что коды эти нужны для
того, чтобы компьютер узнал, чему равна длина стороны квадрата".
// Вычисление площади квадрата
nArea = r . S i z e * n S i z e ;

"Да, да! Это я понимаю! В этой строке длина стороны умножается на саму себя, и мы на-
ходим площадь квадрата"'.
Ну и так далее.

30 Часть I, Первое знакомство с Visual C++ .NET


Закончив читать эту книгу, вы будете знать, для чего нужна каждая из встретившихся вам
здесь команд. Более того, вы будете знать не только как читать коды программ, но и как их
создавать. Другими словами, вы научитесь программировать.

inenefib немного fneoftuu


Вы только что узнали, как выглядят коды настоящей программы на С+-=-. Однако в этой
программе не были использованы возможности объектно-ориентированного программирова-
ния. Язык C++ настолько популярен во многом благодаря именно этим возможностям. Поче-
му? На это есть ряд причин.
I S Объектно-ориентированное программирование позволяет повторно использовать
I одни и те же коды, что значительно экономит время и усилия, необходимые при
\ создании программы.
;• ^ Объектно-ориентированные программы имеют хорошую структуру, что сущест-
:. венно облегчает чтение и понимание их кодов.
I S Объектно-ориентированные программы легко тестировать. Их можно разбить на
% небольшие компоненты и отдельно проверять работу каждого из них.
;• ^ В объектно-ориентированные программы очень легко вносить изменения и добав-
i лять новые возможности.

Основная идея объектно-ориентированного программирования проста. Данные и про-


цедуры, обрабатывающие эти данные, объединяются в одно целое, называемое классом.
Если нужно получить доступ к данным какого-то класса, используются процедуры этого
же класса.
В старых процедурных языках программирования, напротив, данные и процедуры, их
обрабатывающие, не могли быть объединены и воспринимались как совершенно разные
компоненты.

Что такое объект и с чем его едят


В основу всего объектно-ориентированного программирования положена такая идея:
разбить решаемую задачу на группу объектов. Объекты состоят из данных и процедур,
которые эти данные обрабатывают. Например, рассмотрим программу, управляющую
дисководом с автоматической сменой дисков. Она может состоять из нескольких объек-
тов, обрабатывающих информацию о воспроизводимых с устройства музыкальных тре-
ках. Один объект, например, может использоваться для представления воспроизводимых
треков, а второй для представления самого дисковода. Каждый объект должен содер-
жать данные, которые его описывают (объект Трек может содержать информацию о
продолжительности песни), и функции, которые оперируют этими данными (объект
Трек может иметь функцию, отображающую название песни и ее продолжительность).
Объединение данных и обрабатывающих их функций в отдельные объекты называется
инкапсуляцией.
Уже готовые объекты можно объединять между собой для создания новых объектов. Это
называется композицией. Например, можно создать объект Караоке, объединив объект
ДИСКОБОЛ С объектом Микрофон. (Можно создать объект Сутки, объединив объект День с
объектом Ночь.)

Глава 2. Что такое программа 31


Можно создавать новые объекты на основе уже существующих объектов. Например, на
основе объекта, воспроизводящего видео, можно создать объект, распространяющий видео,
добавив в него функции обработки денежных переводов. Изменение существующих свойств
объектов или добавление к ним новых функций для создания новых объектов называется на-
следованием.
Наследование— одно из наиболее важных свойств объектно-ориентированного програм-
мирования. Создавая новые объекты путем наследования кодов уже существующих рабо-
тающих объектов, вы получаете ряд преимуществ.
S \ 1е нужно повторно набирать те же коды: все коды, которые набраны для исходных
объектов, автоматически могут быть использованы новыми объектами.
*•" Снижается вероятность возникновения ошибок: если вы точно знаете, что ис-
ходный объект работал правильно, значит, любые возникшие ошибки следует
искать в кодах, которые были добавлены к новому объекту при его создании. С
другой стороны, если вы найдете и исправите ошибку в исходном объекте, она
автоматически будет исправлена для всех других объектов, созданных из данно-
го путем наследования.
•S Коды программы становятся легче для чтения и понимания: нужно понять, как ра-
ботают исходные объекты. Понять, как работают объекты, созданные путем насле-
дования, будет намного проще, поскольку вам останется только изучить, как рабо-
тают добавленные к ним данные и функции.

Еще одним свойством объектно-ориентированного программирования является тот факт,


что одна и та же функция может выполняться по-разному в зависимости от того, данные ка-
кого объекта обрабатываются. Это свойство называется полиморфизмом. Например, если
функция должна распечатать содержимое ячейки электронной таблицы, на печать выводится
число. Вели же функция должна распечатать диаграмму, на печать выводится изображение. В
обоих случаях используется одна и та же ф\нкция, но поскольку объекты разные (ячейка и
диаграмма), выполняемые действия б\дуг совершенно различными.

Инкапсуляция
Инкапсуляцией называется выделение данных и функций, обрабатывающих эти данные, в
отдельные 'элементы, называемые объектами или классами. Данные, в зависимости от того
как они используются, иногда называют свойствами классов. Функции также иногда называ-
ют методами классов.
Секрет создания качественной объектно-ориентированной программы заключается в том,
чтобы выделить классы, которые бы максимально точно описывали реальную решаемую
проблему и которые можно было бы максимально часто повторно использовать. Поначалу
эта задача может показаться довольно сложной, однако по мере приобретения практического
опыта вы научитесь это делать почти автоматически.

Наследование
Ото одно из наиболее интересных свойств объектно-ориентированного программи-
рования. Как отмечалось ранее, с помощью этого свойства можно создавать новые объ-
екты, используя для этого уже существующие. Новый класс, созданные на основе гото-
вого класса, называется производным. А тот класс, который был взят за основу, называ-
ется базовым. (Иногда производные классы называются дочерними, а базовые —
родительскими.')

32 Часть!. Первое знакомство с Visual C++ .NET


На рис. 2.2 показано семь классов. Класс Звук является самым простым. Он содержит
название воспроизводимого звука и его продолжительность. Класс Трек основан на классе
Звук, а потому также содержит информацию о названии и продолжительности. Кроме то-
го, он содержит данные об исполнителе и о дате записи. Класс Rock является производ-
ным от класса Трек. Он содержит все элементы класса трек (как и все элементы класса
Звук) плюс еще некоторые. Таким образом, на основе любых классов можно создавать
более сложные классы, содержащие в себе больше разных данных и функций для их обра-
ботки. При этом каждый производный класс содержат в себе все данные и функции, кото-
рые есть в базовом классе.

Звук

Название
Продолжительность

ЗвукиЖивотных
7 \Трек

Животное Исполнитель
ДатаЗаписи

t
Rock Классика Pop

Группа Дирижер Альбом


Альбом Оркестр Рейтинг
ПерваяСкрипка

1
Alternative Rock
Направление

Рис. 2.2. Новые классы можно создавать путем наследования свойств и методов уже создан-
ных классов

Полиморфизм
При создании новых классов можно использовать не только возможность наследова-
ния, но также полиморфизм для определения способа поведения новых классов. Если вы
еще не запомнили все греческие слова, употребляемые в этой главе, напомним, что под
полиморфизмом подразумевается возможность одних и тех же функций по-разному об-
рабатывать данные, принадлежащие разным объектам. Например, класс Звук может
иметь функцию Получить. Если запустить ее для объекта ЗвукиЖивотных, она может
отобразить на экране сообщение, советующее взять с собой диктофон и сходить в бли-
жайший зоопарк. Если запустить эту же функцию для объекта Rock, может быть пред-
принята попытка открыть соответствующую Web-страницу с возможностью загрузки
нужного файла.

Глава 2. Что такое программа 33


Используя одновременно возможность наследования и полиморфизм, вы сможете легко
создавать наборы подобных, но уникальных объектов. Благодаря наследованию объекты бу-
дут во многом похожи друг на друга. А благодаря полиморфизму каждый из них может чем-
ю оыичатьея от других. Так, если вы используете полиморфизм, то функция, прнсутствую-

. а я в разных объектах (такая, как функция Получить), для каждого из них будет работать
по-разному.
Ьслч для какой-то функции полиморфизм не используется, для производного класса она
будет выполняться точно так же, как и для базового. Если для класса, производного от класса
;
•- •:'-:. не перегрузить функцию " с л у ч и т ь (т.е. просто наследовать ее без внесения измене-
нии), то ее выполнение также будет приводить к попытке открыть ту же Web-страницу. Та-
ким образом, производные классы изначально имеют ту же функциональность, что и базовые.
h e m же какие-то функции отличаются, значит, к ним был применен полиморфизм.

34 Часть I. Первое знакомство с Visual C++ .NET


Глава3

Приступаем к созданию программ


В э/Оой главе...
> Что такое исходные файлы
> Создание и выполнение управляемых программ .NET C++
> Создание и выполнение неуправляемых программ C++

о
+у та глава поможет вам создать свою первую программу C++. (И даже не одну.) Нач-
^ ^ нем с создания управляемой программы C++. Это дружественное определение дано
разработчиками среды .NET программам, создаваемым для работы в этой среде, и обознача-
ет, что при разработке этих программ предоставляется множество дополнительных возмож-
ностей (благодаря CLR) и обеспечивается их корректное выполнение (благодаря правильному
управлению памятью и многому другому). Также вы научитесь создавать добрые старые (или
стандартные) программы C++, для которых разработчики среды .NET придумали нехорошее
название— неуправляемые программы. Но вначале вы научитесь создавать то, что является
основой любой программы C++ — исходные файлы.

Зачем нужны исходные файлы


Исходные файлы, или файлы реализации с расширением СРР (читается как С Plus Plus)
либо СХХ, содержат в себе основную часть выполняемой программы. Именно здесь опреде-
ляются функции, данные и ход выполнения программы. (Под ходом выполнения программы
подразумевается последовательность, в которой будут выполняться отдельные выражения
или части программы.)
Исходные файлы состоят из выражений. Выражением является строка программы, кото-
рая дает указание компьютеру выполнить какое-то действие.
Например, приведенная ниже строка является выражением, которое указывает компьютеру,
что необходимо вычислить площадь квадрата путем умножения длины стороны на саму себя:
nArea = n S i z e * n S i z e ;
Каждое выражение должно заканчиваться точкой с запятой. Точка с запятой по-
казывает компьютеру, где заканчивается одно выражение и начинается другое
(так же, как на письме одно предложение заканчивается точкой, после чего начи-
нается другое предложение).
Можно объединить несколько выражений, заключив их в фигурные скобки ( { } ). На-
пример, допустим, что есть такое выражение:
i f чай горячий, подожди немного;
(Команда if, которая будет более подробно рассмотрена в главе 11, указывает компьюте-
ру на необходимость выполнить какое-то действие в случае, если справедливо некоторое ус-
ловие.) Если вы хотите, чтобы в это же время компьютер сделал еше какое-то действие, мож-
но набрать такое выражение:
i f чай горячий {подожди немного; просмотри почту;}

Глава 3. Приступаем к созданию программ 35


Два выражения были объединены с помощью фигурных скобок.
Открывающую и закрывающую фигурные скобки можно также использовать для обозна-
чения начала и окончания функции. Более подробно об этом речь идет в главе \2.
Если для выполнения функции требуется указание параметров, они заключаются в круг-
лые скобки. Например:
i f номер з а н я т {немного п о д о ж д и ( 2 м и н у т ь : ) ; п о з з о н и еще р а з ; }
Таким образом функция подожди точно указывает, сколько нужно подождать, перед тем
как еще раз позвонить. Узнать об этом более подробно (не о телефонных звонках, а об ис-
пользовании параметров), вы сможете, прочитав главу 12.
Итак, подытожим полученные знания об использовании выражений:
; S строки заканчиваются точкой с запятой (;);
1 S объединение строк осущест вляется с помощью фигурных скобок ({ });
\ S значения параметров заключаются в круглые скобки — ( ) .

все s0io на nfiatctnuice


Пожалуй, теории пока достаточно. Самое время заняться делом. Начнем с создания ново-
го проекта .NET.
1. Выберите команду File^New^Project (Файл^Создать^Проект).
2. Откроется диалоговое окно New Project.
3. В разделе Project Types (Тип проекта) выберите пункт Visual C++ Project.
4. В разделе Templates (Шаблоны) выберите Managed C++ Application.
5. В поле Name (Имя) наберите H e l l o World.
6. В папке Visual Studio Projects (она расположена в папке My Documents) будет
создана папка Hello World.
Обратите внимание на рис. 3.1. Если вы хотите создать папку Hello World в какой-
нибудь другой папке, укажите ее название в поле Location (Размещение) или щелкните
на кнопке Browse (Обзор), чтобы открыть окно для поиска этой папки.

Hew Project Ш
pree
l ct Types: i i! SJ
_J VsiualC* Proj-ts
:1 _j V KualC++ Proe j ct! Prcnie^ur Dll
_J Seu to and DepolymentProects
i + _| «ret PrDejftS
! _J VsiualStudo
i Sou lo
tins ' ManaaedC+-V Managed :++ Man
EmpaytgedProCft+
c+
t
i Appkatian try

i n thatau«M ^ E -ten»ms fen Ci +.


An appcilato
faj me: - Hdo lA
'o
'rW
Locator: i С >p«unents arid Se'tings^Bdrriian My Docurceri •№
•*\ &owse., }
&dd to ^oJuo tin jkjse Solution

o
tfejct wB
i be createdссл.-ЛВаг son'iMv Documents\Vimal Studio ProiecB\HeloW»ld.
- ^ J I -
ncef i •
Рис. З.1. Использование диалогового окна New Project
для создания проекта Hello World

36 Часть t. Первое знакомство с Visual C++ .NET


7. Щелкните на кнопке ОК.
Visual C++ создаст целый набор файлов, принадлежащих одному проекту. Один или
несколько создаваемых проектов составляют одну задачу {solution). Более подробно о
задачах речь идет в главе 4. Окно Solutin Explorer (Разработчик задач), показанное на
рис. 3.2, отображает файлы, из которых состоит проект Hello World.

Solution Explorer - HelbWorld

5olution 'HelloWorid' (l project)


VbrUj
•~ j Source Files
j£] HelloWorid.cpp
i j Assemblylnfo.cpp
\£\ stdafx.cpp
C^ Header Files
У stdafx.h
?"""! Resource Files
У ReadMe.txt

i!3 Solution Explorer \Щ

Рис. З.2. S окне Solution Explorer


можно увидеть файлы, используемые
данным проектом

Все эти файлы были созданы для того, чтобы можно было реализовать простую програм-
му, отображающую на экране сообщение H e l l o World. Чтобы увидеть исходные коды про-
граммы, дважды щелкните в окне Solution Explorer на значке HelloWorid. cpp. Этот файл
расположен в папке Source Files (Исходные файлы) проекта Hello World.
К счастью, эта простая программа, отображающая на экране сообщение H e l l o World,
может быть создана автоматически. Для этого выполните следующее.
1. Выберите команду Debugs Start Without Debugging (Отладка'ФЗапустить без
отладки).
Visual C++ отобразит сообщение о том, что проект HelloWorid - Debug Win32 явля-
ется устаревшей программой.
2. Щелкните на кнопке Yes, чтобы Visual C++ создал этот проект автоматически.
Откроется окно Output (Выходное окно), в котором вы сможете наблюдать за про-
цессом построения проекта.
Как только проект будет создан, на экране мелькнет черное окно и сразу же исчезнет.
Разумеется, вы не успеете заметить, что было в этом окне. Так случается всегда, когда
программа просто выполняет набор каких-то действий и сразу завершает свою работу.
Но как же определить, правильно программа работает или нет? И делает ли она вообще
что-то? Сделать это можно разными способами. Из материала главы 16 вы узнаете, как мож-
но использовать отладчик для остановки в самом конце выполнения программы. А далее в
этой главе описано, какие коды необходимо добавить, чтобы программа в нужном месте ос-
тановилась и ожидала, пока пользователь не даст ей сигнал продолжать работу. По мере при-
обретения опыта программирования вы оцените полезность и универсальность этого приема.
Ну а пока выполните следующее.

1. Откройте окно для ввода команд.

Глава 3. Приступаем к созданию программ 37


Если у вас установлена Windows 2000 или Windows XP, выберите команду
S t a r t ^ P r o g r a m s ^ A c c e s s o r i e s ^ C o m m a n d Prompt. Если у вас установлена Win-
dows NT, выберите команлу S t a r t s P r o g r a m s ^ C o m m a n d Prompt.
2. Используйте команлу CD, чтобы перейти в папку с проектом HelloYVorld.
Если имя папки содержит пробелы, возьмите его в кавычки. Для Windows 2000 и
Windows XP путь к папке может выглядеть приблизительно так: "C:\Documeius and
.Seltins£s\MyUserNaine\My DocumentsWisual Studio ProjeclsVHclloWorld". (В действи-
тельности путь пуде'1 зависеть от настроек вашего компьютера. Например, вместо
слова MyUserName нужно будет указать ваше пользовательское имя.)
Чтобы запустить программу, наберите D e b u g \ H e l l o W o r l d .
Поскольку' Visual Studio создала проект автоматически, т.е. со всеми установками, задан-
ными по умолчанию, она поместила файл, с которого начинается выполнение программы.
в папку Debug. Результат выполнения программы вы можете увидеть на рис. 3.3.

Ш Command Prompt

:\Docunents and SettingsNBarnsonSMy DoeumentsMJisual Studio Projects\HellolJorld


M « bf f le l l d
lello World
,'i\I>ocuinents And SettingsNBarnsonNMy DocunentsMJisual Studio PrajectsSHelloUor

Рис. 3,3. В окне для ввода команд отображается надпись Не 11 о Worl a

С чего начинается выполнение программы


Теперь, когда вы видели результат выполнения программы, давайте посмотрим, каким
оорачоч все это происходит. Для начала компьютер должен определить, с какой строки нуж-
но начинать выполнение программы. Вместо того чтобы нумеровать все строки (как это де-
лалось в старые добрые времена), нужно просто поместить первую строку программы в
функцию, именуемую m a i n . С первой строки .пой функции начинается выполнение любой
программы. Например, вот код простой программы, которая ничего не делает:
дг; nic-iin? v o i d )

.' ' '_;;;ось а б с о л ю т н о н и ч е г о не п р о и с х о д и т

Выполнение этой программы, как и всех остальных, начинается с функции m a i n . По-


ско.1ьк> функция m a i n (как и любая другая функция) может использовать в своей работе зна-
чения параметров, после ее названия следует пара круглых скобок. (В среде .NET функции
обычно используются без каких-либо параметров, поэтому вместо них постоянно набирают
сюоо vMia. обозначающее факт их отсутствия.) Далее следует открываюшая фигурная скоб-
!..л. которая указывает компьютеру, что за ней идут строки функции m a i n . Закрывающая фи-
г
\ рнля скобка говорит о том, что на этом выполнение функции заканчивается. Вам может по-
ка^п;.ся непонятным значение слова : n t , стоящего перед названием функции. Более под-
робно это рассматривается в главе 12, а пока напомним, что функция— это отдельная
подпрограмма, которая может выполнять какие-то действия {например, выводить что-то на
печать), обрабатывать данные или запрашивать какую-либо информацию. Функции могут
!а].же возвращать в качестве результата какое-то значение. В С~+ mair. также является
фу.чмшей. а следовательно, может возвращать какое-нибудь число (integer).

Часть I, Первое знакомство с Visual C++ .NET


Начало функций main всех управляемых программ C++, коды которых вы встре-
тите в этой книге, выглядит несколько сложнее:
# i f d e f JJNICODE
i n t wmain(void)
#else
i n t main(void)
#er:dif
Слово _UNICODE обозначает, что будет использоваться стандарт, предусматри-
вающий ввод с клавиатуры букв и символов других языков, включая азиатские.
При компиляции программы может быть выбрана опция, указывающая на то, что
программа должна быть откомпилирована с учетом этой возможности.

Как организовать диалог с пользователем


Конечно, можно создавать программы, которые ничего не будут делать. Но все же лучше,
если программа будет в состоянии отобразить что-либо на экране и принять какую-нибудь
информацию от пользователя. Есть миллион способов (ну хорошо, пять или чуть больше)
сделать это в C++ .NET. Самый простой— использовать для этого функции Console. Они
позволяют считывать и отображать сообщения в окне консоли (это то черное окно, которое
вы видели при выполнении вашей первой программы). Чтобы отобразить сообщение на экра-
не, используйте команду Console : : WriteLine. Дайте ей любой текст, и она высветит его
в окне консоли. Например, показанная ниже строка выводит на экран сообщение H e l l o
World 'обратите внимание, что текст взят в кавычки):
C o n s o l e : : W r i t e L i n e ( " H e l l o World") ;
Допустим, вместо этого сообщения нужно отобразить текст Здравствуйте, я вл^а
тетя ! Делается это аналогичным образом:
Console::WriteLine("Здравствуйте, я ваша т е т я ! " ) ;
Чтобы получить информацию от пользователя, используйте команду C o n s o l e : :
ReadLir.e. Полученная информация будет воспринята как строка текста. (В главе 7 рассмат-
ривается, как преобразовать эту информацию к какому-нибудь полезному виду.) Например,
приведенная ниже строка заставит компьютер ждать, пока пользователь не введет каком-
нибудь текст:
Console::ReadLine();

Обратите внимание, что команда C o n s o l e : :ReadLir.e ничего не отображает


на экране. Обычно для получения от пользователя каких-либо данных вначале
нужно использовать команду C o n s o l e : : W r i t e L i n e , чтобы отобразить на эк-
ране соответствующий вопрос, а затем уже применить команду
Console : : ReadLine, чтобы принять от пользователя ответ. Например:
Console::WriteLine(L"Нажмите E n t e r , чтобы завершить
выполнение программы");
Console::ReadLine();

Функции Console являются частью .NET библиотек CLR, Поэтому, перед тем как их мс-
пользов;у"Ь, нужно указать Visual C++ .NET, в каком месте их искать. Сделать э'ю можно с
помощью ключевого слова f u s i n g . Все функции CLR разбиты на множество динамических
библиотек (файлы с расширением DLL). Строки со словом #usir.g сообщают компьютеру,
функции каких именно библиотек будут использованы в данной программе.

Глава 3. Приступаем к созданию программ 39


CLR является аОбревиатурой от Common Language Runtime (переводится приблизительно
как общий язык выполнения программ), но, как вы уже знаете, представляет собой нечто
иное, а именно совокупность библиотек с различными полезными функциями. Название
Common Language Runtime было выбрано исходя только лишь из маркетинговых соображе-
ний. Кстати, сокращенное название CLR впервые упоминается только в этой книге, так что,
если вы произнесете его в разговоре со своими друзями-программистами и они не поймут, о
чем речь, просто посмотрите на них свысока и скажите, что они отстали от жизни.
Большинство основных функций .NET, включая функции Console, расположены в библио-
теке m s c o r l i b . Ее название происходит от слов Microsoft core library, что переводится как
''корневая библиотека". Возможно, это поможет вам запомнить ее название. Если в своей про-
грамме вы захотите использовать функции библиотеки mscorlib.dll, наберите такую строку:
"using <mscorlib.dll>
Все функции CLR разбиты на отдельные группы, обозначаемые термином manespace
(пространство имен). Эти группы состоят из классов (более подробно классы описаны в гла-
ве 17). А классы, в свою очередь, содержат функции. Например, функции ReadLine и
W r i t e L i n e принадлежат классу Console. Класс Console относится к группе System. Та-
ким образом, полное имя функции ReadLine будет выглядеть так: System: : C o n s o l e
::ReadLine.
Ключевые слова u s i n g manespace могут избавить вас от необходимости каждый раз
при вызове функции набирать название ее группы. Например, в кодах программы HelloWorld
вы могли видеть такую строку:
u s i n g namespace System;
Именно благодаря ей далее по тексту программы можно набирать Console : : ReadLine
вместо System: : Console : : ReadLine.
Обратите внимание, что ключевые слова #using и u s i n g namespace не явля-
ются частью стандартного языка С+-г. Точно так же, как и все функции CLR. По-
этому все команды, с которыми вы познакомились до этого, могут быть исполь-
зованы только при создании программ .NET. Далее в главе вы узнаете, как напи-
сать аналогичные программы, используя возможности стандартного языка C++.

Не скупитесь на комментарии
Только вы можете точно знать, для чего вами была набрана та или иная команда. Но если
коды вашей программы должен будет смотреть еще кто-то, он этого, скорее всего, не поймет.
Или, если спустя несколько лет вы захотите внести в программу некоторые изменения, вы
уже вряд ли сможете вспомнить, как это все работает. Вот почему так важно использовать
комментарии. Комментарии объясняют простым русским языком (или не русским, но обяза-
тельно тем, на котором вы привыкли разговаривать), что вы пытались сделать, набирая все
эти коды.
В кодах программ, приводимых в этой книге, вы уже сталкивались с комментариями. Рас-
познать их помогут две косые черты (//). Как вы уже могли догадаться, весь текст строки,
следующий за ними, является комментарием. (Прочитайте раздел "Старый формат коммента-
риев", чтобы знать, как еще можно создавать комментарии.)
Комментарий может быть выделен в отдельную строку:
//Это строка комментариев.
Также комментарий может завершать собой какую-то строку:
э = 10; //Присвоение переменной значения 10.

40 Часть I. Первое знакомство с Visual C++. NET


Старый формат комментариев
В языке C++ для обозначения комментариев используются две косые черты (//). Ранее в языке С ком-
ментарии выделялись несколько иначе; начало комментария обозначалось символами / *, окончание -
* /. Этот же способ по-прежнему можно использовать в языке C++. Например:
/* Это старый способ выделения комментариев */
а = 10; /*Присвоение переменной значения 10*/
/^Переменной а*/ а = 10; /"^присваивается значение 10*/

При использовании старых комментариев самое главное - не забыть обозначить их окончание! Если вы
забудете набрать в конце комментария символы */, компилятор проигнорирует все, что было набрано
после открытия комментария символами /*.
Обратите внимание, что, когда для создания комментария используются две кхые черты (//), никакими
специальными символами заканчивать строку не нужно. Две косые черты говорят компилятору, что далее
вся строка (и только эта строка) является комментарием. Однако, в отличие от старого формата, если
комментарий занимает несколько строк, каждую новую строку должны предварять две кхые черты {//).

Исходный код программы HelloWorld


Теперь, когда вы уже достаточно знаете о принципах создания программ Visual C++ .NET,
посмотрите на исходный код созданной ранее в этой главе программы HelloWorld. Он дол-
жен быть вам понятен (в нем действительно нет ничего сложного).
//Это код исходного файла приложения VC+ +
//Программа была создана с использованием мастера

#include "stdafx.h"

#using < m s c o r l i b . d l l >

u s i n g manespace System;

//С этой строки начинается выполнение программы


#ifdef ^UNICODE
i n t wmain(void)
#else
i n t main(void)
#endif
{
C o n s o l e : : W r i t e L i n e ( " H e l l o World");
Return 0;
}
Как видите, программа начинается с комментариев. Далее следуют строки с ключевыми
словами # u s i n g и u s i n g namespace, определяющие способ использования CLR. Затем,
после еше одного комментария, следуют коды функции main, с которых, собственно, и на-
чинается выполнение программы. На экране будет отображена надпись H e l l o World, сразу
после чего выполнение программы прекратится. Строка r e t u r n 0 говорит о том, что функ-
ция main возвращает нулевое значение. Вы можете пока не обращать на нее внимания.

Глава 3. Приступаем к созданию программ 41


Строки бывают разными
В компьютерной терминологии текстовое значение, такое как "Hello World", называется строкой (string).
При использовании строк перед ними можно добавлять специальные буквы, предоставляющие компью-
теру дополнительную информацию. Например, если строка должна быть интерпретирована как набор
международных символов {Unicode), наберите перед ней букву L;
Console::WriteLine(L"Hello World");
Строки Unicode могут содержать в себе любые международные символы, включая символы японского
алфавита. Однако в языках .NET, если вы не укажете, что строка содержит символы других алфавитов,
она все равно будет преобразована к строке Unicode в процессе выполнения программы. Поэтому само-
стоятельно указывать, что строка является строкой Unicode, особого смысла не имеет.
Намного разумнее набрать перед текстовым значением букву s:
Console::WriteLine(S"Hellc World");
Sio будет сигналом компьютеру использовать встроенный класс string, что предоставит вам дополни-
тельные возможности по обработке этих данных.

Пару штрихов к программе Hello World


Теперь добавим некоторые изменения к программе HelloWorld. Для начала обозначьте все
строки как строки .NET (поставьте перед ними буквы S). Затем добавьте в конце программы
несколько команд, которые позволят выполнять ее, находясь в среде разработки Visual О -
\Г Г : IDKi. и видеть результат выполнения до того, как окно исчезнет с экрана. Итак, окон-
fi код программы должен выглядеть следующим образом:
: W r i t e L i n e (S " H e l l o Werlei" ) ;
!>:e , пока пользователь не решит
ж.: :'•.- выполнение прогр^ммь:
: vVui'.GLine (5"На.кмлте Entzer, чтобы завершить выгт'"'л но -:ио
программы") ;
: RoadLine ( ) ;

о J/SJ
Все програм\ш. которые были рассмотрены ранее, являлись программами .NET.
Это означает, что они не будут работать вне среды .NET. Напротив. С.--+ является
стандартизированным языком программирования, и созданные с его помощью
программы могут работать в различных компьютерных системах. Далее аы узнае-
те, как создать обычную программу С+->- для отображения на экране надписи
"Hello World". Напомним, что разработчики .NET назвали программы, созданные
без использования возможностей .NET (к числу которых относятся и обычные
программы C++), неуправляемыми программами.

Однако, перед тем как приступить к программированию, познакомьтесь еще о некоторы-


ми тонкостями терминологии. В среде /NET доступ к встроенным функциям обеспечивает
CLR- В обычном C++ функции расположены в различных библиотеках. В .NET нужно ис-
пользовать ключевое слово frusing. чтобы получить доступ к функциям CLR. В C++ для ис-
пользования других функций нужно подключить заголовочный фай:1 (header file). Заголовоч-
ным файл сообщит компьютеру названия и характеристики подпрограмм из других файлов
и. in библиотек, которые вы намериваетесь использовать. Например, предположим, что вы хо-

42 Часть I. Первое знакомство с Visual C++ .NET


тите использовать функцию f сю, хранящуюся в библиотеке. Для этого необходимо подклю-
чить заголовочный файл, который сообщит компьютеру, что f o o — это подпрограмма, и
укажет, данные каких типов потребуются для ее выполнения.
Если вы создаете подпрограмму в одном файле и хотите использовать ее затем в другом
файле, нужно создать заголовочный файл, описывающий эту подпрограмму. Подключая этот
файл к другим файлам, вы можете повторно использовать созданную подпрограмму в других
программах.

Для чего нужен компоновщик


Когда вы определяете внешнюю функцию в заголовочном файле, вы указываете только ее название и пара-
метры, но не название самой библиотеки или исходного файла, в которых она может храниться. Компилятор
генерирует список всех используемых программой функций, куда входят названия как внешних функций, так
и функций, созданных в самой программе. После компилятора в действие вступает компоновщик (linker).
Помимо прочего, компоновщик проверяет список названий всех требующихся для выполнения программы
функций и ищет сами функции в других файлах и библиотеках. Если функция найдена, она используется ав-
томатически. Если компоновщик не может найти функцию, он выдает сообщение об ошибке.

Отображение информации на экране


Если вы хотите сообщить какую-то информацию пользователю, ее нужно отобразить на
экране монитора. Работая с .NET, мы использовали для этого команду
C o n s o l e : :WriteLir.e. В неуправляемых программах вместо нее используется выражение
c o u t (читается как see-out— "вижу-вывожу"). Если вы даете выражению cout какое-
нибудь значение, оно с радостью выводит его на экран. Чтобы передать выражению cout
значение, используется команда <<.
Как и в случае создания программ .NET, текстовые значения должны быть взяты в кавычки.
Ниже приведено несколько примеров. В каждом случае все, что следует за командой
c o u t «, будет выведено на экран.
// Отображается H e l l o World
c o u t << " H e l l o World";
// Отображается Apollo 16
c o u t << "Apollo 16";
Можно также одной командой отобразить сразу несколько значений, как это сделано в
следующих двух примерах. В первом примере отображается текст "Меня зовут " и "Мишель"
(это две разных строки). Во втором примере отображается одновременно текст "Apollo " и
число 16.
//Отображается текст Меня зовут Мишель
cout << "Меня зовут " << "Мишель";
//Отображается Apollo 16
c o u t << "Apollo " << 16;
Как видите, комбинировать текст и числа очень легко. Обратите также внимание, что ка-
ждое выражение заканчивается точкой с запятой.
Ну как? Не правда ли, в C++ отображать информацию на экране еще проше, чем в
среде .NET?

Глава 3. Приступаем к созданию программ 43


Специальные символы
Есть целый набор специальных символов, определяющих параметры печати.
\п Продолжить со следующей строки
\ t Табуляция
\Ь Вернуться на один знак назад
\ f Продолжить со следующей страницы
\\ Отобразить символ \
\' Отобразить символ '
\" Отобразить символ "
Например, нужно отобразить на экране сообщение "И снова "Здравствуйте!"".
Для этого наберите такую команду: :
cout « "И снова \"Здравствуйте!\"\п";

Печать с новой строки


Как видите, специальных символов не так уж мало. Наиболее часто используемым среди
них является символ \п, который выполняет переход на новую строку. (Его иногда так и на-
зывают— символ новой строки.) Например, если вы хотите, чтобы выводимый текст ото-
бражался в разных строках, наберите такие команды:
//Отображается текст Меня зовут Мишель
cout << "Меня зовут " << "Мишель\р.";
//Отображается Apollo 16
c o u t << "Apollo " << 16 << " \ n " ;
Символ \п интерпретируется так же, как и обычные символы. Другими словами, вы мо-
жете использовать его отдельно (как во втором примере: 16 << " \ п " ) либо в любом соче-
тании с другими символами (как в первом примере: Мишель \п). Можно набрать нечто вроде
Миш\пель, в результате чего в одной строке будет отображено "Миш", а во второй строке
•'ель". В отличие от обычных символов, \п не отображает ничего на экране; он используется
только для перехода на новую строку.
Поначалу использование всех этих стрелочек (<<) и всяких специальных символов может
показаться крайне неудобным и слишком запутанным, но вскоре вы сами удивитесь, насколь-
ко быстро к ним привыкнете.
При использовании функции cout, специальный символ \п не является единст-
венно возможным средством для начала новой строки. В C++ есть также специ-
альная функция e n d l (ее название образовано от слов end line— конец строки),
которая выполняет то же действие:
cout << "Меня зовут Джимми" « endl;
cout « "Мой номер " << 12 << endl;

В отличие от \п, функцию endl нельзя использовать в конце или в середине строки. По-
этому при необходимости нужно просто разделить строку на две части, как показано ниже:
Cout « "Меня зовут " << endl << "Джимми" « e n d l ;
Хотя использовать функцию e n d l несколько сложнее, чем символ \п, код с ее участием
намного проще для чтения и понимания.

44 Часть I. Первое знакомство с Visual C++ .NET


Обратная связь: получение ответа
Считывать информацию, набранную пользователем, так же просто, как и отображать дан-
ные на экране. Для этого используются функция c i n и команда >>. (название функции про-
износится как "sin", что означает "see-in" — "вижу-получаю"). Например, если нужно, чтобы
пользователь указал свой номер телефона, наберите;
cin » НомерТелефона;
Обычно информация, поступающая от пользователя, сохраняется как значение какой-
нибудь переменной. Об использовании переменных речь идет в главе 8.

Использование библиотечных функций


Полученных вами знаний уже почти достаточно, чтобы вы могли приступить к созданию
своей первой неуправляемой программы. Однако функции c o u t и c i n являются частью биб-
лиотеки, и, как отмечалось ранее в главе, чтобы использовать функции библиотек, нужно
подключить к программе заголовочный файл. Делается это с помощью команды # i n c l u d e .
Все команды, которые начинаются с символа #, называются директивами препроцессора.
Этим длинным названием обозначаются команды, дающие указания компилятору. Директивы пре-
процессора не преобразуются в коды программы, они лишь управляют процессом компиляции.
Например, директива # i n c l u d e указывает компилятору, какой файл нужно подключить
к программе. Описания функций c i n и cout хранятся в файле, именуемом iostream..h.
(Расширение .h является стандартным для заголовочных файлов.) Чтобы эти описания стали
доступными, наберите в начале программы такую строку:
#include <iostream.h>
Эта команда подключает файл с описаниями c i n , c o u t и многих других функций, яв-
ляющихся частью библиотеки i o s t r e a i n .
Обратите внимание, что строки с директивами препроцессора не заканчиваются точкой с
запятой. Отдельная директива препроцессора может состоять только из одной строки. По-
этому компилятор сам знает, что окончание строки означает окончание директивы.

Почему настоящие хакеры вместо < > чаще набирают""


За командой # i n c l u d e следует название заголовочного файла. Если этот файл является стандартным,
т.е. тем, который поставляется вместе с компилятором, возьмите его название в угловые скобки (< >):
#include <iostream.h>
Компилятор знает, где искать свои собственные заголовочные файлы, и сразу переходит к нужной дирек- :
тории. Если вы хотите подключить заголовочный файл, который вы создали сами, возьмите его название
в кавычки. Это будет указанием компилятору начать поиск в текущей папке, а в случае необходимости
продолжить в папке со стандартными заголовочными файлами: ;
. #include " f o o . h "
Можно также указать полный путь к файлу:
#include "\michael\jukebox\foo.h"

Язык C++ имеет очень мало встроенных команд, но зато очень много библиотечных функ-
дий. Многие библиотечные функции доступны всем компиляторам C++, поэтому вы всегда мо-
жете получить к ним доступ и использовать их в своей работе. Другие функции являются над-
стройками (add-ons). Можно, например, приобрести дополнительные библиотеки, содержащие
функции, для проведения статистических вычислений или для обработки изображений.

Глава 3. Приступаем к созданию программ 45


займемся делом
Теперь вы знаете достаточно для того, чтобы создать версию программы HelloWorld, на-
пиеаннио на стандартном C++. А потому приступим.
1. Выберите команду F i l e ^ N e w ^ P r o j e c t ( Ф а й л ^ С о з д а т ь 1 ^ П р о е к т ) .
2. В разделе Project Types (Тип проекта) выберите Visual C + + Project. В разделе
T e m p l a t e s ( Ш а б л о н ы ) выберите W i n 3 2 Project.
3. В поле Name (Название) наберите HelloWorld2.
По умолчанию Visual C++ приведет в действие команду Close Solution (Закрыть за-
дачу), в результате произойдет закрытие текущей задачи (открытой для создания
проекта HelloWorld) и открытие повой для создания проекта Hel!o\Vorid2. He отме-
няйте выполнение этой команды. Одна задача может содержать в себе несколько
проектов (для этого, собственно, задачи и существуют), однако разработка много-
проектных з а д а ч — дело далеко не из легких, поэтому лучше для каждого нового
проекта открывать новую задачу.
4. Щ е л к н и т е на кнопс О К .
Будет запущен мастер Win32 Application Wizard, окно которого появится на экране
(рис. ЪЛ).

Vin33 Application Wizard -


Welcome to I he Win 12 Application Wizard
1hs wiii-d gen appScatcr projirt Tr*fKojpct :sn oe а соли* вргкасюп, а НЕ
•-VridO44 app|tc iDLL.orssidfcllbr.

t Г These flre the current protect seUngf:


• WIKJOWS ipptcatlon
Ck^Fttitth from any wurtw to «cept the cwrenl settings.
After you create the prelect, s*t the pro|#i;t't readme, t*r Fie for Wnrmatnn
about V-i4 protect feitires and files thai «в generated,

tel j _

РИС. 3.4. Мастер Win32 Application Wizard поможет вам создам про-
грамму HelloWorld2

Щ е л к н и т е на вкладке Application Settings.


Мастер отобразит настройки, которые можно установить для создаваемой программы.
Выберите о п ц и ю Console Application вместо установленной по умолчанию
W i n d o w s Application.
(Console Application также является приложением Windows, как и W i n d o w s Appli-
cation, Но почему-то многие, уиндсв название "Windows Application", думают, что
iiucie выбора этого шаблона будет сраз\ же создано приложение с набором окон,
кнопок и с потрясающей графикой. В действительности все это нужно создавать са-
мостоятельно, используя возможности Visual O ~ . )

46 Часть I. Первое знакомство с Visual C++ .NET


7. Щелкните на кнопке Finish (Готово).
Через несколько секунд будет создана новая задача, уже содержащая какие-то исход-
ные коды.
8. В окне Solution Explorer дважды щелкните на названии файла
HelloWorld2.cpp.
Visual C++ откроет файл в окне редактора кодов. Вы увидите, что Win32 Application
Wizard уже создал для вас какие-то коды. Это очень мило с его стороны, не так ли?
Конечно, большое спасибо, но мы хотим набрать свои собственные коды.
9. Нажмите комбинацию клавиш <Ctrl+-A>, чтобы выделить все коды, и затем
клавишу <Delete>, чтобы избавиться от них.
10. А теперь наберите такой код:
\\ HellcWorld2
\\ Отображение на экране слов Hello World
\\ Неуправляемая

# i n d u c e "stdaf х . h"
# i n d u c e <iosrream. h>

i n t _tmain(int argc, _TCHAR* argv[])


{
\\Вывод на экран
cout -:< "Hello World\n";
return 0;

Синтаксические ошибки
Если вы что-то неправильно наберете или неверно используете команду C++, компилятор сообщит, что вы
. допустили синтаксическую ошибку. Это будет означать, что компилятор либо не может распознать набран-
ное вами слово, либо вы что-то пропустили при использовании команды. Синтаксические ошибки бывают
самых разных видов. Большинство из них, а также способы их устранения описаны в главах 23 и 24.

Вы должны помнить еще по программе HelloWorld, что интегрированная среда IDE не по-
зволила увидеть результат выполнения этой программы. С тех пор ничего не изменилось, по-
этому программу HelloWorld2 также придется выполнять через окно ввода команд, предвари-
тельно построив ее. Для этого выполните ряд действий.
1. Щелкните правой кнопкой мыши на названии HelloWorld2 (на том, которое
выделено полужирным шрифтом, а не на HelloWorld2 . ерр) и выберите ко-
манду Build (Построить).
Visual О ~ откроет окно Output, в котором вы сможете наблюдать проиесс построе-
ния программы и видеть возможные ошибки. Когда построение программы Hcl-
loWorld2 будет закончено, в окне Output отобразится сообщение: "Build: I succeeded.
0 failed. 0 skipped (1 успешно. 0 неудачно, 0 пропущено)".
2. После того как программа Hello\Yorld2 будет построена, откройте окно для вво-
да команд (Command Prompt).
3. Используйте команду CD, чтобы перейти из текущего каталога в каталог, где
сохранен проект HelloWorld2.

Глава 3. Приступаем к созданию программ 47


c
4. Запустите программу, набрав команду Debug >HelloWorld2.
Результат выполнения программы показан на рис. 3.5.

Command Prompt

i:\Documents and Settings\BarnsonNMy DocumentsSUisual Studio ProjectssHellollorld


»>debuffSHelloHorld2.exe
lello World
liSDocunents and Settings\Barnson\Hy DocunentsNUisual Studio Projccts4HelloWorld

Рис. З.5. Выполнение программы HelloWorld2 в окне для ввода команд

Вы могли обратить внимание, что в созданной стандартной программе C++


функция main имеет два параметра. Они называются параметрами командной
строки. Это означает, что при запуске программы из окна Command Prompt, ей
можно передать значения двух параметров. Например, в этом окне можно на-
брать команду ciir * . *. Здесь символы * . * будут параметром командной стро-
ки. Если набрать команду HelIoWorld2 * . *, то символы * . * будут восприня-
ты как значения для параметров a r g c и argv. Однако вряд ли вам это когда-
нибудь пригодится, поэтому не засоряйте голову подобными мелочами.

48 Часть I. Первое знакомство с Visual C++. NET


Глава4
Принятиерешений—делосерьезное
главе...
> Что такое решения и проекты
V Создание новых решений и проектов
> Возможности окна Solution Explorer

Ъ? ели вы читаете эту книгу по порядку, то вы уже должны были создать несколько
^ ^ .NET-про грамм. В процессе их создания использовались многие инструментальные
средства разработки языка Visual C++. Закончив читать часть I книги, вы будете уметь поль-
зоваться еще большим количеством возможностей окружения Visual C++. Основной темой
этой главы являются решения и проекты.
Решения и проекты упрощают процесс создания программ благодаря объединению всех
необходимых исходных файлов и прочих сопутствующих элементов. Файл проекта содержит
информацию обо всех исходных файлах, из которых состоит программа. Файлы проектов
также упрощают добавление других исходных файлов к программе и позволяют контролиро-
вать различные параметры присоединения этих файлов к программе. Решение-— это просто
один или несколько проектов,

Ж/гавильное ftetuettue можешь сделать


вас сшеййливым
Некоторые программы состоят из одного файла. Но большинство программ, в том числе и
те, создание которых было описано в главах 2 и 3, имеют несколько большие размеры. Для их
выполнения требуются различные файлы с исходными кодами, а также заголовочные файлы
и библиотеки. Чтобы создать выполняемую версию программы, нужно откомпилировать все
эти файлы, а затем связать их воедино.
Сделать это можно двумя способами. П е р в ы й — использовать инструмент командной
строки, называемый NMAK.E, и построить формирующий файл (makefile). Второй (более
удобный и элегантный)— использовать решения и проекты. Формирующий файл состоит из
списка команд, с помощью которых создается приложение. Например, в этом списке могут
быть команды, указывающие, что нужно откомпилировать подпрограмму foo, подпрограмму
bar, затем связать их с библиотекой muck и т.д. Создание формирующих файлов сопряжено с
многими трудностями. Нужно знать множество деталей о том, как файлы компилируются и
связываются. Более того, нужно знать специальный язык создания формирующих файлов!
Однако большим преимуществом создания формирующих файлов является тот факт, что
инструмент NMAKE определяет, какие файлы были изменены с момента последнего созда-
ния приложения. Поэтому при повторном построении приложения заново откомпилированы
будут только эти измененные файлы. Иногда это очень экономит время.
Файлы проектов — это те же формирующие файлы, которые Visual CM i создает автома-
тически н позволяет управлять ими с помощью визуальных средств, без необходимости зна-
ния всех деталей работы компилятора.

\ Глава 4. Принятие решений — депо серьезное 49


Файлы проектов сделают вашу жизнь проще
Файлы проектов, как и формирующие файлы, предназначены для построения приложе-
ний. Однако есть несколько причин, по которым использование файлов проектов намного
проще, чем формирующих файлов. Первая— при использовании файлов проектов компиля-
тор сам просматривает исходные файлы и определяет все зависимости. (Зависимости — это
наборы файлов, изменение которых требует повторной компиляции проекта.)
Вторая причина— управление файлами проектов осуществляется визуальными средства-
ми, что намного удобнее и проще. Кроме того, поскольку Visual C++ сам знает, как компили-
руются файлы C++, как они связываются и т.п., вам самим не нужно точно регламентировать
этот проиесс. Все. что от вас требуется. — просто добавлять исходные файлы к файлу проек-
та. Всю остальную работу сделает Visual C++. Удобно, не правда ли?
Кстати, вы уже могли сталкиваться с файлом проекта. t-сли вы пробовали самостоятельно
воспроизвести примеры программ из предыдущих глав, мастер AddWizard создавал для ва-
ших программ файл проекта, содержащий список всех исходных файлов.
Можно использовать окно Solution Explorer для решения разнообразных задач програм-
мирования. Например, в нем можно просмотреть список всех исходных файлов, из которых
состоит проект, и открыть любой из них для редактирования. Там же можно контролировать
все параметры, в соответствии с которыми выполняется построение приложения. Можно
осуществлять саму компиляцию приложения. Более детально возможности этого окна описа-
ны в ра щеле "Что может окно Solution Explorer".

Решения и проекты
В справочной системе Visual C++ слова solution {решение) и project {проект) часто ис-
пользуются вместе. Вообще говоря, эти два слова обозначают разные веши. Решение состоит
из одного или более проектов. Наиболее часто все же одно решение содержит один проект.
Вы сможете смело называть себя хакером, если научитесь создавать решения из нескольких
проектов.
Поскольку решения и проекты, по сути, являются почти одним и тем же, мы будем упот-
реблять их как взаимозаменяемые понятия, за исключением тех случаев, когда информация
будет относиться только к одному из них.

JfocfnftoeHite nftoiftoMM
Построение любой программы подразумевает создание файла проекта и решения. Файл
проекта сообщает компилятору, какие исходные файлы нужно откомпилировать для построе-
ния приложения. Также в нем содержится информация для компилятора о том, какие библио-
теки должны быть присоединены.

Определение параметров нового проекта


Создать новый проект очень просто. Для начата выберите команду File^New^>Project
(Файл 1 ^Создать 1 ^Проект), чтобы открыть показанное на рис. 4.1 диалоговое окно New Project
(Создание проекта). Это окно, в котором определяются параметры будущего проекта. Здесь
нужно указать название проекта, а также каталог, в котором он будет сохранен. Здесь же выби-
рается тип создаваемого проекта и один из стандартных шаблонов.

50 Часть I. Первое знакомство с Visual C++ .NET


j New Project
f"~ —-
Eroe
jct Types;
_j V5
i uaC
l *P^e
j ct:
_1 Seup a'id Depolyment Prc)«ts Гс т с.
_j Other Promts MFC ДФ-еХ MFC Applies ton
_J VsiualStu<b Soktj.ons

> C' MFC ISA?! Win32 Protect v


* WKK console ««Scstlon or other \№i32 project,

yeme: | <£^ter namei

LocsHon: jCi\do;ufn«ntsmd"set(if4s№*f«on\MyboojmenUWi: _-'j

Project wd be ireatud « OV.-^amsc«i\Mv Docur№!4s\Visoal Stud» Proletti^

J СД^'! Help

Рис. 4.1. Использование окна New Project для создания


нового проекта
^ Project Types (Тип проекта): в этом разделе определяется тип создаваемого проек-
та. В данной книге будет рассмотрен только тип Visual С м - Project. В чаиисимости
от того, какой установочный пакет Visual C++ вы приобрели, иписоь лоступных
типов проектов может существенно отличаться от показанного на рис. 4 . 1 .
S Templates (Шаблоны): здесь можно выбрать один из подвидов проектов Visual C++,
основанный на выбранном в разделе Project Types типе. Проекты Visual C++ имеют
более дюжины шаблонов. Некоторые из этих шаблонов в действительности являются
мастерами, которые предоставляют множество возможностей для контроля над тем,
какие именно свойства будут иметь созданные вами программы. С двумя из них вы
будете часто сталкиваться в процессе чтения книги. Это шаблоны Win32 Project и
Managed C++ Application. (Есть много других вариантов, например шаблоны для соз-
дания различных средств управления, библиотек и других \игрых штучек.)

S Name (Название): в этом поле нужно указать название нового файла проекта. Для
проекта обычно используется то же название, что и для соягкшас-чого с его помо-
щью приложения.
S Location (Размещение): укажите в этом поле папку, где должен быть сохранен соз-
даваемый проект. М о ж н о щелкнуть на кнопке Browse (Обюр), чтобы найти суще-
ствующую папку. Если указанной папки еще пег. Visual См- создаст ее автомати-
чески. Хорошей практикой является храпение кеех исходных файлов, имеющих
отношение к определенному проекту, в отдельной пайке. Предположим, например,
что вы создаете приложение, именуемое Jukebo\9. и хотите сохранить его в папке
МояРабота\1икеЬох9. Вначале щелкните на кнопке Browse, чтобы найти папку
МояРабота, или сами укажите путь к ней в поле Location. Затем в поле N a m e в ка-
честве названия проекта наберите J u k e b o x 9 . Visual C++ автоматически создаст
папку Jukebox9 в папке МояРабота.

•/ Add to Solution/Close Solution (Добавить к решению/Закрыть решение): как вы


помните, решение может состоять из идного ИЛИ нескольких проектов. Поэтому
при создании нового проекта его можно диСо добавить к открытому в данный мо-
мент решению (Add to Solution), либо закрыть -г::к\шее решение (Close Solution)
и создать новое для нового проекта.

Глава 4. Принятие решений — дело серьезное 51


После того как вы укажете всю необходимую информацию, щелкните на кнопке ОК.
Затем для большинства типов проектов открывается мастер, позволяющий определить
дополнительные параметры создаваемого проекта. Некоторые мастера, такие как Win32
Application Wizard, с которым вы встречались в главе 3, состоят из нескольких шагов.
Другие, попроще, состоят из одного шага и позволяют лишь добавить какие-то файлы к
вашему проекту.

Добавление файлов к проекту


Чтобы добавить новый файл, выполните ряд действий.
I. Выберите команду File^New^File (Файл^Создать^Файл).
Откроется диалоговое окно New File (рис. 4.2).

_J scrp
it C++Ffe(.cpp) MeadsrFte(.h) M
diP File (.i

Resource File Stmac hie (.Drop) Cursor File (.cur)


(.re)

Creates a C++ source fife.


Cancel

Рис. 4.2. Использование диалогового окна New File для созда-


ния нового исходного файла

2. В разделе Categories (Категория) выберите пункт Visuaf C++.


3. В разделе Templates (Шаблоны) выберите C++ File(.cpp).
4. Щелкните на кнопке Open (Открыть).

К проекту будет добавлен пустой файл C++.


Чтобы добавить уже существующий файл, выполните следующее.
1. Выберите команду Projects Add Existing Item (ПроектО Добавить существую-
щий элемент).
Откроется диалоговое окно Add Existing Item. Принцип его работы тот же, что и у
всех диалоговых окон Windows, предназначенных для открытия файлов.
2. Выберите файл, который хотите добавить к проекту.
Если вы хотите добавить сразу несколько файлов, выделите их, удерживая нажатой
клавишу <Ctrl>. После того как файлы будут добавлены к проекту, окно Solution
Explorer будет выглядеть приблизительно так, как показано на рис. 4.3.

52 Часть I. Первое знакомство с Visual C++ .NET


:5oiJtion Explorer - KllerApp

^ Solution 'KillerApp' (1 project)


- £p KillerApp
- •:_$ Source Files
^] KilerApp.cpp
i^] stdafx.cpp
\J2 MainFrm.cpp
[£j ChildFrrn.cpp
^j) KilerAppDoc,cpp
^ KilerAppView.cpp

L J Header Files
_J Resource Files
\J\ ReadMe.txt

!Sft Solution Explorer | Щ

Рис. 4.3. В окне Solution Explorer отображается спи-


сок файлов, из которых состоит ваш проект (решение)

Ч/но може/н окно Solution


Окно Solution Explorer может быть использовано для решения множества текущих задач.
Ниже приведен список некоторых из них, к которым вы будете наиболее часто возвращаться
в процессе разработки приложений.
| S Просмотр и редактирование файлов, из которых состоит проект. Дважды
\ щелкните на названии файла, чтобы открыть его в окне редактора кодов. Если этот
| файл еще не имеет реального наполнения, Visual C++ спросит, хотите ли вы соз-
| дать новый файл. Ответьте "Да", чтобы увидеть перед собой девственно чистое ок-
| но редактора кодов.
I S Добавление к проекту новых файлов. Щелкните правой кнопкой мыши на на-
s звании проекта и выберите команду A d d ^ A d d Existing Item (рис. 4.4). Эти дейст-
/, вия эквивалентны выбору команды P r o j e c t ^ A d d Existing Item.

AdftdUW . . .

Look л: bKfcTApp - J ,- T- - >• r, R - тоф-

.£3 ChWFm.cpp
History
j cj] KlerApp.cpp

My Projects
Jl] tJlerApoDoc.h

, h ] Cesou-ct.h
* I
FaveKes icjjstdais.cpp

FJe riamei 72 1 2psri "1


MyNetwork
Pisces Visual C-+ Fiss (".c, " cpc.; - c>. • cc; • til; * . * , " h. - ;

Рис. 4.4. Использование Solution Explorer для добавления


к проекту уже существующих файлов

Глава 4. Принятие решений - дело серьезное 53


Окно Solution Explorer отображает зависимости проекта в папках Header Files
(Заголовочные файлы) и Resourse Files (Файлы ресурсов), как показано на рис. 4.5. Файлы ре-
сурсов представляют собой отдельные ресурсы программы, такие как растровые отображения
графических объектов и пиктограммы. Они являются такими же зависимостями, как заголо-
вочные файлы.

Solution Explorer

Solution 'KillerApp' (1 project)

__J;5ource Files;
' M Header Files
_-J KilerApp,h
_J stdafx.h
у MainFrm.h
^ ChildFrm.h
i_J killerAppDoc.h
^\ KilierAppView.h
i j Resource.h
'" j Resource Files
J3 KillerApp.rc
V j KillerApp.rc2
_J] KillerAppDoc.ico
^ J KillerApp ,ko
j^ Toolbar.brnp
_J KillerApp.manifest
J ReadMe.txt

^ SoluHon Explorer 1 Щ

Рис. 4.5. Отображение зависимостей


проекта в окне Solution Explorer
Решения и проекты являются важным аспектом Visual C++. Их использование необходи-
мо для построения программ и для быстрого поиска и просмотра нужных исходных колов.
При разработке приложений работа в окне Solution Explorer занимает наибольшее количест-
во времени. После написания кодов, конечно.

54 Часть I, Первое знакомство с Visual C++ .NET


Глава 5

Хороший редактор — что еще нужно


бывалому программисту?
Вэ&ой главе...
> Открытие и сохранение файлов в окне редактора кодов
> Обычные и специальные задачи редактирования
V Выделение цветом отдельных "элементов программы
> Получение контекстно-зависимой справки
> Использование строки перехода
> Сокрытие отдельных фрагментов кодов
> Поиск нужного текста

Ж0* олыпую часть процесса разраоотки программ!.i занимав! написание кодов в окне ре-
*-^ дактора. Как хороший текстовый редактор кардинально упрошает процесс написа-
ния книги (ну хорошо, не кардинально, но все же упрощает), так и лпрошии редактор кодов
значительно облегчает процесс создания программ. Рс^икптр ><(>••)<>(; позволяет решать не
только обычные задачи редактирования, такие как копирование, вырешше и вставка текста,
но и специальные задачи, касающиеся создания программ.
Visual С+-г имеет многофункциональный настраиваемый релш-лор кодов, позволяющий
решать многие специальные задачи, например такие, как одновременный сдвиг нескольких
строк или быстрое подключение заюловочного файла. В этой главе дается краткий обзор
наиболее важных возможностей этого редактора.

Afoq&t а/щеапвцимп qua tnoio,


их fteqataiiufio^atnb
Visual C++ позволяет открывать для редактирования любое количество файлов (можег и не
любое, но достаточно большое). Если вы открыли cpajy несколько файлов, коды каждого из них
будут отображаться в отдельном окне. Visual C-+ поддерживает как многооконный интерфейс
MD1 (Multiple Document Interface), так и отображение окон а аиле вкшдок. IMDI — ТУ о всего
лишь интерфейс, позволяющий отображать на экране одновременно несколько окон и переме-
щать их независимо друг от друга.) Режим отображения окон в виде вкладок установлен по
умолчанию. Как обычно выглядит окно редактора кодов, показано на рис. 5.1.
Для открытия окна редактора кодов обычно используют один из перечисленных ниже
способов.

^ Открытие для редактирования одного из файлов проекта. Дважды щелкните


\ на названии файла в окне Solution Explorer. Коды л о г о файла б\д\т отображены в
% окне редактора.

Глава 5. Хороший редактор — что еще нужно бывалому программисту? 55


/ Редактирование элемента класса. Дважды щелкните на названии элемента в спи-
ске Class. Файл будет открыт в редакторе кодов, при этом курсор будет помешен
перед выбранным элементом. Можно также щелкнуть на названии элемента правой
кнопкой мыши и выбрать команду Go to Definition (Перейти к определению), что-
бы открыть для редактирования коды исходного файла (с расширением .срр), или
выбрать команду Go to Declaration (Перейти к объявлению), чтобы открыть для
редактирования коды заголовочного файла (с расширением .h).
S Создание нового файла. Выберите команду File^New^File (ФайлОСоздать^
Файл), чтобы открыть диалоговое окно New- File. Выберите категорию создаваемо-
го файла, его тип и щелкните на кнопке ОК.
V Открытие существующего файла. Выберите команду File^Open^File. В от-
крывшемся диалоговом окне укажите название файла, который вы хотите открыть
для редактирования.

Ы^г Годе HelloWorltfc.cpp i i X

^__
f
_ — q пг~. ..--.—* Е :
" / / H e l l o W o r l d 2

/ / P r i n t s h e l l o w o r l d o n t h e s c r e e n

/,/ U n n i d n d g e d

# i n c i u d e " s t d a f x . h "

^ i n c l u d e < i o s t r e a m . h >

i n t _ t m a i n ( i n t a r g c j _ T C H A R * a r g v [ ] )

i X

/ / W r i t e t o t h e s c r e e n

c o u t < < " H e l l o W o r l d \ n " ;

r e t u r n 0 ;

Рис. 5.1. Редактор используется для отображения, ввода и редактиро-


вания кодов программы
Сохранить отредактированный файл также очень просто. Можно даже сохранить сразу
все файлы, открытые для редактирования. Вот как это делается.
I V Сохранение файла. Выберите команду FileVSave НмяФапла.
I S Сохранение файла с присвоением ему нового имени. Выберите команду
1 File\Save ИмяФаша As. В диалоговом окне наберите новое имя файла и щелкните
| на кнопке Save (Сохранить).
\ •/ Сохранение всех файлов, в которые были внесены изменения. Выберите ко-
•• манду File^Save All. Рекомендуется это делать перед тем, как запускать на вы-
•}. полнение только что созданную программу. Если вы этого не сделаете и новая про-
| грамма приведет к сбою в работе компьютера и к его перезагрузке, вся проделан-
|" ная вами работа будет потеряна.

56 Часть I. Первое знакомство с Visual C++ .NET


В процессе написания кодов вы можете заметить, что некоторые действия приходится
выполнять снова и снова. Одни из них относятся к разряду обычных (например, вырезание
или копирование текста). Другие возможны только при написании программ (например, од-
новременное смешение группы строк или открытие заголовочного файла). В табл. 5.1 приве-
ден список наиболее часто выполняемых задач и описание способов их решения. Более спе-
цифические задачи описаны после более общих.

Таблица 5.1. Задачи редактирования, решаемые в процессе написания кодов программ


Задача Способ решения

Выделение текста Щелкните в позиции, с которой должно начинаться выделение. Нажмите


левую кнопку мыши, перетащите курсор к позиции, в которой выделение
должно заканчиваться, и отпустите кнопку мыши.

Или щелкните в позиции, с которой должно начинаться выделение,


нажмите и удерживайте клавишу <Shift> и щелкните в позиции, в
которой выделение должно заканчиваться.

Или используйте клавиши управления курсором, чтобы перейти в


позицию начала выделения, нажмите и удерживайте клавишу <Shift> и,
опять используя клавиши управления курсором, перейдите в позицию
окончания выделения

Вырезание текста Выделите текст. Выберите команду E d i t ^ C u t (Правкаовырезать) или


нажмите клавиши <Ctr1+X>. Можете затем вставить вырезанный текст в
любом другом месте. (Для вырезания текста можно также щелкнуть
правой кнопкой мыши на выделенном фрагменте и выбрать из
открывшегося контекстного меню команду Cut.)

Копирование текста Выделите текст. Выберите команду E d i t ^ C o p y (Правка^Копировать)


или нажмите клавиши <Ctrl+C>. Можете затем вставить скопированный
текст в любом другом месте. (Для копирования текста можно также
щелкнуть правой кнопкой мыши на выделенном фрагменте и выбрать из
открывшегося контекстного меню команду Сору.)

Вставка текста Выберите команду EdhX>Paste (Правка^Вставить) или нажмите


клавиши <Ctrl+V>. В окно редактора будет вставлен текст из буфера
обмена. (Можно также щелкнуть правой кнопкой мыши в позиции, где
должен быть вставлен текст, и выбрать из открывшегося контекстного
меню команду Paste.)

Переход в начало файла Нажмите <Ctn>Home>

Переход в конец файла Нажмите <Ctrl+End>

Переход на страницу назад Нажмите клавишу <PgUp>

Переход на страницу вперед Нажмите клавишу <PgDn>

Переход вправо на одно слово Нажмите «ЗгКстрелка вправо>

Глава 5. Хороший редактор — что еще нужно бывалому программисту?


Окончание табл. 5.1
Задача Способ решения

Переход влево на одно слово Нажмите <С1г1+стрелка влево>

Одновременный сдвиг Выделите несколько строк и нажмите клавишу <ТаЬ>. Это может быть
нескольких строк полезно, например, если вы добавили оператор i f и хотите выделить
часть кодов, которые к этому оператору относятся

Отмена сдвига нескольких строк Выделите строки и нажмите <Sfiift+Tab>. Этот прием может быть
полезен, если вы скопировали часть кодов в другое место программы и
теперь нет необходимости специально выделять их с помощью отступов

Переход ко второй скобке из Нажмите <Ctri+]>, чтобы перейти от открывающей скобки (, {, < или
пары скобок [ - курсор уже должен находиться перед ней - к соответствующей ей
закрывающей скобке:),}, > или ]. Тем же нажатием выполняется
обратный переход.
Эта возможность полезна в случае, если вы имеете много вложенных
друг в друга операторов и хотите найти границы каждого из блоков.
Также таким образом можно проверять, не забыли ли вы закончить
вызов функции закрытием скобки

Использование закладок Поместите курсор в том месте, где вы хотите оставить закладку.
Нажмите дважды < Ш + К > . Таким образом вы огметите позицию в
кодах программы, к которой при необходимости сможете быстро
вернуться.
Например, если вы хотите скопировать фрагмент кодов из одной части
программы в другую, установите закладку, найдите нужный фрагмент
кодов, скопируйте его, вернитесь обратно к закладке и вставьте
скопированный фрагмент

Переход к закладке Нажмите <Ctr!+K> и <Ctr!+N>, чтобы перейти к следующей закладке в


файле. Нажмите <Ctrl+K> и <Clri+P>, чтобы перейти к предыдущей
закладке

Переключение между окнами Нажмите <Ctrl+Tab> или <Ctrl+F6>, чтобы перейти к следующему окну
редактора редактора (которое было открыто после текущего). Нажмите
<Shift+Ctrl+Tab> или <Shift+Ctrl+F6>, чтобы перейти к предыдущему
окну редактора (которое было открыто перед текущим)

Открытие заголовочного файла, Щелкните правой кнопкой мыши на команде # i n c l u d e и выберите из


соответствующего открытому открывшегося контекстного меню команду O p e n Document.
файлу При написании программ C++ редактировать заголовочные файлы
приходится почти так же часто, как и исходные файлы. Иногда открывать
заголовочный файл необходимо для того, чтобы просмотреть, что уже
было определено

Получение справки о командах Щелкните на том элементе программы, по которому нужно получить
справку. Нажмите клавишу <F1 >.
Таким образом, например, можно получать справку относительно
синтаксиса вызова библиотек, Windows API или команд C++

58 Часть I. Первое знакомство с Visual C++ .NET


Зываюйь разные —
белые, красные
В Visual C++ есть возможность выделять цветом отдельные синтаксические элементы,
что значительно упрощает чтение набранных кодов. Выделив цветом отдельные элементы
программы, такие как комментарии, ключевые слова, значения и переменные, вы делаете для
себя их идентификацию автоматической. Этот прием также облегчает обнаружение типичных
синтаксических ошибок.
Например, если все коды программы будут отображаться черным цветом, а коммента-
р и и — синим, вы сразу увидите, что забыли закрыть какой-то комментарий (поставив в его
конце символы * / ) , поскольку далее за ним будет открываться бескрайнее море синего тек-
ста. Если установить, что все ключевые слова должны отображаться зеленым цветом, и если
какое-то набранное ключевое слово не окрасится в зеленый цвет, вы сразу же поймете, что
при его наборе допустили синтаксическую ошибку. Например, если вы наберете c l a p s вме-
сто c l a s s , набранное слово зеленым не станет. Точно так же, если при объявлении какая-то
переменная окрасится в зеленый цвет, вы сразу поймете, что это имя для нее неприемлемо,
поскольку имена переменных не должны совпадать с ключевыми словами. (Более детально
правила присвоения имен переменным будут рассмотрены в главе 8.) Чтобы определить цве-
та для различных синтаксических элементов программы, выберите команду ToolS^Options
(Сервис^Параметры) и во вкладке Environment (Окружение) перейдите к опциям Fonts and
Colors (Шрифты и цвета).
На рис. 5.2 показано окно редактора кодов с включенной возможностью выделения цве-
том. (Разумеется, поскольку вы смотрите на черно-белый рисунок, для вас это больше похоже
на выделение серым цветом.)

1ODO;addmemberinitialisationcodehere~
CChd
liFrame:~CChd
iFrame()

BOOLCChd liFrame:PreCreateWnidow(CREATEST
{
/,'TODO;ModfiytheWn idowd.siborstylesher-
if(!CMDIChd liWnd:PreCreateWnidow(cs))
returnFALSE;
returnTRUE;
Рис. 5.2. Выделение цветом отдельных синтаксических элементов про-
граммы значительно облегчает их визуальную идентификацию

Глава 5. Хороший редактор — что еще нужно бывалому программисту?


59
Ломощь, KOtnofiasi всегда /гл^ом
Если вы используете C++, окружение .NET, библиотечные функции, среду Windows и не пом-
ните или не знаете, как работает то или иное средство, либо что обозначает тот или иной элемент,
нужная подсказка находится на "расстоянии" одного щелчка. Например, допустим, что вы исполь-
зуете библиотечную функцию c o u t , но не уверены в корректности такого выражения:
c o u t << " B o b " ;
Щелкните на слове c o u t , нажмите клавишу <FI>, и тут же откроется окно с необходимой
справочной информацией.
Рассмотрим другой пример. Предположим, вы набрали такое выражение:
w h i l e { s t r l e n ( b a r ) > 10)
Можно щелкнуть на ключевом слове w h i l e , нажать клавишу <F1> и получить справку об
использовании команды w h i l e , а можно щелкнуть на слове s t r l e n , нажать клавишу <F1> и
получить справку об использовании библиотечной функции s t r l e n .

Навигацил по nfioanoficui nftozftaMMbt


Чтобы добиться правильного выполнения программы, в процессе редактирования ее ко-
дов вам придется переключаться с одной функции на другую почти так же часто, как перехо-
дить от одной строки программы к другой в пределах одной и той же функции.
На панели перехода, расположенной в верхней части окна редактора кодов, вы можете
видеть названия классов и функций, используемых в открытом в данный момент файле. Что-
бы быстро перейти к нужной функции выполните следующее.
1. Щелкните на поле со списком, расположенном слева.
Откроется список используемых классов.

ChildFrm.cpp
^•CChildFrame GetRuntimeClass
Y TODO: add member • AssertVaiid
• CChildFrame
• CreateObject
• Dump
CChildFrame::~CChildFr • GetRuntimeClass
{ • GetThisClass
} •f^CreSeWsi
-•-CChiidFrame

BOOLCChildFrame::PreCreateWindow(CREATEST
{
/.,' TODO: Modify the Window rkn^s or stylos her-
if( !CMDIChildWnd::PreCreateWindow(cs) )
return FALSE;

return TRUE;

РИС. 5.З. Панель перехода автоматизирует переход к нужным эле-


ментам программы

60 Часть I. Первое знакомство с Visual C++ .NET


2. Выберите интересующий вас класс.
3. Щелкните на поле со списком, расположенном справа.
Откроется список функций, используемых в выбранном классе (рис. 5.3).
4. Выберите функцию, коды которой хотите просмотреть или отредактировать.
Visual C++ переместит курсор к этой функции.
Обратите внимание, что панель перехода отображает только классы и функции, исполь-
зуемые в текущем файле. Если вы хотите просмотреть другие элементы классов, используйте
средство Class view.

Cacftbtffiue и отображение кодов


Иногда обилие кодов в открытых окнах нескольких проектов может создавать впечатление
хаоса. Разработчики Visual C++ учли эту проблему и проедложили способ ее решения. Те фраг-
менты кодов, которые в данный момент вас не интересуют, могут быть свернуты, чтобы не за-
нимать место на экране. А если вы снова захотите их просмотреть, их отображение можно бы-
стро восстановить. Набор строк, который одновременно может быть свернут или вновь отобра-
жен, называется фрагментом кодов. Фрагменты могут быть созданы автоматически, либо вы
сами можете их определить. Рядом с каждым фрагментом расположен знак "плюс" или "минус",
позволяющий отображать либо сворачивать нужный фрагмент (рис. 5.4).

MainFrm.cpp
j*J CMan
i Frame _•] j •PreCreateWindow

CMainFrame::CMainFrame(),..L
CMainFrame::~CMainFrame().,?i
! int CMainFrame::OnCreate(LPCREATESTRUCT IpCi
BOOL CMainFrame::PreCreateWindow(CREATESTI
{
if( !CMDIFrameWnd::PreCreateWindow(cs) )
return FALSE}

return TRUE;

void CMainFrame::AssertValid()..,

Рис. 5.4. Свернув коды, вы можете видеть "общую картину"


Существует два основных способа свертывания кодов.
!; ^ Свертывание произвольных фрагментов. Вы можете легко свернуть любой нуж-
\ ный вам набор кодов. Для этого просто выделите его, щелкните на нем правой кноп-
1. кой мыши и выберите команду Outlining^Hide Selection (Свернуть1^Скрыть выде-
| ленное). Можно также выделить нужный набор кодов и в главном меню выбрать ко-
| манду Edit^Outlining^Hide Selection (Правка 1 ^Свернуть^Скрыть выделенное).

Глава 5. Хороший редактор — что еще нужно бывалому программисту?


61
Свертывание блоков. Visual C++ автоматически выделяет структурные элементы язы-
ка С+-. Таким образом вы можете сразу сворачивать отдельные блоки программы.
(Более подробно о блоках программы, к которым относятся циклы, операторы управле-
ния потоками данных и функции, речь идет в части П книги.) Чтобы пол>чить возмож-
ность сворачивать отдельные блоки, выберите команду Edit^Outlining^Collapse to
1
Definitions (Правка ^ Свернуть^ Сворачивать по определениям). Visual C++ выделит в
отдельные фрагменты каждое определение функции, определение класса и все прочие
блоки, имеющиеся в кодах открытого в данный момент окна. Также будут выделены все
блоки, входящие в состав дргих блоков.

JIoucic и авйгозамеяа
В процессе написания программ вы очень часто будете сталкиваться с необходимостью
поиска некоторых уже набранных кодов. Вместо того чтобы самостоятельно просматривать
коды всей программы в поиске нужного фрагмента, доверьте это занятие редактору кодов.
Чтобы найти какой-то фрагмент текста программы, воспользуйтесь командой Edit^Find
and Replace^Find (Правка^! 1оиск и замена^Найти) или нажмите клавиши <Cirl+F>. Если
найденный фрагмент текста нужно сразу же заменить каким-то другим, примените команду
Edit^Find and Replace^Replace (Правка^Поиск и замена^Заменить) или нажмите ком-
бинацию клавиш <CtrI+H>. Например, можно использовать команду Edit^Find and Replace
^Replace, чтобы найти в тексте программы все упоминания переменной foo и заменить их
переменной goo.
Диалоговые окна Find (Поиск) и Replace (Замена) позволят вам определить множество
дополнительных установок, контролирующих процесс поиска нужных фрагментов в тексте
программы. Диалоговое окно Find показано на рис. 5.5.

Откуда берутся все эти загадочные термины


Подстановочные знаки иногда называют regular expression matching commands {команды сопоставления ре-
= гулярных выражений). Этот громоздкий термин появился благодаря некоторым особенностям работы компи-
\ ляторов.
При построении компиляторов создаются так называемые лексические анализаторы - специальные про-
граммы, просматривающие текст исходных файлов и разбивающие его на отдельные составляющие, ко-
торые компилятор сможет как-то интерпретировать. Разбивка текста файлов осуществляется на основа-
нии сопоставления его с заранее определенными образцами {или шаблонами). Эти шаблоны и называ-
• ются регулярными выражениями.
Таким образом, сопоставление регулярных выражений обозначает всего лишь поиск и идентификацию
текста, соответствующего заданным шаблонам.
Но вы можете называть это просто использованием подстановочных знаков.
Регулярные выражения, представленные в табл. 5.3, иногда называются регулярными выражениями GREP,
поскольку они используются программой GREP - одной из наиболее распространенных программ поиска
текста. GREP стала популярным инструментом именно в системах UNIX. А как известно, многие инструменты
UNIX имеют такие своеобразные названия, как MAWK, SED, DIFF, или как GREP. В разговоре с каким-нибудь
хакером, можете спокойно использовать слово grep вместо слова поиск, если вы имеете в виду поиск текста
по заданному шаблону. Если это настоящий хакер, он вас поймет.

62 Часть I. Первое знакомство с Visual C++ .NET


Find
! Find what: 'fane and riches

, Г* Match c,ase Search

Г" Match whole word


; Г" Search hidden text All open documents
• Г" Search up

Г Use;

Рис. 5.5. Укажите, что нужно найти, и вы немедленно получите результат


Ниже приведен список основных опиий диалоговых окон Find и Replace.
Match case (С учетом регистра). В языке О + строчные и прописные буквы счи-
таются разными. Например, слова Boogs и boogs рассматриваются как названия
двух разных переменных. Если вы активизируете опцию Match case, будут найде-
ны (или заменены) только те слова, в которых очередность строчных и прописных
букв точно совпадает с заданным образцом. Используйте ее только в том случае,
если точно знаете, какое слово должно быть найдено (учитывая строчные и про-
писные быквы). Если есть какие-то сомнения, отключите эту опцию.
Match whole word only (Только целые слова). Если эта опция активизирована, об-
рабатываться будут только отдельные целые слова (слова, которые отделены от
других слов пробелами, запятыми или скобками). Если вы эту опцию отключите,
найден будет также тот текст, который совпадает со сравниваемым образцом и яв-
ляется составной частью другого слова. Например, если вы отключите эту опцию и
укажете для поиска текст c o n s t , он будет найден в таких словах, как c o n s t a n t и
deconstruct.
• Search hidden text (Просматривать скрытый текст). Если вы активизируете эту
опцию, просматриваться также будет скрытые (в результате сворачивания) фраг-
менты текста.
Search up (Найти выше). Обычно поиск ведется от текущей позиции курсора вниз
до конца файла. Эта опция указывает редактору, что нужно просматривать текст от
текущей позиции курсора вверх до начала файла.
Use regular expression and wildcards (Использовать регулярные выражения и
подстановочные знаки). Подстановочные знаки— это специальные символы, ко-
торые используются для представления других символов. Например, предположим,
что нужно найти какую-то переменную и вы не помните точно ее названия, но
знаете, что оно начинается с буквы. В таком случае при указании образца для по-
иска наберите между буквами S и h подстановочный знак * (звездочка), который
заменит собой произвольное количество любых других символов.
Подстановочные знаки являются упрощенным вариантом более гибкого инстру-
мента— регулярных выражений. В табл. 5.2 приведен список наиболее часто
употребляемых подстановочных знаков. В табл. 5.3 вы найдете перечень основных
регулярных выражений.
Search: Current window^Current document (Поиск: Текущее окно^Текущий до-
кумент). Эта опция сообщает редактору, что нужно просматривать только тот
файл, который открыт в текущем окне.
Search: All Open documents (Просмотр всех открытых документов). Эта опция
сообщает редактору, что нужно просматривать все открытые в данный момент
файлы. Эта возможность может быть полезной, например, если вы хотите изме-
нить имя класса, используемого в разных файлах проекта.

Глава 5. Хороший редактор - что еще нужно бывалому программисту? 63


Search: Only <current block> (Просмотр только текущего блока). Поиск может
быть ограничен пределами блоков, на которые разбивается текст или коды. Если
поиск в пределах блоков будет возможен. Visual C++ сделает эту опцию доступной
и укажет параметры этого поиска.
Search: Selection only (Просмотр только выделенного фрагмента). Эта опция позво-
ляет заменить текст только в выделенном в данный момент фрагменте. Это удобно,
если необходимо, например, изменить имя переменной для какой-то одной функции.
Mark All (Пометить все). Щелчок на этой кнопке дает указание Visual C++ найти
все соответствия, но не отображать их на экране, а просто пометить, с тем чтобы
впоследствии вы могли к ним вернуться.

Таблица 5.2. Подстановочные знаки


Команда Значение
'•• Заменяет один произвольный символ. Например, шаблону s ? i p соответствуют слова S k i p ,
Swip и т.п.
* Заменяет любое количество произвольных символов. Например, шаблону s i * сооветствуют
слова S I , S l i p , S l i d i n g и др.
!
-] Заменяет один из символов, указанных с скобках. Например, шаблону S [ m l ] ug
соответствуют слова Smug и Slug
',! ] Заменяет любой символ, за исключением тех, что указаны в скобках. Например, шаблону
S [ - m l ] ug соответствуют такие слова, как s t u g , Swug, но не Smug и S l u g

Таблица 5.3. Команды сопоставления регулярных выражений


Команда Значение
Заменяет собой любой один символ. Например, шаблону s . ip соответствуют слова S k i p ,
Swip и т.п,
* Заменяет любое количество произвольных символов. Например, шаблону s i * соответствуют
слова S I , S l i p , S l i d i n g и др.
+ Заменяет любое количество символов, предшествующих (по алфавиту} символу, после
которого эта команда набрана. Например, шаблону So* соответствуют слова Soon, Son,
So и др.
Ищет соответствие по началу строки. Например, шаблон Л / / находит все комментарии,
которые начинаются от начала строки
$ Ищет соответствие по концу строки. Например, шаблон f оо$ находит только те слова f оо,
которые набраны в конце строки
['- Заменяет собой один из символов, набранных внутри скобок. Например, шаблону s [ m l ] ug
соответствуют слова Smug и Slug
['" ] Заменяет собой любой символ, кроме тех, что набраны внутри скобок. Например, шаблону
Б [ Л т 1 ] и д соответствуют слова Saug, Skug и другие, но не Smug и S l u g
[ - ] Заменяет собой любую букву из диапазона, указанного в скобках (включая сами набранные
буквы). Например, шаблону S [ c - l ] u g соответствуют только слова из набора Scug,
Sdug, Seug, . . . Slug

64 Часть I. Первое знакомство с Visual C++ .NET


Глава 6

Компиляция программ, или


Первые трудности
главе...
> Компиляция программ
> Синтаксические ошибки и предупреждения
> Различие между компиляцией, построением и повторным построением

'ГТ аписание кодов программ может быть весьма увлекательным занятием. В набранные
• •* формулы и выражения можно вложить самый разнообразный смысл, затем вывести
все эти коды на печать и развеселить ими своих друзей. Например, с помощью команд и опе-
раторов можно перевести на язык программирования хорошо всем известные крылатые фра-
зы. Если бы Шекспир был программистом, он мог бы написать что-то наподобие if (_2В)
{} e l s e {}. (А если бы он был толковым хакером, он написал бы_2В ? {} : { }.)
Однако, если вы хотите, чтобы программа была действительно полезной и выполняла ка-
кие-то действия, одного лишь красивого текста недостаточно. Программу нужно откомпили-
ровать. Компиляция программы — это преобразование исходных кодов программы, которые
понятны программистам, в машинные коды, которые понятны компьютерам.
Процесс преобразования исходных кодов в машинные крайне сложен. Он требует точного
сопоставления всех набранных вами инструкций (которые называются инструкциями высше-
го уровня) соответствующим инструкциям нижнего уровня (понятным компьютеру). Этот
процесс завершается созданием выполняемой программы. Именно она может повлечь за со-
бой какие-то реальные действия.
О процессе преобразования набранных программистами команд в машинные коды напи-
сано множество книг. К счастью для вас, разработчики, занимающиеся созданием компиля-
торов, все эти книги уже прочитали, поэтому вам вникать в различные тонкости этого про-
цесса не нужно. У вас есть Visual C++, который сам откомпилирует набранные вами коды.

Наншйь /сом/гили/10#а/нб nftotftaMMif


оченьnftoc/no
Процесс создания программы включает набор кодов, компиляцию и отладку- В большинстве
случаев происходит также корректировка работы программы и добавление к ней новых возможно-
стей, что влечет за собой многократную повторную компиляцию. К счастью, в Visual C++ компи-
лировать программы очень просто. В действительности вы уже компилировали программу (и не
один раз), если выполняли инструкции, описанные в главе 3.
Чтобы откомпилировать программу, откройте ее решение (solution). (Решения подробно
описаны в главе 4.) Затем выберите команду Build^Build (Построить^Построить) или щелк-
ните на кнопке Build одноименной панели (рис. 6.1).

Глава 6. КОМПИЛЯЦИЯ программ, ИЛИ Первые трудности 65


Bulk*

Рис. 6.1. На этой панели расположены кноп-


ки, которые запускают процесс компиляции
Visual C++ просматривает все исходные файлы открытого решения и преобразует их к
машинным кодам. В результате создается выполняемая программа. На экране вы не увидите
никаких изменений, но теперь у вас уже есть реальная программа, которая хранится на жест-
ком диске. Ее можно запустить на выполнение, скопировать на дискету и подарить кому-
нибудь, а можно делать с ней все, что вы считаете нужным. (Только не предпринимайте ни-
чего такого, что может повредить вашему здоровью или здоровью окружающих.)
Если вы хотите запустить программу, выберите команду Dedug^Start (Отладка^ Запустить)
или щелкните на кнопке Start панели Dedug (рис. 6.2). Если вы уверены, что в программе нет
ошибок (это равносильно тому, что вы считаете себя непревзойденным хакером), выберите коман-
ду Debug^Start Without Debugging (Отладка^Запустить без отладки), чтобы запустить про-
грамму без использования отладчика Visual C++.

Рис. 6.2. На этой панели есть кнопки для запуска,


остановки и пошагового выполнения программы

Синтаксические OUIUJKU: неужели


их можешь ЯьмЯь *нак много?/
Если при написании кодов программы вы допустили какую-то ошибку, например переда-
ли функции неверное количество аргументов, неправильно набрали команду или присвоили
недопустимое имя переменной, компилятор не сможет понять ваши коды и программа не бу-
дет откомпилирована.
Если это случится (правильнее сказать: когда это случится, поскольку случается это прак-
тически всегда), компилятор отобразит список синтаксических ошибок в окне Output, как
показано на рис. 6.3. Синтаксическая ошибка в окне Output сопровождается сообщением о
том, что вы что-то сделали неправильно. Дважды щелкните на этом сообщении, чтобы пе-
рейти к той строке кодов программы, где эта ошибка произошла. Чтобы программа была ус-
пешно откомпилирована, необходимо исправить все синтаксические ошибки.

TaskLt»t? 0ЫИ Егг«>ю»з shown (f fltered) VX


'' Description
1 fie Line л

:ф error Lf4K200S: "public: v ntual void thiscall CMarf ame :Asset tVall*vo«J)con KiHerApp
' ф error LNK2O0S: "puck: v rtual vO'd fMrscall CMan i F ame::Dump(da5S CDumpC KillerApp
•& Fetal error LMKllr59- one or more multiply defined syncols lound CillerApp . .
ф 1
warning C*2W 'return' conversion from 'dousle' to nt', possible loss of data с:'.Documents and ,,.\KillerApp^MainFrm c p p 46 v

в. - -. 21 т*л u« |
РИС. 6.3. Сообщения о синтаксических ошибках, которые отображаются в окне Output
ОДНИ ошибки очевидны и могут быть сразу же исправлены. Другие не так просты, и вам
придется потратить немало времени, чтобы их обнаружить и исправить (особенно если у вас
еще нет в этом большого опыта). Со временем вы научитесь быстро различать типичные
ошибки и сразу же их исправлять.

65 Часть I. Первое знакомство с Visual C++ .NET


Вот несколько правил, которые помогут вам в борьбе с ошибками.

> •/ Сохраните файлы программы, перед тем как приступить к их компиляции.


I S Если вы никак не можете найти ошибку в какой-то строке, проверьте предыдущую
ш строку. Бывает, что ошибка "сидит" именно там.
Г S Многие ошибки случаются из-за недостающих или лишних фигурных скобок ({ })
| или точек с запятой (;).
1 •/ Компилируйте программу небольшими частями. Это позволит вам сосредоточить-
I ся на отдельных фрагментах, а не рассеивать внимание по всей программе.
\ S Сообщение C a n n o t c o n v e r t from . . . to . . . (Нельзя преобразовать ... к ...)
обычно появляется, если вы пытаетесь присвоить переменной значение не того типа.
Например, если текстовой переменной вы пытаетесь присвоить значение типа integer.
Убедитесь, что вы правильно набрали имена переменных. Очень многие ошибки
происходят именно из-за неверного указания имен переменных.
Просмотрите главы 23 и 24. В них вы найдете перечни наиболее часто встречаю-
щихся ошибок и способы их устранения.
Иногда одна небольшая проблема может привести к тому, что компилятор обнаружит
сразу "тысячу" ошибок. Например, неправильно указанный путь к заголовочному
файлу может стать причиной появления нескольких десятков сообщений об ошибках.
(Обычно это случается при создании неуправляемых программ.) Достаточно устра-
нить причину— и многие ошибки сразу же отпадут. Поэтому, если вы компилируете
программу и на вас обрушивается целый поток сообщений об ошибках, не паникуйте.
Скорее всего, большинство из них можно исправить одним простым действием.
I •/ Если вы никак не можете понять, в чем заключается ошибка, попросите кого-
I нибудь о помощи. Иногда свежий взгляд на вещи помогает легко решить казалось
| бы неразрешимую проблему.

Предупреждения
Иногда в процессе компиляции помимо сообщений об ошибках вы можете сталкиваться
также с предупреждениями. Предупреждения появляются тогда, когда компилятор может
правильно интерпретировать набранные вами коды, но считает, что выполнение таких кодов
может привести к возникновению проблем. Например, если вы создаете переменную, но ни
разу ее не используете, компилятор предупредит вас об этом и спросит, все ли здесь правиль-
но. Или, что значительно хуже, вы можете начать использовать переменную еще до того, как
присвоите ей какое-то значение. В этом случае на экране отобразится сообщение с предупре-
ждением, подобное показанным на рис. 6.4.

Ouspu* Я X

Build
StudioP : warning C4Z44 'return' from do Ые H
to mt Ые oss of data
StudioProjects\K !erApp\MainFrrri.cpp(71): warning C4244:'return' conversionfrom Ые to "inf poss Ые oss of data
do
V
< i>

РИС 6.4. Подобные предупреждения появляются на экране, если набранные вами коды могут
привести к возникновению проблем
Относитесь к предупреждениям с должным вниманием. Хотя иногда они могут появляться
как следствие чрезмерной осторожности компилятора, в большинстве случаев они помогают
избежать многих неприятностей. Например, если вы забудете добавить строку, задающую на-

Глава 6. Компиляция программ, или Первые трудности 67


чальное значение переменной, компилятор выдаст предупреждение. Если вы его проигнорируе-
те, в процессе выполнения программы эта переменная будет гринимать совершенно бессмыс-
ленные значения, в результате чего программа, разумеется, будет работать неправильно.

Почему компилятор не исправляет ошибки самостоятельно


Хотя компилятору известно, где и какая ошибка произошла, он не знает, почему она произош-
ла. Например, компилятор может установить, что в конце некоторой строки не хватает точки с за-
пятой. Почему он ее просто не добавит? Потому что у этого могут быть разные причины. Вы могли
просто забыть поставить точку с запятой, могли забыть набрать кроме этой точки с запятой еше
какие-то коды или же могли вообще забыть выделить эту строку как комментарий.
Вместо того чтобы брать на себя ответственность и самостоятельно выбирать один из ва-
риантов устранения ошибки, компилятор не пытается вместо вас создавать программу и
только лишь указывает на ошибку, предоставляя вам право выбора дальнейших действий.
Кроме того, поскольку круг вариантов исправления ошибки, а следовательно, и причин ее
возникновения может быть довольно большим, компилятор иногда в состоянии только при-
близительно указать на ошибку. Например, хотя компилятор может указать на некоторую
строку и сказать, что здесь не хватает точки с запятой, на самом деле ошибка будет возникать
из-за того, что точки с запятой не хватает в предыдущей строке.

можно по-/газноли/
Есть три способа компиляции файла: он может быть построен, откомпилирован
/ либо повторно построен.

При построении программы (команда Build^Build) обновляются файлы OBJ для всех из-
мененных исходных файлов. Когда все файлы OBJ обновлены, программа компонуется для
создания нового выполняемого файла (ЕХЕ). Результатом такой компиляции будет либо на-
бор сообщений о синтаксических ошибках, либо полностью рабочая программа.
Если вы работаете над проектом и вносите изменения только в некоторые его части, ис-
пользуйте именно этот способ компиляции. При этом заново откомпилированы будут только
измененные файлы и вам не нужно будет тратить массу времени и ждать, пока компилятор
сделает бессмысленную работу и еще раз откомпилирует те файлы, которые вы не трогали.
Поэтому используйте команду Builds Build, если вы внесли какие-то изменения в исходные
файлы и вам нужен теперь новый файл ЕХЕ.
При обычной комтияции (команда Build^Compile) компилируется только тот файл, который
открыт в данный момент для редактирования. Эгой командой не создается рабочая программа.
Используйте ее в том случае, если хотите проверить какой-либо исходный файл на наличие син-
таксических ошибок. Если компиляция проходит успешно, создается объектный файл (OBJ).
При повторном построении (команда Builds Rebuild All) все файлы проекта компилиру-
ются и затем компонуются для создания выполняемой программы (ЕХЕ). Как и в случае с по-
строением, результатом такой команды будет либо набор сообщений о синтаксических
ошибках, либо полностью рабочая программа. Даже если вы только что построили програм-
му, при повторном построении каждый отдельный файл компилируется заново. (Результатом
такой операции будут обновленные объектные файлы всех исходных файлов плюс выполняе-
мый файл программы, созданный путем компоновки всех объектных файлов.) Используйте
эту команду в том случае, если хотите быть уверены, что весь проект был построен заново.

68 Часть I. Первое знакомство с Visual C++ .NET


Часть II

Все, что вы хотели знать о C++,


но о чем боялись спросить

МОЖНО

v\tp&bW
В э&ой час&и...
С этой части начинается серьезное обучение; Вы получите
фундаментальные знания о Visual C++, начиная с того, как укомплекто-
вать программу переменными, операторами и указателями. Также вы
познакомитесь с одним из наиболее важных инструментов создания
программ — отладчиком.
Ни в коем случае не игнорируйте приводимые примеры, поскольку
многое вещи познаются на практике. Пробуйте самостоятельно набрать
коды программ и выполнить их на своем компьютере, чтобы быть
уверенным, что вы правильно понимаете излагаемьгй здесь материал.
Сразу скажем, что не нужно пугаться этой части, поскольку сухость и
серьезность рассматриваемых здесь вопросов компенсируется хорошим,
:
добрым юмором. V
Глава 7

Типы данных — это серьезно


Вэ/Яой иа£е...
> Строгие и нестрогие языки программирования
> Объявление типов переменных
У* Преобразование строк в числа
> Создание констант
>* Работа со строками

^ к о м п ь ю т е р н ы е программы занимаются обработкой различных данных, которые, в свою


/ V* очередь, могут принадлежать различным типам. Например, в электронных таблицах
данные могут быть представлены числами с плавающей запятой, в инвентаризационных
системах— порядковыми записями, а в программах для рисования— автофигурами. Однако
нужно понимать, что, хотя режиссеры Голливуда и создают мифы о всемогущих и всезнающих
компьютерах, на самом деле 'это не так. Компьютер может работать только с данными строго
определенных типов, причем предварительно (гужно указать, данные какого именно типа будут
ему передаваться. В этой главе вы познакомитесь с основными типами данных, особенно
детально обсуждается один из самых важных, который обозначается словом S t r i n g .

С/н/гогие и неоплате

Строгие языки программирования, такие как C++, требуют, чтобы программисты заранее
объявляли, данные каких типов они собираются использовать. Например, если вы хотите со-
хранить число, нужно заранее сообщить об этом компьютеру, чтобы он был готов принять от
вас это число.
Строгие языки имеют ряд неоспоримых преимуществ. Например, если вы случайно попы-
таетесь запись о сотруднике интерпретировать как число, компилятор выдаст сообщение об
ошибке. Это хорошо, поскольку без подобного контроля вы можете непреднамеренно унич-
тожить важную информацию. Предположим, запись о сотруднике занимает 8 байт памяти, а
для числа отводится только 2 байта. Когда вы удаляете запись, освобождается 8 байт памяти.
Если же вы используете эту запись как число и затем это число удалите, помимо числа будет
удалено еще 6 байт памяти, в которых может содержаться очень важная информация. Воз-
можно, потеря этой информации будет иметь для вас очень плохие последствия.
Помимо строгих языков программирования, есть также нестрогие, к числу которых отно-
сится, например, JavaScript. Они не требуют предварительного объявления типов данных и
сами оценивают данные в тот момент, когда с ними сталкиваются. Например, если вы при-
своите переменной текстовое значение r o o t , языковой процессор сделает эту переменную
текстовой. Нестрогие языки программирования могут быть проще в использовании, и неко-
торые программисты отдают им предпочтение, однако они не умеют предостерегать вас от
ошибок, как это делают строгие языки.

Глава 7. ТИПЫ данных — это серьезно 71


ОЗшвлениепе/геменных
В языке C++ необходимо заранее объявлять переменную и указывать тип используемых
ею данных. Другими словами, нужно сразу указать компилятору, значения какого типа могут
быть присвоены переменной. (Более подробно об использовании переменных речь идет в
главе 8. Пока же рассматривайте переменную как нечто, способное сохранять информацию,
например как ячейку электронной таблицы, которая, однако, имеет свое собственное имя.)
Объявить тип переменной можно несколькими способами, но обычно это делается в момент
ее создания.
Процесс создания переменной называется ее определением. Чтобы определить перемен-
ную, нужно просто указать используемый ею тип данных, а затем ее имя. Ниже приведено
несколько примеров определения переменных. В следующей строке создается переменная
foe типа i n t (integer, число):
int too;
Далее создается переменная b a r типа c h a r (character, символ):
char bar;
И ниже сообщается о том, что функция min принимает два числа типа i n t в качестве
значений параметров и возвращает другое число типа i n t в качестве результата. (Более под-
робно определение и использование функций рассматривается в главе 12.)
int min{int f i r s t , i n t second);
Обратите внимание, что объявление переменной отличается от ее опреоеления.
Когда вы объявляете тип данных, вы просто указываете, значения какого типа
будет принимать эта переменная. При этом выделения памяти для объявляемой
переменной не происходит. (Физически переменная не создается.) Когда вы что-
то определяете, вы создаете это физически. В случае с переменными это различие
не столь принципиально. Почти всегда при определении переменной указывается
и ее тип. Хотя делать это можно и раздельно. Познакомившись со структурами
(глава 9) и классами (глава 17), вы увидите, что для них определение и объявле-
ние — это два разных действия, каждое из которых выполняется отдельно.

Наиболее часто используемые типы данных


В языке C++ имеется набор заранее определенных типов данных, которые сразу можно
использовать. Используя стандартные типы данных, можно создавать собственные, более
сложные, типы данных, что описывается в главе 9. Вот три наиболее часто используемых ти-
па данных.
j S double. Число с плавающей запятой. (Не думайте, что это число с водой, в кото-
рой плавает запятая. Это всего лишь вещественное число с десятичной запятой, ко-
торая может ''плавать'" в зависимости от количества десятичных знаков в числе и
не имеет точно зафиксированной позииии. Сюда относятся, например, такие числа,
как 4,3444; 42,1; 3,И.) Переменные такого типа могут содержать значения в преде-
лах от ± 1,7*1(Гзс8до± 1,7*1О+зот.
i n t . Целое число. Значение может быть положительным, отрицательным либо ну-
левым. Например, к этому типу относятся значения 0, 1, 39, -42, но не 1,5.
S t r i n g . Любой текст, например H e l l o World.

72 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Например, вам нужна переменная для хранения значения радиуса круга. Можете опреде-
лить ее таким образом:
double dblRadius;
Этой строкой создается переменная d b l R a d i u s , которая может принимать значения ти-
па d o u b l e (числа с плаваюшей запятой). Для сохранения значения радиуса круга такая пере-
менная вполне подойдет.
Перед созданием программы определите для себя, переменные каких типов вам потребу-
ются для хранения всех тех данных, которые вы собираетесь использовать. Например, для
сохранения числа, обозначающего количество людей, посетивших концерт, можно создать
переменную типа i n t (целое число), поскольку, разумеется, количество людей не может
быть дробным. (Правда, кто-то может прийти со своей "второй половиной", но это для него
она "половина", а для всех остальных— отдельный человек.) Если же вам нужна переменная
для сохранения значения радиуса крута, лучше использовать тип double, поскольку радиус
вполне может обозначаться дробным числом. И если вам нужно сохранять текстовую инфор-
мацию, создайте переменную типа S t r i n g .

Реже используемые типы данных


Ниже дается описание некоторых других типов данных, которые тоже используются при
создании программ, но не так часто, как приведенные выше.
I S f l o a t . Вещественное число с плавающей запятой; однако переменные этого типа
\ данных могут принимать значения из диапазона гораздо меньшего, чем перемен-
| ные типа double. Для этого типа допустимые значения лежат в интервале от
\ ±3,4*10~38до±3,4*10'зв.
| V long. Это слово может быть добавлено перед словом i n t , чтобы обозначить, что
| для хранения одного значения должно быть выделено 32 бит памяти. В Visual C++
? .NET такой объем памяти выделяется по умолчанию, поэтому в использовании это-
! го слова нет необходимости.
ё S s h o r t . Это слово может быть добавлено перед словом i n t . чтобы обозначить,
| что для хранения одного значения должно быть выделено 16 бит памяти.
[ •/ s i n g e d . Это слово может быть добавлено перед словом i n t , чтобы обозначить,
I что принимаемые значения могут быть как позитивными, так и негативными. (В
| Visual C++ эта возможность предусмотрена по умолчанию.)
| •/ u n s i g n e d . Это слово может быть добавлено перед словом i n t , чтобы обозна-
1 чить, что все принимаемые значения будут только позитивными. Благодаря этому
I освобождается один дополнительный бит памяти для обозначения величины при-
I нимаемого значения. Так, если переменная типа i n t может принимать значения в
| диапазоне от -2 147 483 648 до +2 147 483 647, то слово u n s i g n e d определяет
| диапазон от 0 до 4 294 967 295.
е
|^ void. Тип не определяется. Используется для обозначения того, что функция не
| возвращает никакого результата. (Более подробно об этом речь идет в главе 12.)
'? S c h a r . Символ. А, В или & — это все символы. Этот тип данных чаще всего исполь-
I зуется в неуправляемых программах, в то время как в управляемых в основном
| применяется более гибкий тип S t r i n g .

Глава 7. Типы данных — это серьезно 73


Обеспечение типовой безопасности
Языки машинного уровня не заботятся о корректном использовании типов данных. Для
них д а н н ы е — это всего лишь нечто, занимающее часть памяти. Вот почему программы, на-
писанные на языках машинного уровня, могут так просто сохранять числа поверх записей о
сотрудниках, символы поверх чисел, и вообше делать все, что вы им прикажете. (Многие
компьютерные вирусы уничтожают данные именно таким способом.)

Тип, который отличается от всех остальных


Чтобы убедиться, что все объявленные типы используются корректно, Visual C++ проделывает большую
работу. Во время компиляции исходного файла формируется так называемая таблица имен. Она содер-
жит подробную информацию обо всех переменных и функциях, используемых в исходном файле. Каждый
раз, когда переменная (или функция) как-то используется, компилятор находит ее описание в таблице
имен, проверяет объявленный для нее тип данных и определяет, возможно ли в отношении переменной
(или функции) предпринимать такие действия.
Компилятор имеет также набор строгих правил, определяющих, как правильно преобразовать значения
одного типа к значениям другого. Например, если функции требуется для работы значение параметра
типа d o u b l e {более детально передача функции значений параметров описана в главе 12), а вы пере-
даете ей значение типа i n t , компилятор сам сможет преобразовать целое число к числу с плавающей
запятой. Если же он обнаружит несоответствие типов, для которых не определено правило взаимного
преобразования, будет возвращено сообщение об ошибке.
При компиляции программы, состоящей из нескольких исходных файлов, ситуация усложняется. В
;
одном исходном файле могут использоваться переменные или функции, объявленные в других фай-
лах. Чтобы убедиться в правильности использования типов данных, компилятор теперь должен срав-
нивать переменные и функции из разных файлов. Строго говоря, этот шаг выполняется уже на этапе
компоновки программы.
Проверка правильности использования типов данных (так называемый внешний анализ) осуществляется
с использованием техники, которая называется корректировкой имен. Когда вы объявляете переменную,
функцию или любой другой элемент, компилятор несколько изменяет присваиваемое вами имя (это имя
также называют внешним). Внешнее имя содержит информацию о типе элемента. Можно сказать, напри-
мер, что компилятор добавляет к исходному имени букву i, чтобы обозначить, что элемент имеет тип
i n t , и буквы с р , чтобы обозначить, что элемент имеет тип c h a r . Далее предположим, что а одном из
исходных файлов вы создали такую функцию:
i n t NumberOfSongs();
В другом исходном файле вы набрали такой код:

char
MyText = NumberOfSongs{);
Компилятор преобразует имя N u m b e r O f S o n g s к имени i N u m b e r O f S o n g s , а имя M y T e x t - к
имени c p M y T e x t . Сами вы никогда не увидите внутренних имен (разве что посмотрите на листинг ко-
дов языка ассемблера), но компоновщик видит их очень хорошо.
Для него, например, последняя строка кодов выглядит так;
CpMyText = iNumberOfSongs{);
Сравнивая скорректированные имена, компоновщик фиксирует несовпадение типов (поскольку символ и
число - это разные данные) и выдает сообщение об ошибке.

74 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Однако О + заботится о правильном использовании типов данных. Если вы объявите для
переменной один тип данных, а затем попробуете использовать ее как переменную другого
типа, компилятор выдаст сообщение об ошибке. Эта возможность называется обеспечением
типовой безопасности и является преимуществом языка О + , поскольку таким образом ав-
томатически выявляются наиболее распространенные ошибки. (Это действительно лучше,
чем игнорирование компилятором неправильного использования типов данных, за исключе-
нием разве что тех случаев, когда вы создаете самоуничтожающуюся программу.)
Приведенный ниже код будет работать корректно.
//Создание переменной типа i n t
int nSize;
//Присвоение переменной значения 7
n S i z e = 7;
Но, если вы наберете следующий код, компилятор выдаст сообщение об ошибке, по-
скольку переменная типа i n t не может принимать текстовые значения:
//Попытка присвоить переменной т е к с т о в о е значение
n S i z e = " H e l l o World";
Иногда компилятор автоматически преобразует значения одного типа данных к значениям
другого типа. В приведенном ниже примере число с плавающей запятой автоматически пре-
образуется к целому числу:
i n t nSize;
nSize = 6.3;
После выполнения этого кода переменной n S i z e присваивается значение 6. (Компилятор
автоматически округляет число. Все цифры, расположенные справа от десятичной запятой,
просто отбрасываются.) Однако при этом компилятор отображает на экране предупреждение
о том, что будет выполнено преобразование чисел. Хорошие и по-настоящему качественные
программы должны компилироваться без предупреждений и ошибок. Чтобы избежать подоб-
ных предупреждений, можно явно указать компилятору на необходимость преобразования
одного типа данных к другому. Для этого перед переменной, значение которой должно быть
преобразовано, в круглых скобках укажите требующийся тип данных. Например:
int nSize;
double BigSize;
nSize = (int} BigZise;
В этом случае компилятор без лишних предупреждений преобразует значение типа
double к значению типа i n t . Это означает, что дробная часть значения переменной
B i g Z i s e будет просто усечена. Например, если эта переменная имеет значение, равное чис-
лу 3,141592653, переменной n S i z e будет присвоено значение, равное числу 3.

*пипов
В стандартном C++ типы данных являются просто структурами, которые требуют для себя
некоторого количества памяти. С другой стороны, в среде .NET типы данных обладают еще и
встроенной функциональностью. Технически они являются объектами. (Более подробно объек-
ты описаны в главе 17.) Как и другие объекты, типы данных имеют встроенные функции, наи-
более полезными из которых являются функции преобразования одних типов в другие. В
табл. 7,1 приведен список функций преобразования типов для основных типов данных.

Глава 7. ТИПЫ данных — это серьезно 75


Таблица 7 . 1 . Функции преобразования типов данных
Тип Функция Выполняемое действие
String ToDouble{) Преобразует тип s t r i n g к типу double

String Tolnt32() Преобразует тип S t r i n g к типу i n t

double ToStringO Преобразует тип d o u b l e к типу S t r i n g

double Parse() Преобразует тип S t r i n g к типу d o u b l e

int ToStrinaO Преобразует тип i n t к типу S t r i n g

int Parse Преобразует тип S t r i n g к типу i n t

Предположим, например, что вам нужно отобразить на экране число. Для этого предвари-
тельно необходимо преобразовать его в строку:
i n t nNumber = 3;
Console::WriteLine(nNumber.ToString{));
Теперь предположим, что у вас есть текстовое значение ( S t r i n g ) и из него нужно сде-
лать число (double). Делается это так:
S t r i n g *szNumber = S " 3 . 1 4 "
d o u b l e dNumber;
dNumber - D o u b l e : : P a r s e ( s z N u m b e r ) ;
Вы, наверное, обратили внимание на использование звездочки (*) перед именем
текстовой переменной. Она там нужна, поскольку при работе со строками прихо-
дится использовать указатели. Более подробно указатели описаны в главе 13. По-
ка же просто примите к сведению, что при объявлении текстовых переменных
перед их именем нужно добавлять звездочку (*). И каждый раз при вызове встро-
енных для типа S t r i n g функций вместо точки (.) используйте символы ->.
Если вас интересуют другие подробности приведенного выше кода, прочитайте
этот абзац. Буква 3 перед значением " 3 . 1 4 " указывает компилятору, что это
должна быть строка, предусмотренная для использования в среде .NET. На самом
деле можно создавать текстовые значения самых разных видов, но в данном слу-
чае создается именно такое значение, которое для среды .NET подходит наилуч-
шим образом. Использование двух двоеточий (: :) в фрагменте Double : : P a r s e
является специальным синтаксическим приемом, позволяющим применять мето-
ды (такие, как P a r s e ) объектных типов (таких, как d o u b l e ) без предварительно-
го создания самих объектов. В будущем вы сможете по достоинству оценить эту
возможность языка C++.

— riio, ч,*но никогда


не меняемся
Иногда при написании программы требуется многократно использовать одно и то же чис-
ло или одну и ту же строку. Например, если вы знакомы с математикой, то знаете, что число
п всегда равно 3,141592.... Если вы составляете программу, вычисляющую ваш гороскоп, то
используемая в вычислениях дата вашего рождения также будет оставаться неизменной.

76 Часть U. Все, что вы хотели знать о C++, но о чем боялись спросит


В подобных случаях становится возможным создание констант. Сами константы — это
просто названия элементов, которые в процессе выполнения программы никогда не изменя-
ются. Чтобы сделать элемент константой, начните его объявление со слова c o n s t . Напри-
мер, следующим кодом создастся константа PI, значение которой постоянно будет равно
числу 3,141592:
c o n s t double PI = 3.141592;
Использование констант значительно облегчает чтение кодов ваших программ, поскольку
обычные числа можно заменить объясняющими их именами. Кроме того, это удобно, если
одно и то же значение должно быть изменено во всей программе. Для этого нужно просто
определять новое значение для соответствующей константы. Например, если вы вдруг реши-
те приспособить написанную вами программу к некоторой виртуальной реальности, где про-
странство и время деформированы и число п равно, скажем, числу 7, вам нужно будет всего
лишь изменить значение соответствующей константы. При этом все вычисления, выполняе-
мые вашей программой с участием числа п, автоматически будут использовать новое указан-
ное значение, что избавляет вас от необходимости самостоятельно просматривать коды всей
программы и вносить нужные коррективы.
В кодах программы использовать константы можно везде, где может быть использован
элемент того же типа, что и сама константа. Например, если у вас есть константа, представ-
ляющая число с плавающей запятой, вы можете применять ее везде, где будет уместно ис-
пользование числа с плавающей запятой. Так. константа PI может быть умножена на диа-
метр круга, в результате чего будет вычислена длина окружности.

Использование констант в кодах программы


Ниже приведен код программы, в которой используются константы и выполняется преоб-
разование типов данных. В процессе ее выполнения пользователю предлагается указать ради-
ус, в соответствии с которым вычисляется площадь круга. Полученный результат отобража-
ется на экране. Не поленитесь и создайте такую же программу. Для этого создайте новую
программу .NET, наберите приведенный здесь код и запустите программу на выполнение.
//CircleArea

//Вычисление площади по значению радиуса

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

//С этой строки начинается выполнение программы


#ifdef _UNICODE
int wmain(void)
telse
int main(void)
#endif
{
daub]e fitRadius;
double i'ltArpa;
Глава 7. Типы данных — это серьезно 77
const double PI = 3.141592;

//Отображение вопроса о значении радиуса


Console::WriteLine(S"Укажите радиус круга");

//Получение отЕета и преобразование его к числу с


//плавающей запятой
fltRadius = Double::Parse(Console::ReadLine());
//Вычисление площади круга
fltArea = PI*fltRadius*iltRadius;

//Отображение результата на экране


//Обратите внимание, что для этого число должно быть
//предварительно преобразовано в строку
Console::WriteLine(3"Площадь круга составляет {0}
единиц.", Area.ToString () ) ;

//Ожидание, пока пользователь не остановит


//выполнение программы
Console::WriteLine(S"Нажмите клавишу Enter, чтобы
остановить выполнение программы");
Console : : ReadLir.e () ;

return 0;
}

Константы и борьба с ошибками


Еще одно преимущество использования констант: если вы случайно попытаетесь изме-
нить их значения в процессе выполнения программы, компилятор заметит это и выдаст со-
общение об ошибке. Предположим, например, что где-то в кодах программы вы набрали та-
кую строку:
?! = 15;
Как только компилятор увидит этот код, он сразу же выдаст сообщение об ошибке, в ко-
тором будет сказано, что изменять значение константы нельзя. Своевременное обнаружение
и устранение таких, казалось бы, мелких ошибок позволяет избежать возникновения в даль-
нейшем более серьезных проблем.

С/н/to/cu/са/сoquftизнаиболееважных
!пипов данных
Строки (тип S t r i n g ) являются основными данными почти для всех создаваемых про-
грамм. По крайней мере почти ни одна программа не может без них обойтись. Чтобы эффек-
тивно их использовать, нужно владеть некоторым количеством не очень сложных приемов.
Основные их них описаны в табл. 7.2.

78 Часть U. Все, что вы хотели знать о C++, но о чем боялись спросить


Таблица 7.2. Способы обработки текстовой информации
Задача Решение
Комбинирование двух строк sResult = S t r i n g : :Concat ( s t l , s t 2 ) ;

Отображение на экране текста со C o n s o l e : : W r i t e L i n e (3"3начение 1: {0}


значениями Значение 2 | 1 } " , з н 1 , з н 2 } ;

Сравнивание двух строк cResult = stl->Equals (st2) ;

Определение позиции, начиная с которой n O f f s e t = s t l - M r . d e x O f ( s t 2 ) ;


одна строка входит во вторую

Получение п символов начиная с m-й m R e s u l t = s t l - > S u b s t r i n g ( m , n ) ;


позиции

Определение длины строки nLength = s t - > L e n g t h ;

Удаление лишних пробелов в начале и в R e s u l t = s t l - > T r i m ( ) ;


конце строки

В кодах программ, которые приведены на страницах этой книги, вы будете часто сталки-
ваться со строками. Ниже дан код небольшой программы, на примере которого вы можете
увидеть в лействии различные способы обработки текстовой информации,
//StringsiOl
'Некоаорые приемы работы со строками

#induce "stciafx.h"

#using <mscorlib.dll>

using namespace System;

этой строки начинается выполнение программы


#ifdef jJNICODE
int wraain(void)

int main(void)
#endif
{
String *pszString;
int nNumber;
dcub i.e f 11Number;

//Комбинирование двух строк


pszS-ririg = String::Concat(3"Майкл", S" Джексон")
Cons з!е: :WriteLine(pszString);

Глава 7. Типы данных — это серьезно 79


//Преобразование типов и комбинирование строк
nNumber = 3;
pszString - String::Concat(Э"Преобразованное '
nNumber.ToString()};
Console::WriteLine(pszString);

//Преобразование строки к типу double


Console::WriteLine(S"Введите числе");
fItNumber = Double::Parse(Console::ReadLine()),
//Добавление к этому значению числа
fltNumber = fltNumber + 30.5;
//Отображение полученного результата на экране
Console::WriteLine(String: :Concat ^"Результат:
fltNumber.ToStringO ) ) ;

//Объединение строк и их отображение


Console::WriteLine(String::Concat(3"Один
", 3"Три"));

//Отображение с использованием символов форматирования


Console::WriteLine(S"Строка: {0}ХпЧисло: {1}",
S " M O H строка", nNumber.ToString());

//Удаление лишних пробелов в начале и в конце строки


pszString = " Hello World ";
Console: :WriteLine (S''CT-рока с пробелами: {0}\п
и без пробелов: {1}", pszString,
pszString->Trim());

//Определение символа середины строки


Console::WriteLine(S"Посередине строки {0}
расположен символ \"{1}\"", pszString,
pszString->Substring (pszSt rir.g->Length/2, 1) ) ;

//Ожидание, пока пользователь не остановит


//выполнение программы
Console::WriteLine(S"Нажмите клавишу Enter, чтобы
остановить выполнение программы")
Console: :ReadLine() ;
return 0;

80 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Глава 8

Использование переменных
главе...
> Имена переменных
> Определение и инициализация переменных
V Соглашения о присвоении имен переменным

рограммы получают, обрабатывают и возвращают данные. Если нужно сохранить


какое-то значение или результат каких-то вычислений, используются переменные.
Переменная— это имя, которым обозначается какой-либо фрагмент информации. Перемен-
ные могут быть использованы для хранения самых разнообразных видов данных, начиная от
баллов, набранных в компьютерной игре, и заканчивая биржевыми показателями.
Имена переменных часто несут в себе какую-то информацию о принимаемых значениях.
Например, по имени n R a d i u s можно догадаться, что переменная используется для хранения
(и представления) радиуса круга. С другой стороны, переменным иногда присваиваются не
очень удачные имена, по которым трудно сделать вывод об их назначении. Например, имя
С4Р0 может ассоциироваться с серийным номером, паролем и вообще с чем угодно.
Каждый раз, когда необходимо получить доступ к хранимой в переменной информации,
ссылаются на имя переменной. Поскольку C++ является строгим языком программирования,
перед тем как приступить к использованию переменной, нужно объявить ее тип данных.
В этой главе рассматриваются вопросы, связанные с именованием переменных, их опре-
делением и инициализацией.

Ымеяование переменных
При выборе имени для ребенка родители сталкиваются с множеством ограничений. До-
пустим, например, что они сами помешаны на программировании и надеются, что, когда их
чадо вырастет, непременно станет великим программистом. Тогда вместо имени Джон они
могли бы дать ему имя \У\У\УДЖОН. ИЛИ вместо имени Джессика назвали бы девочку double.
Но к сожалению, даже если соответствующее административное учреждение (и то не каждое)
зарегистрирует такое имя, у ребенка будут проблемы со сверстниками, тяжелые школьные
годы, и он никогда не простит своих родителей за такой "подарочек".
Выбрать имя для переменной намного проще. Ей безразлично, как вы ее назовете, поэто-
му смело давайте ей любое понравившееся имя. Правда, и здесь существуют некоторые огра-
ничения. Впрочем, они вполне разумны и их легко запомнить. Итак, имя переменной:
| S не должно начинаться с цифры;
| S не должно содержать пробелов;
| S может состоять только из букв, цифр и символов подчеркивания ( __ ). Нельзя ис-
I пользовать специальные символы, такие как точка, запятая, точка с запятой, ка-
!) вычки и т.п.;
| ^ не должно совпадать с названиями библиотечных функций;
I ^ не должно совпадать с зарезервированными ключевыми словами.

Глава 8. Использование переменных 81


В табл. 8.1 приведен перечень ключевых слов Visual C++, которые являются командами языка
C++. Дочитав книгу до конца, вы будете знать, как пользоваться большинством из эгих ключевых
слов. Слова, начинающиеся с двух символов подчеркивания ( ), являются специальным расши-
рением Visual C++, которое призвано упростить процесс создания программ для персональных
компьютеров. Просмотрите таблицу и не присваивайте переменным таких же имен.

Таблица 8.1. Ключевые слова C++


• , и „ 4 - „ „ ~ 4 -
c D S U c i - i . hock multiple inheritance

a l i g n o f identifier nog с
a s m if exists no op
a s s n i n e if not exists __Pin
b a s e d i n l ir.e property
b o x intS raise
c d o c i intl6 sealed
d e c l s p e c in-.32 single inheritance
d e l e g a t e int64 stdcail
e v e n t interfase super
e x c e p t leave try cast
f a s t c a l l rri6 4 try/ except
f i n a l l y ml2 8 try/ finally
t o r e e i n l i n e ml28d unhook
g - ml28i uuidof
v a l u e flea: signed
v i r t u a l i n h e r i t a n c e for sizeof
w M friend static
w c h a r t goto static cast
b o o l if struct
b r e a k inline switch
c a s e int template
c a t c h long this
С hi Г mutable thread
class naked threw
с •. n ? t namespase true
const cast new try
continue r.cinl i n e typedef
iefault r.creturn typeid

82 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Окончание табл. 8.1
delete nothrow typename
deprecated novtable union
dllexport operator unsigned
dllimport private using
do property uuid
double protected virtual
dynamic_cast public void
else register voletile
enum reinterpret cast wchar t
explicit return while
extern selectany
false short
Вот примеры допустимых имен переменных: way_cool, RigthOn, B i t s 3 2 . А такие
имена присваивать переменным нельзя: c a s e (совпадает с ключевым словом), 52PickUp
(начинается с цифры), A Louse (содержит пробел), н—v (включает недопустимые символы).
В именах переменных строчные и прописные буквы воспринимаются как разные. Напри-
мер, имена b a r s , E a r s , bArs и BARS обозначают разные переменные.

Определение nefiejietiitMX
Перед тем как использовать переменную, ее нужно определить. Для этого просто укажите
тип принимаемых значений и ее имя. Вот, например, определение нескольких переменных:
i n t Counter;
double OrNothing;
long J o h n s ;
Если объявляемые переменные имеют один и тот же тип данных, объявить их можно в
одной строке. Например, если нужно объявить переменные F i r s t , Second и T h i r d , каждая
из которых должна принимать значения типа f l o a t , можете сделать это так:
float F i r s t ;
float Second;
float Third;
Или так:
float First, Second, Third;

пе/геменных
Одновременно с объявлением переменные можно инициализировать. Этот громоздкий
термин обозначает всего лишь присвоение переменным исходных значений. Чтобы сделать
это, наберите после имени переменной знак равенства ( = ) и затем укажите нужное значение.

Глава 8. Использование переменных 83


Например:
int Counter = 3;
double OrNothing = 3.5;
long Johns = 32700;
Вот и все. Ничего сложного, не так ли?

/date сделсипь имя ин.фо(и1сипи£н.ым


Вы, конечно, можете присваивать переменным самые разнообразные имена (за неболь-
шими исключениями, они упоминались выше), однако обычно программисты стараются при-
держиваться некоторых общих соглашений, цель которых — упростить чтение и понимание
кодов программ. Основным из них является так называемое венгерское обозначение, приду-
манное, как говорят, неким венгром, работающим на компанию Microsoft. Его идея состоит в
том, чтобы начинать имена со специальных префиксов из нескольких букв, обозначающих
тип переменной. В этой книге при назначении имен переменным используется одна из версий
венгерского обозначения, которая, на наш взгляд, является самой простой и удобной.
Например, чтобы обозначить, что переменная имеет тип integer ( i n t ) , начинаем ее имя с
буквы п:
int nRadius;
int nCount - 0;
В табл. 8.2 приведен список префиксов, используемых в книге. Напомним, что, хотя ис-
пользование таких префиксов является хорошей практикой, позволяющей помимо прочего
допускать меньше ошибок, вы всегда можете отказаться от них и присваивать переменным
такие имена, которые вам больше нравятся.

Таблица 8.2. Использование префиксов


Тип данных Префикс Пример

Integer n nCount

Double dbl dblRadius

String psz pszName

Boolean f fFinished

object о oLiriG

object pointer po poCircle

array a aShapes

member m m nShapes

Некоторые типы данных, упомянутые в этой таблице, вы встречаете, наверное, впервые.


Например, массивы (array) будут описаны в главе 14, а объекты (object) —- в главе 17.

84 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 9

Структуры данных
й гла£е...
> Объявление и использование структур
V- Комбинирование структур
>• Добавление структур к приложению

предыдущей главе рассматривались простые типы данных и способы использования


переменных для хранения информации. Но предположим, что необходимо сохранить
более сложную информацию, которая не может быть представлена только лишь одной пере-
менной какого-то определенного типа данных. Например, информацию о сотруднике, кото-
рая включает его имя, адрес и телефонный номер. Если рассматривать только простые типы
данных, вы должны использовать для этой цели три различные переменные: одну для пред-
ставления имени, вторую для адреса и третью для телефонного номера.
Такой подход нельзя назвать самым удачным, поскольку в реальном мире почти каждое
явление описывается целым набором разных значений. Представим, например, что вам необ-
ходимо отобразить информацию о сотруднике. Несмотря на то что эта информация состоит
из разных частей (имя, адрес, телефон и т.п.), вы все равно представляете ее как нечто целое.
По крайней мере намного предпочтительнее и удобнее сказать: "отобрази информацию о со-
труднике", чем: "отобрази имя сотрудника, его адрес, телефон и т.д.".
В этой главе описано, как объединить несколько отдельных переменных в нечто целое,
называемое структурой. Группируя связанные по смыслу элементы в общую структуру, вы
значительно упрощаете чтение и понимание кодов программ. Возможно, вы будете использо-
вать структуры во всех своих будущих программах, по крайней мере в этой книге вы будете
часто с ними встречаться. В главе 17 рассматривается, как обычные структуры могут быть
преобразованы в классы—• самые главные компоненты, из которых строятся объектно-
ориентированные программы.

Объявление структур во многом похоже на объявление обычных типов данных (см. главу 7).
Чтобы объявить структуру, наберите слово c l a s s , затем укажите название структуры. Дапее в фи-
гурных скобках наберите p u b l i c : и объявите переменные, из которых будет состоять структура.
Вот пример объявления структуры:
class Circle Info
{
public:
double dblRadius;
double dblArea;
};
В данном случае структура названа именем C i r c l e l n f о (Информация о круге). Она со-
стоит из переменной d b l R a d i u s типа double, в которой хранится значение радиуса крута, и
переменной d b l A r e a (также типа double), в которой хранится значение площади круга.
Чтобы определить переменную, которая будет представлять структуру, просто наберите
название структуры и затем название переменной.

Глава 9. Структуры данных 85


//Создание переменной, представляющей структуру
Circlelnfo oCircle;
Что в действительности означает определение переменной, представляющей структуру? При-
веденной выше строкой была создана переменная o C i r c l e типа C i r c l e l n f o . Это означает, что
переменная c C i r c I e содержит две другие переменные — dblRadius и dblArea, поскольку эти
переменные определены для структуры CircleTnfo. Можно сказать, что одна переменная
(oCircle) типа C i r c l e l n f o содержит несколько значений (dblRadius и dblArea).
Объединяя различные переменные в отдельные структуры, можно логически правильно
организовать используемую в программе информацию. Далее вы узнаете, как используются
переменные, представляющие структуру.

Разница между объявлением и определением


В C++ объявление и определение являются техническими терминами. Их смысл несколько различается,
но часто их используют как взаимозаменяемые (хотя по сути это неправильно).
Обьявляя структуру, вы сообщаете компьютеру, из чего зта структура будет состоять. Например:
class Circlelnfo
{
public:
double dblRadius; -
double dblArea;
• J;
При этом память для структуры не выделяется.
Определяя переменную, вы даете компьютеру указание создать эту переменную. В этом случае для соз-
даваемой переменной выделяется память:
Circlelnfo oCircle;

использование э/них загадочных critfu/jctni//i


После того как структура создана, чтобы получить доступ к хранимой в ней информации (к
значениям переменных, из которых состоит структура), наберите имя переменной, представ-
ляющей структуру, затем точку (.) и затем имя переменной, значение которой нужно получить.
Например, чтобы отобразить значение радиуса крута, наберите такой код:
//Создание переменней, представляющей структуру
Circlelnfo oCircle;
//Отображение части информации, хранимой в структуре,
//которая обозначается именем d b l R a d i u s
Console::WriteLir.e(oCircie.dolRadius.ToString());
Как видите, выражение o C i r c l e . d b l R a o i u s выступает здесь в роли обычной перемен-
ной. Использование элементов, входящих в структуру, по существу, ничем не отличается от
использования стандартных переменных, представляющих только одно значение.

использование oqnux aftfu/tafUffi


длл созданил
Можно объединить несколько структур для создания одной большой структуры. Структу-
ры, входящие в состав других структур, называются вложенными.

86 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Прежде чем использовать структуру для создания другой структуры, ее нужно объявить.
Например, перед тем как создать структуру MegaCircle, состоящую из структур
C i r c l e l n f o , нужно объявить структуру C i r c l e l n f o . Впрочем, это вполне логично. Нуж-
но просто не запутаться, что и в какой последовательности должно быть объявлено. Если же
вы нарушите естественный порядок. Visual C++ .NET выдаст сообщение об ошибке.
Вот пример объявления структуры, состоящей из двух структур C i r c l e l n f o :
c l a s s MegaCircle
{
public:
Circlelnfo oTopCircle;
Circielnfo oBottoraCircle;

dnfttfiantffibi на п/ии&ишсе
Ниже приведен код программы CircieArea, который был немного изменен в связи с ис-
пользованием структуры для представления данных.
//CircleArea3
//Вычисление площади по значению радиуса
//Использование структуры для представления данных

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;


//Объявление структуры для представления информации
//о параметрах круга
class Circlelnfo
{
public:
double dblRadius;
double dblArea;

//С этой строки начинается выполнение программы


#ifdef ^UNICODE
int wmain(void)
#else
int main(void)
#endif
{
const double PI = 3.141592;
Circlelnfo oCircle;
//Отображение вопроса о значении радиуса
Console::WriteLine(S"Укажите радиус круга");

//Получение ответа и преобразование его к числу с


//плавающей запятой

Глава 9. Структуры данных 87


oCircle.dblRadius = Double::Parse(Console::ReadLine());

//Вычисление плошади круга


oCircle.dblArea ~ PI*oCircle.dblRadius*oCircle.dblRadius;

//Отображение результата на экране


//Обратите вникание, что для этого число должно
//быть предварительно прообразовано в строку
Console::WriteLine(S"Площадь круга составляет {0}
единиц,", oCircle.dblArea.ToString{));

//Ожидание, пока пользователь не остановит


//выполнение программы
Console::WriteLine(S"Нажмите клавишу Enter, чтобы
остановить выполнение программы");
Console;:ReadLine();

return 0;
}
Вы могли обратить внимание на несколько интересных моментов в этой программе. Так,
структура C i r c l e l n f o была объявлена перед функцией main (это функция, с которой на-
чинается выполнение программы). Именно так и нужно делать, поскольку структура должна
быть объявлена до того, как вы начнете ее использовать. В данном случае функция main ис-
пользует структуру C i r c l e l n f o . поэтому она и должна быть объявлена перед кодами функ-
шш main. Это общий принцип для всех программ на C++. Сначала объявляется класс. Опре-
деляются все его функции. И только после этого класс можно использовать.
Поначалу вам может показаться это несколько странным, поскольку более простые и, по
сути, служебные элементы описываются раньше, чем тс элементы, в которых они использу-
ются. Но по мере приобретения опыта программирования на языке C++ вы к этому привык-
нете. Пока же для удобства, если хотите, можете приступить к чтению кодов с того места, где
начинается выполнение программы.

Кое-что о структурах и классах


В этой книге при создании структур будет использоваться ключевое слово c l a s s . Это же слово исполь-
зуется и при создании классов. (О них речь идет в главе 17.)
В действительности в языке C++ есть два слова, которые могут быть использованы для объявления
структур (или классов): c l a s s и s t r u c t . Использование слова s t r u c t почти эквивалентно исполь-
зованию слова с 1 as s, за исключением лишь того, что в этом случае вам не обязательно добавлять сло-
во p u b l i c : перед описанием переменных, входящих в структуру. Некоторые предпочитают использо- ,
вать слово s t r u c t при объявлении структур и слово c l a s s при объявлении классов (которые пред-
ставляют собой те же структуры, содержащие в себе не только переменные, но и функции). Обратите
внимание, что для объявления классов также можно использовать слово s t r u c t .
Когда вы поближе познакомитесь с классами (начиная с главы 17), вы узнаете, что означает слово
p u b l i c , а также слова p r i v a t e и p r o t e c t e d . Если вы применяете слово s t r u c t , по умолча-
нию используется слово p u b l i c , а если c l a s s - слово p r i v a t e . Наш совет: чтобы не путаться,
постоянно используйте слово c l a s s при объявлении классов и структур.
Почему два ключевых слова выполняют практически одни и те же функции? Слово s t r u c t использова-
лось еще в языке С и было оставлено для того, чтобы программы, написанные на языке С, можно было
компилировать с помощью компилятора C++. В языке C++ этому слову добавили немного функциональ-
ности, и теперь оно почти идентично слову c l a s s . Но, поскольку вы являетесь программистами на
C++, используйте при написании своих программ только слово c l a s s ,

88 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава10

Выразите свои желания


главе...
> Чем являются выражения
> Знакомство с основными операторами
> Логические выражения
> Оценка шачений
> Сравнение значений
V Использование математических функций

*77
им рограммы обрабатывают данные, и одним из видов такой обработки является вы-
• ^ •" полнение различных вычислений. Указание провести вычисления (которое записы-
вается в виде формулы), в языке Visual C++ .NET называется выражением. Если вы знакомы
с электронными таблицами (например, Excel), то наверняка уже имеете опыт использования
выражений: каждый раз, набирая формулы в ячейках, вы набирали выражения.
Выражения применяются для получения новых данных исходя из уже имеющихся. Напри-
мер, выражения можно использовать для вычисления площади круга, объема воздушного шара,
веса танка или проведения более сложных вычислений, скажем, подведения итогов голосования.

<М,6ЖН6 UU
Использование выражений просто необходимо при создании серьезных программ. Если
вы читаете главы по порядку, то уже сталкивались с выражениями, вычисляющими площадь
квадрата и площадь круга. Например, в следующей строке, взятой из программы CircleArea3
(глава 9), вычисляется площадь круга исходя из информации о значении его радиуса:
o C i r c l e . d b l A r e a = PI * o C i r c l e . d b l R a d i u s * o C i r c l e . d b l R a d i u s ;
Выражения также используются для того, чтобы определить, выполняется ли определенное
условие. Например, если нужно определить, достигнут ли лимит по кредитной карточке, следует
написать выражение, которое будет сравнивать значение лимита с остатком денег на счете.
Если вы хотите создать программу, способную на какие-то реальные действия, вам обязательно
придется прибегнуть к помощи выражений. Посмотрите на выражения, приведенные ниже:
2 + 2
3.14159 * Radius * Radius
ОстатокНаСчете < Лимит
Как видите, без помощи выражений вы не сможете даже сложить двух чисел!

Jlftoctnue onefta^ioftbt
В табл. 10.1 приведен список из пяти операторов, наиболее часто используемых при по-
строении выражений. Оператором называется математический символ, указывающий, какое
действие необходимо произвести для получения результата. Например, в выражении 4+5
оператором будет знак +.

Глава 10. Выразите свои желания 89


Таблица 10.1. Математические операторы
Оператор Пример Выполняемое действие
* foo * bar Умножает одно число на другое. Например, 6*3 будет 18
/ foo / bar Делит одно число на другое. Например, 6/3 будет 2
+ foo + bar Суммирует два числа. Например, 6+3 будет 9
foo - bar Отнимает от одного числа другое. Например, 6-3 будет 3
% foo % bar Возвращает остаток от деления двух чисел. Например, 10%3 даст число
1, поскольку 10/3 будет 3 и остаток 1

Вычисление остатка от деления часто используется для того, чтобы не позволить значе-
ниям выходить из заданного диапазона. Например предположим, что вы создаете программу,
которая перемещает фигурку космического корабля вдоль нижней части экрана (как в игре
Space Invaders, если вы ее видели). Для того чтобы корабль, залетая за правую часть экрана,
появлялся с левой стороны, используйте оператор вычисления остатка от деления. Допустим,
экран имеет в ширину 10 единиц и p o s — это позиция фигурки корабля, тогда результат опе-
рации p o s % 10 всегда будет принадлежать диапазону от 0 до 9, независимо от того, какое
значение имеет переменная p o s . Теперь, когда корабль достигает позиции 9 (крайняя спра-
ва), к ее значению прибавляется число 1, чтобы перейти в следующую позицию (корабль
пропадает с экрана), далее позиция вычисляется как результат выражения p o s % 10, полу-
чаем число 0, и корабль отображается в позиции 0 (крайняя слева).

сложные
Операторы, показанные в табл. 10.2, несколько сложнее, чем операторы, приведенные в
табл. 10.1. На данном этапе вы, наверное, могли бы обойтись и без них. Но по мере приобре-
тения опыта создания программ вы оцените эти операторы по достоинству и с удовольствием
будете их использовать. Некоторые из этих "сложных" операторов рассмотрены ниже в от-
дельных разделах этой главы. (Вы, наверное, подумаете: "Неужели они настолько сложные,
что для их описания нужно выделять отдельные разделы?!".)

Таблица 10.2. Операторы увеличения, уменьшения и сдвига


Оператор Пример Выполняемое действие
++ f o o + + , ++foo Увеличение. Добавляет число 1 к значению элемента. Например, если
переменная nAge имеет значение 2, то пхле выполнения операции
пАде-t-i- она будет иметь значение 3. (Кстати, свое название язык
C++ получил благодаря этому оператору.)
foo--, --foo Уменьшение. Его действие противоположно действию оператора
увеличения. Например, если переменная а имеет значение 2, после
выполнения операции а — она будет иметь значение 1
>> b a r Сдвиг разряда вправо. Выражение f o o >> b a r возвращает тот же
результат, что и выражение f o o / 2 b a r . Более подробно этот
оператор описан ниже в главе
< < bar Сдвиг разряда влево. Его действие противоположно действию оператора
>>. Более подробную информацию вы найдете далее в главе

90 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить


Оператор
Этот оператор не так прост, как может вначале показаться, поскольку степень уве-
личения зависит от типа переменной, значение которой увеличивается. Например,
если у вас есть указатель на переменную f оо и значение переменной foo занимает
четыре байта, то оператор увеличения, примененный к указателю, увеличит его зна-
чение на число 4. поскольку именно в этом случае указатель будет ссылаться на
следующее значение после значения переменной foo. Сложновато, не так ли? Бо-
лее детальную информацию об указателях вы можете найти в главе 13.
Есть два способа использования оператора ++. Его можно набрать или перед именем пе-
ременной: ^ +bar, или после: саг++.
Набрав +-+bar, вы даете указание вначале увеличить значение переменной b a r . а затем
его использовать. Рассмотрим уго на таком примере:
i r . t b a r :-- 1 ;
Console::WriteLine(+-bar.ToString());
В этом случае значение переменной b a r будет увеличено на число 1, и на экране будет
отображено число 2 (новое значение переменной bar).
Напротив, если вы наберете bar++, значение этой переменной вначале будет обработано,
а затем увеличено. Например:
i n t bar •-- 1;
cout << bar++;
В этом случае значение переменной b a r будет увеличено на единицу, но на экране будет
отображено се старое значение (число !), поскольку увеличение происходит после отображе-
ния значения переменной. (В первом примере для отображения значения использован управ-
ляемый коп, а во втором — старый неуправляемый код С•*-+.)
Оператор ++ часто используется в циклах для отсчета количества итераций. (Вам это не-
понятно? Не переживайте. Эти вопросы подробно обсуждаются в главе 11.)
Оператор -- работает по тому же принципу, что и ++, с той лишь разницей, что он
уменьшает значения переменных на единицу. (Ну хорошо, не всегда на единицу. В некоторых
случаях, например при работе с указателями, значение может уменьшаться более чем на еди-
ницу. Но в основном значение уменьшается именно на число 1. Уф!)

Оператор »
Оператор сдвига очень полезен при работе с двоичными числами. Вот некоторые приме-
ры его использования:
16 >> 1 возвращает число 8;
16 >> 2 возвращает число 4;
16 >> 3 возвращает число 2;
15 >> 1 возвращает число 7;
15 >> 2 возвращает число 3.
Для получения результата первое число представляется в двоичном виде, а затем все биты
сдвигаются вправо на количество позиций, указанное вторым числом. Обратите внимание,
что при сдвиге битов вправо число уменьшается.
Для внесения ясности рассмотрим действие оператора сдвига подробнее. Число 16, на-
пример, в двоичном виде выглядит так:
10 0 0 0

Глава 10. Выразите свои желания 91


Если сдвинуть все биты вправо на одну позицию, получим число
0 1000
В десятичной системе это число соответствует числу 8, поэтому выражение 16 >> 1
даст число 8.
Рассмотрим другой пример. Число 15 в двоичной системе счисления выглядит так:
0 1111
Поэтому результатом операции 15 >> 2 будет число
0 0 0 11
В десятичной системе ему соответствует число 3.

Оператор «
Ниже приведены примеры использования оператора <<.
16 <<" 1 возвращает число 32;
16 << 2 возвращает число 60.
Если получаемое число превышает максимапьно допустимое значение переменной, сдви-
гаемые влево биты обрезаются. Например, если для значения переменной зарезервировано
только 8 бит памяти и вы сдвигаете исходное значение на 8 бит влево, то в результате полу-
чите число 0 (нуль). Это произойдет потому, что все биты исходного значения были сдвину-
ты за пределы переменной и обрезаны.
Обратите внимание, что оператор << выглядит точно так же, как символы <<, используе-
мые вместе с командой c o u t . (Напомним, что c o u t является неуправляемым оператором
языка C++, который используется для отображения данных на экране.) Если вы попытаетесь
использовать оператор c o u t для отображения результата выполнения операции сдвига вле-
во, его символы << будут иметь приоритет перед такими же символами оператора сдвига, из-
за чего будет отображен не тот результат, который вам нужен (дело в том, что выражения об-
рабатываются слева направо и команда c o u t << будет обработана первой).
Поэтому, если вам нужно отобразить результат выполнения операции сдвига влево, возь-
мите это выражение в скобки:
cout « (16 << 2) << e n d l ;

Uctnutta и ложь в логических #btftajfcenusuc


Все рассмотренные ранее операторы используются в выражениях для вычисления какого-
то определенного результата. Например вы видели, как используется оператор умножения
для вычисления площади круга, если известно значение его радиуса.
Теперь же вы познакомитесь с логическими выражениями. Логические выражения ис-
пользуются не для вычисления какого-то конкретного результата, а для определения ложно-
сти или истинности проверяемого условия.
[ !апример. иас могут интересовать такие вопросы: "Любит ли она меня?", "Есть ли деньги
па моем счете?" или "Нажал ли пользователь клавишу Enter?". Почти всегда, когда нужно по-
лучить ответ на вопрос о выполнении какого-то условия, используются логические выраже-
ния. Обычно в программах истинность условий проверяется в выражениях, подобных этому:
"Если проверяемое условие истинно, выполните вот этот набор инструкций".
Если логическое выражение возвращает нулевое значение, проверяемое условие считается
ложным (т.е. оно не выполняется). Если возвращаемое значение отлично от нулевого, усло-
вие считается истинным (оно выполняется).

92 Часть //. Все, что вы хотели знать о C++, но о чем боялись спроси
В языке C++ логические выражения используются настолько часто, что для представле-
ния возвращаемых ими результатов предусмотрен даже отдельный тип данных: b o o l . У это-
го типа данных есть только два значения, для представления которых специально зарезерви-
рованы два ключевых слова: t r u e (Истина) и f a l s e (Ложь).
В табл. 10.3 рассмотрены операторы, которые используются в логических выражениях.
Отсюда происходит их название: логические операторы. В следующей Главе описано, как ло-
гические операторы могут использоваться для определения условий, проверяемых условными
операторами (такими, например, как оператор if).
Таблица 10.3. Операторы сравнения (или логические операторы)
Оператор Пример использования Выполняемое действие
> f оо > b a r Больше чем. Возвращает логическое значение t r u e , если
значение слева больше значения справа. Например:
3 > 5 возвращает значение f a l s e ;
3 > 1 возвращает значение t r u e ;
3 > 3 возвращает значение f a l s e , поскольку три равно
трем, но не больше
> =
foo >= bar Больше или равно. Возвращает логическое значение t r u e ,
если значение слева больше значения справа или равно ему.
Например:
3 >= 5 возвращает значение f a l s e ;
3 >= 1 возвращает значение t r u e ;
3 >= 3 возвращает значение t r u e , поскольку три равно
трем
< foo < bar Меньше чем. Возвращает логическое значение t r u e , если
значение слева меньше значения справа. Например:
3 < 5 возвращает значение t r u e ;
3 < 1 возвращает значение f a l s e ;
3 < 3 возвращает значение f a l s e
< =
f o o <= b a r Меньше или равно. Возвращает логическое значение t r u e ,
если значение слева меньше значения справа или равно
ему. Например:
3 <= 5 возвращает значение t r u e ;
3 <= 1 возвращает значение f a l s e ;
3 <= 3 возвращает значение t r u e
f o o == b a r Равенство. Возвращает логическое значение t r u e , если
значение слева равно значению справа. Например:
1 == 2 возвращает значение f a l s e ;
1 == 1 возвращает значение t r u e
!= f o o != b a r He равно. Возвращает логическое значение t r u e , если
значение слева не равно значению справа. Например:

Глава 10. Выразите свои желания 93


Окончание табл. 10.3
Оператор Пример использования Выполняемое действие
1 != 2 возвращает значение t r u e ;
1 != 1 возвращает значение f a l s e
f
! ! °° Не. Требует значение только одного аргумента. Если
аргумент имеет значение t r u e , оператор возвращает
значение f a l s e . Если аргумент имеет значение f a l s e ,
оператор возвращает значение t r u e . Например:
! 1 возвращает значение f a l s e ;
! О возвращает значение t r u e
&& f o o && b a r Логическое И. Возвращает значение t r u e только в том
случае, если значению справа и значению слева
соответствуют значения t r u e . Например:
1 && 1 возвращает значение t r u e ;
О && 1 возвращает значение f a l s e ;
используется в выражениях наподобие: "Если есть
возможность && есть желание, тогда ..."
II f o o || b a r Логическое ИЛИ. Возвращает значение t r u e в случае, если
хотя бы одному из проверяемых значений соответствует
значение t r u e . Например:
1 | | 0 возвращает значение t r u e ;
1 1 1 1 возвращает значение t r u e ;
О М 0 возвращает значение f a l s e

Оие/ia/no/iп
Оператор присвоения (=) используется в тех случаях, когда необходимо присвоить значе-
ние какой-нибудь переменной. Например, если нужно сохранить входящую информацию или
вычисленный результат. С оператором присвоения вы уже встречались ранее и видели, на-
пример, в такой строке:
f l t A r e a = PI * f l t R a d i u s * f l t R a d i u s ;
Когда происходит присвоение значения, то значение, вычисляемое расположенным спра-
ва от знака равенства выражением, присваивается переменной, указанной слева от знака ра-
венства (=).
Оператор присвоения можно использовать несколько раз в одном и том же выражении. На-
пример, в приведенном ниже коде значение 0 (нуль) присваивается сразу нескольким переменным:
а = b = с = 0;

Переменной можно присвоить только значение того же типа, что и сама перемен-
ная, или типа, который может быть автоматически преобразован к типу перемен-
ной. Продемонстрируем это на таком примере:
i n t a = 10;

94 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Ошибки здесь нет, поскольку присваиваемое значение имеет тот же тип, который
объявлен для переменной.
А вот в следующей строке содержится ошибка:
i n t a = "Sasha";
Переменная а может принимать только числовые значения типа integer ( i n t ) . в то время
как предпринимается попытка присвоить ей текстовое значение Sasha. Компилятор C++ бу-
дет с этим не согласен,

Не путайте оператор присвоения с оператором равенства


> Обратите внимание, что логический оператор равенства == отличается от оператора присвоения =. Опе-
: ратор присвоения (=) присваивает переменной, указанной слева, значение, расположенное справа. Опе-
ратор равенства {==) проверяет, совпадает ли значение, расположенное слева, со значением, располо-
; женным справа, но при этом ни одно из значений не изменяется. Использование оператора = в тех мес-
\ тах, где подразумевается использование оператора -=, является довольно распространенной ошибкой, -
способной повлечь за собой серьезные проблемы.
; Например, в результате выполнения приведенного ниже кода переменной а всегда будет присваиваться
; значение 2. Это будет происходить потому, что при проверке условия оператора i f переменной а при-
; сваивается значение 1, а поскольку числу 1 соответствует логическое значение t r u e , инструкция ус-
; ловного оператора а = а + 1 будет выполняться:
: i f (а = 1)
а = а + 1;
Совершенно иначе выполняется код, показанный ниже, поскольку в этом случае значение переменной а
: изменяется только при условии, что оно равно единице:
' if (a == 1)
а = а + 1;
Чтобы не ошибиться и быть точно уверенным, что значения будут сравниваться, а не присваиваться,
• можно поступить следующим образом. Если нужно сравнить значение переменной с каким-то числом или
каким-то постоянным значением, укажите его первым, а затем наберите знаки равенства и название пе-
ременной. Так, вместо кода
i f (a == 1)
а = а + 1;
наберите код
i f (1 == а)
" а = а + 1;
Оба фрагмента будут работать одинаково. Однако вы можете случайно пропустить один знак равенства и
' набрать код
:
i f (1 = а)
а = a f 1;
В этом случае компилятор выдаст сообщение об ошибке. Таким образом вы рискуете получить только син-
таксическую ошибку (которая сразу же будет обнаружена), вместо логической ошибки, найти которую не так
просто и которая может оказаться не такой уж безобидной. Кстати, ошибочное использование оператора
присвоения вместо оператора равенства настолько распространено, что во всех спорных случаях предусмот-
• рено генерирование предупреждения компилятором. Поэтому старайтесь не оставлять без внимания появ-
ляющиеся предупреждения - это поможет вам сэкономить время и усилия при отладке программы.

Глава 10. Выразите свои желания 95


/See orf onefta0ioftax
Довольно часто возникает необходимость совершить какое-то действие в отношение
только одной переменной. Например, нужно прибавить какое-то число к общей сумме или
умножить значение на какую-то константу.
Конечно, это можно сделать, набрав выражение наподобие такого:
foo = foo * 3;
b a r = b a r + 2;
Однако C++ предлагает для таких случаев набор специальных операторов присвоения, ко-
торые одновременно обрабатывают значение переменной и присваивают ей полученный ре-
зультат. Выражения с такими операторами более лаконичны и наглядны. Работают они по
следующему принципу. Выражение, наподобие
foo = foo operator b a r ;
можно заменить эквивалентным ему выражением
foo operator bar;
Например, вместо выражения
b = Ь + 1;
можно набрать
с += 1;
В табл. 10.4 приведен список всех специальных операторов присвоения и описаны выпол-
няемые ими действия.
Таблица 1 0 . 4 . Специальные операторы присвоения

Оператор Пример Выполняемое действие

+= f o o += b a r Добавляет значение, указанное справа, к значению переменной,


указанной слева. Например, чтобы добавить к значению переменной
f o o число 3, наберите
foo += 3;
f
° o -= b a r Отнимает от значения переменной, указанной слева, значение,
указанное справа. Например, чтобы вычесть из значения переменной
f o o число 3, наберите

foo -= 3;
f o
"'" = ° *= bar Умножает значение переменной, указанной слева, на значение,
указанное справа. Например, чтобы умножить значение переменной
f o o на число 3, наберите
foo *= 3;
f o
/= ° /= bar Делит значение переменной, указанной слева, на значение, указанное
справа. Например, чтобы разделить значение переменной foo на
число 3, наберите
foo /= 3;

96 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Окончание табл. 10.4
Оператор Пример Выполняемое действие
% b a r
%- f°o ~ Присваивает переменной, указанной слева, остаток, получаемый в
результате деления исходного значения этой переменной на значение,
указанное справа. Например, чтобы присвоить переменой f o o
остаток, получаемый при делении ее значения на число 10, наберите
f o o %= 1 0 ;
<<= f o o <<= b a r Выполняет сдвиг влево значения переменной, указанной слева, на
число битов, равное значению, указанному справа. Например, чтобы
сдвинуть значение переменной f o o на два бита влево, наберите
foo <<= 2;
>>= f o o >>= b a r Выполняет сдвиг вправо значения переменной, указанной слева, на
число битов, равное значению, указанному справа. Например, чтобы
сдвинуть значение переменной f o o на два бита вправо, наберите
foo >>= 2;
&= f o o &= bar Выполнение поразрядной операции И с присвоением полученного
результата переменной, указанной слева. Например, если переменная
f o o имеет значение 10, то в результате выполнения следующего
выражения ей будет присвоено значение 2:
f o o &= 2 ;
|= f o o |= bar Выполнение поразрядной операции ИЛИ с присвоением полученного
результата переменной, указанной слева. Например, если переменная
f o o имеет значение 10, то в результате выполнения следующего
выражения ей будет присвоено значение 11:
fOO | = 1;
л
foo = bar Выполнение поразрядной операции ИСКЛЮЧИТЕЛЬНОЕ ИЛИ с
присвоением полученного результата переменной, указанной слева.
Например, если переменная f o o имеет значение 10, то в результате
выполнения следующего выражения ей будет присвоено значение 8:
л
foo = 2;

Работа с битами
Для сохранения одного числа используется некоторое количество битов. Например, для
числа типа integer ( i n t ) выделяется 32 бита памяти. Количество битов, выделяемых для пе-
ременной, определяет максимальное значение, которое она может принять.
Для логических переменных, несмотря на то что они могут принимать только два значе-
ния — t r u e и f a l s e , обычно выделяется столько же памяти, сколько и для числовых пере-
менных. Допустим, вам нужно сохранить и обработать большое количество логических зна-
чений. В этом случае вы можете в значительной степени оптимизировать использование па-
мяти, если для представления одного логического значения будете использовать только один
бит памяти (вместо обычных 32 битов).
Например представим, что создаваемая программа должна сохранить и обработать результа-
ты опроса 10 000 респондентов, каждому из которых будет задано 32 вопроса, требующих отве-
та "нет" или "да". Если вы используете для этого обычные логические значения, для которых
памяти нужно столько же, сколько для представления обычных чисел, программа потребует для

Глава 10. Выразите свои желания 97


своего выполнения столько памяти, сколько нужно для представления 10 000 х 32 (320 000) чи-
сел. Это даже больший объем памяти, чем имели некоторые первые компьютеры!
Если же для сохранения одного ответа вы будете использовать только один бит, ситуация
меняется кардинальным образом. Действительно, если ответа может быть только два, 0 мож-
но использовать для представления отрицательного ответа, а 1 — для положительного. Тогда
нулевой бит числа можно использовать для хранения информации об ответе на первый во-
прос, первый б и т — для хранения информации об ответе на второй вопрос и т.д. (нумерация
битов начинается с нулевого). Таким образом, каждая числовая переменная может хранить
данные об ответе на 32 вопроса (так как в ее распоряжении есть 32 бита памяти) и ваша про-
грамма уже будет требовать для своего выполнения память в объеме, необходимом для хра-
нения 10 000 чисел. Экономия существенная, не так ли?
Если вы используете числа именно таким образом, это называется созданием битовых по-
лей. В табл. 10.5 описаны операторы, которые позволяют обрабатывать значения переменных
на уровне битов (в таблице обрабатываемые значения и получаемые результаты представле-
ны в двоичном виде).

Таблица 10.5. Побитовые операторы

Оператор Пример Выполняемое действие


~ f оо Побитовое НЕ. Нули меняются на единицы и наоборот. Например:
- 1 0 1 1 даст 0100
<< f o o << b a r Сдвиг влево на указанное количество битов. Например:
1011 « 2 даст 1100
(см. также табл. 10.2)
» f o o >> b a r Сдвиг вправо на указанное количество битов. Например:
1011 » 2 даст 0010
(см. также табл. 10.2)
& foo & bar Побитовое И. Если биту в значении слева и биту под тем же номером в
значении справа соответствует единица, возвращается единица. Во всех
остальных случаях возвращается нуль. Например:
1 0 1 1 & 1010 даст 1010
I foo | bar Побитовое ИЛИ. Если биту в значении слева или биту под тем же
номером в значении справа соответствует единица, возвращается
единица. В противном случае возвращается нуль. Например:
1011 I 1010 даст 1011
A
foo bar Побитовое ИСКЛЮЧИТЕЛЬНОЕ ИЛИ. Только если одному из
сравниваемых битов соответствует единица, а второму- нуль,
возвращается единица. В остальных случаях возвращается нуль.
Например:
л
1011 1010 даст0001

Условный оператор
Условным оператором называется оператор i f . Он работает по тому же принципу, что и
оператор IF, который вы могли видеть в электронных таблицах. Оператору if требуются три
выражения. Вначале оценивается первое выражение. Если ему соответствует значение t r u e ,

98 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
возвращается результат, получаемый при обработке второго выражения, если f a l s e — ре-
зультат, получаемый при обработке третьего выражения.
В электронных таблицах синтаксис оператора ГР выглядел так:
IF (exprl, excr2, ехргЗ)
Это означало следующее: если выражение e x p r l истинно, вернуть значение ехрг2, в
противном случае вернуть значение ехргЗ.
В языке C++ то же самое записывается несколько иначе:
e x p r l ? expr2 : ехргЗ
Таким образом, для карточной игры можно было бы набрать нечто подобное:
UserMessage = (Очки > 21) ? " П е р е б о р ! " : "Еще к а р т у ? " ;

Приоритет операторов
Если вы еще не забыли того, что учили в школе на уроках математики, для вас не будет
неожиданностью выполнение в формулах одних математических операций раньше других
(даже если они указаны в конце формулы).
При обработке математических выражений компьютер придерживается тех же правил,
которые вы учили в школе (и, возможно, уже забыли). Выражения обрабатываются слева на-
право, но все же некоторые операторы обрабатываются в первую очередь. Например, резуль-
татом выражения 3 + 2 x 3 будет число 9. Почему? Потому что оператор умножения имеет
больший приоритет, чем оператор сложения, следовательно, вначале два умножается на три и
только потом к полученному числу прибавляется три. Если бы приоритет операторов не учи-
тывался и выражение просто вычислялось бы слева направо, мы получили бы число 15.
Чтобы изменить порядок обработки операторов, определяемых их приоритетом, исполь-
зуйте круглые скобки. Например, можете набрать выражение (3 + 2) х 3. В этом случае вна-
чале будет обработан тот оператор, который заключен в скобки, т.е. оператор сложения, и
только потом полученное число будет умножено на три.
В табл. 10.6 представлен список операторов с учетом их приоритета, Те операторы, кото-
рые указаны выше, обрабатываются раньше, чем расположенные ниже (другими словами,
они имеют больший приоритет). Например, оператор + имеет больший приоритет, чем опе-
ратор >. Поэтому, выражение 1 + 0 > 1 равнозначно выражению (1 + 0) > 1. Но сами по себе,
как вы понимаете, эти выражения ложны.
Те операторы, которые расположены в одной и той же строке таблицы, имеют одинако-
вый приоритет, поэтому если в выражении они стоят рядом, то вычисляются по очереди сле-
ва направо. Например, 3 x 4 / 2 равнозначно ( 3 x 4 ) / 2.

Таблица 10.6. Приоритет операторов


Приоритет Операторы
Высший приоритет ()

< < = > >=

Глава 10. Выразите свои желания 99


Окончание табл. 10.6

Приоритет Операторы

Низший приоритет

Если вы не уверены, в каком именно порядке будут обрабатываться операторы в


набранном выражении, всегда добавляйте круглые скобки, чтобы убедиться, что
вычислен будет именно тот результат, который вам нужен.

Двоичная и десятичная системы счисления


Если вы не занимались раньше программированием, упоминания о двоичных числах и побитовых опера-
циях могут поставить вас в затруднительное положение. Система счисления, которой мы пользуемся в
повседневной жизни и к которой привыкли, называется десятичной. Каждая цифра десятичного числа, в
зависимости от своей позиции, означает единицы, десятки, сотни, тысячи и т.д. Например, число 125
представляется как 100+ 20+ 5 или, что то же самое, как 1 х 10г + 2 x 1 0 1 +5 х 100. (Со школы вы на-
верное еще помните, что любое число в нулевой степени равно единице.)
Компьютер не может представлять числа с использованием десяти различных цифр. Так как он может
различать информацию только в формате "да-нет", каждая позиция в числе может быть представлена
только одной из двух цифр: нулем и единицей. Числа, представленные с помощью нулей и единиц, назы-
ваются двоичными. Каждая цифра такого числа называется битом (или двоичной цифрой}. Например,
двоичное число 1101 расшифровывается как 1 х 23 + 1 х 2s + 0 х 21 + 1 х 2°. Если перевести это в деся-
тичную систему счисления, то получим 8 + 4 + 0 + 1, или просто число 13.
Для хранения одного числа компьютер использует набор из восьми битов, который обозначается термином
байт. Один байт может представить 256 (или 2°) уникальных значений. Два байта, объединенные вместе для
представления одного значения, называются словом. Слово имеет в своем распоряжении 16 битов и может
уже представить 65 536 (или 216) уникальных значений. Четыре байта (32 бита) образуют двойное слово.
Для тех, кто имеет отношение к компьютерам, число 210 является ключевым. Это 1024 байта, или кило-
байт памяти (К). Хотя приставка кило обозначает тысячу, в мире компьютерных технологий она обознача-
ет число 1024. Таким образом, например, 64К означает 64 х 1024, или 65 536 байт памяти.
Точно так же, хотя приставка мега (Щ в обычном мире означает миллион, для компьютерщиков это число
1024x1024, или 1048 576.
Поскольку двоичные числа не очень удобны (они слишком длинные), иногда используется шестнадцатерич-
ная система счисления. В этой системе каждая позиция в числе может быть представлена 16 цифрами. Че-
тыре бита объединяются для представления одной шестнадцатеричной цифры, которая называется гексит.
Поскольку гексит может принимать значения от 0 до 15, для представления цифр, обозначаемых десятичны-
ми числами от 10 до 15 используются первые шесть букв латинского алфавита (от А до F). Другими словами,
буква А обозначает 10, В- 11 и т.д. Если вам нужно набрать шестнадцатеричное число в кодах C++, начни-
те его с приставки Ох. Так, число ОхОА соответствует десятичному числу 10, а число OxFF - десятичному чис-
лу 255. Если среди ваших знакомых есть хакер, помешанный на языках программирования, спросите, сколь-
ко ему лет в шестнадцатеричной системе, и он не задумываясь ответит.
Почему компьютеры используют двоичную систему счисления? Это связано прежде всего с аппаратной
частью, в частности с принципами работы транзисторов. Но это уже отдельная тема, и, если вас эти во-
просы действительно интересуют, обратитесь к специальной литературе.

100 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
Примеры работы операторов
Чтобы получить лучшее представление о работе операторов, просмотрите приведенные
ниже примеры.
Пример 1. Вычисление площади круга.
dblArea = PI * d b l R a d i u s * d b l R a d i u s ;
Пример 2. Вычисление объема налога, которым облагаются продаваемые товары, если
известны налоговая ставка и сумма, вырученная от продажи.
dblTax = d b l P u r c h a s e * d b l T a x R a t e ;
Пример 3. Вычисление розничной цены товара при условии, что она должна включать в
себя налог на продаваемые товары, объем которого был вычислен во втором примере.
d b l P r i c e = (1 + d b l T a x R a t e ) * dblPurchase;
Пример 4. Сравнивается значение кредитного лимита с ценой товара, и, если лимит
меньше цены, его значение увеличивается на 500.
DblCreditLimit = (dblPrice > dblCreditLimit) ?
b l C r e d i t L i m i t + 500 : d b l C r e d i t L i m i t ;

UtcunejicunuHeacue функции
Во всех выражениях, с которыми вы сталкивались до этого, использовались только самые
простые операторы. Но предположим, вам нужно вычислить что-то более серьезное и специ-
фическое, связанное, например, с тригонометрическими операциями. Средство языка Visual
C++, обозначаемое термином CLR (Common Language Runtime), предоставляет вам возмож-
ность использования целого набора математических функций для проведения специальных
вычислений. Все они принадлежат классу Math. Наиболее часто используемые из них пред-
ставлены в табл. 10.7.

Таблица 10.7. Математические функции

Функция Пример использования Выполняемое действие

Abs Math: :Abs(-4) Возвращает абсолютное значение числа (модуль)

Ceil Math: :Ceil(4.2 Округление до ближайшего большего целого. Например,


применив эту функцию к числу 4,2, получим число 5

Cos Math: :Cos{.03) Возвращает косинус числа. Число выражается в радианах

Е Math: :E Возвращает значение числа е, которое основано на


вычислении натурального логарифма и равно
приблизительно 2,718

Exp Math: :Exp(4) Возвращает результат вычисления е п , где вместо п


используется аргумент функции

Floor Math::Floor(4 . 2 Округляет число до ближайшего меньшего целого.


Например, если аргументом является число 4,2,
возвращается число 4

Глава 10. Выразите свои желания 101


Окончание табл. 10.7
Функция Пример использования Выполняемое действие

Log Math: :Log(4 ) Возвращает значение натурального логарифма для


указанного числа

Max Math: :Max{4 .5, 8£3.2) Возвращает большее из двух чисел

Min Math: :Min(4 .5, 8E3.2) Возвращает меньшее из двух чисел

PI Math: :PI Возвращает значение числа п, которое равно


приблизительно 3,141592

Pow Math: :Pow(4 , 2) п


Возвращает число х , где х - это первый аргумент, п -
2
второй. В данном случае 4 даст число 16

Round Math: :Round (4.2) Округляет число до ближайшего целого. Например,


число 4,2 будет округлено до числа 4, а число 4,8 - до
числа 5

Sin M a t h : : S i n (. 03) Возвращает синус числа. Число представляется в

радианах

Sqrt M a t h : : S q r t (А) Возвращает квадратный корень числа

Tan M a t h : :Tan{ .03) Возвращает тангенс числа. Число представляется в


радианах

Увидеть некоторые из этих функций в действии вы можете в приведенном ниже листинге


программы, которая, как и прежде, вычисляет площадь круга.
//CircleArea4
//Вычисление площади по значению радиуса

//Использование математических функций

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

//Объявление структуры для представления информации


//о параметрах круга
class Circlelnfo
{
public:
double dblRadius;
double dblArea;

//С этой строки начинается выполнение программы


t i f d e f _UNICODE
i n t wmain(void)
#else
int main(void)

102 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
#endif

Circlelnfo oCircle;

//Отображение вопроса о значении радиуса


ConscLe::WriteLine(S"Укажите радиус круга");

//Получение ответа и преобразование его к числу с


//плавающей запятой
cCirc Le.dbJ.Radius = Double: :Parse(Console: :ReadLine{> ) ;

//Вычисление площади круга


oCircle .dblArea =• Math: : PI *oCircie . dblRadius*
oCircle-dblRadius;

//Отображение результата на экране


//Обратите внимание, что для этого число должно
//быть предварительна преобразовано в строку
Console: :WriteLine (S"Площадь круга составляет {С}
единиц.", cCircle.dblArea.ToString()

//И еце одна (может и ненужная) математическая операция


Console: :Writ.eLine {3"£сли вам это интересно, то
корень квгдратньгй от радиуса равен {01",
Math: :Sqrt. (oC.i rcle . dblRadius) .ToString ()

//Ожидание, пока пользователь не остановит


//выполнение программа
Сопзс1е::WriteLine(S"Нажмите клавишу Enter, чтобы
остановить выполнение программы")
ConsoJе::ReadLine();

return 0;

Cfnafiuu cftofutatH мсипемсиничеосих


функций
В действительности все основные математические функции, которые доступны при
написании программ .NET, используются также и при написании стандартных программ
C++. Эти функции являются составной частью библиотеки math, которая подключается
таким кодом:
# i n c l u d e <math.h>
т
•J^^S» абл. 10.8 представлены наиболее часто используемые функции библиотеки
4 N l :
math. Помните, что их использование уместно только при написании неуправ-
ляемых программ.

Глава 10. Выразите свои желания 103


Таблица 10.8. Математические функции языка C + +
Функция Пример использования Выполняемое действие

abs aos(-4) Возвращает абсолютное значение числа (модуль)

ceil ceil (4.2) Округление до ближайшего большего целого

cos cos ( .03) Возвращает косинус числа. Число выражается в радианах

exp exp (4) Возвращает результат вычисления е п

floor floor (4.2 Округляег число до ближайшего меньшего целого

log log (4) Возвращает значение натурального логарифма для указанного


числа

pow pow(4, 2) Возвращает число х п , где х- это первый аргумент, п-

второй
sin sin(.03)
Возвращает синус числа. Число представляется в радианах
sqrt sqrt(4)
Возвращает квадратный корень числа
tan tan(.03)
Возвращает тангенс числа. Число представляется в радианах

104 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Глава 11

Ключевые слова — ключ к диалогу


с компьютером
гла£е...
У Ключевые слова операторов управления
У Использование if для проверки условий
)*• Создание циклов с использованием слов for и while
> Использование слов switch, c a s e и b r e a k

Ъ* ели вы читаете книгу по порядку, вы уже получили представление об основных ас-


^ ^ пектах написания программ. Но до сих пор все рассматриваемые программы выпол-
нялись только последовательно. Другими словами, выполнение программы всегда начина-
лось с первой строки функции main, а затем последовательно осуществлялся переход от од-
ного выражения к другому, и порядок этот никогда не нарушался.
Однако, как вы уже могли убедиться на собственном опыте, течение жизни не всегда пря-
молинейно и порой случаются отклонения от общего курса. Хорошие программы обладают
подобной гибкостью. Существуют, конечно, задачи, которые могут быть решены с помощью
последовательно выполняющихся программ. Но в большинстве случаев программы должны
уметь реагировать на некие внешние воздействия или команды и, в зависимости от этого, как-
то изменять свое поведение. Для моделирования таких ситуаций C++ предлагает целый набор
операторов, позволяющих управлять последовательностью выполняемых действий. Эти опе-
раторы разрешают выполнение определенной операции только в случае выполнения указан-
ного условия. Кроме того, они способны выполнять один и тот же набор инструкций до тех
пор, пока заданное условие не перестанет выполняться.
Существует множество ситуаций, которые невозможно смоделировать без использования
таких операторов. Ниже приведено несколько примеров типичных задач, требующих для сво-
его решения многократного повторения одних и тех же действий.
$ •/ Планомерное повышение цен до тех пор, пока товары успешно продаются
I (ценовое регулирование спроса).
I •/ Продолжение стрельбы до тех пор, пока цель не будет поражена.
? S Определение среднего возраста для группы из 32 студентов путем суммирования
••
• возраста всех студентов и деления полученного числа на 32.

В других ситуациях вам необходима возможность выбора. Обычно эта возможность фор-
мулируется так: если условие выполняется, сделайте это. Приведем примеры нескольких по-
добных ситуаций.
| /" Если профессор настаивает на выполнении домашнего задания, сделайте его. (В
| противном случае не делайте.)
Xf Если покупатель сделал более 3 000 покупок, предоставьте ему скидку.
| V Если цвет желтый — жмите на газ. Если красный — тормозите.

Глава 11. Ключевые слова— ключ к диалогу с компьютером 105


Как видите, существует очень широкий спектр самых типичных задач, требующих для
своего выполнения либо многократного повторения одних и тех же действий, либо осуществ-
ления выбора. Справиться с такими задачами призваны операторы, управляющие последова-
тельностью выполняемых инструкций. И эта глава посвящена именно им.

великолепная tnfioiiica: ключевые слова if


for и while
Три оператора управления используются почти во всех программах: if, f o r и w h i l e .
Оператор if (называемый также условным оператором) выполняет определенный набор ин-
струкций в том и только том случае, если заданное условие выполняется. Операторы for и
while (называемые иногда цикл for и цикл w h i l e ) повторяют нужное количество раз один
и тот же заданный набор инструкций.

Условный оператор
Синтаксис условного оператора i f очень прост:
if {exprl)
szmt;
(Обратите внимание, что здесь и далее в книге словом ехрг будет обозначаться любое
выражение, например такое, как i < 1, а словом s t m t — инструкция, например
c o s t = c o s t + 1.)
Вместо e x p r l можно подставить любое выражение, которому может быть сопоставлено
логическое значение. Если выражение истинно, выполняется инструкция stmt. Можно ис-
пользовать фигурные скобки, для того чтобы заключить в них набор инструкций, которые
должны будут выполняться в случае, если проверяемое выражение истинно. Например, сле-
дующим кодом проверяется значение логической переменной f Do Do, и, если оно совпадает
со значением t r u e , выполняется набор из трех инструкций:
if (fDcDo)

nDeedle = 0;
nDidle = 1;
nDum = 0;
}
А этот код моделирует ситуацию, в которой покупателю предоставляется скидка, если он
сделал покупки на сумму, превышающую $3 000:
if (nCount '-> 3000)
{
dblDiscount = .2;
}
Можно придать оператору i f большую гибкость, если использовать вместе с ним ключе-
вое слово e l s e :
if (exprl)
stmtl;
else
stmt 2;

В этом случае, если проверяемое выражение (exprl) ложно, выполняется инструкция stint 2.

106 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Приведенный ниже код моделирует эпизод карточной игры, когда проверяются набран-
ные игроком очки, и, если их сумма превышает число 21, фиксируется перебор (fBusted), в
противном случае предлагается взять еше одну карту:
if {nHandValue > 21)
{
//В картах перебор
nUserScore -= nBet;
fBusted = true;
}
else
{
//Игроку предлагается взять еще одну карту
cout << "Еще?\п";
cin >> fHitMe;

Введение в форматирование кодов программ


Коды программ можно форматировать множеством способов. И хотя для внешнего оформления кодов не
существует никаких стандартов и ограничений, вы можете применить пару приемов, позволяющих значи-
тельно упростить их чтение и понимание.
Хорошей практикой является выделение с помощью отступов всех строк, заключенных в пару фигурных
скобок. Таким образом вы сразу можете видеть набор инструкций, выполняемых вместе. Например, сле-
дующий код легко читается, поскольку к нужным строкам добавлены отступы:
i f ( n H a n d V a l u e > 21)
{
//В картах перебор
nUserScore -= nBet;
fBusted = true;
} '
else
<
//Игроку предлагается взять еще одну карту
cout « "Еще?\п";
cin » fHitMe;
> ' -
А вот тот же код, набранный без отступов:
if (nHandValue > 21)( ' ' '
//В картах перебор
nUserScore -= nBet; fBusted = true;} else {"
//Игроку предлагается взять еще одну карту
cout « "Еще?\п"; cin >> fHitMe;}
Первый код проще, поскольку из него ясно видно, какие инструкции будут выполнены в случае выполне-
ния условия, а какие в случае невыполнения.
Если вы читаете главы книги по порядку, то уже видели использование этого приема на примере функции
m a i n , все строки которой выделяются отступами. Атакже видели нечто подобное при создании структур.
При использовании вложенных операторов выделяйте коды каждого из них дополнительными отступами.
Например:

Глава 11. Ключевые слова — ключ к диалогу с компьютером 107


if (too)
{
bar-f+
if (bar > 3)
{
baz = 2;
}
if (goober < 7)
{
flibber - 3;

Другим приемом, облегчающим чтение кодов программы (который также использовался во всех приве-
денных выше примерах), является размещение закрывающей фигурной скобки (}} на одном уровне с со-
ответствующей ей открывающей скобкой:
if (fFoo)

} '
Благодаря этому легко увидеть, где начатый блок кодов заканчивается. Кроме того, хотя для оператора
i £ использовать фигурные скобки обязательно только в том случае, если он содержит более одной ин-
струкции для выполнения, постоянное их применение может предотвратить возникновение некоторых
тривиальных ошибок. Например, в следующем коде фигурные скобки не используются:
if (fFoo) ' ,
nVal++;
nCheck++;
Приведенный ниже код выполняет точно те же действия, но читается намного проще и позволяет в случае
необходимости без особых усилий добавить к оператору i f дополнительные инструкции для выполнения:
if (fFoo)
{
nVai++;

. nCheck++;
Visuaf C++ .NET форматирует коды вашей программы автоматически. Например, если вы выделите с по-
мощью отступа какую-то строку, набираемые после нее строки будут автоматически выравниваться по
: ней. Если вы наберете скобку }, она автоматически будет выровнена по соответствующей ей скобке {.

Следующий код определяет размер предоставляемой скидки в зависимости от суммы со-


вершенных покупок:
//Если сумма превышает $5000, скидка равна 30%
if (nSumrr, > 5000}
{
dblDiscount = 0.3;
}
else
{
//Если сумма превышает $3000, скидка равна 20%
if (nSumm > 3000)

108 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
dblDiscount = 0.2;
}
//В противном случае скидка не предоставляется
else
{
dblDiscount = 0.0;

В некоторых случаях, как в приведенном выше примере, один оператор if используется


внутри другого оператора if. Это называется вложением операторов. Чтобы коды вложен-
ных операторов были проще для чтения и понимания, добавляйте отступы, выделяющие гра-
ницы каждого из них.

Оператор for
Ключевое слово for используется в тех случаях, когда какие-то инструкции должны вы-
полняться подряд определенное количество раз. Синтаксис оператора for выглядит сле-
дующим образом:
for (expl; expr2; ехргЗ)
stmtl;
Такой код еше называют IJUXJIOM for.
При выполнении цикла f o r в первую очередь обрабатывается выражение e x p r l , кото-
рым определяется начальное значение переменной, используемой для контроля за количест-
вом итераций. {Итерация — это одноразовое выполнение инструкций цикла.) Затем оценива-
ется выражение ехрг2. (Оно оценивается перед каждой итерацией, для того чтобы опреде-
лить, следует ли продолжать выполнение цикла.) Если выражение ехрг2 истинно,
выполняется инструкция s t r a t i , после чего обрабатывается выражение ехргЗ. Выражение
ехргЗ используется обычно для изменения значения переменной, контролирующей количе-
ство итераций. Если выражение ехрг2 ложно, выполнение цикла прекращается и программа
переходит к обработке следующих за этим циклом кодов.

Пример использования цикла for


Приведенное выше объяснение может показаться вам не очень понятным, поэтому про-
демонстрируем работу цикла f o r на практическом примере.
int i;
for (i = 0; i < 2; i++)
{
Console::WriteLine(i.ToString());
}
Опишем, как этот код выполняется.
1. Вначале обрабатывается выражение ехрг]. Переменной i присваивается значение 0.
2. Затем обрабатывается выражение ехрг2. Проверяется, меньше ли значение перемен-
ной i числа 2. Поскольку переменной i только что было присвоено значение 0, выра-
жение ехрг2 будет истинным, что дает разрешение на выполнение инструкции
s tint 1. В данном случае эта инструкция выглядит так:
Console::WriteLine(i.ToString()) ;
Ее выполнение приводит к отображению на экране значения переменной i.

Глава 11. Ключевые слова — ключ к диалогу с компьютером 109


3. Далее обрабатывается выражение ехргЗ. В данном случае оно выглядит как i++, та-
ким образом, значение переменной i увеличивается на единицу.
4. Поскольку выражение ехрг2 должно оцениваться перед выполнением каждой итера-
ции, программа снова возвращается к нему. Переменная i имеет уже значение 1, но это
все равно меньше числа 2. Поэтому инструкция s t r a t i выполняется еще раз, и на эк-
ране отображается следующее значение переменной i.
5. Снова обрабатывается выражение ехргЗ. Значение переменной i увеличивается на
единицу и становиться равным числу 2.
6. Программа переходит к проверке выражения ехрг2. Теперь оно ложно, поскольку зна-
чение переменной i (2) не меньше числа 2. На этом выполнение цикла заканчивается.
Изменяя и усложняя выражение ехрг2, можно задавать самые разные условия, опреде-
ляющие количество выполняемых итераций и момент прекращения выполнения никла. Далее
в примерах этой книги вы еще неоднократно встретитесь с ним.

Повторение ради повторения


Если вы хотите, чтобы инструкция была выполнена несколько раз подряд, наберите такой код:
for (i = 0; i < n; i++)
{
//Инструкция или набор инструкций для повторения
}
Значение п определяет количество выполняемых итераций. Например, если вам нужно,
чтобы строка "Терпение и еще раз терпение" была выведена на экран 50 раз подряд, наберите
такой код;
for ( i = 0; i < 50; i++)
{
Console: :WriteLine (S''Tepn-ение и еще раз терпение");
)
Чтобы не показаться навязчивым, можете позволить пользователю самому определить,
сколько раз строка должна отобразиться на экране. Делается это так:
i n t nCount;
Console::WriteLine(S"Сколько строк отобразить?");
nCour.t = Console: : ReadLine () ->ToInt32 {);

for (i = 0; i < nCount;


{
Console: :WriteLine (3"Терпение ~л еще раз терпение") ;
}
Вычисление факториала
Наверное, каждый программист когда-нибудь сталкивался с такой проблемой: как напи-
сать код, вычисляющий п-факториал?
Это выражение (п-факториал) вычисляется по такой формуле: пх (п - 1) х (п - 2) х ... х 1.
Так, 2-факториал (пишется как 2! в математических книгах, но не в компьютерных кодах)
вычисляется как 2 х 1, а 3! ~ как 3 х 2 х 1.
Одним из вариантов решения такой проблемы может быть приведенный ниже код.
//Вычисление г.!
р. = I n t 3 2 : : P a r s e (Console : : ReadLine () ) ;

110 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
if (n == 1)

Console::WriteLine(S"l" ) ;
}
else if fn == 2)
{
Console::WriteLine(S"2" ) ;
}
else if (n == 3)
{
Console::WriteLine(S"6") ;
}
Этот способ, несмотря на свою простоту, крайне неэффективен, поскольку, даже если вы
будете продолжать аналогичным образом набирать коды для чисел 4, 5, ... 35 и т.д., все равно
вы не сможете перебрать даже малую часть всех возможных вариантов. Реальным решением
этой проблемы может быть использование цикла f o r .
//Factorial
//Вычисление п!

#include "stdafx.h"

#using <mscorlib.dll>

using namespace System;

//С этой строки начинается выполнение программы


#ifdef JJNICODE
int wmain(void)
#eise
int main(void)
#endif
{
int nNumber; //Число, определяемое пользователем
int nResult = 1;

int i; //Переменная, используемая для отсчета


//количества итераций
//Получение числа, для которого нужно вычислить
//факториал
Console::WrileLine("Для какого числа?");
nNumber = Int32: :Parse(Console::ReadLine()) ;
//Далее вычисляется факториал путем умножения на
//каждой итерации значения переменной i на
//общий результат. Таким образом мы получим число,
//равное 1*2*3*...*п
for (i=l; i<=nNuruber;
{
nResult *= i;

//Отображение полученного значения


Console: :WriteLine(S"n! равен {0 } ", nResult.ToString());
//Ожидание, пека пользователь не остановит

Глава 11. Ключевые слова — ключ к диалогу с компьютером 111


//выполнение программы
Console::WriteLine(S"Нажмите клавишу E n t e r , чтобы
остановить выполнение программы");
Console::ReadLine();

r e t u r n 0;

Оператор while
Как и цикл for, цикл w h i l e используется в тех случаях, когда некоторая инструкция
(или набор инструкций) должна быть выполнена несколько раз подряд. Его синтаксис даже
проще, чем синтаксис оператора for, и выглядит следующим образом:
while [exprl]
stmt;
Вначале оценивается выражение e x p r l . Если оно истинно, выполняется инструкция
stmt. Затем опять проверяется выражение e x p r l . До тех пор пока выражение e x p r l будет
оставаться истинным, инструкция stmt будет выполняться снова и снова. Как только выра-
жение e x p r l становится ложным, выполнение цикла прекращается.
Например, если нужно создать цикл, выполняющий десять итераций, наберите такой код:
i n t i = 0;
w h i l e (i < 10]

Среди инструкций s t m t (те, которые выполняются на каждой итерации) обяза-


тельно должна быть та, которая как-то изменяет значение переменной, входящей
в состав выражения e x p r l . В противном случае выполнение цикла никогда не
прекратится.

Бесконечный цикл
Ниже приведен пример небольшой программы, выполнение шторой неминуемо приведет к зависанию
компьютера.
//Компьютер завис
i n t main(void)

while(1);
} ' ;
Произойдет это по следующей причине. Выполнение цикла w h i l e продолжается до тех пор, пока вы-
ражение e x p r l не станет ложным. В данном случае выражению e x p r l постоянно соответствует
число 1, которое всегда воспринимается как истинное. Поэтому выполнение цикла w h i l e без внешнего
вмешательства никогда не прекратится. Подобные циклы обычно называют бесконечными.

Ключевые слова switch u do


Visual C++ имеет в своем запасе еще пару операторов, управляющих последовательно-
стью выполняемых инструкций. Познакомимся с ними поближе.

112 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
Оператор switch
Оператор s w i t c h подобен оператору if, но позволяет учесть сразу множество вари-
антов и выбрать только один из них. (Описание каждого варианта начинается с ключе-
вого слова case.) Таким образом, если необходимо создать код, решающий задачу на-
подобие; "если это Вася, тогда ..., если это Маша, тогда ..., если это Дима, тогда ...". ис-
пользуйте оператор s w i t c h , который может заменить собой сразу несколько
операторов if. Синтаксис оператора s w i t c h выглядит так:
switch '. ехрг)
{
case vail:
stmtl;
case val2:
stint 2;

default:
dfIt stint;
}
Вначале оценивается выражение ехрг и его значение сравнивается со значением v a i l .
(Значение v a i l должно быть каким-нибудь числом, например 1 или 23,5.) Если значение
выражения ехрг совпадает со значением v a i l , выполняется инструкция s t m t l и все по-
следующие за ней инструкции цикла s w i t c h . Если значение выражения ехрг не совпада-
ет со значением v a i l , оно сравнивается со значением v a l 2 и т.д. Если вы хотите быть
уверены, что по крайней мере какая-то инструкция будет обязательно выполнена (в том
случае, если ни одно из заданных значений не совпадет со значением выражения ехрг),
добавьте ключевое слово d e f a u l t (как показано выше) и укажите после него нужную ин-
струкцию. Если после выполнения какой-то инструкции вы не хотите, чтобы все после-
дующие инструкции также выполнялись, наберите после нее слово break, которое сразу
же остановит выполнение оператора s w i t c h .
Если вы знакомы с языком Visual Basic, вы, наверное, ожидаете, что, как и в
Visual Basic, в качестве проверяемых значений v a l могут быть использованы
строки. К сожалению, это не так. Сравниваться могут только числовые значения,
но не текстовые.
Продемонстрируем работу оператора s w i t c h на примере, отображающем названия неко-
торых чисел:
//Названия чисел 1, 2, 3 и 4
switch 'n)
{
c a s e 1:
Console::WriteLine(3"один");
break;
case 2 :
Console::WriteLineIS"два");
break;
case 3:
Console::WriteLine(Битри");
break;
case 4:
Console::WriteLine(S"четыре");

Глава 11. Ключевые слова — ключ к диалогу с компьютером 113


пгеак;
default:
Console::WriteLine(S"неизвестное число");
}
Обратите внимание на наличие ключевых слов break после каждой инструкции, сле-
дующей за словом case. Если бы их там не было, возвращаемый программой результат вы-
глядел бы так:

п Результат

1 одиндватричетыренеизвестное число

2 дватричетыренеизвестное число

и т.д.

|_je забывайте добавлять после инструкций, следующих за каждым словом c a s e


ключевое слово break. Если этого не делать, выполняться будут все инструкции
оператора switch, набранные после слова case со значением, которое совпало
с проверяемым.

Оператор do
Выполнение цикла do во многом подобно выполнению цикла while. Отличие состоит в
том, что цикл while проверяет истинность выражения до того, как будет выполнена какая-
либо инструкция. Поэтому возможен вариант (если выражение сразу окажется ложным), при
котором ни одна из инструкций не будет выполнена. Цикл do, наоборот, вначале выполняет
первую итерацию и только затем проверяет истинность выражения. Если выражение истин-
но, выполняется следующая итерация и т.д. Когда выражение становится ложным, выполне-
ние цикла прекращается.
Do
s tin t;
while (expr) ;
Вот пример использование цикла do, который выполняется до тех пор, пока значение пе-
ременной i не сравняется со значением переменной п:
i n t i = 0;
do

Console::WriteLine(i.ToString{));

while (i < n);


Если даже переменная п имеет значение 0, это число все равно будет отображено на экра-
не, поскольку проверяемое выражение (в данном случае i<n) оценивается только после вы-
полнения первой итерации.

Выражение i = i + l эквивалентно выражению i++.

114 Часть II, Все, что вы хотели знать о C++, но о чем боялись спросить
51 Hi м , КАК 9Y*o t\pou^ouuh\/ но,
мои игрок Obi^w клюшкой wo
мою МАлемьки! комки

Глава 11. Ключевые слова — ключ к диалогу с компьютером 115


Глава 12

Внимание! Повышенная
функциональность
В э/Яой глабе...
> Что такое функция
> Создание функций
> Использование аргументов
>• Результат, возвращаемый функцией
> Рекурсия и значения аргументов, установленные по умолчанию

ольшинство создаваемых программ сложны и объемны. Некоторые из них состоят из


тысяч и даже миллионов инструкций. Чтобы упростить процесс создания, отладки,
модификации и вообще понимания огромных (и не очень) программ, их разбивают на неко-
торое количество более простых подпрограмм.
Visual C++ позволяет разбивать программу на более мелкие составляющие путем объеди-
нения логически связанных между собой выражений и присвоения им какого-то имени. Такие
выделенные в отдельную группу выражения называются функциями. (Иногда функции назы-
вают подпрограммами или процедурами. В данной книге в основном будет использоваться
термин функция, однако другие два термина также корректны.)
Функции могут иметь различную область видимости. Глобальные функции могут быть вы-
званы из любой части программы. Библиотечные функции могут вызываться самыми разны-
ми программами. Однако большинство создаваемых вами функций будут, скорее всего, ис-
пользоваться в пределах только одного объекта. Такие функции, называемые функциями-
членами, подробно рассматриваются в главе 17.
Можно также использовать ранее созданные функции для построения новой функции.
Этот прием позволяет значительно упростить процесс написания, чтения и редактирования
кодов программ. В этой главе описывается, как использовать функции для разбивки большой
программы на небольшие и понятные составляющие.

fie/co/HO/tbte ##оуные замечания


Если вы просматривали предыдущие главы, то наверняка обратили внимание на приводи-
мые в качестве примеров коды программ, В главе 3 упоминалось правило, согласно которому
все выражения должны заканчиваться точкой с запятой (;). Но как известно, из любого пра-
вила есть исключения, и подтверждением тому могут быть некоторые выражения в приводи-
мых ранее примерах, после которых точка с запятой не ставилась.
Итак, общее правило звучит так:
Почти все выражения должны заканчиваться точкой с запятой.
А вот исключения из этого правила.
;: S Если выражение начинается с символа # (знак "решетки"), в конце него точка с за-
| пятой не ставится.

Глава 12. Внимание! Повышенная функциональность 117


Если выражение начинается с символов //, точкой с запятой заканчивать его не обя-
зательно, поскольку это комментарий и компилятор все равно его проигнорирует.
Точка с запятой не ставится после закрываюшеГ! фигурной скобки {}). за исключе-
нием тех случаев, когда этим символом заканчивается объявление класса ( c l a s s ) ,
структуры ( s t r u c t ) или перечня (enum).

Создание функций
Определение функции начинается с набора ее имени и пары круглых скобок (). (Позже, ко-
гда вы познакомитесь с аргументами, в упомянутых скобках можно будет определить список
этих самых аргументов.) Далее следуют выражения, из которых собственно и состоит функция.
Правила присвоения функциям имен в точное™ совпадают с правилами присвоения имен пере-
менным, которые были рассмотрены в главе 8. Итак, вот код, которым определяется функция:
void f u n c t i o n n a m e ( )

Например, следующим кодом создается функция, отображающая на экране текст "Hello


World".
void PrintHW()

Console::WriteLine(S"Hello World");

Теперь каждый раз, когда вы хотите, чтобы функция была выполнена, наберите ее имя и
сразу за ним пару пустых скобок. Это действие называется вызовом функции. Функция может
быть вызвана неограниченное количество раз.
Как и в случае со структурами, функции должны быть определены до того, как будут ис-
пользованы, что показано в приведенном ниже примере. Это программа HelloWorld, которую
вы уже видели ранее, но здесь код, заставляющий программу ожидать, пока пользователь не
нажмет на клавишу <Enier> и не закончит ее выполнение, выделен в отдельную функцию, на-
званную именем HangOut. Функция HangOut определена в начале программы и затем вы-
зывается функцией main.
//HelloWorld4
//Использование функции

#include "stdafx.h"

#using < m s c o r l i b . d l l >

u s i n g manespace System;

//Ожидание, пока пользователь не остановит


//выполнение программы
v o i d HangOut()

C o n s o l e : : W r i t e L i n e ( S " Н а ж м и т е клавишу E n t e r , чтобы


остановить выполнение программы");
Console::ReadLine();
}

118 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
//С этой точки начинается выполнение программы
#ifdef JJNICODE
i n t wmain(void)
#else
i n t main(void)
#endif
{
//Отображение текста на экране
Console::WriteLine{"Hello World");

//Ожидание, пока пользователь не остановит


//выполнение програло/ы
HangOit ( ! ;
return 0;

Использование аргументов
Функция может требовать для своего выполнения передачи ей значений аргументов. Для
каждого аргумента (их называют также параметрами) должно быть определено имя и тип
данных. Функции, работа которых зависит от передаваемых значений аргументов, более уни-
версальны и могут многократно использоваться в процессе выполнения программы. Для каж-
дой фуикиии можно определить любое количество аргументов любого типа данных.
Вот как определяются аргументы:
void fur;ction_r;ame ( d a c a _ t y p e l a r q l , d a t a _ r y o e 2 a r g 2 , ...)

Например, приведенная ниже функция вычисляет факториал числа. Число, представляе-


мое переменной п, передается функции в качестве значения аргумента. Далее это значение
используется функцией при проведении вычислений.
//Вычисление и отображение факториала числа
v o i d F a c t o r i a l ( i n t nNumber)
{
int nResult = 1;
int i; //Используется для отсчета итераций

//Цикл, вычисляющий факториал. На каждой итерации


//общий результат умножается на i
for (i = 1; i<~nNuir.ber; i++)
{
nFesult *= i;

//Отображение полученного результата


Console::WriteLine(nResult.ToStting{));
}
Каждый раз, когда потребуется отобразить на экране значение факториала какого-нибудь чис-
ла, просто вызовите эту функцию. Например, ниже приведен код цикла, выполняющего три итера-
ции. На каждой итерации программа запрашивает число, для которого нужно вычислить фактори-
ai, и затем вызывает функцию F a c t o r i a l , которая отображает нужное значение на экране.
int r.Number;
int i ;

Глава 12. Внимание! Повышенная функциональность 119


//Цикл, выполняющий три итерации
for (i=0; i<3;

//Получение числа от п о л ь з о в а т е л я
nNumber = I n t 3 2 : : P a r s e ( C o n s o l e : : R e a d L i n e ( ) ) ;
//Вызов функции с передачей ей значения аргумента
Factorial(nNumber);
}
Так же просто создается функция, требующая значений нескольких аргументов. Напри-
мер, следующая функция отображает результат, вычисленный по формуле f o o * n ! , где зна-
чения переменных f оо и п являются аргументами этой функции:
//Функция, использующая значения двух аргументов
v i o d F o o c t o r i a l ( i n t r.Foo, i n t nNumber)
{
int nResuIt = 1;
int i;

//Цикл, вычисляющий факториал. На каждой итерации


//общий результат умножается на i
for (i=l; i<=nNumber;
(
nResult * = i;

//Умножение факториала на число foo


nResult *= nFoo;

//Отображение полученного результата


Console::WriteLine(nResult.ToString() ) ;
}

Функции, которые возвращают результат


Все функции, рассмотренные ранее, выполняли какие-то действия (например, вычисляли
факториал и отображали полученное значение на экране). Однако функции могут также воз-
вращать некоторое значение в качестве собственного результата. Эта возможность очень по-
лезна, поскольку позволяет использовать функции при построении выражений. Например,
можно использовать математические библиотечные функции, такие как M a t h : : Cos ( ) ,
внутри выражений, подобных этому: 3 * M a t h : : C o s ( a n g l e ) .
Вы можете создавать собственные функции, возвращающие результат. Например, можете
создать функцию, просматривающую базу данных и возвращающую имя клиента, совершив-
шего наибольшее количество покупок. Или можете создать функцию, возвращающую в каче-
стве результата общую сумму совершенных за последний месяц сделок.
Чтобы функция возвращала какой-то результат, нужно выполнить два условия:

.' S при определении функции перед ее именем вместо слова v o i d указать тип данных,
I который будет иметь возвращаемое функцией значение;
| S использовать ключевое слово r e t u r n в кодах функции до того, как выполнение
| функции будет остановлено.

Словом r e t u r n возвращается полеченный результат, а выполнение самой функции не-


медленно прекращается. Если вы наберете слово r e t u r n посреди кодов функции и оно будет

120 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
обработано, все остальные коды функции, набранные после слова r e t u r n , выполняться не
будут. (Не все слова r e t u r n обрабатываются в обязательном порядке. Например, если оно
будет набрано среди инструкций оператора if, обрабатываться оно будет только в случае
выполнения проверяемого условия.)
Ниже приведен пример функции, вычисляющей факториал и возвращающей его в качест-
ве своего значения. Эта функция работает так же, как и предыдущая, за исключением того,
что полученное значение не отображается на экране, а возвращается как результат функции.
//Вычиспение факториала числа
void Factorial tint nNumber)
{
int nResult = 1;
int i; //Используется для отсчета итераций
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for fi=l; i<=nNumber;
{
nResult *= i;

//Полученное значение возвращается как результат


return nResult;
}
Поскольку теперь функция F a c t o r i a l возвращает результат, вы можете использовать ее
при построении выражений. В некоторых случаях это может быть очень полезной возможно-
стью. Вот, например, код, где функция F a c t o r i a l вызывается функцией WriteLine, кото-
рая сразу же отображает полученное значение на экране:
nNumber = I n t 3 2 : : P a r s e ( C o n s o l e : : R e a d L i n e ( ) ) ;
C o n s o l e : : W r i t e L i n e ( S " Ф а к т о р и а л числа {0} равен {1}",
nNumber. T o S t r i n g () ,
Factorial(nNumber).ToString() ) ;
Функции, возвращающие значения, могут быть использованы в любом месте, где могут
быть использованы значения того же типа, что и возвращаемые функциями результаты. Так,
если функция возвращает числовое значение типа integer, ее можно использовать в любом
месте, где можно подставить число типа integer. Например, в таком выражении:
nMyNurober = 3 * F a c t o r i a l (nNumber) ;
Возвращаемые функциями результаты могут также быть использованы как значения ар-
гументов, передаваемые другим функциям. Например, чтобы вызвать функцию F a c t o r i a l ,
ей нужно передать в качестве аргумента числовое значение типа integer. Поскольку сама
функция также возвращает значение типа integer, оно может быть использовано и как аргу-
мент этой функции. Ниже показан код, в результате выполнения которого на экране отобра-
жается значение факториала от факториала числа.
nNumber = I n t 3 2 : : P a r s e ( C o n s o l e : : R e a d L i n e ( } ) ;
Console::WriteLine(Factorial(Factorial(nNumber)).ToString());
А этим кодом определяется, будет ли факториал заданного числа больше числа 72:
if ( ( F a c t o r i a l ( n N u m b e r ) > 72)
{
C o n s o l e : : W r i t e L i n e ( S " Ф а к т о р и а л больше чем 7 2 " ) ;

Глава 12. Внимание! Повышенная функциональность 121


Плюсы и минусы глобальных переменных
После того как функция завершает свою работу, все значения всех переменных, объявленных внутри этой
функции, теряются. После этого восстановить информацию, которая хранилась как значения этих пере- *
менных, уже невозможно. Если же она вам еще нужна, ее необходимо вернуть с использованием слова
r e t u r n как результат этой функции.
Предположим, например, что вам нужно узнать имя самого высокооплачиваемого сотрудника вышей op- :
ганизации. Если вы хотите только отобразить это имя на экране и больше никогда к нему не возвращать-
ся, наберите команду вывода на экран внутри функции и не возвращайте никакого результата. Но, если
это имя должно быть как-то использовано за пределами функции, например при создании какого-нибудь
отчета, верните его как результат функции.
Сохранить информацию можно и без использования ключевого слова r e t u r n . Делается это с помощью
так называемых глобальных переменных, которые объявляются до начала работы функции m a i n . Свое
название эти переменные получили благодаря возможности их использования в любом месте програм- :
мы. Если вернуться к нашему примеру, то имя самого высокооплачиваемого сотрудника может быть со-
хранено как значение одной из глобальных переменных. Значения глобальных переменных не пропадают
после завершения работы какой-либо отдельной части программы, поэтому они могут использоваться •
для сохранения информации, полученной во время выполнения функции.
Однако, к сожалению, использование глобальных переменных может сделать коды программы похожими
:
на спагетти (так называют коды, которые трудно прочитать и понять, как они работают). Если вы будете
использовать глобальные переменные внутри функций, понять, что произойдет в результате выполнения
кодов программы, будет крайне сложно, так как вам придется просматривать подряд все коды програм-
мы. Это нельзя считать хорошей практикой, так как в идеале вы должны без просмотра каждой отдельной
строки функции суметь точно определить, значения каких аргументов ей нужно передать и что произой-
дет в результате ее выполнения. Это важно, поскольку в этом случае вы можете понимать назначение и
использовать функции без необходимости вникать во все нюансы выполнения отдельных кодов,
Большинство сложных для обнаружения логических ошибок возникают из-за неаккуратного использова-
ния глобальных переменных. Поэтому намного предпочтительнее возвращать результат функции, чем
использовать внутри нее глобальные переменные. Если необходимо вернуть много значений, восполь-
зуйтесь структурой или ссылкой на структуру.

U снова вернемся к фсимпо^иалам


В этом разделе мы вновь вернемся к программе, вычисляющей факториал заданного чис-
ла. Но теперь программа будет состоять из нескольких функций. Когда будете просматривать
коды программы, обратите внимание, что, хотя теперь она выглядит несколько сложнее, ко-
лы функции main стали намного проще. Ведь теперь они состоят всего лишь из четырех вы-
ражений (если не считать комментариев).
В программе кроме функции main используются еще две функции: F a c t o r i a l и
GetNumber. Функция F a c t o r i a l вычисляет факториал заданного числа. Она принимает в
качестве аргумента число, для которого нужно вычислить факториал, и возвращает в качест-
ве результата факториал этого числа. Функция GetNumber используется для получения от
пользователя числа, для которого нужно вычислить факториал.
Функция main снова и снова использует функцию GetNumber для получения от пользо-
вателя новых чисел. Получив очередное число, функция main вызывает функцию
F a c t o r i a l для вычисления факториала и отображения его на экране. Это продолжается до
тех пор, пока пользователь не наберет число 0 (нуль).
Обратите внимание функция main определяет, когда нужно остановиться:
while (nNumber ••- GetNumber ( ) )

122 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Помните, что цикл while использует выражение как условие для выполнения или невы-
полнения следующей итерации. Итерация выполняется, если проверяемое выражение истин-
но. В нашем случае выражение вызывает вначале функцию GetNumber для получения числа
от пользователя. Затем полученное число присваивается переменной nNumber. Далее воз-
можны два варианта. Если пользователь набирает число 0, выполнение цикла прекращается,
поскольку этому числу соответствует логическое значение f a l s e и выражение воспринима-
ется как ложное. Если же пользователь набирает любое положительное число, оно присваи-
вается переменной nNumber и выполняется итерация цикла. Этот способ использования
цикла w h i l e довольно часто можно видеть в программах C++. (Если вы тестируете про-
грамму или просто любите создавать проблемы, можете ввести отрицательное число, тогда
программа выдаст неправильный результат. Чтобы избежать этого, можете добавить код, ко-
торый будет проверять вводимые пользователем числа и просить ввести число заново, если
оно окажется отрицательным.)
Итак, вот новый код программы, вычисляющей факториал:
//Вычисление факториала до тех пор, пока
//пользователь не наберет число О

# include; " s t d a f x . h"

#using <mscorlib.dil>

u s i n g manespace System;

//Функция вычисляет и возвращает значение факториала


i n t F a c t o r i a l ( i n t nNumber)
{
i n t n R e s u l t = 1;
int i; //Используется для отсчета итераций
//Цикл, вычисляющий факториал. На каждой итерации
//общий результат умножается на i
for (i=l; i<=nNumber; i++)
{
nResult *= i;
}
//Возвращение результата
return nResult;

//Функция просит пользователя набрать число,


//а затем возвращает это число как результат
int GetN-imber (}
{
int nNjmber;
Console::WriteLine(S"Введите число");
nNumber = I n t 3 2 : : P a r s e ( C o n s o l e : : R e a d L i n e ( ) )
r e t u r n nNuraber;

//С этой точки начинается выполнение программы


Hfdef _ UNICODE

Глава 12. Внимание! Повышенная функциональность 123


ir:t wmain (void)
iel.se
irrc main (void)

i n t nNumber;

хпучение чисел ст пользователя до тех пор,


//пока он не наберет число О
while (nNumber = GetNumber())
{
//Отображение значение факториала для указанного
/ / ч и с л а . При этом используется значение,
//возвращаемое функцией F a c t o r i a l
C o n s o l e : : W r i n e L i n e ( 5 " Ф а к т о р и а л числа {0} равен {1}
nNumber.ToString(),
Factorial(nNumber).ToString{));

//Окончание выполнения программы


Console::WriteLine(S"Bye");
r e t u r n О;

Чтение программ, содержащих функции


Если программа содержит функции, они обычно определяются до того, как будут использованы. Поэтому,
если вы начнете читать коды программы от начала и до конца строка за строкой, вам вначале придется
проштудировать множество разрозненных деталей и только в конце увидеть, для чего они используются и
в какой последовательности выполняются.
Приведем несколько советов, которые могут заметно упростить этот процесс.
s Если файл содержит функцию main, пропустите остальные коды и начинайте с просмотра
именно этой функции. Если функция m a i n вызывает какую-то другую функцию, вернитесь к
определению этой функции и просмотрите ее коды.
^ Если файл состоит только из набора различных функций и не включает в себя функцию main,
просмотрите вначале названия всех функций. Прочитайте комментарии, объясняющие назна-
чение каждой функции. После этого определите для себя, детали выполнения каких функций
вас интересуют, а какие функции, наоборот, можно проигнорировать.
s Обычно коды наиболее важных функций набраны в конце файла.
Если вас удивляет тот факт, что переменным внутри функций присваиваются те же имена, что и пере-
менным, используемым за пределами этих функций, не переживайте - в главе 15 этот вопрос рассмот-
рен подробно.

Текфсия: спасибо мне, 4,tiio еапь


л и менл
h:ciii функция вызывает саму себя, это называется рекурсией. Рекурсия очень часто при-
ш'чистся в тех случаях, если одну и ту же задачу намного проще выполнить для меньшего
ч'.сла исходных элементов.

124 Часть If. Все, что вы хотели знать о C++, но о чем боялись спросить
Предположим, например, что необходимо отсортировать большой набор чисел. {Это
классическая задача, часто рассматриваемая в книгах по программированию. Возможно,
предлагаемое здесь решение с помощью использования рекурсии вам покажется не вполне
очевидным, но, в отличие от реальной жизни, в контексте программирования именно этот
подход считается одним из самых оптимальных.) Отсортировать большой набор чисел край-
не сложно. Самый простой способ сделать это — просмотреть весь список и найти наимень-
шее число, перенести его в новый список, который будет итоговым, и затем продолжать этот
процесс до тех пор, пока все числа не будут перенесены в этот новый список. Недостаток ме-
тода состоит в том, что один и тот же список приходится просматривать снова и снова, а это
занимает много времени. Так что проблема сортировки чисел не так проста, как это может
вначале показаться, и именно поэтому ей посвящены целые книги.
Чтобы ускорить процесс сортировки, используют рекурсию, разбивая при этом исходный
большой список на более мелкие. Предположим, например, что вместо сортировки одного
большого списка вам нужно правильно объединить вместе два небольших уже отсортирован-
ных списка. В действительности сделать это довольно просто.
Вы спросите, как это делается? Назовем список, который начинается с меньшего значения,
списком А. Второй список назовем списком Б. Итоговый список назовем В. Объединить списки
А и Б так, чтобы в итоговом списке все значения также были отсортированы, можно следую-
щим образом. Возьмите первый (наименьший) элемент списка А и перенесите его в список В.
Затем сравните второй элемент списка А с первым элементом списка Б. Если он также будет
меньше, чем наименьший элемент списка Б, перенесите его в список В. Продолжайте это до тех
пор, пока элемент списка Б не окажется меньше, чем элемент списка А. Тогда перенесите в спи-
сок В элемент списка Б. Теперь сравните следующий элемент списка Б с наименьшим (он будет
наименьшим из оставшихся) элементом списка А и снова меньший из этих двух элементов пе-
ренесите в список В. Продолжайте этот процесс до тех пор, пока все элементы из списков А и Б
не будут перенесены в список В. Возможно, на бумаге этот путь кажется несколько запутанным,
но поверьте, что это намного быстрее и эффективнее, чем снова и снова просматривать один и
тот же список. Попробуйте реализовать этот процесс на компьютере, и вы в этом убедитесь.
Теперь возникает вопрос, как из одного большого неотсортированного списка сделать два
меньших, но отсортированных (чтобы потом получить один общий отсортированный спи-
сок). Можно разбить большой список пополам и отсортировать каждую половину. Но как от-
сортировать половину списка? Ее можно также разбить пополам и отсортировать половину
от половины. Если продолжать делить списки пополам, рано или поздно они будут разбиты
на списки, состоящие из одного или двух элементов. Понятно, что такие списки отсортиро-
вать проще простого.
Далее, имея отсортированные списки из одного-двух элементов, двигайтесь в обратном
направлении, объединяя списки так, чтобы порядок сортировки не нарушался. На последнем
шаге у вас будут два больших отсортированных списка, объединив которые вы получите не-
обходимый результат. Таким образом, используя рекурсию, вы упрощаете задачу, разбивая ее
на небольшие подобные подзадачи.
Продемонстрируем сказанное на маленьком примере.
1. Вот список, состояший из чисел, которые нужно отсортировать:
1375927
2. Раюбьсм его на два списка поменьше:
1375 927
3. ЭТИ СПИСКИ по-прежнему большие, поэтому разобьем их на еще более мелкие:
13 75 92 7
4. Вот такие списки отсортировать очень просто:
13 5 7 29 7

Глава 12. Внимание! Повышенная функциональность 125


5. Теперь пойдем в обратном направлении и объединим ранее разделенные списки:
13 57 279
6. И наконец, получим окончательный результат:
1 235779
Ура! Получилось!
Код, реализующий этот процесс, может выглядеть приблизительно так:
СписокЧисел
S o r t (СписокЧисел)
{
if (КоличествоЭлеыентов(СписскЧисел) -= 1)
{
return СписокЧисел;
}
if (КоличествоЭлементоз(СписокЧисел) == 2)
{
код для сортировки двух чисел //Путем их сравнения
return СортированныйСписок;
}
//Если обрабатывается этот код, значит, список
//состоит из более чем двух элементов. Он разбивается
//пополам, и функция Sort вызывается снова
Объединить(Sort(первая половина списка чисел),
Sort(первая половина списка чисел));
)
Задача вычисления факториала числа, которая рассматривалась ранее в главе, также ино-
гда решается путем использования рекурсии. Приведенная ниже функция F a c t o r i a l во
многом похожа на свою предшественницу, но здесь для вычисления факториала вместо цикла
for функция вызывает саму себя со значением аргумента, равным числу п-1. Другими сло-
вами, используется тот факт, что формулу вычисления факториала п ! =п* (п-1) * (п-2 ) . . .
можно представить в виде п ! =п * ( (п -1) ! ).
Разумеется, (п-1) ! равен (п-1) ' ( (п-2) ! ) . Таким образом, функция, вычисляющая
факториал для какого-то числа, может делать это путем умножения этого числа на результат,
возвращаемый ею же для числа, на единицу меньшего.
//Использование рекурсии для вычисления факториала

#include "stdafx.h"

#using < m s c o r l i b . d l l >

u s i n g rr.anespace System;

//Функция вычисляет и возвращает значение факториала,


//используя при этом метод рекурсии.
//Факториал числа 1 равен 1. Это просто.
//Если число не равно 1, вызывается функция F a c t o r i a l
//для числа, меньшего на единицу
i n t F a c t o r i a l ( i n t nNumber)
{
for (1=1; i<=r,NuTTLber; i++)

126 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
nResult *= i;
}
//Возвращение результата
return nResult;

//Функция просит пользователя набрать число,


//а затем возвращает это число как результат
int GetHumber{)
{
int nNumber;
Console::WriteLine(S"Введите число");
nNumber = Int32 : : Parse (Console : : ReadLine () )
return nNumber;

//С этой точки начинается выполнение программы


iifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
int nNumber;
//Получение чисел от пользователя до тех пор,
//пока он не наберет число О
while (nNumber = GetNumber())
{
//Отображение значения факториала для указанного
//числа. При этом используется значение,
//возвращаемое функцией Factorial
Console::WriteLine(S"Факториал числа {0} равен {1}",
nNumber.ToString(),
Factorial{nNumber) .TcString () };

//Окончание выполнения программы


Console::WriteLine(S"Bye");
return 0;
}

Сели шш afttifMetunoe не определен . . .


Чтобы определить, что функция может принимать любое количество параметров, набери-
те в списке аргументов три точки ( . . . ) • Например, приведенный ниже код сообщает компь-
ютеру, что функции может быть передано произвольное количество значений аргументов, и
уже ее заботой будет определить их тип и разобраться, что делать с ними дальше.
int factorial (...)

Глава 12. Внимание! Повышенная функциональность 127


Как правило, использовать при определении списка аргументов три точки ( . . . ) — плохая
идея, поскольку в последующем вы случайно можете передать этой функции совершенно не
те данные, которые ей нужны. Это может повлечь за собой самые непредсказуемые последст-
вия. И хотя вы могли видеть, что в некоторых библиотечных функциях, используемых не-
управляемыми программами, вместо списка аргументов фигурируют именно эти три точки,
старайтесь не пользоваться этим приемом при создании собственных функций.

Значение, установленные по умолчанию


Всем или некоторым аргументам функции можно присвоить значения, используемые по
умолчанию. Предположим, например, что есть функция, названная именем foo, для работы
которой требуются значения трех аргументов типа integer: a, b и с. Предположим также, что
почти при каждом вызове функции в использовании аргумента с не будет необходимости. В
•этом случае уместно присвоить аргументу с значение, используемое по умолчанию. Это зна-
чение будет использоваться каждый раз, когда при вызове функции аргументу с не будет пе-
редаваться никакое другое значение. Другими словами, вызывая функцию, вы можете на-
брать foo ( 1 , 2 ) ; это будет означать, что аргументу а присваивается значение 1, аргументу
Ъ — значение 2, а аргумент с будет использовать значение, присвоенное по умолчанию. Если
же вы наберете £оо ( 1 , 2 , 3 ) , это будет означать, что в данном случае а = 1, b = 2, а с = 3.
Определение значений, используемых по умолчанию, полезно тогда, когда функции ис-
пользуют аргументы, значения которых имеет смысл специально указывать только в особых
случаях. Тот, кто будет впоследствии вызывать эти функции, может спокойно проигнориро-
вать аргументы с установленными по умолчанию значениями, что не приведет к сбою в рабо-
те этих функций. Однако, если в этом возникнет необходимость, таким аргументам можно
передать нужные в данный момент значения.
Чтобь! присвоить значения, используемые по умолчанию, укажите их при определении
функции в списке аргументов:
i n t t o o ( i n t a, i n t b, i n t с = 3)

128 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 13

Указатели
иа£е...
> Причины, по которым имеет смысл использовать указатели
> Как использовать указатели
> Рисование графических элементов с среде .NET
> Создание связанного списка
> Освобождение памяти
> Сборка мусора
> Строки в неуправляемых кодах

* / аверняка некоторые из вас думают, что понять принципы работы указателей очень и
* ** очень сложно. Возможно, это действительно было так в те времена, когда при изу-
чении процесса программирования больший акцент делался на теорию, чем не практику. Од-
нако сейчас положение изменилось. После того как у вас пройдет первый шок от соприкос-
новения с таким предметом, как указатели, вы увидите, что они весьма полезны и не так
сложны, как казалось раньше.
В этой главе рассматривается, как и зачем использовать указатели. Но, чтобы вам не показа-
лось, что все настолько уж просто (чуть ли не тривиально), в конце главы вы найдете несколько
отступлений, посвященных техническим деталям. Кроме того, вы сможете приступить к созда-
нию более интересных приложений .NET, снабженных некоторыми элементами графики.

Лочелсц цказсипели
В предыдущих главах вы познакомились с множеством технических приемов, позволяю-
щих сохранять и обрабатывать данные. Но поскольку сами данные становятся все более и бо-
лее сложными, представлять их с помощью именованных переменных становится все труднее
и труднее. Что, если вам необходимо, например, сохранить список, состоящий из произволь-
ных по объему фрагментов информации? Предположим, вам нужно отсканировать фотогра-
фию, но пока что вы не знаете, каких она будет размеров. Если использовать обычные пере-
менные, необходимо заранее указать, каких размеров фотография будет отсканирована. Если
же использовать указатели, программа приобретет большую гибкость и универсальность.
Другой пример: нужно создать программу, позволяющую пользователю рисовать графические
элементы. С обычными переменными нужно заранее точно знать, сколько именно фигур нарисует
пользователь (или наложить ограничения на количество фигур, которые пользователь может нари-
совать). Если в программе для хранения информации о каждой нарисованной фигуре будут исполь-
зоваться указатели, пользователь сможет нарисовать столько фигур, сколько захочет.
Еще одна причина, по которой указатели настолько полезны, заключается в том, что, хотя
сами по себе они невелики, ссылаться они могут на большие объемы информации. Предпо-
ложим, например, что есть большая база данных, содержащая информацию о пациентах не-
которой клиники, и каждая запись в этой базе данных может занимать объемы памяти, ис-
числяемые тысячами байтов. Если возникнет необходимость изменить порядок расположения
записей таким образом, чтобы они были отсортированы, например, по городам, и это можно
сделать только путем копирования каждой записи в новую позицию, эта процедура окажется

Глава 13. Указатели 129


слишком долгой. Но, если у вас есть указатели, ссылающиеся на каждую запись; отсортиро-
вать можно именно их, и сделано это будет намного быстрее. Таким образом, хотя сами запи-
си так и остались на своих местах, изменившийся порядок указателей позволяет увидеть за-
писи в совершенно другом ракурсе.

Указатели и переменные
Чтобы понять, в чем заключается преимущество указателей, нужно разобраться в том, как
работают переменные. Все данные, которые обрабатывает компьютер, хранятся в его памяти.
Когда переменной присваивается какое-то значение, оно заполняет собой некоторую часть
памяти. Когда значение переменной должно быть использовано, оно считывается из памяти.
Таким образом, каждая переменная — это всего лишь имя для определенного участка памяти.
Указатели — это те же обозначения каких-то отдельных фрагментов информации, запи-
санных в памяти. Указатель точно так же, как и переменная, указывает на участок памяти.
Поэтому каждый раз, когда вы используете переменную, по сути, вы используете указатель.
Различие между переменными и указателями заключается в том, что переменные всегда
указывают на один и тот же участок памяти, в то время как один и тот же указатель в разные
моменты может указывать на разные участки памяти.
На рис. 13.1 показаны три переменные: foo, b a r и d r i b b l e , адреса в памяти, на кото-
рые они указывают, и принятые ими значения. Переменная bar, например, ссылается на ад-
рес 4, в котором сохранено значение 17. Также на рисунке показаны два указателя: baz и
goo. Первый содержит значение 4, которое представляет собой указатель на адрес 4. Таким
образом, указатель baz может быть использован для получения значения, которое хранится в
переменной bar. Если поменять значение baz с 4 на 8, его можно будет использовать для
получения значения, которое хранится в переменной d r i b b l e .

Память
Переменные Адрес

fbo 0 32

bar 4 17

dribble • Я 'Heyttere"

Указатели Значение

baz 4

goo о

Рис. 13.1. Переменные являются обозначением определенных участков памяти. Указатели , •


ссылаются на различные участки памяти и могут быть использованы для получения дос-
тупа к хранящимся там данным

130 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
->'i"B действительности указатели— это один из самых полезных и мощных инструментов,
используемых при создании программ, поскольку они обладают большой гибкостью и пре-
доставляют разработчикам большую свободу действий.

Что вы! Указатели — это очень сложно


У неопытных программистов использование указателей вызывает сложности по двум
причинам. Во-первых, указатели предоставляют доступ к двум различным фрагментам ин-
формации. Во-вторых, указатели могут ссылаться на данные, которые не имеют имени.

Информация и ее адрес
Как только что было отмечено, указатели предоставляют доступ к двум различным фраг-
ментам информации.

/ Значение, сохраненное самим указателем. Этим значением всегда будет адрес в


памяти, где хранится другая информация. Например, если указатель содержит зна-
чение 4, это значит, что он указывает на адрес 4.
V Значение, на которое указывает указатель. Например, если в памяти под адре-
сом 4 хранится значение 17, указатель, содержащий значение 4, указывает на зна-
чение 17.

Значение, которое содержится в указателе, является просто адресом в памяти компьютера.


Если вывести на экран значение указателя, вы увидите только число, являющееся адресом.
(Поскольку само по себе это число не несет никакой смысловой нагрузки, на экран оно почти
никогда не выводится.) Но указатель может также предоставить доступ и к более полезной
информации — к значению, которое хранится под тем адресом, на который он указывает.
Определение значения, на которое ссылается указатель, называется его разъшеновывтшем.
Например, указатель b a z содержит значение 4 (см. рис. 13.1). Если вы разыменуете его,
то получите значение 17, поскольку именно оно хранится в адресе 4.
Звучит слишком абстрактно? На самом деле в этом нет ничего сложного, ведь даже в по-
вседневной жизни мы сталкиваемся с разыменовыванием. Например, когда вы набираете но-
мер телефона, автоматически вызов направляется именно к тому абоненту, с которым вы хо-
тите поговорить. Другими словами, номера телефонов в вашей записной книге являются ука-
зателями на самых разных людей. И когда вы разыменовываете номер телефона (т.е. просто
набираете его), вы получаете доступ к нужному человеку.

Безымянные данные
Вторая причина, вызывающая трудности при работе с указателями, заключается в том,
что они могут указывать на данные, которые никак не обозначаются (т.е. не имеют никаких
имен). На рис. 13.1 вы видели, как используются указатели для получения доступа к значени-
ям различных переменных. Так, например, указатель b a z ссылался на значение переменной
b a r . Следовательно, вы могли разыменовать указатель b a z , чтобы получить значение пере-
менной bar. В данном случае указатель ссылается на участок памяти, который обозначен оп-
ределенным именем (этим именем является имя переменной, т.е. b a r ) .
Вы можете также попросить компьютер сохранить какую-то часть информации в памяти
без присвоения этой информации имени. К этой возможности очень часто обращаются в слу-
чае, если возникает необходимость в динамическом использовании памяти. В указателе мож-
но сохранить адрес информации, не обозначенной именем, и затем использовать его для дос-
тупа к этим данным.

Глава 13. Указатели 131


Не засоряйте свой компьютер
В компьютерах определенная часть памяти отведена для хранения специальной информации. Например,
:
первые 1000 байт используются для хранения информации, объясняющей компьютеру, как реагировать
; на нажатия клавиш, как отсчитывать время и т.п. Далее какая-то память отведена под файлы операцион-
ной системы. Еще какая-то память используется видеокартой.
; Если вы знаете адреса этой памяти, то можете получить к ней доступ с помощью указателей и как-то из-
менить хранящиеся там данные. Иногда это может быть очень полезной возможностью. Например,
; большинство программ с видеоиграми знают точные адреса памяти, где хранится информация, отобра-

жаемая на экране. Благодаря этому они могут в режиме реального времени показывать видеоизображе-
ния и сопровождать их специальными эффектами.
Но, с другой стороны, если вы заполните эту память, отведенную для специальных данных, разным ин-
формационным мусором, это может привести к возникновению всяких неожиданных проблем. Это со-
стояние называют трещингом компьютера (от слова trash - засорять), поскольку в действительности
специальная память засоряется ненужными данными. Последствия могут быть самыми разными, но в
любом случае нежелательными.
•• Windows пытается отслеживать корректность выполняемых программами действий. Она обычно может
• отличить "хорошие" указатели от "плохих" и останавливает выполнение программы до того, как она успе-
ет сделать какую-нибудь глупость. Более подробно эти вопросы рассматриваются ниже в главе.
Испугались? То-то. Будьте аккуратны при использовании указателей.

Связанный список — размер не ограничен


В этом разделе рассматривается пример, в котором доступ к фрагменту памяти будет
осуществляться с помощью указателя. Предположим, нужно создать программу, сохраняю-
щую информацию о линиях, которые должны быть нарисованы на экране. Каждый раз, когда
пользователь вводит новые координаты, динамически выделяется новая память для их хране-
ния. И затем остается только сохранить адрес этой памяти с помощью указателя.
При этом применяются некоторые маленькие хитрости. В момент создания программы
точно не известно, сколько линий захочет нарисовать пользователь. Но сохранить нужно бу-
дет информацию обо всех линиях, сколько бы их ни было. Можно, конечно, объявить целое
множество указателей (Линия!, Линия2, ЛинияЗ и т.д.) и затем использовать их один за
другим. Это не самая удачная идея, поскольку количество объявленных переменных априори
должно быть больще количества линий, которые может нарисовать пользователь (что, если
он захочет нарисовать, например, 20 000 линий). А кроме того, придется использовать ги-
гантский оператор s w i t c h , чтобы определить, какой указатель должен быть использован
следующим.
Намного разумнее и удобнее использовать нечто, называемое связанным списком. Это
список элементов, в котором первый элемент указывает на втррой, второй на третий и т.д.
Это похоже на поезд: первый выгон тянет за собой второй и т.д.
Чтобы сохранить информацию о целом множестве линий также можно использовать свя-
занный список. Каждый элемент этого списка будет хранить информацию о координатах ли-
нии, а также указатель на следующую линию. На рис. 13.2 показан связанный список, со-
стоящий из трех элементов. Каждый элемент содержит данные о своей линии, а также указа-
тель на следующий элемент.
Связанные списки очень удобны и часто используются для сохранения данных об элемен-
тах, количество или размеры которых заранее не известны.

132 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Line Pointer •*- Linei
Next item Pointer

Line Pointer -» Line 2


Next Hem Pointer

Line Pointer •*- Line 3


Next Item Pointer

Рис. 13.2. Указатели могут указывать на большие и сложные структуры данных. Элемен-
ты этого связанного списка состоят из двух указателей; один указывает на информацию
о координатах линии, а второй - на следующий элемент списка

использование цказайьелей в С+ +
Указатель, ссылающийся на значения определенного типа данных, создается точно так
же, как и переменная того же типа, за исключением того, что перед именем указателя нужно
набрать звездочку (*).
Например, чтобы создать указатель, ссылающийся на значения типа integer, наберите та-
кой код:
int

Если вы хотите придерживаться соглашений о присвоении имен переменным и


указателям, принятым в данной книге, начинайте имена указателей с буквы р
(pointer— указатель), затем набирайте префикс, который соответствует тому ти-
пу данных, на значения которого указатель будет ссылаться, и далее указывайте
само имя. Например, указатель, ссылающийся на значения типа integer, должен
начинаться с префикса рп, на значения типа double — с префикса p d b l и т.д.

Объявлять тип данных значений, на которые ссылается указатель, необходимо. Этим


обеспечивается надежность и безопасность создаваемой программы, поскольку в этом случае
компилятор может проверить, не будет ли указатель по ошибке ссылаться на данные не того
типа. Ведь указатель — это всего лишь адрес в памяти компьютера. Если вы определяете для
него тип данных (скажем, integer), а затем по какой-то причине пытаетесь, например, в па-
мять, на которую ссылается этот указатель, записать текстовое значение, компилятор выдаст
значение об ошибке. (Это очень хорошая услуга, поскольку именно такие ошибки могут по-
влечь за собой крайне нежелательные последствия.)

Глава 13. Указатели 133


Если вы набрали i n t *pnFoo, то это не значит, что название указателя —
*pnFoo. На самом деле он называется pnFoo, а звездочка не является частью его
имени и лишь сообщает компилятору, что это указатель.

Дайте указателю адрес


Если вы только что объявили указатель, он еще ни на что не ссылается. Пока это указа-
тель в никуда. И в таком состоянии его лучше не оставлять. Прежде чем использовать указа-
тель, ему нужно присвоить значение (это значение будет адресом в памяти компьютера, и та-
ким образом указатель начнет на что-нибудь ссылаться).
Довольно часто указатели используются для получения доступа к данным, которые явля-
ются значениями переменных. Другими словами, указатели содержат в себе адреса перемен-
ных, используемых в той же программе. Когда вы разыменовываете такой указатель, вы по-
лучаете значение какой-то переменной.
Чтобы получить адрес переменной, наберите амперсант (&) и ее имя. Например, можете
набрать такой код:
//Создание у к а з а т е л я на значение типа i n t e g e r
int *pnPosition;
//Создание переменной типа i n t e g e r
i n t n X P o s i t i o n = 3;
//Присвоение указателю адреса переменной n X P o s i t i o n
p n P o s i t i o n = &nXPosition;
Здесь создается указатель p n P o s i t i o n , который может ссылаться на значения типа
integer, и ему присваивается адрес переменной nXPosition. Если вы разыменуете этот ука-
затель, то получите значение переменной n X P o s i t i o n .
Указатель может указывать на значения только того типа, которые для него объ-
явлены. Так, если для указателя был объявлен тип integer, он может ссылаться
только на переменные типа integer.

Как получить значение, на которое ссылается указатель


Разыменовывание указателя (т.е. получение значения, на которое он ссылается) выполня-
ется очень просто: нужно только перед его именем набрать звездочку (*). Например, чтобы
получить значение, на которое ссылается указатель p n P o s i t i o n , наберите такой код:
//Отображение значения на экране
Console::WriteLine((*pnPosition).ToString());
В результате выполнения этого кода на экране отображается значение, полученное при
разыменовывании указателя p n P o s i t i o n . Этот указатель содержит адрес переменной
n X P o s i t i o n , ее значение равно числу 3, которое и будет отображено на экране.

Пример программы, использующей указатели


А теперь рассмотрим простенькую программу, использующую указатели. (Не огорчай-
тесь: скоро мы перейдем к более сложным.) В программе объявлена переменная типа integer
и указатель, ссылающийся на значение этой переменной. Вот как указателю присваивается
адрес этой переменной:
pnNumber = &nNumber;
По ходу выполнения программы пользователь набирает число, которое сохраняется как
значение переменной. Затем это значение отображается на экране с помощью кода
nNumber.ToString()

134 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Далее;это Ш значение отображается еще раз, но теперь доступ к нему осуществляется по-
средством разыменовывания указателя pnNumber:
(*pnNumber).ToString()

Каждый раз, когда вы вызываете функцию (например, T o S t r i n g ) для значения,


получаемого в результате разыменовывания указателя, всегда берите этот указа-
тель в круглые скобки.

//Point
//Объявление к разыменовывание указателя

#include "stdafx.h"

#using <mscorlib.dll>

using manespace System;


//Ожидание, пока пользователь не остановит
//выполнение программы
void Har.gOut ()
{
Console::WriteLine(3"Нажмите клавишу Enter, чтобы
остановить выполнение программы");
Conso]e::ReadLine();

//С этой точки начинается выполнение программы


#ifdef _UNICODE
int wmain (void)
#else
int mair. (void)
#endif
{
int *pnNumber;
int nNumber;
//Присвоение указателю адреса переменной nNumber
pnNumber = SnNumber;

//Получение числа от пользователя


Console::WriteLine(S"Укажите число");
nNumber = Int32::Parse(Console::ReadLine());

//Отображение полученного числа


Console::WriteLine(S"Введенное число {0}",
nNumber.ToString());
//Отображение числа с помощью указателя
Console::WriteLine(5"Введенное число {0}",
(*pnNumber).ToString()

• HangOut();
r e t u r n 0;

Глава 13. Указатели 135


Изменение значения, на которое ссылается указатель
Можно не только просматривать значения, на которые ссылается указатель, но и изменять
их. Другими словами, можно не только считывать данные из памяти, но и записывать в па-
мять новые данные.
Для этого, как и прежде, нужно лишь использовать звездочку (*). Допустим, например,
что указатель pnNumber ссылается на значение типа integer (как в предыдущем примере).
Чтобы изменить это значение, наберите такой код:
*pnNumber = 5;

Изменение значений в структурах данных


Если указатель ссылается на структуру, можно изменить отдельный элемент этой структуры.
В приведенном ниже примере MyStruct — структура, a poFoo — указатель, который на эту
структуру ссылается. Это значит, что для получения доступа к данным, которые в этой структу-
ре хранятся, можно использовать код *poFoo. Например, можно набрать код наподобие
(*poFoo).value = 7.6;
Продемонстрируем это на таком примере:
c l a s s MyStruct
<
public:
int nData;
double dblValue;
}
//Создание указателя, ссылающегося на такие структуры
MyStruct *poFoo;
//Создание самой структуры
MyStruct oRecordl;
//Присвоение указателю адреса этой структуры
poFoo = &oRecordl;
//Присвоение значения элементу структуры
CpoFoo).dblValue = 7.6;

Использование стрелки
Поскольку набирать код (^указатель) .элемент не очень удобно, C++ пред-
лагает упрощенный вариант:
//Изменение значения элемента структуры
poFoo->dblValue = 7 . 6 ;

Код указатель->элемент можно встретить почти во всех программах C++, исполь-


зующих указатели.

Динамическое выделение памяти


Каждый раз, когда требуется обработать элементы, количество или размеры которых зара-
нее не известны, вы сталкиваетесь с необходимостью динамического распределения памяти.
Об этом уже упоминалось, когда рассматривался пример программы, обрабатывающей
задаваемые пользователем координаты линий. Количество линий, которые захочет отобра-
зить пользователь, заранее не известно, поэтому при указании координат очередной линии
сразу же выделялась память для объекта, представляющего следующую линию.

136 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Динамическое выделение памяти происходит благодаря использованию ключевого слова
new. Необходимо только сообщить, для данных какого типа нужно выделить память, и опе-
ратор new вернет ссылку на участок памяти этого типа. Если в процессе выполнения про-
граммы необходимо выделить память для значения типа integer, наберите такой код:
//Создание указателя на значение типа i n t e g e r
int *pn?osition;
//Выделение памяти для значения типа i n t e g e r и
//присвоение этого адреса указателю p n P o s i t i o n
p n P o s i t i o n = new i n t ;
Если нужно выделить память для структуры P o i n t L i s t , наберите
//Создание указателя на структуру P o i n t L i s t
P o i n t L i s t *poPoint
//Выделение памяти для структуры P o i n t L i s t и
//присвоение этого адреса указателю poPoint
p o P o i n t = new P o i n t L i s t ;
Когда вы используете оператор new для создания элемента, указатель запоминает только
адрес, указывающий на выделенный фрагмент памяти. При этом следует быть очень внима-
тельным, чтобы по ошибке не потерять необходимый адрес, на который ссылается указатель,
так как если вы сохраните новый адрес, прежний восстановить будет уже невозможно. Про-
демонстрируем это на таком примере:
//Забывчивая программа
//Создание указателя типа i n t e g e r
int *pnPosition;
//Выделение памяти для значения типа i n t e g e r
p n P o s i t i o n = new i n t ;
//Сохранение значения по этому адресу
•*pnPosition = 3;
//Выделение памяти для нового значения
p n P o s i t i o n = new i n t ;
Последней строкой этого кода выделяется память для нового значения типа integer. Адрес этого
нового участка памяти будет сохранен в указателе p n P o s i t i o n . Но что случится со значением 3,
сохраненным по адресу, на который до этого ссылался указатель p n P o s i t i o n ? Это значение по-
прежнему будет храниться в памяти, но, поскольку его адрес потерян (указатель уже ссылается на
совсем другой участок памяти), получить к нему доступ не будет никакой возможности.
Если вы по какой-либо причине потеряете адрес, который для каких-то данных был выделен
динамически, эти данные останутся на своем месте, но найти их уже будет невозможно. Это явле-
ние называется утечкой памяти. Потерянные данные так и будут занимать память, и вы не сможе-
те их удалить до тех пор, пока программа не завершит работу. (В среде .NET есть множество воз-
можностей, позволяющих справиться с этой проблемой, о чем речь пойдет несколько ниже.)
Перед тем как использовать оператор new, убедитесь, что данные, адрес которых пока
еще хранится в указателе, более не нужны. Кроме того, если вы хотите создать действительно
качественную программу, перед использованием оператора new освободите память от тех
данных, которые вам больше не нужны, с помощью оператора d e l e t e (более подробно об
операторе d e l e t e речь идет ниже в главе).

/Зниимшие — графика
Теперь, когда у вас есть общее представление об указателях, можно приступить к созданию
более интересных программ .NET. В этом разделе коснемся графических возможностей среды
.NET. Так же как до этого вы использовали функции C o n s o l e для считывания и отображения
текста, теперь вы будете использовать функции G r a p h i c s для отображения на экране графики.

Глава 13. Указатели 137


Но, прежде чем отобразить что-то на экране, нужно создать графический объект. Делает-
ся это с помощью кода, подобного приведенному ниже,
Form *poForm = new Form О;
Graphics *poGraphics = poForm->CreateGraphics();
Сплошные указатели! Когда вы хотите что-то нарисовать, сделать это можно в окне (не в
том, из которого вы смотрите на улицу, а в окне, похожем на те, что используются в среде
Windows). Один из самых простых способов сделать это— использовать класс Form. Затем
можно вызвать процедуру CreateGraphi.es, для того чтобы создать графический элемент,
называемый объектом G r a p h i c s . Обратите внимание, что для доступа к объектам Form и
G r a p h i c s используются указатели, а для вызова методов этих объектов — символы ->.
Перед тем как что-то нарисовать, нужно сделать форму видимой с помощью кода, подоб-
ного этому:
poForm->Show();
Затем можно вызывать графические функции .NET, часть из которых показана в
табл. 13,1. Нарисовав все, что нужно, вызовите метод Dispose:
poGraphics->Dispose() ;

Таблица 13.1. Некоторые графические команды


Метод Пример Описание
Clear poG->Clear(Color::Red} Заполняет весь графический элемент
определенным цветом (см, также табл. 13.2)

Dispose poG->Dispose Освобождает память, занятую информацией о


графических объектах, Метод вызывается
после завершения этапа рисования
DrawEllipse poG->DrawEllipse Рисует эллипс, используя указанное перо.
(poPen, 10, 20, 150, (Перья описываются несколько ниже.)
200) Числовые параметры - это координаты х и у,
которые определяют верхний левый угол
эллипса, а также значения ширина и в ы -
с о т а . В действительности х, у, ширина и
в ы с о т а определяют положение и размеры
прямоугольника, внутри которого будет
нарисован эллипс. Если высоту и ширину
сделать равными, получится круг

DrawLine poG->DrawLine(poPen, Рисует прямую линию, соединяющую две точки.


10, 20, 160, 220) В данном случае линия будет соединять точку с
координатами (10,20) и точку с координатами
(160,220)
DrawReсtangle poG->DrawRectangle Рисует прямоугольник. Два первых числовых
(poPen, 10, 20, 150, параметра определяют верхний левый угол
200) прямоугольника, два следующих - его ширину и
высоту
DrawString poG->DrawString Рисует текст указанными шрифтом, кистью и
fS"Test", poFont, начиная с указанной позиции, Шрифты и
poBrush, 10, 20) кисти будут описаны ниже

138 Часть П. Все, что вы хотели знать о C++, но о чем боялись спроси
Окончание табл. 13.1
Метод Пример Описание

poG->FillEllipse Рисует эллипс, закрашенный указанной


(poBrush, 10, 20, 150, кистью
200)
FillRectangle poG->FillRectangle Рисует прямоугольник, закрашенный
(poBrush, 10, 20, 150, указанной кистью
200)

Все цвета радуги


Для каждого графического элемента необходимо указывать цвет, которым он будет нари-
сован. Осуществляется это с помощью структуры C o l o r . Всего возможны два варианта: ли-
бо исполыовать один из заранее заданных цветов, либо создать свой цвет, вызвав метод
FromArgb.
В табл. 13.2 показан список предопределенных цветов. Единственное предостережение: не
принимайте пищу во время отображения объектов с такими цветами, как PapayaWhip или
LemonChi f f on. Иначе вы рискуете испортить себе аппетит и вызвать расстройство желудка.

Таблица 13.2. Предопределенные цвета


AliceBlue DarkOrange Khaki
AntiqueKhite DarkOrchid Lavender
Aqua DarkRed LavenderBlush
Aquamarine DarkSalmon LawnGreen
Azure DarkSeaGreen LemonChiffon
Beige DarkSlyteBlue LightBlue
Bisque DarkSlyteGray LightCoral
Black DarkTurquoise LightCyan
BlanchedAlmond DarkViolet LightGoldenrodYellow
Blue DeepPink LightGray
BlueViolet DeepSkyBlue LightGreen
Brown DimGray LightPink
BurlyWood DodgerBlue LightSalmon
CadetBlue Firebrick Light5eaGreen
Chartreuse FloralWhite LightSlateGray
Chocolate ForestGreen LightSteelBlue
Coral Fuchsia LightYellow
Cornflower Gainsboro Lime

Олава 13. Указатели 139


Окончание табл. 13.
Cornsilk GhostWhite LimeGreen
Crimson Gold Linen
Cyan Goldenrod Magenta
DarkBlue Gray Maroon
DarkCyan Green MediumAquamarine
DarkGoldenrod GreenYellow MediumBlue
DarkGray Honeydew MediumOrchid
DarkGren Hotpink MediumPurple
DarkKhaki IndianRed MediumSeaGreen
DarkMagenta Indigo MediumslateBlue
DarkOliveGreen Ivory MediumSpringGreen
MediumTurquoise PaleVioletRed SlateBlue
MediurnVioletRed PapayaWhip SlateGray
MidnightBlue PeachPuff Snow
MintCream Peru SpringGreen
MistyRose Pink SteelBlue
Moccasin Plum Tan
NavajcWhite PowderBlue Teal
Navy Purple Thistle
OldLace Red Tomato
Olive RosyBrown Transparent
OliveDrab RoyalBlue Turquoise
Orange SaddleBrown Violet
OrangeRed Salmon Wheat
Orchid SandyBrownSeaShe11 White
PaleGoldenrod Sienna WhiteSmoke
PaleGreen Silver Yellow
PaleTurquoise SkyBlue YellowGreen
Метод FromArgb получил свое название от слов Alpha-Red-Green-Blue (альфа-красный-
зеленый-голубой). Любой другой цвет из диапазона, воспринимаемого человеческим глазом.
может быть получен путем смешивания в разных пропорциях этих трех основных цветов.

140 Часть //. Все, что вы хотели знать о C++, но о чем боялись
Количественное вхождение основного цвета в состав производного измеряется в пределах от
О (означает полное отсутствие) до 255 (означает максимальную интенсивность). Например,
если взять красного 255 единиц, зеленого 0 и голубого 0, то получим ярко-красный цвет. Если
красного взять 128, зеленого 128 и голубого 128, получим светло-серый цвет. Я бы с удоволь-
ствием рассказал о всех возможных цветах и оттенках, которые можно получить, комбинируя
основные цвета, но тогда ни на что другое в книге не хватило бы места. (Ради интереса по-
пробуйте посчитать, сколько разных цветов можно получить с помощью этой системы.)

Если вы хотите точно узнать, в каких пропорциях входят основные цвета в состав
нужного вам цвета, откройте какую-нибудь графическую программу, например
Adobe Photoshop. В окне цветоподборщика (color picker) найдите нужный вам
цвет. В этом же окне будут показаны параметры выбранного цвета.
Чтобы использовать голубой цвет, наберите
Color::31ue
Если же вам нужен какой-то специфический цвет, наберите код наподобие этого:
C o l o r : :FormArgb(255, 12 8, 0) ;
Пример использования структуры Color будет показан несколько ниже в этой главе.
В приведенном выше примере указаны значения для красного, зеленого и голубо-
го цветов. Но метод FromArgb может принимать значение еще одного парамет-
р а — альфа, которым обозначается прозрачность графического элемента. Поэкс-
периментируйте с этой возможностью — быть может, она вам пригодится.
При использовании объекта Color не нужно набирать оператор new для созда-
ния нового объекта (как это делается в случае с объектом Graphics). Вместо
этого наберите символы : :, чтобы указать один из предустановленных цветов
или вызвать метод FromArgb.

Перья для рисования


Рисуя линию (метод DrawLine) или фигуру (методы DrawEllipse и
DrawRec'angle), вы используете объект Реп (перо). Этот объект переносит в мир компь-
ютерной графики свойства обычного пера или карандаша. Он рисует линии определенного
цвета и определенной толщины. Чтобы создать красный карандаш, который будет рисовать
линию толщиной в один пиксель, наберите такой код:
poPen = new P e n ( C o l o r : : R e d ] ;
А этим кодом создается синий карандаш с толщиной следа в пять пикселей:
poPen = new Pen(Color::Blue, 5 ) ;
Цвет и толщину объекта Реп можно изменять в процессе выполнения программы. Это
делается путем изменения значений свойств Color и Width этого объекта. Например:
poPen->Wigth = 12;
poPen->Color = C o ] o r : : S a d d l e B r o w n ;

Графические функции среды .NET иногда обозначаются как GDI-K

Глава 13. Указатели 141


Кисточка для раскраски
Если вы хотите нарисовать заполненную чем-то фигуру (используя, например, методы
F i l l E l l i p s e или F i l l R e c t a n g l e ) , вместо объекта Реп используется объект Brush
(кисточка). Эффект применения этого объекта подобен эффекту использования обычной кис-
точки для рисования. В электронном варианте есть два вида кисточек: S o l i d B r u s h и
T e x t u r e B r u s h . Кисточка S o l i d B r u s h заполняет фигуру сплошным цветом, в то время как
T e x t u r e B r u s h может заполнить фигуру каким-нибудь рисунком или изображением.
Чтобы создать новую кисточку, наберите такой код:
poBrush = new S o l i d B r u s h ( C o l o r : : R e d ) ;
Для изменения цвета кисточки измените свойство Color этого объекта:
poBrush->Color = C o l o r : : T h i s t l e ;

Шрифты
Когда вы применяете метод D r a w s t r i n g для отображения текста на экране, необходимо
выбрать используемый шрифт. Для этого наберите код наподобие
p o F o n t = new F o n t ( " A r i a l " , 12);
Значением первого параметра определяется сам шрифт, а второго— его размер. Вообще
сушествует множество различных шрифтов. Примеры некоторых из них показаны на рис. 13.3.

Шрифт Пример

Arial Hello 123

Courier New Hello 123

Times New Roman Hello 123

Symbol HEUO 123

Verdana Hello 123

WingDings

Рис. 13.3. Наиболее часто используемые шрифты


Можно использовать вспомогательную программу Character Map, чтобы увидеть,
как одни и те же буквы отображаются в разных шрифтах. Это очень полезная
возможность, особенно когда речь идет о шрифтах Symbol и WingDings. Если вам
скучно и вы не знаете, чем заняться, можете составить с помощью этих шрифтов
многозначительное послание, состоящее из разных значков и символов, отпра-
вить его своим друзьям и посмотреть, как они на него отреагируют.

Займемся рисованием
Теперь, когда вы познакомились с основами создания графических элементов в среде
.NET, можно приступать к использованию этих возможностей на практике. Ниже приведен
код небольшой программы, которая рисует на экране линии и некоторые геометрические фи-
гуры. При этом используются объекты, представляющие перья, кисти и шрифты.

142 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросит
//Graphics
//Демонстрация графических возможностей .NET

#include "stdafx.h"

#using <mscorlib.dll>
#using <System.Windows.Forms.dll>
#using <System.dll>
fusing <System.Drawing.dll>
using manespace System;
using manespace System::Drawing;
using manespace System::Windows::Forms;

//С этой точки начинается выполнение программы


tifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{
//Создание структур для отображения графики
Form *poForm = new Form();
Graphics *poGraphics = poForm->CreateGraphics();
//Создание красного пера
Pen *poPen = new Pen(Color::Red);
//Отображение окна для рисования
poForrr.->Show () ;

//Отображение линии
poGraphics->DrawLine{poPen, 10, 10, 120, 150);
//Изменение свойств пера и
//отображение новой линии
poPen->Color = Color::FormArgb(100, 255, 200);
poPen->Wigth = 5;
poGraphics->DrawLine(poPen, 120, 150, 10, 150);

//Отображение эллипса с использованием того же пера


poGraphics->DrawEllipse(poPen, 10, 150, 40, 60) ;

//Создание новой кисти


SolidErush *poBrush = new SolidBrush(Color::Tomato);
//Отображение прямоугольника, закрашенного этой кистью
poGraphics->FillRectangle(poBrush, 50, 150, 40, 60);

//Создание шрифта
Font *poFont = new Font("Helvetica", 22);
//Отображение текста этим шрифтом
poGraphics->DrawString(S"Hello", poFont, poBrush, 200, 200);

//Освобождение памяти

£лава 13. Указатели 143


poGraphics->Dispose() ;

//Ожидание, пока пользователь не закроет окно с графикой


Application::Run(poForm);
}
В этой программе необходимо обратить внимание на следующее. Во-первых, на наличие
нескольких новых строк с ключевыми словами t u s i r . g и u s i n g namespace в начале про-
граммы. Они нужны потому, что для использования графики и форм в среде .NET требуется
подключение некоторых дополнительных файлов.
Во-вторых, на использование кода A p p l i c a t i o n : :Run в конце программы. Каждый
раз, когда вы используете форму, необходимо набирать код, указывающий системе, что не
нужно прекращать выполнение программы до тех пор. пока пользователь не закроет окно
этой формы. Это намного удобнее применения функции HangOut, которая постоянно ис-
пользовалась в предыдущих примерах. Форма отображается на экране, пользователь про-
сматривает все, что на ней нарисовано, а когда щелкает на кнопке закрытия формы, програм-
ма прекращает работу.

списки и графика
После того как вы немного поупражнялись с указателями (вся предыдущая программа
была построена с их использованием), перейдем к рассмотрению более типичных способов
их применения. В этом разделе внимание будет уделено вопросу создания с помощью указа-
телей связанных списков. Рассмотрено это будет на таком примере: пользователь набирает
координаты произвольного количества точек, для хранения которых используется связанный
список. Когда он закончит, эти точки соединяются между собой с помощью графических ко-
манд среды .NET.

Как эта программа работает


В программе создается связанный список. Каждый элемент этого списка представляет со-
бой структуру, состоящую из координат точки, а также из указателя, который ссылается на
следующий элемент этого списка. Указатель последнего элемента списка имеет значение,
равное нулю. Указатели с таким значениями называются пустыми (или нулевыми).
Итак, элемент связанного списка должен содержать в себе значения координат точки и
указатель на следующий элемент. Ниже показан код, которым объявляется структура
P o i n t L i s t , содержащая в себе два значения типа integer и указатель на такую же структуру.
class PointList
{
public:
int nX;
ir.t nY;
PointList *poNext;
};
Программа должна содержать в себе коды, необходимые для добавления в список новых
элементов, и коды, позволяющие находить нужную информацию в этом списке. Для этого
нужны три дополнительных указателя. Первый будет ссылаться на первый элемент списка;
таким образом, вы всегда будете знать, где список начинается. Это будет как бы точкой от-
счета, дающей ключ ко всем остальным элементам списка:
P o i n t L i s t * p o P o i n t s = С;

144 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Второй указатель будет каждый раз ссылаться на новый элемент, который добавляется к
списку:
P o i n t L i s t *poNew;
Третий указатель будет содержать ссылку на последний элемент списка:
P o i n t L i s t *poLast;
Он также потребуется при добавлении в список нового элемента, так как последний элемент
списка (теперь уже бывший последний) должен ссылаться на вновь добавляемый элемент.
Теперь рассмотрим, как связанный список будет использоваться для хранения вводимой
пользователем информации. Вначале выделяется память для нового элемента списка:
poNew = new P o i n t L i s t ;
Если это будет первый элемент списка, указатель p o P o i n t s должен ссылаться именно на
него. (Помните, что p o P o i n t s всегда указывает только на первый элемент?)
if (!poPoints)
{
//Еслл poPoints пока еще не ссылается ни на какой
//элемент, пускай он ссылается именно на него
poPoints = poNew;
}
Оператор if проверяет, не является ли указатель p o P o i n t s нулевым. Если он нулевой, вы-
ражение ! p o P o i n t s истинно и инструкция p o P o i n t s = poNew выполняется. Если же указа-
тель p o P o i n t s уже ссылается на какой-то элемент, новый элемент добавляется в конец списка:
poLast->poNext = poNew;
Смысл выполняемых этим кодом действий следующий: определяется элемент, на который
ссылается указатель poLast, и составляющей poNext этого элемента присваивается адрес
создаваемого элемента, на который в данный момент ссылается указатель poNew.
И наконец, обновляется значение указателя poLast так, чтобы теперь он ссылался на
только что созданный объект, и вызывается функция NewPoint, которая принимает значе-
ния координат новой точки:
poLast •= poNew;
NewPoint(poNew) ;
После того как пользователь укажет координаты всех точек, программа отобразит их на
экране и соединит между собой прямыми линиями. Этот процесс начнется с отображения
первой точки, после чего будет проведена линия к следующей точке, затем к следующей и
т.д. Процесс рисования завершится, как только программа дойдет до нулевого указателя, по-
скольку нулевой указатель принадлежит последнему элементу списка.
PoLast = p o P o i n t s ;
w h i l e (ooLast)
{
//Отображение линий
DrawPoint(poGraphics, p o L a s t ) ;
//Переход к следующему элементу
poLast = poLast->poNext;
}
После того как все линии будут нарисованы, необходимо очистить память от ненужной
более информации (о том, для чего это нужно делать, речь идет ниже в этой главе):
PoLast = p o P o i n t s ;
w h i l e (poLast)

Глава 13. Указатели 145


PointList *poNext;
poNext = poLast->poNext;

//Освобождение памяти
delete poLast;

//Переход к следующему элементу


poLast = poNext;

Код программы
Ниже приведен код всей программы, работа которой обсуждалась в предыдущем разделе.
//Draw
//Применение связанного списка для сохранения
//координат произвольного количества точек
//и их отображение с использованием GDI +

jfinclude "stdafx. h"

#using <mscoriib.
#using<3ystern.Windows.Forms.dll>
#using <System.dll>
fusing <System.Drawing.dlI>
using manespace System;
using manespace System::Drawing;
using manespace System::Windows::Forms;
//Создание структуры, представляющей элемент связанного списка
class PointList
{
public:
int nX;
int nY;
P o i n t L i s t *ooNext;

//Получение координат новой точки


void NewPoint(PointList *poNew)
{
//Чтение координат Х и Y
Console::WriteLine(S"Введите координату X")
poNew->nX = Int32::Parse(Console::ReadLine(
Console::WriteLine(S"Введите координату Y")
poNew->nY = Int32::Parse(Console::ReadLine{
//Присвоение указателю нулевого значения
poNew->poNext = С;

//Отображение точек на экране


void DrawPoint(Graphics *poGraphics, PointList *poPoint)

ть ли точки для отображения?


If (poPoint->poNext)

/ 46 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
//Создание пера для рисования
Pen *poPen = Pen(Color::Red);
//Рисование линии от текущей точки к следующей
poGraphics->DrawLine(poPen, poPoint->nX, poPoint->nY,
роPoint->poNext~>nX, poPoint->poNext->nY);

//С этой точки начинается выполнение программы


#ifdef JJNICODE
int wmain(void)
#else
int main(void)
#endif
{
//Указатель для первого элемента списка
PointList *poPoints = 0;
//Указатель для последнего созданного элемента
PointList *poLast;
//Указатель для нового элемента списка
PointList *poNew;
//Указатель для принятия ответа от пользователя
String *pszMore;

//Структуры для отображения графики


Form *poForm = new Form();
Graphics *poGraphics = poForm->CreateGraphics();
while (!fFinished)
<
//Выделение памяти для нового элемента
poNew = new PointList;
if •!poPoints)
{
//Если это первый элемент, присвоение его
//адреса указателю poPoints
poPoints = poNew;
)
else
{
//Присоединение элемента в конец списка
poLast->poNext = poNew;
}
//Обновление значения указателя poLast
poLast = poNew;
//Принятие от пользователя координат новой точки
NewPoint(poNew);
//Хочет ли пользователь ввести координаты для
//следующей точки?
Console::WriteLine("Нажмите у для определения
следующей точки");
pszMore = Console::ReadLine();
if (!pszMore->Equals(S"y"))

Глава 13. Указатели 147


fFinished = t r u e ;

//Отображение окна для рисования


poForm->Show();

//Отображение точек на экране


PoLast = poPoints;
while {poLast}
{
//Отображение линий
DrawPoint(poGraphics, poLast);
//Переход к следующему элементу
poLast = poLast->poNext;

//Освобождение памяти, которая использовалась для


//отображения графики на экране
poGraphics~>Dispose();

//Освобождение памяти, занятой элементами списка


PoLast = poPoints;
while (poLast)
{
PointList *poNext;
poNext = poLast->poNext;

//Освобождение памяти
d e l e t e poLast;

//Переход к следующему элементу


poLast = poNext;

//Ожидание, пока пользователь не закроет окно формы


Application::Run(poForm);

Использование ссылочных аргументов


Как уже отмечалось, изменять значения глобальных переменных в процессе выполнения функций - пло-
хая практика. Намного разумнее использовать функции для возвращения значений и затем уже как-то
изменять эти значения вне функций. {В будущем это поможет вам избежать многих ошибок и без труда
понимать смысл выполняемых функциями действий.)
Если же функция должна изменить значения нескольких элементов или элементов, принадлежащих ка-
кой-то структуре, передайте ей в качестве параметра указатель, ссылающийся на нужный элемент. В
процессе своего выполнения функция может разыменовать указатель и изменить значение элемента. Это
намного лучше, чем изменять значения глобальных переменных {или переменных, область видимости ко-
торых распространяется за пределы этой функции), поскольку при передаче функциям значений указате-
лей вы точно определяете, какие элементы будут изменены. Таким образом, не вникая в подробности ;
выполнения функции, вы или другие программисты сможете сразу же определить, значения каких эле-
ментов могут быть изменены этой функций.

148 Часть It. Все, что вы хотели знать о C++, но о чем боялись спросить
Другой возможностью является использование ссылочных аргументов. При этом функции передаются
указатели на аргументы (т.е. их адреса в памяти), но не сами аргументы.
Поскольку в распоряжение функции попадают адреса элементов, она может изменять их значения непо-
средственно.
Использовать ссылочные аргументы удобнее, чем передавать в качестве аргументов значения указате-
лей, поскольку в этом случае не нужно разыменовывать указатели внутри функции. Чтобы определить,
что функция будет использовать ссылочный аргумент, наберите в списке аргументов перед названием
этого аргумента символ &:
int Factorialfint ^Number)

Теперь все изменения, выполненные функцией в отношении переменной Number, будут иметь постоянный
характер, и это будет выглядеть так, как будто значение переменной изменяется за пределами функции.

Воп/мсм Зезопасноопи
Как известно, большие полномочия сопровождаются большей ответственностью, что
справедливо и в отношении указателей. Язык C++обладает большими возможностями, и с
помощью указателей программист получает прямой доступ к памяти компьютера. Это озна-
чает, что использовать их нужно очень осторожно. В этом разделе затрагиваются вопросы, о
которых нужно помнить при работе с указателями, а также описываются возможности среды
.NET, которые помогут вам избежать ошибок.

Освобождение памяти
Если вы видите, что в выделенной ранее памяти больше нет необходимости, сразу же ос-
вобождайте ее. Таким образом, ваши программы не будут занимать больше памяти, чем им
требуется.
Для освобождения памяти используется команда d e l e t e , действие которой в точности
противоположно действию оператора new:
//Выделение памяти для элемента P o i n t L i s t
P o i n t L i s t * p n P o i n t = new P o i n t L i s t ;
//Освобождение только что выделенной памяти
delete pnPoint;
//Обнуление у к а з а т е л я
p n P o i n t = 0;
Обратите внимание, что при обнулении указателя не набирается звездочка (*), поскольку
обнуляется значение самого указателя, а не участок памяти, на который он ссылается.

Когда вы удаляете какой-то элемент, значение указателя не изменяется. Однако


тех данных, на которые ссылается указатель, в памяти больше нет. Таким обра-
зом, указатель по-прежнему ссылается на какой-то адрес, но этот адрес уже пуст.
Если вы его сейчас разыменуете, то получите совершенно бессмысленное значе-
ние. Поэтому каждый раз, удаляя какой-то элемент, обнуляйте указатель, который
на него ссылался. Благодаря этому вы будете точно знать, что все указатели ва-
шей программы ссылаются на реальные данные.

Глава 13. Указатели 149


Общее нарушение защиты
Запутавшись с указателями, вы можете столкнуться с серьезными проблемами.
Такого рода ошибки обозначают термином GPF (General Protection Fault— общее
нарушение защиты). Иногда это означает, что придется закрывать некоторые
приложения или даже перезагружать компьютер. Windows отслеживает возник-
новение ошибок GPF и, как только их находит, останавливает выполнение про-
граммы. На рис. 13.4 показано, как может выглядеть диалоговое окно с сообще-
нием о нарушении общей защиты.

Kiler Doc hat encountered a proble


We die tony lor I he n
i convene
i nce

I f you were in tHe mitts of Joroelhing. the information you weie workhg on
night be lost
Phsase l e i М к н л о П about lhi> piotrien.
We Iwve creeled an епя tepcrl that you can tend to us We writ Beat
th» report as confidential and anonymous.

To tt* »Mhat data this

Oefeug I

РИС. 13.4. Если Windows столкнется с


GPF, она закроет вашу программу и отобразит
окно с предупреждением

Неизменяемые ссылочные аргументы


Если вы передаете функциям в качестве аргументов большие структуры данных, это может занять слиш-
ком много времени, поскольку при этом компьютер должен создать новые копии всех элементов, входя-
щих в эти структуры. Чтобы ускорить этот процесс, передавайте структуру как ссылочный аргумент. В
этом случае передается только информация об адресе структуры, которая сама по себе занимает очень
мало места.
Но теперь функция получает возможность изменять данные, хранящиеся в структуре. Предположим, вам
этого не нужно. Чтобы решить этот вопрос, можно обозначить структуру как неизменяемый ссылочный
аргумент. При этом вы как бы сообщаете компилятору: "Я делаю это только для ускорения работы про-
граммы. Не изменяй данные, доступ к которым получает функция".
Вот пример объявления неизменяемого ссылочного аргумента:
i n t D r a w l t ( c o n s t P o i n t L i s t &MyPoint)

Ниже перечислен ряд причин, по которым работа с указателями может обернуться ма-
ленькой трагедией.
• S Запись информации на нулевой адрес. Например, если указатель f оо обнулен и
\ вы набираете код *foo = х, то это означает, что вы отправляете значение х
;
"туда, не знаю куда". Windows это не понравится. Это вообще никакой системе не
; понравится. Результат таких действий будет плачевным.
: •/ Запись информации не на тот адрес. Предположим, например, что указатель f оо
\ ссылается на первый элемент списка, а указатель b a r — на последний. Если вы

150 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросит
используете b a r в том месте, где должны были использовать foo, можете столк-
нуться с совершенно неожиданными последствиями.
Освобождение памяти без обнуления указателей. Например, после освобожде-
ния памяти вы преспокойно записываете по тому же адресу целую кипу новых
данных (при том, что теперь эта память отведена для совершенно другой информа-
ции). Результат будет ужасен.

Старайтесь не делать таких глупостей, иначе проблем не оберешься.

Генеральная уборка
В программе Draw было написано немало кодов для того, чтобы очистить память, которая
использовалась в процессе ее выполнения. Если хотите, можете воспользоваться возможностью
среды .NET, называемой сборкой мусора (garbage collection), которая автоматически выполнит
всю "грязную" работу. Если при объявлении объекта вы укажете, что память после него нужно
освобождать автоматически, среда .NET будет отслеживать его в процессе выполнения про-
граммы. Как только .NET увидит, что объект больше не используется, она освободит занимае-
мую им память. (Поскольку сборка мусора— дело не только грязное, но и отнимающее много
времени, .NET занимается этим тогда, когда для этого появляется удобный момент.)
Чтобы активизировать для объекта эту возможность, при его объявлении наберите в нача-
ле строки дс, например:
дс class PointList
{
public:
int nX;
int nY;
PointList *poNext;

Универсальные указатели
На первый взгляд может показаться, что использовать указатели, которые могут ссылаться на все, что
угодно, - вещь удобная. При объявлении таких указателей вместо слова, обозначающего тип данных, на-
бирается слово v o i d (что означает "пустой тип данных"}, но хотя эти указатели очень удобны (поскольку
вы можете использовать их для получения доступа к значениям любых типов), они также и очень опасны
(поскольку компилятор не может отследить правильность их использования).
Вот как можно создать такой указатель (но потом не говорите, что вас не предупреждали):
//Пусть pOff ссылается на что угодно
void *pOff; . •
Указатели v o i d опасны {и одновременно удобны) именно тем, что для них не определен тип данных и
поэтому возникает реальная возможность неправильного использования памяти.
Предположим, что в рассмотренном ранее примере вы используете для создания связанного списка ука-
затели v o i d и случайным образом вместо структур с информацией о координатах точек добавляете в
список числа, строки, записи о сотрудниках и еще неизвестно что. Компилятор никак не сможет опреде-
лить, что здесь что-то не так. Но пользователи вашей программы непременно это заметят.
Подведем итог; используйте указатели v o i d только тогда, когда без этого никак не обойтись. J

Глава 13. Указатели 151


octnfooicax
Если вы занимались программированием раньше, то должны знать, что термином
строки обозначается текстовая информация, точнее, наборы следующих друг за
другом символов. В .NET есть встроенный класс S t r i n g , который предназначен
для работы со строками. Если же вы пишете неуправляемый код, вам придется
делать все самому, используя указатели типа char. Но не расстраивайтесь: есть
множество библиотечных функций, которые специально созданы для обработки
текстовой информации.
Строки хранятся в памяти компьютера как массивы расположенных друг за другом сим-
волов, заканчивающиеся нулевым значением. (Из-за этого нулевого байта в конце строки они
называются также строками с завершающим нулем.) Доступ к текстовой информации можно
получить с помощью указателей, которые ссылаются на первый символ строки. Чтобы соз-
дать строку, наберите такой код:
char *szMyString = "романтическое путешествие";
Этим кодом создается строка, содержащая текст "романтическое путешествие". Указатель
szMyString ссылается на эту строку. Чтобы отобразить эту строку на экране, наберите
c o u t << szMyString;

Указания для указателей


Здесь вы найдете несколько советов и напоминаний, которые помогут вам избежать проблем при работе
с указателями,
•/ Указатель содержит в себе лишь адрес, указывающий на некоторые данные, сохраненные в памяти.
Если вы как-то изменяете значения указателей (добавляете, вычитаете и т.п.), еы изменяете только ад-
реса, но не то, что находится по этим адресам. Обычно, это не является вашей целью: вы хотите обра-
батывать именно те данные, на которые ссылаются указатели. Для этого указатели нужно разыменовы-
вать.
•/ *pFoo — это не имя указателя. Его имя — pFoo. А кодом *pFoo указатель разыменовывается.
v Если указатель pnFoo имеет тип integer, можете набирать *pnFoo в любом месте программы, где
может быть использовано значение типа integer. Если указатель poFoo ссылается на данные какого-
нибудь типа х, можете набирать *poFoo везде, где значения типа х могут быть использованы. Это
значит, что вы можете набирать * p o F o o = j u p i t e r ; или j u p i t e r = *poFoo;HT.n.
s Если вы динамически выделяете какую-то память {т.е. в процессе выполнения программы), убеди-
тесь, что адрес этой памяти сохранен как значение указателя. В противном случае вы никак не смо-
жете получить доступ к этой памяти.
^ После того как вы освобождаете память, выделенную динамически, значение указателя, ссылающегося
на эту память, никак не изменяется и он по-прежнему на нее указывает. Чтобы избежать ненужных
проблем, всегда обнуляйте соответствующие указатели после использования команды d e l e t e .
s Если вы в больших объемах используете динамически выделяемую память, подключите возмож-
ность среды .NET, называемую сборкой мусора.
s Если вы устали и "зависать" начинает уже ваша черепная коробка, сделайте перерыв, покурите си-
гару или позвоните любимой.

Вам наверняка будет интересно, какие библиотечные функции предназначены для работы
со строками и что они могут делать. Их будет легче найти, зная о том, что названия почти
всех из них начинаются с букв s t r . Например, функция s t r l e n возвращает количество
символов, из которых состоит строка.

152 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
В C++ есть также такой объект, как текстовый класс ANSI, который может помочь вам в
работе со строками.
Выделяя память для строк, не забывайте о том, что они должны заканчиваться нулевым
значением, которое занимает один байт памяти. Убедитесь, что вы включили этот байт в об-
щее количество байтов, выделяемых для размещения текстового значения. В противном слу-
чае возникнут проблемы с использованием памяти.
Поскольку можно набирать код c h a r * для создания ссылок на текстовые данные, вы
можете в полной мере использовать все возможности указателей для обработки текстовой
информации. Напомним, что в C++ строки должны заканчиваться нулевым байтом. Именно
по нему библиотечные функции определяют, где находится коней строки.
Если необходимо, например, отобразить все символы строки поочередно (по одному за
раз), можно использовать значение указателя этой строки, увеличивая его каждый раз на еди-
ницу. Если указатель ссылается на какой-то символ строки, добавив к его значению число 1,
вы получите адрес следующего символа строки. Например:
//Строка
char *szMyString = "hello world";
//Еще един указатель
char *s?CharPtr;
//Изменение первого символа строки
szCharPtr = szMyString;
*szCharE'tr = " j " ;
//Теперь перехолим к следующему символу
//Для этого значение указателя увеличиваем на 1
szCharPtr++;
//Изменяем второй символ
*szCharPtr = "о";
//Теперь строка выглядит как "jollo world"
//Отображение строки на экране
cout << szMyString;

итог
Вы уже знаете об указателях достаточно много. Это просто переменные, значениями ко-
торых являются адреса различных данных, сохраненных в памяти компьютера. Указатели ис-
пользуются для получения доступа к памяти, которая выделяется динамически (это нужно в
тех случаях, когда вы заранее не знаете, какой объем памяти должен быть выделен). Также
указатели используются для создания связанных списков (они нужны тогда, когда заранее не
известно, сколько элементов будет использовано). Есть еще много задач, которые решаются
именно с использованием указателей. Вы будете встречаться с ними каждый раз, когда будут
использоваться такие возможности .NET, как команды работы с графикой или методы обра-
ботки текстовой информации.
Но, даже если вам кажется, что вы уже стали экспертом по указателям, не обольщайтесь:
вам предстоит еще многому научиться.

Глава 13. Указатели 153


Глава 14

Масса информации? Используйте


массивы!
главе...
> Что такое массив
> Сортировка и извлечение информации из массива
> Многомерные массивы
> Использование A r r a y L i s t
> Использование Stack
> Использование перечислимых типов вместо констант

асеив — это определенный спосоо представления данных, используемый во мно-


гих программах. Строение массива похоже на строку (или столбец) ячеек элек-
тронной таблицы: в вашем распоряжении есть целое множество отдельных ячеек, в которые
можно записывать информацию. Такая структура очень удобна для сохранения данных и их
последующей обработки.
В этой главе описывается использование массива для сохранения списка элементов, содер-
жащих данные о графических объектах. Также вы познакомитесь со специальными классами
.NET, которые предназначены для оптимизации процесса сохранения данных в программе.

цМасси€ы: нозна/солсимсл поближе


Особенность массивов состоит в том, что каждый их элемент имеет порядковый номер,
называемый индексом. Индекс используется для получения доступа к данным, сохраненным в
каждом отдельном элементе. Индекс позволяет также использовать циклы для просмотра
значений всех элементов массива или значений только некоторого диапазона элементов. С
помощью индекса можно сразу же получить непосредственный доступ к данным любого эле-
мента массива. Такой способ доступа к данным называется произвольным; он намного быст-
рее использования связанных списков.
Предположим, вы используете массив для хранения информации о продолжительности
музыкальных записей. Тогда, если нужно просмотреть, например, продолжительность первой
записи, вы просто смотрите значение элемента под номером 1. Точно так же можно сохра-
нять информацию о котировках акций на биржах, данные о сотрудниках, результаты фут-
больных матчей и т.д.
Прежде чем создавать массив, вы должны определить, из какого количества элементов он
будет состоять. В этом отличие массивов от списков, где количество элементов заранее не
известно. (Хотим вас обрадовать: далее в главе рассматривается специальный класс .NET
A r r a y L i s t , который объединяет в себе все лучшие свойства массивов и списков.)
Предположим, например, что a n F o o — это массив чисел типа integer. Тогда он может
выглядеть приблизительно так:

Глава 14. Масса информации? Используйте массивы! 155


Индекс Значение

0 32

1 10

2 17

3 -5

4 10

Как видите, первый элемент массива имеет нулевой индекс, второй — индекс 1 и т.д. И в
данном случае элемент с индексом 0 имеет значение 32, а злемент с индексом 4 — значение 10.

Если вы придерживаетесь соглашений о присвоении имен, принятых в этой кни-


ге, начинайте имена массивов с буквы а, затем набирайте префикс, соответст-
вующий типу данных массива, и далее само имя. Например, имя массива типа in-
teger начинайте с букв an, типа double — с букв a d b l и т.д.

Чтобы создать массив, нужно просто указать тип данных, имя массива и в квадратных
скобках ( [ ]) количество элементов.
Например, чтобы создать массив типа integer, наберите такой код:
//Создание массиза типа integer, состоящего из 20
//элементов (индексы от 0 до 19)
;nt anFoo[20];
Помните, что первый элемент массива имеет нулевой индекс. Поэтому, если мас-
сив имеет п элементов, последний элемент будет иметь индекс (п - 1). Например,
для массива, показанного несколькими абзацами выше, п равнялось числу 5
(поскольку массив состоит из пяти элементов). Первый элемент имеет нулевой
индекс, а последний — индекс 4 (или 5 - 1 ) .

Те, кто только начинает использовать массивы, очень часто забывают об этом и думают,
что первый элемент имеет индекс 1, а потом долго не могут понять, почему они получают из
массива не те значения, которые им нужны.
Аналогичную ошибку совершают и в отношение последнего элемента массива: при обра-
щении к нему вместо индекса {п -\) используют индекс п. В результате этого либо будут по-
лучены совершенно нечитаемые данные, либо возникнет ошибка GPF (общее нарушение за-
щиты). Последнее случается из-за того, что C++ не может предотвратить обращение к памя-
ти, расположенной за пределами массива. Если вернуться к приведенному выше коду и
предположить, что вы ошибочно набрали a n F o o [ 2 0 ] , это будет обращение к участку памя-
ти, расположенному сразу после области памяти, выделенной для элементов данного масси-
ва. Последствия могут быть самыми непредсказуемыми.

б же "элемен&а/гно ", /Зсыпсон


Чтобы получить доступ к значению какого-либо элемента массива, наберите имя массива
и укажите в квадратных скобках индекс этого элемента. Продемонстрируем это на примере.
//Массив типа integer из 20 элементов
i:iL anFco [20] ;
//Первому элементу присваивается значение 20,

156 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
//а второму значение 3
anFoo[0] = 20;
a n F o o [ l ] = 3;
//Отображение значения второго элемента
Console::WriteLine(anFoo[1]);
//Отображение значения третьего элемента, умноженного на 5
Console::WriteLine(5*anFoo[2] ) ;
Обратите внимание, что при отображении только элементов массива не нужно
вызывать функцию T o S t r i n g для преобразования числового значения в тексто-
вое. Среда .NET может это сделать автоматически. Если же вы используете более
сложные варианты функции Console: :WriteLine, например форматируете
строки, вызвать функцию T o S t r i n g все же придется.

инициализация, массива
Инициализация —• это присвоение начальных значений. Инициализировать массив можно
несколькими способами. Можно, например, вручную присвоить значения каждому элементу:
anFoo[С] = 1;
a n F o o [ l ] = 3;

Другой способ — использовать цикл. Этот путь очень удобен, если можно использовать
какой-то шаблон для присвоения значений или если эти значения могут быть получены из
файла данных. Например, если необходимо, чтобы массив содержал значения от 1 до 20, на-
берите такой код:
//Массив из 20 элементов
int anlntegers[20];
//Цикл, на каждой итерации которого очередному
//элементу присваивается значение,
//большее значения предыдущего элемента на 1
f o r ( i n t i = 0; i < 20; i++)
a n l n t e g e r s [ i ] = i + 1;
Еще один способ инициализировать массив — указать значения элементов при объявле-
нии этого массива. При этом можно указывать значение не всех элементов, а только некото-
рых (остальным элементам, для которых вы значения не укажете, автоматически будут при-
своены нули). Например, массив может быть инициализирован таким образом:
i n t anMyInts[10] = ( 1 , 3, 5, 9, 4, 8 } ;
В данном случае первым шести элементам будут присвоены перечисленные значения
(первому элементу— 1, второму— 3 и т.д.), а последние четыре элемента получат значения,
равные нулю.
В своей программе вы можете также создать и инициализировать массив, состоящий из
текстовых значений. В этом случае при объявлении массива его размер указывать не нужно.
Например:
//Создание массива, состоящего из строк
S t r i n g *aszFoo[] = { S " h e l l o " , S"goodbye"};
C o n s o l e : : W r i t e L i n e ( S " B массиве есть слово {0}", aszFoo[l]);

Глава 14. Масса информации? Используйте массивы! 157


Связь между массивами и указателями :
Массив элементов со значениями типа х, по сути, является указателем на значение типа х. Поэтому, на-
бранное вами
int foo[8];
будет почти аналогично набранному
int *foo;
Единственная разница в том, что когда вы объявляете массив, выделяется память сразу для множества
значений указанного типа, в то время как при объявлении указателя память выделяется только для одно-
го элемента. Переменная массива ссылается на первый элемент этого же массива.
Указатели иногда используются для получения доступа к элементам массива. Рассмотрим это на таком примере:
//Массив из 8 элементов с числовыми значениями
int foo{8];
//Указатель на числовое значение
int *bar;
//Указателю присваивается адрес первого элемента" массива
bar = foo;
.//Отображение значения первого элемента массива
-cout « *bar;
//Отображение значения второго элемента .массива
//Обратите внимание, что это равнозначно foo[l]
.cout << * (bar + 1) ;
Если вы используете указатель для ссылки на значение элемента массива, при добавлении единицы в
действительности добавляется размер памяти, выделенный для одного элемента этого массива. Таким
образом вы получаете адрес следующего элемента.

В неуправляемых программах массивы, состоящие из строк, создаются несколько


иначе. Если вы помните, сама строка — это массив символов (объявляется как
c h a r *). Ниже приведен код неуправляемой программы, при выполнении кото-
рого создается и инициализируется массив из строк, а затем все значения этого
массива отображаются на экране.
//Создание массива из трех строк и присвоение его
//элементам начальных значений
char *aszFoo[3] = {"hello", "goodbye", "bye-bye"};
//Отображение строк на экране
cout << aszFoo[0] << aszFoo[l] << aszFoo[2] « endl;

Ultioiojteftttbte массивы
Массивы, которые рассматривались до сих пор, были одномерными. Кроме них, сущест-
вуют также многомерные массивы, без использования которых вряд ли можно обойтись при
решении многих типичных и не очень типичных задач. Представим, например, что вам нужно
сохранить информацию о том, сколько домов обозначено в каждом квадрате на карте города.
Поскольку карта двухмерна, для решения этой проблемы удобнее и логичнее использовать
двухмерный массив (рис. 14.1).
Или возьмем другой пример: нужно смоделировать перемещение какого-нибудь летающе-
го насекомого в пределах вашей комнаты. Для этого можно условно разбить пространство
комнаты на кубики, соизмеримые с размерами животного, и отмечать его перемещение из
кубика в кубик. Множество кубиков будет представлять собой трехмерный массив. Много-

158 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
мерные массивы могут быть использованы при решении не только "пространственных" за-
дач. Например матрицы, которые применяются для обработки графики, являются двухмер-
ными массивами. Управление большими объемами разнообразных данных может быть све-
дено к работе с многомерными массивами.

0 1

о 1ш1Т1 Ш 0 1 2 0

ШШ /Л! /Ш&
2 4 0
I l l ШШШШ
mi 0 0 1
il
Карта Массив
Рис. 14.1. Квадраты обычной двухмерной карты представлены ячейками двухмерного мас-
сива, значения которых соответствуют количеству обозначенных на карте домов

Чтобы определить многомерный массив нужно использовать столько квадратных скобок ([ ]),
сколько размерностей будет иметь этот массив. Следующим кодом, например, создается двухмер-
ный массив, представляющий шахматную доску (восемь клеток в ширину и восемь в длину):
i n t a r . C h e s s B o a r d [ 8 ] [8] ;
Чтобы обратиться к нужной ячейке, опять-таки необходимо использовать столько квад-
ратных скобок, сколько размерностей имеет массив:

//Получение значения из ячейки 3,4

anFull = anChessBoard[3] [4];

/dасе
Самостоятельно создавать массивы — это здорово. Не менее увлекательное занятие — соз-
давать собственные связанные списки (что было продемонстрировано в главе 13). Но если вы не
в духе или просто не хочется напрягаться, среда .NET предлагает вам воспользоваться специ-
ально разработанными классами для представления структур данных. В действительности это
даже лучше, чем создавать массивы и списки самостоятельно. Пускай Microsoft выполнит за вас
,вск> рутинную работу, а также найдет и исправит все возможные стандартные ошибки. Вы же в
это время можете заняться более важными делами и создать ошибки посерьезнее.
г Одна из лучших структур .NET носит название A r r a y L i s t . Она похожа на массив в том
смысле, что к ее элементам можно обращаться непосредственно (т.е. они имеют порядковые
номера), и в то же время подобна списку, поскольку на количество ее элементов нет ограни-
чений (т.е. в любой момент в конец этой структуры может быть добавлен новый элемент).
Долее того, элементы этой структуры могут содержать значения любых типов, для которых
активизирована возможность сборки мусора (garbage collection).

Глава 14. Масса информации? Используйте массивы! 159


"Многомерные" советы
Ниже приведено несколько советов, которые могут вам пригодиться при создании и использовании мно-
гомерных массивов. _•••..

^ При создании многомерного массива точно укажите объем каждой размерности. (Можно ука-
зать объем п - 1 размерности л-мерного массива, но, чтобы уменьшить вероятность возникновения
ошибок, укажите лучше объем для всех л размерностей.)
•/ Компьютеру совершенно все равно, какая именно размерность будет использована для
представления того или иного свойства. Например, если вы используете двухмерный массив для
представления объектов с координатами X и Y, можете использовать первую размерность как коор-
динату X, а вторую как координату Y. А можете наоборот: первую — как У, а вторую — как X. Глав-
ное, сами не запутайтесь,
•/ Индексы каждой размерности должны указываться отдельно, каждый в своей паре квадрат-
ных скобок. Например, [ 1 ] [ 7 ] — это не то же самое, что [ 1 , 7 ] . Индекс [ 1 ] [ 7 ] предостав-
ляет доступ к элементу двухмерного массива, а [ 1 , 7 ] — это то же самое, что и [ 7 ].
v Компилятор не проверяет, выходит ли указанный вами индекс за пределы массива. Так, если
вы объявили массив из 20 элементов и затем обращаетесь к 50-му элементу, компилятор без про-
блем вычислит "его" адрес и вернет то, что там записано. Поэтому сами проверяйте, принадлежат
ли используемые вами индексы к объявленному диапазону.
s Компилятор также не будет "возражать", если вы обратитесь к двухмерному массиву как к
одномерному. Компилятору индексы нужны только для того, чтобы вычислить нужный адрес. Вы
можете использовать этот факт обхода некоторых условностей и для написания более быстродей-
ствующих кодов.

Вот как создается структура A r r a y L i s t :


A r r a y L i s t *poArray = new A r r a y L i s t ( ) ;
А вот как созданная структура заполняется информацией:
давление строки
poArray->Add(5"Строка") ;
//Добавление у к а з а т е л я
pcArray->Acid ( p o F i r s t ) ;
Можно также присвоить элементу индекс, а потом получать доступ к
его значению, ссылаясь на этот и н д е к с :
p o A r r a y - > s e t _ I t e r n (О, S" H e l l o " ) ;
Console : : W r i i o L i n e ( p o A r r a y - > g e t _ I t e r n ( 0 ) ) ;

При назначении индекса или при попытке использовать индекс для получения
значения элемента, убедитесь, что этот индекс не ссылается за пределы созданной
структуры.

Структура A r r a y L i s t обладает некоторыми весьма полезными возможностями. Например,


можно определить, какой из элементов структуры имеет то или иное конкретное значение:
Console::WriteLine(poArray->IndexOi~(S"Значение"));
Единственной сложностью, которая имеет место при использовании структуры
A r r a y L i s t (а также при использовании структуры Stack, с которой вы вскоре познакоми-
тесь}, является переход от одного элемента к другому. Предположим, например, что структу-
ра A r r a y L i s t состоит из пяти элементов и вам нужно просмотреть значения ее элементов,
начиная с первого и далее. Можно создать цикл, который будет использовать для доступа к

7 60 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
значениям элементов метод get_Itera. Или можете использовать более сложный прием, ко-
торый, однако, отлично работает со всеми классами .NET, представляющими структуры дан-
ных, включая те, которые не упоминаются на страницах этой книги. Этот более сложный
прием заключается в создании нумератора (его еще называют перечислителем), название ко-
торого расшифровывается приблизительно как "специальная "штуковина", позволяющая по
порядку, один за другим, просматривать значения элементов структуры".
Вот как создается нумератор:
IEnumerator *poEnumerator ~ p o A r r a y - > G e t E n u m e r a t o r ( ) ;
После того как нумератор создан, можете использовать любой из методов, приведенных в
табл. 14.1. Если, например, вам нужно поочередно отобразить значения всех элементов
структуры A r r a y L i s t , наберите такой код:
IEnumerator *poEnumerator - p o A r r a y - > G e t E n u m e r a t o r ( ) ;
while ( poEnumerator->MoveNext() )
Console::WriteLine( poEnumerator->Current );
Этим кодом вначале создается нумератор, а затем компилятор, переходя от одного эле-
мента к другому, пока не будет достигнут конец структуры, отображает значения этих эле-
ментов. Обратите внимание, что метод MoveNext, выполняющий переход к следующему
элементу, должен быть вызван до отображения значения первого элемента.
Таблица 1 4 . 1 . Методы, используемые при работе с нумератором

Метод Пример Описание

Current poE->Current Возвращает значение элемента, на который указывает


нумератор

MoveNext p o E - > M o v e N e x t () Изменяет значение нумератора с тем, чтобы он указывал на


следующий элемент структуры. Если элементов больше нет,
возвращает логическое значение f a l s e

Reset p o E - > R e s e t () Нумератор возвращается к первому элементу структуры

/Ctacc Stack
Другая замечательная структура .NET, используемая для представления данных, называ-
ется Stack. Принцип ее работы очень простой. Представьте, что вы собираетесь в поход и
вам нужно сложить вещи в рюкзак. Вначале разумнее положить то, что нужно будет доста-
вать в последнюю очередь, затем можно положить какие-то другие вещи, а на самый верх то,
что придется доставать в первую очередь. Когда вы будете вынимать вещи из рюкзака, вы
вначале достанете то, что положили туда последним, потом другие вещи и наконец ту вещь,
которую клали самой первой.
Итак, последний элемент, записанный в Stack, извлекается из него первым. Как и в слу-
чае со структурой A r r a y L i s t , в Stack можно записывать данные любых типов, для кото-
рых активизирована возможность сборки мусора.
Чтобы создать Stack, наберите такой код:
Stack *poStack = new S t a c k ( ) ;
Следующим кодом элемент помещается в Stack:
poStack->Push(3"Какая-нибудь с т р о к а " ) ;

Глава 14. Масса информации? Используйте массивы! 161


Чтобы извлечь элемент из структуры Stack, наберите:
poStack->Pop ( ) ;
Переход от одного элемента к другому осуществляется тем же способом, который исполь-
зовался для просмотра элементов структуры ArrayList:
poEnumerator = poStack->GetEnumerator();
while ( poEnumerator->MoveNext() )
Console::WriteLine( poEnumerator->Current );

типы
Вы только что узнали о том, как можно использовать нумератор для просмотра элемен-
тов, содержащихся в структурах A r r a y L i s t и s t a c k . Но есть еще одна разновидность ну-
мераторов (или перечислителей), которая обозначается термином перечислимый тип. Пере-
числимые типы удобны в тех случаях, когда есть набор элементов, но, вместо того чтобы
идентифицировать их по числовым константам, вы хотите поставить им в соответствие ка-
кие-нибудь легкочитаемые понятные обозначения. Например, вещи в рюкзаке можно обозна-
чить словами Верхняя, средняя и Нижняя. Первый вариант: сказать, что 0— это вещь,
положенная сверху, 1 — вещь, положенная посередине, и 2 — вещь, положенная на самый
низ. Второй вариант: создать перечислимый тип.
Так, например, вместо кода
c o n s t i n t Верхняя = 0;
c o n s t i n t Средняя - 1;
c o n s t i n t Нижняя = 2 ;
вы могли бы н а б р а т ь :
enum {Верхняя, Средняя, Нижняя};
Любое слово, указанное в списке enum (Верхняя, Средняя или Нижняя), может быть
использовано в любом месте программы в качестве константы.

Безопасность при использовании перечислимых типов


Если хотите снизить вероятность возникновения ошибок при написании программы, ука-
жите, что набор элементов, перечисленных в списке enum, является отдельным типом дан-
ных. Таким образом будет исключена возможность случайного использования констант из
одного набора enum (созданного, скажем, для представления названий музыкальных записей)
в тех выражениях, где должны использоваться константы enum из другого набора
(представляющего, например, типы моторных масел).
Можно, например, указать, что константы, обозначающие способ размещение вещей в
рюкзаке, принадлежат типу Размещение:
enum Размещение {Верхняя, Средняя, Нижняя};
Если вы поступите таким образом, компилятор сможет проверять, действительно ли пе-
ременная, для которой определен тип Размещение, принимает только те значения, которые
для этого типа предусмотрены. Продемонстрируем это на небольшом примере:
enum Shapes

Circle,
Square

162 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
//Использование списка констант enum в качестве
//типа данных
Shapes oMyShape;

//Присвоение переменной значения из списка enum


//Обратите внимание, что код oMyShape = Oval будет
//воспринят как синтаксическая ошибка
oMyShape = C i r c l e ;

По возможности старайтесь обозначать списки констант enum как перечислимые


типы данных. Тогда, если вы по ошибке попытаетесь использовать константу пе-
речислимого типа для обозначения информации не того вида, компилятор выдаст
предупреждение. Программа из-за этого работать не перестанет, но предупреж-
дающее сообщение поможет вам определить источник возникновения этой ошиб-
ки и вовремя ее устранить.

Одно маленькое "но"


Перед тем как создать целый букет перечислимых типов, обратите внимание на
тот факт, что команда c i n может считывать значения только стандартных
(предустановленных) типов данных. Если вы попытаетесь использовать команду
c i n для получения от пользователя значения, указанного в списке enum, компи-
лятор выдаст сообшение об ошибке.
Например, приведенный ниже код вызовет сообщение об ошибке, суть которого будет
приблизительно в том, что оператор не может определить, как правильно обрабатывать зна-
чения типа enum Размещение.
//Создание перечислимого типа
enum Размещение {Верхняя, Средняя, Нижняя};
//Переменная foo типа Размещение
Размещение foo;
//Пользователь должен у к а з а т ь значение для foo
cin » foo;

Глава 14. Масса информации? Используйте массивы! 163


Глава 15
Пришел, увидел, применил
В э&ой главе...
> Что такое область видимости
> Глобальные и локальные переменные
У Почему в программе разные переменные могут иметь одинаковые имена

анные в программе сохраняются как значения переменных. Разные переменные


используются в разных местах программы. В этой главе вы познакомитесь с та-
ким понятием, как область видимости переменных, которая определяется набором правил,
устанавливающим, к каким переменным имеет доступ каждая отдельная часть программы.

Немного tneofiuu
Программы становятся все больше и больше, и вместе с этим возрастает количество ис-
пользуемых ими функций и переменных. Большинство из этих переменных используются
только при решении какой-то одной небольшой задачи. Например, многие переменные ис-
пользуются только для отсчета количества итераций при выполнении циклов или для вре-
менного хранения значения, которое ввел пользователь. К счастью, не нужно каждой из таких
переменных присваивать какое-то уникальное имя. Ведь в противном случае вам пришлось
бы придумывать миллионы отличающихся друг от друга имен.
Итак, разные переменные могут быть названы одинаковыми именами. До тех пор пока та-
кие переменные используются разными функциями, они между собой не конфликтуют. Вы
можете, например, определить переменную к для функции foo. А потом определить пере-
менную к для функции baz. Хотя названия одинаковые, это разные переменные: одна ис-
пользуется только функцией foo, а вторая — только функцией baz.
Эти переменные разные, потому что имеют разную область видимости. Область видимо-
сти переменных— это место в программе, где эти переменные могут быть использованы.
Например, областью видимости переменной к, определенной внутри функции foo, является
функция foo. Это значит, что данная переменная может быть использована только в преде-
лах функции foo, а вне ее эта переменная не определена.
Все переменные могут быть разделены на две категории: глобальные и локальные. Гло-
бальные переменные— это те, доступ к которым имеет любая функция программы
(включая n a i n ) . Они определяются за пределами какой-либо функции. Такие переменные
нужны для тех случаев, когда некоторое значение должно быть доступным независимо от
того, какая из функций в данный момент выполняется. Имена всех глобальных переменных
должны быть разными.
Локальные переменные — это временные переменные, которые используются какой-
то одной функцией. Локальная переменная создается тогда, когда функция начинает ра-
ботать, используется с процессе выполнения функции, и уничтожается, как только

Глава 15. Пришел, увидел, применил 165


функция прекращает работу. Можно использовать только одну переменную с тем же
именем внутри одной функции, однако этим же именем можно обозначать переменную,
используемую в другой функции. (Изменение значения одной из этих переменных никак
не отразится на значении другой переменной.)
На самом деле все несколько сложнее. Переменная видима в пределах пары фи-
гурных скобок ({}). внутри которых она была определена. Так, если у вас есть
переменная nBob, объявленная внутри функции F i r s t , и функция F i r s t вызы-
вает функцию Second, переменная nBob не будет доступна для функции
Second (если только вы не передадите этой функции переменную nBob в каче-
стве аргумента). Точно так же, если вы определите переменную fYou внутри
блока оператора if, например:
i f (nBob > 10) {
b o o l fYou = t r u e ;
}
В этом случае переменная fYou будет видима только внутри данного блока.
Это очень удобно, и вот почему. Предположим, например, что у вас есть функция, ото-
бражающая числа от одного до десяти (пусть она называется CountUp). Чтобы сделать
это, в ней используется цикл. В самом цикле используется переменная i для отсчета коли-
чества итераций. Если у вас есть другая функция, отображающая числа в обратном поряд-
ке — от десяти до одного (назовем ее CountDown), она также может использовать какую-
то переменную для отсчета количества итераций. Поскольку обе переменные используются
только в пределах своего цикла, вторую также можно назвать i. Это означает, что вам не
нужно придумывать уникальные имена для переменных при написании каждого нового
цикла. Если вы создаете большую программу, состоящую из огромного количества функ-
ций, часть из которых написана другими программистами, вам не придется кричать на всю
комнату: "Эй! Кто-нибудь уже использовал для переменной цикла название
sdbsdbsdbsdb3?"
Каждый раз, когда вы определяете переменную внутри какой-то функции, область ее
видимости будет ограничена пределами этой функции (т.е. она будет локальна). Вы можете
использовать ее внутри этой функции, а также передавать в качестве аргумента другим
подпрограммам, которые этой функцией вызываются. Как только функция заканчивает ра-
боту, локальная переменная прекращает свое существование. Использование переменных,
которые передаются функциям в качестве аргументов, также ограничено пределами этих
функций.

Лочемц ЭЙГО tfLcuc €ажко


На первый взгляд область видимости переменных может показаться вопросом второ-
степенным. Действительно, при написании программ многие программисты об этом почти
не задумываются. Однако для тех, кто только начинает программировать, вопросы, связан-
ные с областью видимости, могут стать источником многих проблем. Поняв принципы оп-
ределения области видимости, вы сможете избежать некоторых трудных для обнаружения
логических ошибок,
Рассмотрим небольшой пример.

166 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
i n t x ;

v o i d Z e r o l t ( i n t x )

x = C ;

i n t m a i n ( v o i d )

X = 1 ;

Z e r o l t ( x ) ;

Console::WriteLine(x);
}
Что произойдет при выполнении этой программы? Вначале глобальной переменной х бу-
дет присвоено значение 1. Затем будет вызвана функция Z e r o l t , которая присвоит этой пе-
ременной значение 0 (нуль). Как вы думаете, какое число будет отображено при выполнении
инструкции Console : : WriteLine? Отображено будет число 1.
Почему именно 1? Переменная х внутри функции Z e r o l t является локальной по отно-
шению к ней (ее область видимости ограничена пределами этой функции). Поэтому она соз-
дается, когда какая-то другая функция вызывает функцию Z e r o l t . Этой переменной при-
сваивается значение, которое передается функции в качестве аргумента (в данном случае это
значение 1). Затем функция Z e r o l t присваивает переменной х значение 0. Когда функция
заканчивает работу, переменная х (имеющая значение 0 и являющаяся локальной по отноше-
нию к этой функции) уничтожается. Безвозвратно. Теперь компилятор возвращается к функ-
ции main и переходит к инструкции C o n s o l e : : WriteLine, которая отображает значение
переменной х на экране. Но это уже другая переменная х — та, которая имеет глобальную
область видимости. (Как вы помните, переменные, объявленные за пределами любой функ-
ции, являются глобальными.)
Точно такой же результат будет получен при выполнении кода, который приведен
ниже. Как и в предыдущем примере, изменение значения переменной х в процессе вы-
полнения функции Z e r o l t никак не отобразится на значении глобальной переменной х,
которая используется функцией main. Причиной тому является локальная область ви-
димости переменной х, которая используется внутри функции Z e r o l t . Следовательно,
эта переменная вместе со своим значением исчезает, как только Z e r o l t заканчивает
работу.
i n t x;

void Ze rolt
int X
x = 0

int main(void)
{
у — 1 •

Z e r o l t ( x ) ;

C o n s o l e : : W r i t e L i n e ( x ) ;

Глава 15. Пришел, увидел, применил 167


Совершенно другую картину мы наблюдаем в следующем примере:
int x;

void Z e r o l t ( i n t у)
{
у = 7;
х = 0;
}
i n t main(void)
{
х = 1;
Zerolt{x) ;
Console::WriteLine(x) ;
)
Если будет выполнен этот код, на экране отобразится число 0 (нуль).
Почему теперь будет получен О? В данном случае аргумент функции Z e r o l t обозна-
чен именем у. Если вы передаете этой функции в качестве аргумента значение перемен-
ной х (глобальной переменной х, которая в данном примере своей локальной тезки не
имеет), оно присваивается переменной у. Затем переменной у присваивается значение 7,
а переменной х — значение 0. Поскольку для функции Z e r o l t локальной переменной х
не создавалось, это значение присваивается глобальной переменной х. Когда функция
Z e r o l t заканчивает работу, переменная у уничтожается. Поскольку х не является ло-
кальной по отношению к функции Z e r o l t , она никуда не пропадает и сохраняет свое
измененное значение.
Вообше-то последний приведенный фрагмент кода служит примером плохого стиля про-
граммирования. Поскольку функция Z e r o l t изменяет значение переменной, которое не бы-
ло передано ей как аргумент, вы не узнаете, что функция изменила это значение, если не бу-
дете просматривать все ее коды. Как следствие, вполне вероятно, что написанная вами про-
грамма не будет работать так, как вы ожидаете. В общем случае не стоит изменять значения
тех переменных, указатели или ссылки на которые не были переданы функции в качестве ар-
гументов. Функция свободно может изменять значения только тех переменных, которые яв-
ляются для нее локальными.

Хорошие привычки
Вот несколько советов, которые сделают ваши программы удобными для чтения и понимания и избавят
вас от ошибок, связанных с неправильным пониманием области видимости.
s Создавая функцию, передавайте ей в качестве аргументов асе данные, необходимые для ее выполнения.
* Избегайте использования глобальных переменных. Создавайте их только для представления посто-
янных значений.
s Не изменяйте какие-либо значения за пределами видимости. Другими словами, не изменяйте зна-
чения переменных там, где это не будет для вас очевидно. Если функции будут изменять значения
только локальных переменных и тех переменных, ссылки на которые им переданы в качестве аргу-
ментов, понять, что к чему в программе, будет не так уж сложно.

168 Часть И. Все, что вы хотели знать о C++, но о чем боялись спросить
Т^гавилаonfieqeuenu&ocfuactnu
виуимоапи
К счастью, в C++ правила определения области видимости переменных довольно просты
и могут быть легко перечислены,
I •/ Область видимости переменных, определенных внутри функции, ограничена
8 пределами этой функции. Если вы определяете переменную внутри функции, она
создается, когда эта функция вызывается, используется в процессе выполнения
функции и уничтожается, когда функция заканчивает работу.
S Область видимости переменных, объявленных внутри блока, ограничена
пределами этого блока. Переменные, объявленные внутри какого-то блока
(например, блока оператора if, выделенного парой фигурных скобок), видимы
только в пределах этого блока.
•/ Любые аргументы, передаваемые функции, видимы только в пределах этой
функции. Например, если вы определяете, что функция использует параметр
ARose, область его видимости будет ограничена пределами этой функции (точно
так же, если бы вы создали переменную ARose внутри этой функции). Имя того
элемента, значение которого вы передаете функции в качестве аргумента, роли не
играет. Значение имеет только имя самого аргумента.
•S Когда выполняется функция, переменная, локальная для этой функции, ис-
пользуется вместо глобальной переменной с тем же именем. Предположим, на-
пример, что внутри функции f оо вы объявили переменную ARose типа integer, но
глобальная переменная типа double также имеет имя ARose. При выполнении
функции foo использоваться будет именно та переменная ARose, которая имеет
тип integer и является локальной по отношению к этой функции. Чтобы получить
доступ к глобальной переменной ARose, перед ее именем нужно будет набрать два
двоеточия (: :). Таким образом, локальная переменная ARose — это совсем не то
же самое, что глобальная переменная ARose. Это две разные переменные, которые
имеют одинаковые имена, но используются в разных частях программы.
S Изменение значения локальной переменной никак не влияет на значения пе-
ременных с тем же именем, но с другой областью видимости. Если вы, напри-
мер, измените значение локальной переменной ARose, то это никак не отразится
на значении глобальной переменной с тем же именем.
•/ Если внутри функции используется переменная, которая не является локаль-
ной для этой функции, компилятор пытается найти глобальную переменную
с тем же именем. Предположим, например, что вы используете переменную
ARose при написании кодов функции foo. Однако ни среди аргументов функции
foo, ни среди локальных переменных, объявленных внутри этой функции, имя
ARose не встречается. Что в этом случае будет делать компилятор? Он попытается
найти глобальную переменную с таким именем. Если среди глобальных перемен-
ных также не окажется переменной с именем ARose, компилятор выдаст сообще-
ние об ошибке.

Глава 15. Пришел, увидел, применил 169


Если необходимо получить доступ к глобальной переменной во время выпол-
нения функции, в которой используется локальная переменная с тем же име-
нем, наберите перед именем этой переменной два двоеточия (: :). Если, напри-
мер, внутри функции объявлена локальная переменная ARose и в этой же про-
грамме используется глобальная переменная, также названная именем ARose, код
: : ARose будет указывать на глобальную переменную, a ARose — на локальную.

170 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 16
Через тернии к... работающей
программе
гла£е...

> Отладчик программ и его возможности


> Процесс отладки программ
> Точки останова
> Пошаговое выполнение программ
> Просмотр текущих значений
> Средство QuickWatching
> Пример отладки и корректирования программы

редставьте, что вы блуждаете по лабиринту и не можете найти выхода. Заряд бата-


реи заканчивается, и ваш фонарик вот-вот погаснет. Повсюду слышится непонятное
"клик, клик, клик...". Вы опускаете глаза и видите, что по колено стоите в кишащей массе ог-
ромных тараканов!
Это может выглядеть как кошмарный сон, но примерно так чувствует себя программист, ко-
торый всю ночь не отрывался от компьютера и чем больше писал кодов, тем больше появлялось
ошибок— этих мерзких и противных программных тараканов. И в результате, когда на сле-
дующее утро он попытался запустить написанную программу... кошмар! Ничего не работает.
Есть четыре варианта дальнейших действий.
I •/ Можно вообще отказаться от идеи написания программы. (Это путь неудачников.)
| •/ Можно попробовать начать все сначала. (Это плохая идея.)
IS Можно к каждой строке кодов добавить команду p r i n t , чтобы получать от про-
\ граммы отчет о каждом выполняемом ею шаге и таким образом проследить, что
% именно работает не так, как нужно. (Ход мыслей правильный, но выполнить это на
I практике будет очень сложно.)
| S Можно воспользоваться отладчиком программ. (Это самый лучший вариант.)

Отладчик — это специальный инструмент, который позволяет выполнять программы


пошагово, строка за строкой. Благодаря этому вы сможете проследить, как в действительно-
сти будет выполняться написанная вами программа, определить, что именно работает не так,
как было задумано, и затем исправить допущенные ошибки.
В этой главе речь пойдет об использовании для корректировки программ отладчика Visual
C++ .NET.

Сшипсиссичеасие и логические ошибки


Как отмечалось в главе 6, синтаксические ошибки случаются тогда, когда компилятор не
может "понять" написанный вами код. Например, если вы в слове вместо одной буквы набра-
ли другую, пропустили часть команды либо неправильно использовали какую-то переменную
или класс, все это будет воспринято как синтаксические ошибки. До тех пор пока в програм-

Глава 16. Черезтерниик... работающей программе 171


ме есть хоть одна синтаксическая ошибка, она не может быть выполнена, поскольку компи-
лятор не сможет ее откомпилировать.
Кроме синтаксических, есть еще логические ошибки (программисты называют их bugs —
жучки). Программа может быть написана на безупречном C++, но выполнять совершенно
бессмысленные действия. Или она может работать, но несколько не так либо совсем не так,
как было задумано. В таком случае в программе есть логические ошибки.
Рассмотрим, например, инструкцию по приготовлению печеной картошки.
1. Взять картошку.
2. Завернуть ее в фольгу.
3. Положить в микроволновую печь на три часа.

Инструкции просты и без труда могут быть выполнены кем угодно. Синтаксических ошибок
здесь нет. Но с другой стороны, если эти инструкции выполнить, микроволновая печь просто сго-
рит (черт с ней, с картошкой). Наверное, это не тот результат, который нужно было получить.
Чтобы решить такого рода проблему, нужно проанализировать все предпринимаемые ша-
ги и определить, какой из них приводит к плачевному результату. В этом и заключается про-
цесс отладки: строка за строкой просмотреть проблемный фрагмент кода и определить, что
именно работает неправильно.
Если программа довольно большая, устранить все логические ошибки очень сложно.
Причина в том, что некоторые ошибки могут проявляться только при стечении целого ряда
обстоятельств. Множество нестандартных ситуаций возникает тогда, когда программа попа-
дает в руки пользователей. Если с программой будет работать много людей, все логические
ошибки рано или поздно будут обнаружены. Пользователи, конечно же, сообщат вам об этом,
и вы сможете эти ошибки исправить.

Jlfto^ecc OfnuaqKU п/гог/гаммы


В основном процесс отладки сводится к определению в коде программы того места, где
кроется ошибка. Цель состоит в том, чтобы определить, в каком фрагменте кода может со-
держаться та или иная ошибка, и затем проверить, как выполняется именно этот фрагмент.
Если, например, вы точно знаете, что ошибка скрывается где-то в кодах определенной проце-
дуры, вы можете дать указание, чтобы выполнение программы останавливалось каждый раз.
когда будет вызываться эта процедура. Затем можно пошагово проследить за выполнением
кодов этой процедуры, чтобы точно знать, что при этом происходит. Если вы увидите, что
выполнение какой-то инструкции не приводит к ожидаемому результату, измените эту инст-
рукцию, заново откомпилируйте программу и попробуйте запустить ее снова.
В процессе отладки программы можно использовать несколько полезных инструментов.
'•} S Breakpoints (Точки останова). Дает указание отладчику остановить выполнение
Ь программы тогда, когда будет достигнута определенная строка кодов. Этот инст-
| румент удобен в том случае, если у вас большая программа, а вы хотите просле-
I дить за выполнением только какого-то отдельного фрагмента кода. Поставьте точ-
I ку останова перед этим фрагментом и запустите программу. Отладчик выполнит
> все инструкции, набранные до точки останова, и остановится, чтобы дать вам воз-
[ можность далее выполнять программу пошагово.
|; S Step Into (Пошаговое выполнение) и Step Over (Пошаговое выполнение с пе-
| ре шаги вал нем через процедуры). Выполнение кодов программы по одной строке
| за раз. Используйте эти средства для контроля за выполнением каждой отдельной
| инструкции. Если вы будете внимательны, то обязательно обнаружите искомую
| ошибку (если она там есть).

172 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Watches (Просмотр). Отображение текущих значений переменных в процессе вы-
полнения программы. Это средство поможет вам проследить за жизненным цик-
лом интересующих вас переменных и увидеть, как изменяются их значения после
выполнения тех или иных инструкций. Это же средство позволяет просматривать
значения выражений, благодаря чему вы можете видеть, как они изменяются в ре-
зультате изменения значений переменных, входящих в эти выражения.

O/nuaqtUfc плюсfieqatanofi —
и €се в наших [гс/ках
Окно редактора кодов является также и окном отладчика. Другими словами, если вам
нужно совершить такие действия по отладке программы, как, например, определение точки
останова или просмотр значений переменных, сделать вы это можете прямо в окне редактора.
Процессы редактирования и отладки программы тесно связаны между собой. Иначе и
быть не может, поскольку при отладке программы необходимо просматривать исходные ко-
ды, чтобы видеть, выполнение каких из них приводит к тем или иным результатам. Так как
Visual C++ объединяет процессы редактирования и отладки, при отладке можно использовать
любые возможности редактора кодов (такие, как прокрутка окна, разделение окна или поиск
нужного слова). Вы можете даже при отладке сразу же вносить изменения в исходные коды.
Но не вся информация отображается непосредственно в окне редактора. Некоторые дей-
ствия, связанные с отладкой программ, сопровождаются открытием специальных окон. На-
пример, если вы захотите просмотреть значения переменных, для этого откроется отдельное
окно Watch. (Более подробно это описывается несколько ниже в главе.)

Оанановись, мгновение!
Точки останова позволяют остановить выполнение программы в нужном месте. Предпо-
ложим, например, что у вас есть подпрограмма, вычисляющая факториал числа. По какой-то
причине возвращаемый ею результат всегда равен нулю. Чтобы определить, в чем заключает-
ся ошибка, можно установить точку останова в начале этой подпрограммы. Затем, когда вы
запустите программу, ее выполнение будет остановлено в момент вызова процедуры, вычис-
ляющей факториал, и вы сможете использовать все средства отладчика для определения, что
именно в этой процедуре работает не так. Забегая вперед, скажем, что весь материал, изло-
женный далее в главе, посвящен решению подобных проблем.
Установить точку останова довольно просто. В окне редактора кодов щелкните правой
кнопкой мыши в том месте, где нужно установить точку останова, и в открывшемся меню
выберите команду Insert Breakpoint (Установить точку останова). Слева от выбранной стро-
ки появится значок точки останова. Ну хорошо, этим значком будет красный кружок, а не
ромбик или что-то еще, но главное, чтобы вы поняли общую идею,

Если вы установили точку останова и не видите слева от строки соответствующий


значок, значит, у вас, скорее всего, отключена возможность отображения допол-
нительной информации на границе строк. Выберите команду Tools ^ O p t i o n s
(Сервис^Параметры), чтобы открыть диалоговое окно Options (Параметры). В
категории Text Editor/General (Редактор/Общие) выберите General (Общие) и
затем опцию Selection Margin (Выделение на границе). Щелкните на кнопке ОК,
и знак точки останова появится напротив выбранной строки.

Глава 16. Через тернии к... работающей программе 173


Удаляется точка останова так же просто, как и устанавливается. В окне редактора кодов
щелкните правой кнопкой мыши на строке, для которой установлена точка останова, и в от-
крывшемся меню выберите команду Remove Breakpoint (Удалить точку останова). Распо-
ложенный напротив этой строки значок точки останова исчезнет с экрана.

Точки останова не исчезают после перезагрузки системы. Другими словами, если вы


установили точку останова и не удаляли ее, а позже вновь открыли этот же проект,
созданная точка останова по-прежнему будет на своем месте. Это очень удобно, по-
скольку процесс отладки программы может затянуться на какое-то время и вы мо-
жете прерывать его на то, чтобы, например, поспать, перекусить или побродить по
Internet. Затем, когда вы снова вернетесь к отладке программы, вам не придется за-
ново восстанавливать всю картину и расставлять прежние точки останова. Однако с
другой стороны, если программа уже будет работать так, как нужно, но вы забудете
убрать какие-то точки останова, то она потом непременно выдаст вам парочку
"сюрпризов". (Выполнение программы будет прерываться каждый раз, когда в ко-
дах будут встречаться точки останова.) Если вы не уверены, все ли точки останова
удалены, выберите команду Debug^Windows^Breakpoints (Отладка^Окна 1 ^
Точки останова). В результате откроется окно, в котором будут показаны все суще-
ствующие на данный момент точки останова.

Шаг за шагом: движение к и,ели


После того как точка останова достигнута, самое время проследить, как будет выполнять-
ся каждая последующая инструкция. Сделать это можно, выполняя по одной строке кода за
раз. Такие действия называются пошаговым выполнением программы.
Есть два варианта: использовать команду Step Into (Пошаговое выполнение) либо коман-
ду Step Over (Пошаговое выполнение с перешагиванием через процедуры). Если в процессе
использования команды Step Into программа вызывает какую-то функцию, все ее коды также
выполняются пошагово. Если программа вызывает функцию в процессе использования ко-
манды Step Over, все коды функции выполняются сразу же, без остановки на каждом шаге.
Команду Step Into следует использовать в том случае, если какая-то часть программы ра-
ботает не так, как нужно, но вы не уверены, содержится ли ошибка в этом фрагменте кода
или в той функции, которая при этом вызывается. Команда Step Over используется в том
случае, если проверяемые коды должны выполняться пошагово, а вызываемые при этом
функции — как единое целое.
Рассмотрим это на таком примере:
foo = MyRoot(x);
foo = foo + 1;
Если вы точно знаете, что функция MyRoot работает правильно, и хотите только прове-
рить, как будет выполнена строка f o o + l , можете использовать команду Step Over. Первая
строка будет выполнена за один шаг (включая все инструкции, из которых состоит функция
MyRoot), и затем на втором шаге будет выполнена строка f o o = f o o + l .
Если вы сомневаетесь в правильности выполнения функции MyRoot, используйте коман-
ду Step into. При этом, выполняя первую строку, вы перейдете к первой строке функции
MyRoot. Затем пошагово, одна за другой, будут выполняться все строки этой функции, и ес-
ли вам повезет, вы найдете там свою ошибку.
В процессе пошагового выполнения программы слева, напротив строки, которая должна
выполниться на следующем шаге, будет отображаться желтая стрелка. Если для этой строки
определена точка останова, стрелка будет отображаться сверху над красным кружком.

174 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Если при пошаговом выполнении программы вы не видите желтой стрелки, зна-
чит, у вас, скорее всего, отключена возможность отображения дополнительной
1
информации на границе строк. Выберите команду Tools ^ Options, чтобы от-
крыть диалоговое окно Options. В категории Text Editor/General выберите Gen-
eral и затем опцию Selection Margin. Щелкните на кнопке ОК, и желтая стрелка
появится на экране.
Для пошагового выполнения с перешагиванием через процедуры нажимайте клавишу
<F10> или щелкайте на кнопке Step Over панели Debug (Отладка), показанной на рис. 16.1.
Если вызываемые функции также должны выполняться пошагово, нажимайте клавишу <F11>
или щелкайте на кнопке Step Into.

Рис. 16.1. На панели Debug расположены


кнопки, управляющие процессом выполне-
ния программы

Обратите внимание, что команды Step Into и Step Over могут быть использованы без
предварительного определения точек останова. Если вы сделаете это, пошаговое выполнение
будет начато с самой первой строки программы.

JToC4iO/ft/tUM, t/ИО ПОЛЕЧИЛОСЬ


Занимаясь отладкой программы, очень важно знать, правильный ли результат возвращает
формула, какое значение имеет переменная или что сохранено в качестве элемента массива.
Visual C++ предлагает несколько способов просмотра значений переменных и получаемых
при обработке выражений результатов.
Окно Autos (Автоматическое) открывается автоматически, когда вы приступаете к отлад-
ке программы (рис. 16.2). В нем отображаются названия и значения переменных, которые ис-
пользуются в текущем и предыдущем выражениях. Лично вам не нужно указывать имена пе-
ременных — они сами и их значения отображаются автоматически, по мере того как вы пе-
реходите от одной строки программы к другой.

РИС. 16.2. В окне Autos отображаются имена и значения переменных, используе-


мых в предыдущем и текущем выражениях

Также при отладке программы автоматически отображается окно Locals (Локальные), по-
казанное на рис. 16.3. Как и в окне Autos, в нем автоматически отображаются названия пере-
менных и их значения. Но здесь уже отображаются все локальные переменные той функции,
коды которой выполняются в данный момент (т.е. переменные, объявленные внутри этой
функции).

Глава 16. Через тернии к... работающей программе 175


Рис. 16.3. В окне Locals отображаются имена и значения всех локальных переменных

Окна Autos и Locals предоставляют полезную информацию, но, поскольку они сами оп-
ределяют, значения каких переменных должны отображаться, иногда они не очень удобны.
Но в этом нет ничего страшного, так как в Visual C++ есть другие средства для контроля за
значениями переменных. В окне Watch (Просмотр) вы можете сами указать, какие именно
значения вас интересуют. Всего есть четыре окна Watch, пронумерованных от Watchi до
Watch4, что позволяет выделить несколько наборов переменных и выражений, значения ко-
торых вы хотите проконтролировать.
Чтобы просмотреть значение какого-либо объекта, используя окно Watch, выполните
следующее.
1. В окне Watch щелкните на пустой строке в колонке Name (Имя).
2. Наберите имя объекта, значение которого вы хотите видеть.
3. Нажмите клавишу <Enter>.

Каждый раз, когда значение объекта изменяется, информация в окне Watch обновля-
ется. Например, на рис. 16.4 показано окно Watch со значениями трех числовых пере-
менных. Как только значение переменной nNumber, n R e s u l t или i изменится, окно
Watch будет обновлено.

Watch 1
Name Value j Type
nNumber 5 int32
nResult 0 :: int32
' . i" ' •' 2 '. . ' ' -* '_W32

QAute'CKc: Ь ££3 Watch 1


Рис. 16.4. Окно Watch позволяет видеть, как изменяются значения интересующих
вас переменных в процессе выполнения программы

Не нравится значение — измените его


Если это необходимо, вы можете даже сами изменять значения просматриваемых пере-
менных. Для этого выполните следующее.
1. Выделите переменную, значение которой вы хотите изменить.
2. Выделите значение этой переменной.
3. Наберите новое значение.
4. Нажмите клавишу <Enter>.

176 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Эта возможность может вам пригодиться для того, чтобы проверить, правильно ли будет
работать вся остальная программа, если переменная будет иметь корректное значение. Когда
вы это проверите, вам нужно будет вернуться назад, чтобы определить, почему эта перемен-
ная принимает неверное значение.

Торопитесь? Нет проблем!


Visual C++ позволяет просматривать значения переменных и выражений без необходимо-
сти добавления их в окно Watch, используя для этого средство QuickWatch.
Чтобы использовать QuickWatCh для просмотра значения какого-либо объекта, выполните в
окне редактора следующие действия: щелкните правой кнопкой мыши на названии интересую-
щего вас элемента и выберите в открывшемся меню команду QuickWatch. Откроется диалого-
вое окно QuickWatch (рис. 16.5). Затем, если вы захотите перенести выбранный объект из окна
QuickWatch в окно Watch, просто щелкните на кнопке Add Watch (Добавить в окно Watch).

I Пяти Value
nNunbw 5

Рис. 16.5. Значения переменных можно про-


сматривать, не добавляя их в окно Watch

Очень торопитесь?
Средства Watch и QuickWatch удобны, но все же, чтобы их использовать, вам придется
несколько раз щелкнуть кнопкой мыши. Представим, что вы этого делать не хотите. Тогда
Visual C++ может предложить другое средство, которое называется DataTips (Подсказка).
Оно позволяет увидеть значение переменной без единого щелчка кнопкой мыши.
Чтобы просмотреть значение переменной, используя средство DataTips, выполните сле-
дующее.
1. В окне редактора поместите курсор над той переменной, значение которой вы
хотите увидеть.
2. Подождите полсекунды.
Рядом с курсором появится подсказка, в которой будет показано текущее значение этой
переменной (рис. 16.6).

Глава 16. Через тернии к... работающей программе 177


5 ?.i - Риде BadFact.cpp i I X
j(Globals) ••j •wmain J
rtefse —-

intmain(void)
- — •

#endif
intnNumber;
///types
/Getnu0mbersfromtheuser,untiltheuser
о while(fN i umber=GetNumberQ)
i -f //N n
(N
ouw
mbew
re
=wi5|l outputtheresult
//Notethatwearecallingthefunction
//Factorial
Console:WrtieLn
i e(S"Thefactorialof{0}is{1}",nNu
//Nowwearefinished
//Hangoutuntiltheusethe
Console::Wntel_ine(L"Hit risenter
finished
key to stop the ргодгг
Console::ReadLine(); - —

return0; • Г
Рис. 16.6. DataTips показывает значения переменных без каких-либо усилий
с вашей стороны

Тене/ьальналi/<?oft/ca
Хотите посмотреть, что такое отладка программы на практике? Далее в этой главе будут
продемонстрированы различные приемы, позволяющие проверить правильность выполнения
программы, установить наличие ошибок и затем устранить их.

Итак, приступим
Начнем с того, что создадим новую управляемую программу C++. Код этой программы
показан ниже. Как и одна из рассмотренных ранее программ, эта программа отображает на
экране факториал указанного пользователем числа.
#include " s t d a f x . h "
#using <mscorlib.dll>
us.ir.g manespace System;
//Функция возвращает факториал числа
int F a c t o r i a l ( i n t nNumber)
{
int nResult = n N u m b e r ;
int i; //Переменная для отсчета итераций
//Цикл, на каждой итерации которого общий результат

178 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросит
//умножается на значение переменной i
for (i=0; i<=nNumber;
{
r.Result *= i;
}
//Возвращение результата
return nResult;

//Функция проект пользователя набрать число,


//а затем возвращает это число как результат
int GetNumber()
{
int nNumber,•
Console::WriteLine(3"Введите число");
nNumber = Int32::Parse(Console::ReadLine()
return nNumber;

//С этой точки начинается выполнение программы


#ifdef _UNICODE
int main (void)
#else
int main(void)
#endif
{
int nNumber;
//Получение чисел от пользователя до тех пор,
//пока он не наберет число О
while (nNumber = GetNumber())
{
//Отображение значения факториала для указанного
//числа. При этом используется значение,
//возвращаемое функцией Factorial
Console::WriteLine(Б"Факториал числа {0} равен {!)",
nNumber.ToString(),
Factorial(nNumber).ToString()

//Ожидание, пока пользователь не остановит


//выполнение программы
Console::WriteLine(Б"Нажмите клавишу Enter, чтобы
остановить выполнение программы");
Console::ReadLine() ;
return 0;
}
Если процесс создания управляемых проектов на C++ вызывает у вас затруднения, верни-
тесь к главе 3.
А теперь попытайтесь несколько раз подряд выполнить эту программу.
1. Щелкните на кнопке Start или выберите команду Debug<=>Start (Отладка^
Начать).
2. Наберите число и нажмите клавишу <Enter>.
3. Посмотрите на полученный результат.

Глава 16. Через тернии к... работающей программе 179


4. Нажмите любую клавишу, чтобы закрыть программу.
5. Повторите этот процесс, начиная с шага 1.
Вы увидите, что какое бы число вы не набирали, программа всегда будет возвращать ну-
левой результат. Но ведь это же неправильно!

Так где же ошибка?


Попытаемся определить, из-за чего программа не работает (точнее, работает, но не так, как
нужно). В кодах для получения числа от пользователя вроде бы нет ничего подозрительного;
Console::WriteLine(3"Введите число");
nNumber = I n t 3 2 : : P a r s e ( C o n s o l e : :ReadLine() ) ;
r e t u r n nNumber;
Выполнять эти инструкции пошагово, по-видимому, нет необходимости.
Вместо э т о г о сосредоточим внимание на работе функции F a c t o r i a l .
Почему именно эта функция? Потому что это единственная функция в программе, выпол-
няющая действия, понимание которых может вызвать у вас затруднения. Отладку более слож-
ных программ обычно производят небольшими частями — по одной функции или объекту за
раз. Если вы убедитесь, что отдельные небольшие части программы работают правильно, зна-
чит, коды, в которых эти части объединяются, также будут работать правильно. Таким образом,
вы всегда можете сосредоточиться на небольших, легких для понимания фрагментах програм-
мы. Иногда этот метод называют структурной проверкой (structured testing).
Сначала можно установить точку останова перед той строкой, где вызывается эта функ-
ция. Для этого выполните следующее.
1. Прокрутите окно редактора, для того чтобы увидеть коды функции
Factorial.
Коды этой функции набраны в самом начале программы.
2. Щелкните правой кнопкой мыши на строке i n t F a c t o r i a l ( i n t nNumber) и
выберите команду Insert Breakpoint.
Напротив этой строки отобразится символ точки останова подобно тому, как показа-
но на рис. 16.7.
Теперь запустите программу.
1. Щелкните на кнопке Start.
Программа начнет работать, и на экране появится надпись, предлагающая ввести
число.
2. Наберите число.
Далее программа должна вызвать функцию F a c t o r i a l . Это значит, что точка оста-
нова будет достигнута, в результате чего откроется окно отладчика. Напротив стро-
ки, которая должна будет выполнятся следующей, вы увидите желтую стрелку
(рис. 16.8).
Теперь можно приступить к пошаговому выполнению функции F a c t o r i a l и постарать-
ся определить, где именно прячется ошибка. Поскольку точно известно, что функция возвра-
щает неправильный результат, а сам вычисляемый результат сохраняется как значение пере-
менной n R e s u l t , вам нужно проследить, как значения этой переменной изменяются в про-
цессе выполнения функции. Таким образом, вы рано или поздно поймете, почему функция
каждый раз возвращает нулевое значение. Для просмотра значения переменной можно ис-
пользовать окно Locals или подсказку DataTip. Либо можно воспользоваться окном Watch,
для чего выполните следующее.

180 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
1. В окне Watch щелкните на пустой строке в колонке Name.
2. Наберите n R e s u l t .
3. Нажмите клавишу <Enter>.
Можно также воспользоваться средством QuickWatch.
1. В окне редактора щелкните правой кнопкой мыши на переменной n R e s u l t и в
открывшемся меню выберите команду QuickWatch.
2. В окне QuickWatch щелкните на кнопке Add Watch.
После этого нижняя часть экрана будет выглядеть приблизительно так, как показано
на рис. 16.9.
3. Проследите за выполнением этой функции, щелкнув несколько раз на кнопке
Step Over.
Информация о значении переменной n R e s u l t , отображаемая в окне Watch, будет
обновляться каждый раз при изменении этого значения.
В результате вы увидите, что сразу же после выполнения инструкции n R e s u l t *= i
значение переменной n R e s u l t станет нулевым.
Более того, по мере выполнения программы это значение никогда не будет изменять-
ся. Как вы узнаете, что значение переменной n R e s u l t равно нулю? Очень просто: для
этого существует окно Watch (или Locals, или QuickWatch, или подсказка DataTip}.
Если вы используете окно Watch, то оно постоянно будет выглядеть так, как показано
на рис. 16.10.

BadFactcpp
•(Globals)
int w m a l n ( v o i d )

int maln(void)
#endif
int nNumber;
/'/Get (lumbers from the user, until the user
//types О
while (nNumber = GetNumber())

.//Now we w i l l output the result


/ / N o t e that we are calling the function
//Гас tor ia I
Console::Writel_ine(SMThe factorial of { 0 } is { 1 } " , nNu

/ / N o w we ore finished
/ / H a n g out until the user is finished
Console::Writel_ine(l_"Hit the e n t e r k e y to stop t h e progre

Рис. 16.7.0 наличии точки останова свидетельствует красный кружок, ото-


бражаемый слева от строки

Глава 16. Через тернии к... работающей программе 181


(Global?)
Badfact.cpp
nNumber=Int32:Parse(Console:ReadLine());
returnnNumber;
' Thisisthe»entryp<for this application
,V
fd
i ef „UNC I ODE
intwmain(void)
intmain(void)
ftendif
intnNumber;
//Gi'tnumbersfromtheuser,untilIheuser
//types О
о while (nNumber = GetNumber())

//Now v-zt! w i l l output the result


/ / N o t e thai we ere calling the function
//Factorial
Console::WriteL!ne(S"The factorial of ^O> is-ПУ 1 , nNu.

Рис. 16.8. В процессе пошагового выполнения программы значок в виде желтой стрел-
ки отображается напротив строки, которая должна будет выполняться следующей

Watch 1
(Мате Value Туре
nResult 5 int32

ii,,-.-;:, ^ 1 Watch 1 Г

Рис. 16.9. В процессе отладки программы в окне Watch постоянно будет отобра-
жаться текущее значение переменной nResul t

Watch 1 4- X
Name Value , Type
nResult Jnt32

Й1-. j$ Watch 1

Рис. 16.10. Значение переменной nRes ult постоянно будет оставаться нулевым

182 Часть If. Все, что вы хотели знать о C++, но о чем боялись спроси
Поскольку переменная n R e s u l t принимает нулевое значение именно в результате вы-
полнения цикла for, вы точно можете сказать, что ошибку нужно искать именно там. По-
этому нужно более тщательно проанализировать коды этого цикла. Так как первая (и един-
ственная) строка цикла умножает значение переменной n R e s u l t на значение перемен-
ной i, возможно, что-то не в порядке с переменной i. Если вы посмотрите на начало
цикла for, то увидите, что значения переменной i изменяются от нуля до числа, введен-
ного пользователем:
for ( i = 0 ; i<=nNumber; i++)
Это значит, что на первой итерации значение переменной i равно нулю. А поскольку
умножение на ноль всегда дает нулевой результат, после выполнение инструкции
n R e s u l t *= i значение переменной n R e s u l t становится нулевым и более не изменяет-
ся. Вот в чем проблема!

Что теперь?
Ошибка содержится в цикле for. Чтобы определить это, вам пришлось пошагово выпол-
нять часть кодов программы, следить за изменением значения переменной n R e s u l t , анали-
зировать работу кодов, после выполнения которых переменная n R e s u l t получает нулевое
значение. В результате вы обнаружили, что проблему можно устранить, если переменная
цикла будет изменяться не от нуля до п, а от единицы до п.
Теперь исправьте код прямо в окне редактора.
1. Щелкните в окне редактора.
Это окно станет активным.
2. Найдите следующую строку:
f c r ( i = 0 ; i<=nNumber; i++)
3. Измените ее так, как показано ниже.
f c r ( i = l ; i<=nNumber; i++)
После этого нужно опять запустить программу, чтобы убедиться, что теперь она ра-
ботает правильно.
4. Выберите команду DebugOStop Debugging (Отладка о О стан овить отладку).
Выполнение программы будет остановлено. (До этого ее выполнение было останов-
лено на промежуточной стадии, так как ранее вы установили точку останова.)
5. Щелкните на кнопке Build (Построить).
Программа будет откомпилирована.
6. Щелкните на кнопке Start (Запустить).
Программа снова будет запущена на выполнение.
Обратите внимание, что для завершения выполнения программы шаг 4 не обязателен —
можно просто щелкнуть на кнопке Build. Отладчик знает, что в коды программы были внесе-
ны изменения, поэтому он спросит, хотите ли вы остановить отладку программы. Чтобы вне-
сенные изменения вступили в силу, программа должна быть заново откомпилирована, поэто-
му щелкните на кнопке Yes. Отладка будет прекращена, а программа заново откомпилирова-
на. После этого можете щелкнуть на кнопке Start, чтобы запустить программу снова.
Когда появится сообщение с просьбой ввести число, наберите, например, число 5 и
посмотрите, как поведет себя программа. Переменная n R e s u l t больше не будет прини-
мать нулевое значение, благодаря чему можно решить, что теперь программа работает
правильно.

Глава 16. Через тернии к... работающей программе 183


Но в программе по-прежнему есть ошибка
Минуточку! Если вы продолжите пошаговое выполнение программы, то увидите, что не
все так хорошо, как хотелось бы. Что-то уж слишком быстро увеличивается значение пере-
менной n R e s u l t .
Как и прежде, проблемы начинаются уже с первой итерации. В момент, когда вы перехо-
дите к выполнению цикла for, значение переменной n R e s u l t уже равно числу 5. На сле-
дующей итерации оно становится равным числу 10, затем числу 20 и т.д. Следя за этими из-
менениями в окне Watch, неизбежно приходим к выводу, что нужно заново разобраться в
том, какое начальное значение присваивается этой переменной и каким образом оно потом
изменяется.
Почему уже на первой итерации переменная n R e s u l t имеет значение 5? Еще раз поду-
майте о том, как программа должна работать, и сравните это с тем, что она делает на самом
деле. (Точнее, нужно сравнить, что вы хотите от программы и какие инструкции вы оставили
ей в действительности.) Чтобы вычислить факториал, нужно начать со значения переменной
n R e s u l t , равного числу 1. затем умножить его на 2, затем на 3 и т.д. Проверим, так ли это
было указано программе.
Если вы посмотрите на самые первые строки функции F a c t o r i a l , то увидите, что пере-
менная n R e s u l t объявляется и инициализируется следующим образом:
i n t n R e s u l t = nNumber;
Это неверно, и вот почему. Допустим, вам нужно вычислить факториал числа 5. Выпол-
нение функции начинается с того, что переменной n R e s u l t присваивается значение 5. Затем
оно умножается на число 1, потом на 2 и т.д. Таким образом получается результат, вычислен-
ный как 5 x 1 x 2 3 x 4 x 5 вместо 1 x 2 x 3 x 4 x 5 . Теперь становится понятно, что началь-
ное значение переменной n R e s u l t должно быть равным числу 1.

Устранение ошибки
В программу нужно внести небольшое изменение так, чтобы начальное значение пере-
менной n R e s u l t было равным числу 1. Для этого выполните ряд действий.
1. Щелкните в окне редактора.
Окно редактора станет активным.
2. Прокрутите окно редактора, чтобы найти строку
i n t n R e s u l t = nNumber;
3. Внесите в эту строку следующую коррективу:
i n t n R e s u l t = 1;
4. Щелкните на кнопке Build.
5. В ответ на вопрос, действительно ли вы хотите остановить процесс отладки
программы, щелкните на кнопке Yes.
Программа будет откомпилирована.
6. Щелкните на кнопке Start.
Программа вновь будет запущена на выполнение.
Вы можете быть довольны проделанной работой, поскольку теперь программа будет ра-
ботать правильно. Например, если на запрос программы набрать число 6, то возвращен будет
именно тот результат, который действительно соответствует факториалу числа 6 (рис. 16.11).

184 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Ш c:\Documents and Settings\Barnson\... - jnj xj
what is the nunber?
6 " 1
The f a c t o r i a l of б1 is 720
Mhat is the number ?

Рис. 16.11. Программа работает правильно

Последние штрихи
Теперь нужно удалить из программы точку останова и отменить отображение значения
переменной в окне Watch (если вы использовали для просмотра значения именно это окно).
Чтобы удалить точку останова, выполните ряд действий.
1. Выберите команду D e b u g ^ W i n d o w s ^ B r e a k P o i n t s (ОтладкаООкнаОТочки
останова).
Откроется окно, в котором будет показан список всех установленных для программы
точек останова (рис. 16.12).

Breakpori ax
L-.5 New Columns * -^j
Name Condition Hit Count
v • PadFectrSSeiii (no condition) break aw l ays (currently 2)
V. • BadFact.cppjline 15(no condition) break aw
l ays (currently 1)
«8ca!- -.U,. jfi(J Breakpon
i ts j V V J ^ O W
:
в - . - u w . ;

Рис. /б. 12. В этом окне можно увидеть список всех точек останова, которые есть
в программе
1. Выделите точку останова, которую хотите удалить.
3. Щелкните на кнопке Delete (Удалить).
Выделенная на втором шаге точка останова прекратит свое существование.
(Щелкните на кнопке Clear All Breakpoints (Удалить все точки останова), если хо-
тите удалить сразу все точки останова, показанные в окне Breakpoints.)
Отменить отображение значений переменных в окне Watch несколько сложнее, так как
получить доступ к этому окну можно только во время отладки программы.
1. Щелкните на кнопке Step Into или нажмите клавишу <F11>.
Будет запущен процесс пошагового выполнения программы, и на экране отобразится
окно Watch.
2. В окне Watch щелкните правой кнопкой мыши на названии переменной, ото-
бражение значения которой нужно отменить, и в открывшемся меню выберите
команду Delete Watch (Удалить из окна).
3. Выберите команду DebugOStop Debugging (Отладка О Остановить отладку),
чтобы завершить выполнение программы.
Теперь, когда вы снова запустите программу, ее выполнение не будет прерываться ника-
кими точками останова. (Если не верите, можете проверить сами.)

Глава 16. Через тернии к... работающей программе 185


Как видите, процесс отладки программы вовсе не сложен. Правда, нужно приобрести не-
который опыт, чтобы научиться быстро определять, в чем именно кроется ошибка. Чем
больше вы будете программировать, тем лучше у вас это будет получаться.
Вообще говоря, отладчик может предоставить вам множество дополнительных возмож-
ностей. С его помощью можно, например, просмотреть значения регистра CPU, увидеть коды
ассемблера, генерируемые компилятором, установить условные точки останова. Чтобы узнать
больше о возможностях отладчика, поэкспериментируйте с командами меню Debug или про-
смотрите статьи об использовании отладчика, которые можно найти в справочной системе
Visual C++.

186 Часть И, Все, что вы хотели знать о C++, но о чем боялись спросить
Часть

Экскурс в объектно-
ориентированное
программирование
В э/ной час&и...
О языке C++ вы уже знаете достаточно много. Но вот об объектно-
ориентированном профаммировании (ООП) вы пока получили только
самое общее представление. Третья часть устранит этот пробел в вашем
образовании и познакомит вас с такими вещами, как представление,с
помощью объектов самых разнообразных явлений реального мира,
использование принципа наследования для передачи функциональных
возможностей одних объектов другим и т.п. Но главное — не забывайте
о том, что объектно-ориентированное программирование может
значительно упростить процесс создания, отладки и модификации
программ, а также облегчить их чтение и понимание.
Глава 17

Смотрите на мир объективно


В $1&ш главе...
> Общее представление о классах, членах данных и функциях-членах
> Создание объектов
> Права доступа для классов
> Статическое соответствие
> Создание объектно-ориентированной программы
> Освобождение памяти
> Создание функций доступа

озвольте огласить две новости: одну хорошую, другую не очень. Хорошая состоит в
том, что все основные идеи, изложенные в этой главе, довольно просты: вы научи-
тесь создавать классы настолько быстро, насколько это возможно. Более того, каждый раз,
когда вы использовали при написании программы встроенные функции .NET, в действитель-
ности вы использовали классы (так что некоторый опыт работы с классами у вас уже есть).
Новость похуже заключается в том, что для того, чтобы научиться создавать правильно орга-
низованные классы, нужно потратить немало времени и усилий. Именно поэтому большинст-
ву программистов, которые только начинают осваивать азы объектно-ориентированного про-
граммирования, приходится по нескольку раз возвращаться к уже созданным классам, чтобы
внести в них дополнительные изменения или вообще переписать их заново.

игакое классы и с чем их


Классы являются фундаментальной составляющей объектно-ориентированного програм-
мирования (ООП). Класс — это структура, содержащая в себе некоторые данные и функции,
предназначенные для обработки этих данных. Когда кто-то говорит о создании объектов, на
самом деле он имеет в виду создание классов. Классы помогают моделировать состояние и
поведение различных объектов реального мира. Они же значительно упрощают процесс на-
писания и тестирования больших сложных программ. Кроме того, они предоставляют воз-
можность наследования, что позволяет повторно использовать ранее написанные коды (это
не просто удобно — это экономит массу времени и усилий).
Поскольку один класс объединяет в себе данные и функции, которые эти данные обраба-
тывают, понять, как работает тот или иной объект, довольно просто. По крайней мере проще,
чем разобраться в кодах программы, где данные и функции разбросаны в произвольном по-
рядке. Используя классы, вы можете не беспокоиться о том, какую библиотеку нужно под-
ключить, чтобы передвигать картинку по экрану, или как найти домашний телефон сотрудни-
ка, занесенного в базу данных. Все это содержится в объекте. В идеале объект должен вклю-
чать в себя все функции, необходимые для обработки содержащихся в нем данных.
Предположим, например, что у вас есть класс L i n e O b j e c t и вам нужно определить ко-
ординаты линии или отобразить эту линию на экране. Обе функции должны содержаться

Глава 17. Смотрите на мир объективно 189


в этом же классе, поскольку они оперируют с данными этого класса. Следовательно, вам не
придется просматривать коды всей остальной программы в поисках нужных данных или ко-
дов, которые эти данные могут как-то изменить или обработать.
Изменить данные, содержащиеся в классе, или способы их обработки возможно только
путем изменения элементов самого класса. Благодаря этому исключаются такие неприятные
ситуации, при которых изменение одной глобальной переменной может нарушить работу
всей программы. Используя классы, вы всегда сможете контролировать возможные последст-
вия изменения отдельных переменных или их значений.
И в заключение отметим, что правильно организованные классы избавляют пользователя
от необходимости вникать во все технические детали. Программист, который использует го-
товый класс, не должен знать, как именно сохраняются в нем данные и какие алгоритмы де-
лают этот класс рабочим. Нужно только знать, как таким объектом можно манипулировать.
Например, чтобы определить координаты линии, для хранения которых используется класс
L i n e O b j e c t , можно просто вызвать функцию I n i t i a l i z e . Таким образом программисту,
который использует этот класс, требуется всего лишь вызвать эту функцию и совершенно не
нужно разбираться, какие при этом будут задействованы переменные или как именно будет
использована функция C o n s o l e .

qe/палях
Сюрприз! Если вы читали предыдущие главы и повторяли приведенные там примеры,
значит, вы уже создали целую обойму классов. Это именно так, поскольку создание классов и
создание структур — по сути, одно и то же (а структуры вы создавали уже не раз). Единст-
венное отличие между классами и структурами в том, что классы включают в себя функции, а
структуры — нет.
В этом разделе вы найдете все, что нужно знать для создания полноценных классов. Вы
узнаете, что такое члены данных и функции-члены, как объявить класс и как ограничить дос-
туп к отдельным его элементам, а также каким образом объявляются функции-члены.

Члены данных
Переменные, которые принадлежат какому-то классу, официально называются членами
данных. (В терминологии Visual C++ они также обозначаются как переменные-члены, что, по
сути, одно и то же.) Когда вы анализируете реальную проблему и моделируете ее в виде объ-
екта, все то, что как-то описывает ее, представляется как члены данных.
Например, цвет, размер, цена, форма, вес, имя, исполнитель, продолжительность — это
характеристики, которые могут описывать объект. Всю эту информацию можно сохранить
как значения переменных и использовать в качестве членов данных различных классов.

Функции-члены
Функции-члены являются составной частью классов и используются для обработки хра-
нящихся там данных. При анализе реальной проблемы все действия, которые можно совер-
шать над объектом, моделируются в виде функций-членов.
Например, в отношении объектов могут быть предприняты такие действия, как настройка
цвета, вычисление размеров, определение общей цены, отображение названия трека или ис-
полнителя. Все это может быть смоделировано с помощью функций-членов.

190 Часть ///. Экскурс в объектно-ориентированное программирование


Объявление классов
Для объявления классов используется ключевое слово c l a s s :
class ClassName
{
public:
здесь описываются элементы данных и функции-члены, доступные
пользователям этого класса
};
Предположим, вам нужно создать класс LineObject, За основу можете взять структуру
P o i n t L i s t (она использовалась в качестве примера в предыдущих главах), добавив к ней
функции и некоторые новые переменные:
gc c l a s s LineObject
{
public:
void I n i t i a l i z e ( ) ;
void Draw(Graphics *poG);
private:
i n t n_nXFrom;
i n t n_nYFrom;
i n t n_nXTo;
i n t ra_nYTo;
};
Позже вы сможете определить, какие действия выполняют функции I n i t i a l i z e и Draw.
Обратите внимание, что класс L i n e O b j e c t был определен как класс, для кото-
рого активирована возможность сборки мусора (эта возможность рассматрива-
л а с ь в
главе 13). В Visual C++ .NET обязательно объявлять классы именно таким
образом. Если вы этого не сделаете, компилятор выдаст сообщение об ошибке.

Ограничение доступа
Вы можете защитить элементы данных отдельных классов от внешнего воздействия, сде-
лав их закрытыми для всей остальной программы. Доступ к таким элементам данных имеют
только функции-члены того же класса.
О том, что в классе L i n e O b j e c t есть такие переменные, как m_nXFrom, m_nYFroin,
m nXTo и m__nYTo, известно только функциям-членам I n i t i a l i z e и Draw. Для тех, кто
будет просто использовать класс L i n e O b j e c t , эти переменные не существуют. Только
функции-члены класса LineOb j e c t имеют к ним доступ и могут изменять их значения.
Как вы смогли убедиться на примере объявления класса L i n e O b j e c t , ограничить доступ
к членам данных очень просто. После того как вы объявили открытые члены данных и функ-
ции-члены (использовав для этого ключевое слово p u b l i c ) , наберите ключевое слово
p r i v a t e . Затем перечислите закрытые члены данных, которые предназначены для исполь-
зования исключительно внутри данного класса. (Можно создать также закрытые функции-
члены. Они могут быть вызваны только другими функциями-членами этого же класса. По су-
ти, это будут вспомогательные функции для открытых функций-членов, действия и возвра-
щаемые результаты которых тем не менее не предназначаются для непосредственного ис-
пользования за пределами этого класса.)

Глава 17. Смотрите на мир объективно 191


Если вы придерживаетесь соглашений о присвоении имен, принятых в данной
книге, начинайте имена закрытых членов данных символами т_.

Защищенный доступ
До настоящего времени вы сталкивались только с двумя ключевыми словами, регули-
рующими права доступа: p u b l i c и p r i v a t e . Члены данных и функции-члены, объявленные
после слова p u b l i c , являются открытыми (т.е. они могут быть использованы за пределами
класса, в котором объявляются). Открытые члены данных и функции-члены создают внешний
интерфейс класса. В свою очередь, внешний интерфейс — это то, к чему обращаются при ис-
пользовании класса.
Закрытые члены данных и функции-члены (они объявляются после слова p u b l i c ) пред-
назначены для внутреннего использования. Такие функции-члены не могут быть вызваны ко-
дами, набранными вне данного класса. Точно так же значения членов данных с ограничен-
ным доступом не могут быть прочитаны или изменены за пределами своего класса.
Использование закрытых членов данных и функций-членов позволяет создавать сложные
классы с простым внешним интерфейсом. Это достигается благодаря тому, что доступ ко
всем сложным вычислительным процессам, происходящим внутри класса, ограничивается и
они становятся невидимыми для пользователей.
Права доступа регулируются еше одним ключевым словом— p r o t e c t e d . Члены дан-
ных и функиии-члены, объявленные после этого слова, могут быть использованы только
функциями-членами этого же класса либо функциями-членами классов, производных от дан-
ного класса.

Определение функций-членов
После того как вы объявите, из чего состоит класс, можно приступить к определению то-
го, какие именно действия будут выполнять функции-члены. Функции-члены определяются
почти точно так же как и обычные функции (что было описано в главе 32). Но, поскольку
функция-член является составной частью какого-то определенного класса, указывать нужно
как имя функции, так и имя этого класса. (В конце концов, ведь несколько разных классов
могут иметь, например, функцию I n i t i a l i z e . )
Чтобы определить функцию-член, наберите имя ее класса, далее два двоеточия ( : : ) и за-
тем имя самой функции. Эти два двоеточия называются квалифицирующим оператором.
Этот оператор означает, что указанная функция-член (или член данных) является частью ука-
занного класса. Например, код L i n e O b j e c t : : Draw указывает на функцию-член Draw клас-
са L i n e O b j e c t , а код L i n e O b j e c t : :m_nXFroin ссылается на член данных m_nXFrom
этого же класса.
Ниже приведен код, которым определяется функция-член Draw класса LineObject. Эта
функция отображает линию на экране.
v i o d L i n e O b j e c t : : D r a w ( G r a p h i c s *poG)

//Создание пера для рисования


Pen *poPen = new P e n ( C o l o r : : R e d ) ;
//Отображение линии
poG->DrawLine(poPen, m_nXFrcm, nwiYFrom, m_nXTo, m_nYTo);

Обратите внимание, что при вызове функции DrawLine вам не нужно набирать
L i n e O b j e c t : :m_nXFrom, поскольку внутри функции-члена не требуется использовать

192 Часть III. Экскурс в объектно-ориентированное программирование


квалифицирующий оператор (: :) при указании на члены данных. Внутри класса принадлеж-
ность члена данных к этому классу определяется автоматически. Таким образом, использова-
ние названия ra_nXFrom внутри функции-члена L i n e O b j e c t : : Draw равнозначно исполь-
зованию кода S o n g L i s t : :m_nXFrom ( S o n g L i s t — название экземпляра класса
LineObject). Также обратите внимание на то, что, поскольку Draw является функцией-
членом класса LineObject, она имеет доступ к закрытым членам данных этого класса. (В
то же время функции-члены других классов, например C i r c l e : : Draw, не могут получить
непосредственный доступ к закрытым членам данных класса LineObject.)

qeActinb с головыми классами?


После того как вы объявили класс и определили его функции, можно приступить к его ис-
пользованию. Как и в случае со структурами, классы могут создаваться либо статически, либо
динамически.
//Статически создаваемый класс
L i n e O b j e c t 0F00;
//Динамически создаваемый класс
LineObject *poBar = new S o n g L i s t ;
Обычно классы создаются динамически. (Классы .NET CLR всегда создаются динамически.)
Те же правила и принципы, которые относятся к статическим и динамическим перемен-
ным, распространяются и на статические и динамические классы. Создание классов обычно
называют созданием экземпляров классов или созданием объектов.

Доступ к элементам класса


Классы — это те же структуры данных. Если нужно получить доступ к значению, сохра-
ненному как член данных класса, используйте точку (.). Точно так же можно получить дос-
туп и к функции-члену:
//Вызов функции Draw класса L i n e O b j e c t
LineObject o F i r s t ;
oFirst.Exaw();
Если вы используете указатель на экземпляр класса, функция вызывается несколько иначе:
LineObject * p o F i r s t = new L i n e O b j e c t ( ) ;
poFirst->Draw();
Подобно переменным, экземпляры классов (или объекты) имеют свое имя и тип.

Статическое соответствие
В главе 14 отмечалось, что можно использовать классы .NET A r r a y L i s t и S t a c k для хранения лю-
бых объектов, для которых включена возможность сборки мусора. Следовательно, если вы создадите
класс и активизируете для него возможность сборки мусора, в последующем можно будет использовать
A r r a y L i s t и s t a c k для хранения экземпляров этого класса. Это очень удобно, если необходимо
работать с произвольными наборами элементов {вспомните набор линий, отображаемых на экране).
Ниже показано, как можно использовать A r r a y L i s t для хранения объектов L i n e O b j e c t .
LineObject *poLine = new LineObject(};
ArrayList *paLines = new ArrayList();
//Сохранение объекта LineObject в структуре ArrayList
paLine=i->Add (poLine) ;

Глава 17. Смотрите на мир объективно 193


Однако извлечь объект из структуры A r r a y L i s t будет немного сложнее. Поскольку в структуру
A r r a y L i s t может быть записана самая разная информация, ей изначально не известно, как обра-
щаться с каждым отдельным элементом. {Более подробно об этом речь идет в главе 19. Пока же мы про-
сто даем краткий обзор такого явления, как полиморфизм.) Если вы хотите использовать объект, сохра-
ненный с A r r a y L i s t , нужно будет сообщить, что собой представляет этот объект.
Предположим, например, что необходимо вызвать функцию-член Draw объекта L i n e O b j e c t , только
что сохраненного в структуре A r r a y L i s t . Если вы непосредственно обратитесь к этому объекту и по-
пробуете вызвать функцию Draw (как показано ниже), то получите сообщение об ошибке.
(poEnumerator->Current)->Draw(poG);
Причина в том, что компьютер не знает, что собой представляет элемент структуры A r r a y L i s t , к ко-
торому вы обращаетесь, и тем более не знает, что у этого элемента есть функция-член Draw. Чтобы
объяснить ему, чем является этот элемент, нужно использовать ключевое слово s t a t i c _ c a s t (в пе-
реводе это означает установление с