Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
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
Майкл Хаймен
Боб Арнсон
ДИАЛЕКТИКА
1
По общим вопросам обращайтесь в издательство "Диалектика'
по адресу: infoldialcktika.com. http://vvww.dialektika.coni
Итак, вы решили серьезно взяться за Visual C++ .NHT. Это хорошая идея, ведь вы в
действительности убиваете сразу трех зайцев: в ваших руках оказывается мощный, полезный
и широко распространенный инструмент. С языком С+-1- можно сделать очень многое. С его
помощью созданы такие продукты, как Hxeel и Access. 'Jror язык также применяется при
разработке управленческих информационных систем и систем целевого назначения,
используемых для анализа деятельности предприятий и принятия решений в сфере
управления бизнесом. И. конечно же, целые армии хакеров и не только хакеров используют
C++ для создания инструментов, утилит, игр и шедевров мультимедиа. Знания, которые вы
получите, изучив язык C++ .NHT. позволят создавать не просто приложения, а приложения,
работающие в разных операционных системах. Возможности этого языка практически не
ограничены, и вы сами в этом убедитесь, прочитав эту книгу.
Книга предназначена для начинающих программистов.
ББК 32.973.26-018.2.75
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.
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++. А также спасибо всем людям, которые приняли участие в создании этой книги.
Майкл Хаймен
Боб Арнсон;
Введение
Итак, вы решили серьезно взяться за 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
(если вы, конечно, хотите сами попробовать набрать приведенные в книге коды и проверить,
как они работают).
используемые € книге
Пиктограммы— это маленькие картинки, акцентирующие ваше внимание на некоторых
важных вещах. Вот, что они обозначают.
15 Введение
Такой пиктограммой отмечено все, что касается неуправляемых программ, т.е.
!„•;_• 1 программ, написанных на старом добром C++, которым разработчики среды
.NET дали такое пренебрежительное определение.
дальше?
На Гавайи, Тайвань, Фиджи. Но только не туда, куда посылала Михаэля эго жена, оби-
женная тем, что на какое-то время он посвятил себя написанию этой книги.
Введение 16
Часть I
Первое знакомство
cVisualC++.NET
Л1Л
НЕ о VUUAI 6++ .
/3 э&ой
Здесь дается краткий обзор Visual C++, начиная с установочного пакета
и заканчивая описанием его отдельных инструментов.
Вы узнаете, как создаются программы, и сможете получить обшее
представление об основных принципах объектно-ориентированного
программирования. Также вы увидите, как можно использовать мастера
Visual C++ для создания программ .NET. Здесь же вы познакомитесь с
такими важными компонентами окружения Visual C++, как компилятор,
редактор кодоь и средство Solution Explorer.
Глава1
Мнс/и/и/асен/ныVisualC++
Не все качественные приложения занимают много места.
Увидев впервые установочный комплект Visual C++, вы, наверное, решите, что чего-то в
нем не хватает,— настолько он небольшой и компактный. Казалось бы, приложение, зани-
мающее множество мегабайт памяти, должно иметь более внушительный вид. Однако такое
впечатление создается потому, что наиболее массивная часть почти каждого установочного
пакета— руководство пользователя — поставляется не в распечатанном, а в электронном ви-
де. (Таким образом не только экономится место, но и спасается несколько деревьев.)
Открыв установочный пакет Visual C++, вы найдете в нем компакт-диск с программным
обеспечением. На нем содержится множество инструментов, участвующих в создании про-
грамм на языке C++:
| V компиляторы;
f V отладчики;
I S интегрированная среда разработки;
I •/ системы создания приложений;
;. S библиотеки;
i S утилиты Windows;
i S общие утилиты;
\{ S библиотека оперативной документации;
1 S примеры программ.
Далее в этой главе дается краткое описание этих инструментов с тем, чтобы вы имели о
них обшее представление и знали, для чего устанавливаете.
S строковый компилятор;
•/ интегрированная среда разработки.
Отладчик программ
Если вы написали программу, состоящую более чем из двух или трех строк, у вас навер-
няка возникнут проблемы при се компиляции. Если же этого не произойдет, то вы либо вели-
кий программист, либо скопировали коды программы из этой книги.
Все возникающие при запуске программы проблемы можно разделить на две категории:
синтаксические ошибки и логические ошибки. Синтаксические ошибки случаются тогда, ко-
гда вы что-то неправильно набираете, забываете указать информацию, которая нужна компи-
лятору, или некорректно использ\сте команду. Компилятор сам находит синтаксические
ошибки и указывает строки, где такие ошибки встречаются. Вам придется исправить все най-
денные синтаксические ошибки, иначе компилятор не сможет преобразовать набранные вами
коды в выполняемые.
Логические ошибки случаются тогда, когда неправильно разработана или реализована са-
ма программа. Например, вы забыли набрать коды для обработки какой-то информации или
даете команду отобразить на экране не те данные, которые необходимы. Программы, содер-
жащие логические ошибки, процесс компиляции проходят успешно, однако выполняются не
так, как вы хотите.
Рассмотрим это на примере. Допустим, вы создали программу, отслеживающую количе-
ство денег на вашем счете. Но при написании ее кодов забыли набрать команду, прибавляю-
Обратите внимание, что ни одно из этих действий не имеет никакого отношения к выводу
чего-либо на экран.
м< этой главе дается краткий оозор основных этапов создания программ: проектирова-
'•^ ния, написания, компиляции и отладки. Кроме того, вам придется "проглотить" не-
много теории, касающейся объектно-ориентированного программирования. Вас также ожи-
дает встреча с настоящей живой работающей .NET-программой.
/Звес/ениевп
Проектированием программ называется этап, на котором принимается решение о том,
что же именно программа должна делать. Для этого нужно четко определить проблему или
круг проблем, которые должны быть решены, и выработать стратегию их решения.
Написание программ — это этап, во время которого вы садитесь и набираете коды своей
программы. Обычно вы набираете инструкции языков программирования высокого уровня.
Например, одной строкой можно сказать компьютеру, что нужно вывести что-то на экран. В
этом и следующих разделах вы познакомитесь с множеством команд языка C++, с помощью
которых можно заставить компьютер делать что-нибудь полезное.
Далее программу нужно откомпилировать. На этом этапе Visual C++ .NET преобразует
коды C++ высокого уровня, понятные вам, в коды низкого уровня, понятные компьютеру. В
процессе проектирования и написания программы обычно разбиваются на несколько отдель-
ных файлов (поскольку это удобно, а иногда и полезно). В процессе компиляции программы
эти файлы объединяются в приложение. После того как программа откомпилирована, ком-
пьютер знает, как ее нужно выполнять.
Теперь можно запустить программу на выполнение. Если она не примитивна и состоит бо-
лее чем из нескольких строк, вполне вероятно наличие в ней ошибок. Поскольку ошибки не
всегда бывают явными, необходимо тщательно протестировать программу, чтобы убедиться
в том. что она работает именно так, как было задумано.
Процесс поиска и исправления ошибок называется отладкой программы. Для поиска
ошибок обычно используются услуги отладчика. Когда ошибка найдена, вы снова возвращае-
тесь к этапу редактирования кодов программы для ее исправления.
Все программы состоят из команд, объясняющих компьютеру, что нужно делать и как
обрабатывать имеющиеся данные. Почти все действия программ сводятся к получению, об-
работке и отображению (или сохранению) данных. Даже, например, программы видеоигр за-
няты в основном только этим. Они получают данные, следя за нажатием клавиш, движениями
#using <mscorlib.dll>
CtnattqafitnMyte noqnfiotfiaMMU
Коды общих для большинства программ алгоритмов хранятся в компоненте среды
.NET, называемом CLR (Common Language Runtime). Например, почти все программы
отображают какие-то значения на экране. Возможно, это покажется вам странным, но
для выполнения такого простого действия компьютеру нужно сделать множество шагов.
Вместо того чтобы писать целые наборы кодов для всех этих шагов каждый раз, когда
нужно что-то вывести на экран, вы можете просто воспользоваться готовой подпро-
граммой, созданной для среды .NET.
н/гог/гаммы
Любая программа создается для решения каких-то задач. При этом, перед тем как на-
чать писать колы, нужно разделить задачу на несколько отдельных логических частей и за-
тем создать процедуры, каждая из которых будет решать проблемы своей части. Поначалу
перспектива разделения общей задачи на несколько более мелких может показаться до-
вольно сложной, однако по мере приобретения опыта программирования вы научитесь де-
лать по быстро и почти автоматически. (Любые хорошие компьютерные курсы уделяют
лому вопросу большое внимание.)
В качестве примера рассмотрим вариант создания программы для решения обычной зада-
чи. Предположим, вам нужно вычислить площадь квадрата. (Зачем? Какая разница. Может, у
вас вечеринка и вы хотите чем-то удивить своих друзей.) Вше со школы вы помните, что
площадь квадрата вычисляется как умножение его стороны на саму себя.
В контексте создания программы эта задача может быть разбита на три логические
подзадачи.
1. Определение длины стороны квадрата.
2. Вычисление площади квадрата путем умножения стороны на саму себя.
3. Отображение полученного результата.
Теперь, когда общая проблема разбита на отдельные подзадачи, каждую из них следует
преобразовать в коды программы, чтобы компьютер знал, какие задачи ему нужно решать.
Например, на языке C++- .NET эти коды могут выглядеть так:
/ , 3^".^ г.сг. '.'оа
/ /'Ъы'-глг.лен.'ле площади квадрата при условии, что известна
/ ' }\:iVM7i О~С СТОрОНЫ
#.:sinc <irscorlib.dl]>
String *pszSize;
int nSize;
int nArea;
//Получение ответа
DszSiire = Console :: ReadLine
//Отображение результата
//Обратите внимание, что для этого вначале нужно
//преобразовать полученное значение в строку
Conso]е::WriteLine(L"Площадь квадрата составляет {0}
единиц.", nArea.ToString()};
//SquareArea
//Вычисление плошади квадрата при условии, что известна
//длина его стороны
Увидев их, вы можете подумать: "Ну, в этих строках нет ничего сложного. В них говорит-
ся, что эта программа вычисляет площадь квадрата исходя из длины его стороны". (Две ко-
сые черты в начале строк указывают, что эти строки являются комментариями.)
#include "stdafx.h"
#using <mscorlib.dll>
//Получение ответа
pszSize = Console::ReadLine(};
'"Все это выглядит немного странно, но в целом можно понять, что коды эти нужны для
того, чтобы компьютер узнал, чему равна длина стороны квадрата".
// Вычисление площади квадрата
nArea = r . S i z e * n S i z e ;
"Да, да! Это я понимаю! В этой строке длина стороны умножается на саму себя, и мы на-
ходим площадь квадрата"'.
Ну и так далее.
Инкапсуляция
Инкапсуляцией называется выделение данных и функций, обрабатывающих эти данные, в
отдельные 'элементы, называемые объектами или классами. Данные, в зависимости от того
как они используются, иногда называют свойствами классов. Функции также иногда называ-
ют методами классов.
Секрет создания качественной объектно-ориентированной программы заключается в том,
чтобы выделить классы, которые бы максимально точно описывали реальную решаемую
проблему и которые можно было бы максимально часто повторно использовать. Поначалу
эта задача может показаться довольно сложной, однако по мере приобретения практического
опыта вы научитесь это делать почти автоматически.
Наследование
Ото одно из наиболее интересных свойств объектно-ориентированного программи-
рования. Как отмечалось ранее, с помощью этого свойства можно создавать новые объ-
екты, используя для этого уже существующие. Новый класс, созданные на основе гото-
вого класса, называется производным. А тот класс, который был взят за основу, называ-
ется базовым. (Иногда производные классы называются дочерними, а базовые —
родительскими.')
Звук
Название
Продолжительность
ЗвукиЖивотных
7 \Трек
Животное Исполнитель
ДатаЗаписи
t
Rock Классика Pop
1
Alternative Rock
Направление
Рис. 2.2. Новые классы можно создавать путем наследования свойств и методов уже создан-
ных классов
Полиморфизм
При создании новых классов можно использовать не только возможность наследова-
ния, но также полиморфизм для определения способа поведения новых классов. Если вы
еще не запомнили все греческие слова, употребляемые в этой главе, напомним, что под
полиморфизмом подразумевается возможность одних и тех же функций по-разному об-
рабатывать данные, принадлежащие разным объектам. Например, класс Звук может
иметь функцию Получить. Если запустить ее для объекта ЗвукиЖивотных, она может
отобразить на экране сообщение, советующее взять с собой диктофон и сходить в бли-
жайший зоопарк. Если запустить эту же функцию для объекта Rock, может быть пред-
принята попытка открыть соответствующую Web-страницу с возможностью загрузки
нужного файла.
о
+у та глава поможет вам создать свою первую программу C++. (И даже не одну.) Нач-
^ ^ нем с создания управляемой программы C++. Это дружественное определение дано
разработчиками среды .NET программам, создаваемым для работы в этой среде, и обознача-
ет, что при разработке этих программ предоставляется множество дополнительных возмож-
ностей (благодаря CLR) и обеспечивается их корректное выполнение (благодаря правильному
управлению памятью и многому другому). Также вы научитесь создавать добрые старые (или
стандартные) программы C++, для которых разработчики среды .NET придумали нехорошее
название— неуправляемые программы. Но вначале вы научитесь создавать то, что является
основой любой программы C++ — исходные файлы.
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
o
tfejct wB
i be createdссл.-ЛВаг son'iMv Documents\Vimal Studio ProiecB\HeloW»ld.
- ^ J I -
ncef i •
Рис. З.1. Использование диалогового окна New Project
для создания проекта Hello World
Все эти файлы были созданы для того, чтобы можно было реализовать простую програм-
му, отображающую на экране сообщение 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 вы узнаете, как мож-
но использовать отладчик для остановки в самом конце выполнения программы. А далее в
этой главе описано, какие коды необходимо добавить, чтобы программа в нужном месте ос-
тановилась и ожидала, пока пользователь не даст ей сигнал продолжать работу. По мере при-
обретения опыта программирования вы оцените полезность и универсальность этого приема.
Ну а пока выполните следующее.
Ш Command Prompt
Функции Console являются частью .NET библиотек CLR, Поэтому, перед тем как их мс-
пользов;у"Ь, нужно указать Visual C++ .NET, в каком месте их искать. Сделать э'ю можно с
помощью ключевого слова f u s i n g . Все функции CLR разбиты на множество динамических
библиотек (файлы с расширением DLL). Строки со словом #usir.g сообщают компьютеру,
функции каких именно библиотек будут использованы в данной программе.
Не скупитесь на комментарии
Только вы можете точно знать, для чего вами была набрана та или иная команда. Но если
коды вашей программы должен будет смотреть еще кто-то, он этого, скорее всего, не поймет.
Или, если спустя несколько лет вы захотите внести в программу некоторые изменения, вы
уже вряд ли сможете вспомнить, как это все работает. Вот почему так важно использовать
комментарии. Комментарии объясняют простым русским языком (или не русским, но обяза-
тельно тем, на котором вы привыкли разговаривать), что вы пытались сделать, набирая все
эти коды.
В кодах программ, приводимых в этой книге, вы уже сталкивались с комментариями. Рас-
познать их помогут две косые черты (//). Как вы уже могли догадаться, весь текст строки,
следующий за ними, является комментарием. (Прочитайте раздел "Старый формат коммента-
риев", чтобы знать, как еще можно создавать комментарии.)
Комментарий может быть выделен в отдельную строку:
//Это строка комментариев.
Также комментарий может завершать собой какую-то строку:
э = 10; //Присвоение переменной значения 10.
При использовании старых комментариев самое главное - не забыть обозначить их окончание! Если вы
забудете набрать в конце комментария символы */, компилятор проигнорирует все, что было набрано
после открытия комментария символами /*.
Обратите внимание, что, когда для создания комментария используются две кхые черты (//), никакими
специальными символами заканчивать строку не нужно. Две косые черты говорят компилятору, что далее
вся строка (и только эта строка) является комментарием. Однако, в отличие от старого формата, если
комментарий занимает несколько строк, каждую новую строку должны предварять две кхые черты {//).
#include "stdafx.h"
u s i n g manespace System;
о J/SJ
Все програм\ш. которые были рассмотрены ранее, являлись программами .NET.
Это означает, что они не будут работать вне среды .NET. Напротив. С.--+ является
стандартизированным языком программирования, и созданные с его помощью
программы могут работать в различных компьютерных системах. Далее аы узнае-
те, как создать обычную программу С+->- для отображения на экране надписи
"Hello World". Напомним, что разработчики .NET назвали программы, созданные
без использования возможностей .NET (к числу которых относятся и обычные
программы C++), неуправляемыми программами.
В отличие от \п, функцию endl нельзя использовать в конце или в середине строки. По-
этому при необходимости нужно просто разделить строку на две части, как показано ниже:
Cout « "Меня зовут " << endl << "Джимми" « e n d l ;
Хотя использовать функцию e n d l несколько сложнее, чем символ \п, код с ее участием
намного проще для чтения и понимания.
Язык C++ имеет очень мало встроенных команд, но зато очень много библиотечных функ-
дий. Многие библиотечные функции доступны всем компиляторам C++, поэтому вы всегда мо-
жете получить к ним доступ и использовать их в своей работе. Другие функции являются над-
стройками (add-ons). Можно, например, приобрести дополнительные библиотеки, содержащие
функции, для проведения статистических вычислений или для обработки изображений.
tel j _
РИС. 3.4. Мастер Win32 Application Wizard поможет вам создам про-
грамму HelloWorld2
# i n d u c e "stdaf х . h"
# i n d u c e <iosrream. h>
Синтаксические ошибки
Если вы что-то неправильно наберете или неверно используете команду 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.
Command Prompt
Ъ? ели вы читаете эту книгу по порядку, то вы уже должны были создать несколько
^ ^ .NET-про грамм. В процессе их создания использовались многие инструментальные
средства разработки языка Visual C++. Закончив читать часть I книги, вы будете уметь поль-
зоваться еще большим количеством возможностей окружения Visual C++. Основной темой
этой главы являются решения и проекты.
Решения и проекты упрощают процесс создания программ благодаря объединению всех
необходимых исходных файлов и прочих сопутствующих элементов. Файл проекта содержит
информацию обо всех исходных файлах, из которых состоит программа. Файлы проектов
также упрощают добавление других исходных файлов к программе и позволяют контролиро-
вать различные параметры присоединения этих файлов к программе. Решение-— это просто
один или несколько проектов,
Решения и проекты
В справочной системе Visual C++ слова solution {решение) и project {проект) часто ис-
пользуются вместе. Вообще говоря, эти два слова обозначают разные веши. Решение состоит
из одного или более проектов. Наиболее часто все же одно решение содержит один проект.
Вы сможете смело называть себя хакером, если научитесь создавать решения из нескольких
проектов.
Поскольку решения и проекты, по сути, являются почти одним и тем же, мы будем упот-
реблять их как взаимозаменяемые понятия, за исключением тех случаев, когда информация
будет относиться только к одному из них.
JfocfnftoeHite nftoiftoMM
Построение любой программы подразумевает создание файла проекта и решения. Файл
проекта сообщает компилятору, какие исходные файлы нужно откомпилировать для построе-
ния приложения. Также в нем содержится информация для компилятора о том, какие библио-
теки должны быть присоединены.
J СД^'! Help
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 в папке МояРабота.
_J scrp
it C++Ffe(.cpp) MeadsrFte(.h) M
diP File (.i
L J Header Files
_J Resource Files
\J\ ReadMe.txt
AdftdUW . . .
.£3 ChWFm.cpp
History
j cj] KlerApp.cpp
My Projects
Jl] tJlerApoDoc.h
, h ] Cesou-ct.h
* I
FaveKes icjjstdais.cpp
Solution Explorer
__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 Щ
Ж0* олыпую часть процесса разраоотки программ!.i занимав! написание кодов в окне ре-
*-^ дактора. Как хороший текстовый редактор кардинально упрошает процесс написа-
ния книги (ну хорошо, не кардинально, но все же упрощает), так и лпрошии редактор кодов
значительно облегчает процесс создания программ. Рс^икптр ><(>••)<>(; позволяет решать не
только обычные задачи редактирования, такие как копирование, вырешше и вставка текста,
но и специальные задачи, касающиеся создания программ.
Visual С+-г имеет многофункциональный настраиваемый релш-лор кодов, позволяющий
решать многие специальные задачи, например такие, как одновременный сдвиг нескольких
строк или быстрое подключение заюловочного файла. В этой главе дается краткий обзор
наиболее важных возможностей этого редактора.
^__
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
r e t u r n 0 ;
Одновременный сдвиг Выделите несколько строк и нажмите клавишу <ТаЬ>. Это может быть
нескольких строк полезно, например, если вы добавили оператор i f и хотите выделить
часть кодов, которые к этому оператору относятся
Отмена сдвига нескольких строк Выделите строки и нажмите <Sfiift+Tab>. Этот прием может быть
полезен, если вы скопировали часть кодов в другое место программы и
теперь нет необходимости специально выделять их с помощью отступов
Переход ко второй скобке из Нажмите <Ctri+]>, чтобы перейти от открывающей скобки (, {, < или
пары скобок [ - курсор уже должен находиться перед ней - к соответствующей ей
закрывающей скобке:),}, > или ]. Тем же нажатием выполняется
обратный переход.
Эта возможность полезна в случае, если вы имеете много вложенных
друг в друга операторов и хотите найти границы каждого из блоков.
Также таким образом можно проверять, не забыли ли вы закончить
вызов функции закрытием скобки
Использование закладок Поместите курсор в том месте, где вы хотите оставить закладку.
Нажмите дважды < Ш + К > . Таким образом вы огметите позицию в
кодах программы, к которой при необходимости сможете быстро
вернуться.
Например, если вы хотите скопировать фрагмент кодов из одной части
программы в другую, установите закладку, найдите нужный фрагмент
кодов, скопируйте его, вернитесь обратно к закладке и вставьте
скопированный фрагмент
Переключение между окнами Нажмите <Ctrl+Tab> или <Ctrl+F6>, чтобы перейти к следующему окну
редактора редактора (которое было открыто после текущего). Нажмите
<Shift+Ctrl+Tab> или <Shift+Ctrl+F6>, чтобы перейти к предыдущему
окну редактора (которое было открыто перед текущим)
Получение справки о командах Щелкните на том элементе программы, по которому нужно получить
справку. Нажмите клавишу <F1 >.
Таким образом, например, можно получать справку относительно
синтаксиса вызова библиотек, Windows API или команд C++
1ODO;addmemberinitialisationcodehere~
CChd
liFrame:~CChd
iFrame()
BOOLCChd liFrame:PreCreateWnidow(CREATEST
{
/,'TODO;ModfiytheWn idowd.siborstylesher-
if(!CMDIChd liWnd:PreCreateWnidow(cs))
returnFALSE;
returnTRUE;
Рис. 5.2. Выделение цветом отдельных синтаксических элементов про-
граммы значительно облегчает их визуальную идентификацию
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;
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()..,
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.
Г Use;
'ГТ аписание кодов программ может быть весьма увлекательным занятием. В набранные
• •* формулы и выражения можно вложить самый разнообразный смысл, затем вывести
все эти коды на печать и развеселить ими своих друзей. Например, с помощью команд и опе-
раторов можно перевести на язык программирования хорошо всем известные крылатые фра-
зы. Если бы Шекспир был программистом, он мог бы написать что-то наподобие if (_2В)
{} e l s e {}. (А если бы он был толковым хакером, он написал бы_2В ? {} : { }.)
Однако, если вы хотите, чтобы программа была действительно полезной и выполняла ка-
кие-то действия, одного лишь красивого текста недостаточно. Программу нужно откомпили-
ровать. Компиляция программы — это преобразование исходных кодов программы, которые
понятны программистам, в машинные коды, которые понятны компьютерам.
Процесс преобразования исходных кодов в машинные крайне сложен. Он требует точного
сопоставления всех набранных вами инструкций (которые называются инструкциями высше-
го уровня) соответствующим инструкциям нижнего уровня (понятным компьютеру). Этот
процесс завершается созданием выполняемой программы. Именно она может повлечь за со-
бой какие-то реальные действия.
О процессе преобразования набранных программистами команд в машинные коды напи-
сано множество книг. К счастью для вас, разработчики, занимающиеся созданием компиля-
торов, все эти книги уже прочитали, поэтому вам вникать в различные тонкости этого про-
цесса не нужно. У вас есть Visual C++, который сам откомпилирует набранные вами коды.
:ф 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
ОДНИ ошибки очевидны и могут быть сразу же исправлены. Другие не так просты, и вам
придется потратить немало времени, чтобы их обнаружить и исправить (особенно если у вас
еще нет в этом большого опыта). Со временем вы научитесь быстро различать типичные
ошибки и сразу же их исправлять.
Предупреждения
Иногда в процессе компиляции помимо сообщений об ошибках вы можете сталкиваться
также с предупреждениями. Предупреждения появляются тогда, когда компилятор может
правильно интерпретировать набранные вами коды, но считает, что выполнение таких кодов
может привести к возникновению проблем. Например, если вы создаете переменную, но ни
разу ее не используете, компилятор предупредит вас об этом и спросит, все ли здесь правиль-
но. Или, что значительно хуже, вы можете начать использовать переменную еще до того, как
присвоите ей какое-то значение. В этом случае на экране отобразится сообщение с предупре-
ждением, подобное показанным на рис. 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. Подобные предупреждения появляются на экране, если набранные вами коды могут
привести к возникновению проблем
Относитесь к предупреждениям с должным вниманием. Хотя иногда они могут появляться
как следствие чрезмерной осторожности компилятора, в большинстве случаев они помогают
избежать многих неприятностей. Например, если вы забудете добавить строку, задающую на-
можно по-/газноли/
Есть три способа компиляции файла: он может быть построен, откомпилирован
/ либо повторно построен.
При построении программы (команда Build^Build) обновляются файлы OBJ для всех из-
мененных исходных файлов. Когда все файлы OBJ обновлены, программа компонуется для
создания нового выполняемого файла (ЕХЕ). Результатом такой компиляции будет либо на-
бор сообщений о синтаксических ошибках, либо полностью рабочая программа.
Если вы работаете над проектом и вносите изменения только в некоторые его части, ис-
пользуйте именно этот способ компиляции. При этом заново откомпилированы будут только
измененные файлы и вам не нужно будет тратить массу времени и ждать, пока компилятор
сделает бессмысленную работу и еще раз откомпилирует те файлы, которые вы не трогали.
Поэтому используйте команду Builds Build, если вы внесли какие-то изменения в исходные
файлы и вам нужен теперь новый файл ЕХЕ.
При обычной комтияции (команда Build^Compile) компилируется только тот файл, который
открыт в данный момент для редактирования. Эгой командой не создается рабочая программа.
Используйте ее в том случае, если хотите проверить какой-либо исходный файл на наличие син-
таксических ошибок. Если компиляция проходит успешно, создается объектный файл (OBJ).
При повторном построении (команда Builds Rebuild All) все файлы проекта компилиру-
ются и затем компонуются для создания выполняемой программы (ЕХЕ). Как и в случае с по-
строением, результатом такой команды будет либо набор сообщений о синтаксических
ошибках, либо полностью рабочая программа. Даже если вы только что построили програм-
му, при повторном построении каждый отдельный файл компилируется заново. (Результатом
такой операции будут обновленные объектные файлы всех исходных файлов плюс выполняе-
мый файл программы, созданный путем компоновки всех объектных файлов.) Используйте
эту команду в том случае, если хотите быть уверены, что весь проект был построен заново.
МОЖНО
v\tp&bW
В э&ой час&и...
С этой части начинается серьезное обучение; Вы получите
фундаментальные знания о Visual C++, начиная с того, как укомплекто-
вать программу переменными, операторами и указателями. Также вы
познакомитесь с одним из наиболее важных инструментов создания
программ — отладчиком.
Ни в коем случае не игнорируйте приводимые примеры, поскольку
многое вещи познаются на практике. Пробуйте самостоятельно набрать
коды программ и выполнить их на своем компьютере, чтобы быть
уверенным, что вы правильно понимаете излагаемьгй здесь материал.
Сразу скажем, что не нужно пугаться этой части, поскольку сухость и
серьезность рассматриваемых здесь вопросов компенсируется хорошим,
:
добрым юмором. V
Глава 7
С/н/гогие и неоплате
Строгие языки программирования, такие как C++, требуют, чтобы программисты заранее
объявляли, данные каких типов они собираются использовать. Например, если вы хотите со-
хранить число, нужно заранее сообщить об этом компьютеру, чтобы он был готов принять от
вас это число.
Строгие языки имеют ряд неоспоримых преимуществ. Например, если вы случайно попы-
таетесь запись о сотруднике интерпретировать как число, компилятор выдаст сообщение об
ошибке. Это хорошо, поскольку без подобного контроля вы можете непреднамеренно унич-
тожить важную информацию. Предположим, запись о сотруднике занимает 8 байт памяти, а
для числа отводится только 2 байта. Когда вы удаляете запись, освобождается 8 байт памяти.
Если же вы используете эту запись как число и затем это число удалите, помимо числа будет
удалено еще 6 байт памяти, в которых может содержаться очень важная информация. Воз-
можно, потеря этой информации будет иметь для вас очень плохие последствия.
Помимо строгих языков программирования, есть также нестрогие, к числу которых отно-
сится, например, JavaScript. Они не требуют предварительного объявления типов данных и
сами оценивают данные в тот момент, когда с ними сталкиваются. Например, если вы при-
своите переменной текстовое значение r o o t , языковой процессор сделает эту переменную
текстовой. Нестрогие языки программирования могут быть проще в использовании, и неко-
торые программисты отдают им предпочтение, однако они не умеют предостерегать вас от
ошибок, как это делают строгие языки.
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 .
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 приведен список функций преобразования типов для основных типов данных.
Предположим, например, что вам нужно отобразить на экране число. Для этого предвари-
тельно необходимо преобразовать его в строку:
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++.
#include "stdafx.h"
#using <mscorlib.dll>
return 0;
}
С/н/to/cu/са/сoquftизнаиболееважных
!пипов данных
Строки (тип S t r i n g ) являются основными данными почти для всех создаваемых про-
грамм. По крайней мере почти ни одна программа не может без них обойтись. Чтобы эффек-
тивно их использовать, нужно владеть некоторым количеством не очень сложных приемов.
Основные их них описаны в табл. 7.2.
В кодах программ, которые приведены на страницах этой книги, вы будете часто сталки-
ваться со строками. Ниже дан код небольшой программы, на примере которого вы можете
увидеть в лействии различные способы обработки текстовой информации,
//StringsiOl
'Некоаорые приемы работы со строками
#induce "stciafx.h"
#using <mscorlib.dll>
int main(void)
#endif
{
String *pszString;
int nNumber;
dcub i.e f 11Number;
80 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Глава 8
Использование переменных
главе...
> Имена переменных
> Определение и инициализация переменных
V Соглашения о присвоении имен переменным
Ымеяование переменных
При выборе имени для ребенка родители сталкиваются с множеством ограничений. До-
пустим, например, что они сами помешаны на программировании и надеются, что, когда их
чадо вырастет, непременно станет великим программистом. Тогда вместо имени Джон они
могли бы дать ему имя \У\У\УДЖОН. ИЛИ вместо имени Джессика назвали бы девочку double.
Но к сожалению, даже если соответствующее административное учреждение (и то не каждое)
зарегистрирует такое имя, у ребенка будут проблемы со сверстниками, тяжелые школьные
годы, и он никогда не простит своих родителей за такой "подарочек".
Выбрать имя для переменной намного проще. Ей безразлично, как вы ее назовете, поэто-
му смело давайте ей любое понравившееся имя. Правда, и здесь существуют некоторые огра-
ничения. Впрочем, они вполне разумны и их легко запомнить. Итак, имя переменной:
| S не должно начинаться с цифры;
| S не должно содержать пробелов;
| S может состоять только из букв, цифр и символов подчеркивания ( __ ). Нельзя ис-
I пользовать специальные символы, такие как точка, запятая, точка с запятой, ка-
!) вычки и т.п.;
| ^ не должно совпадать с названиями библиотечных функций;
I ^ не должно совпадать с зарезервированными ключевыми словами.
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;
пе/геменных
Одновременно с объявлением переменные можно инициализировать. Этот громоздкий
термин обозначает всего лишь присвоение переменным исходных значений. Чтобы сделать
это, наберите после имени переменной знак равенства ( = ) и затем укажите нужное значение.
Integer n nCount
Boolean f fFinished
object о oLiriG
array a aShapes
member m m nShapes
84 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 9
Структуры данных
й гла£е...
> Объявление и использование структур
V- Комбинирование структур
>• Добавление структур к приложению
Объявление структур во многом похоже на объявление обычных типов данных (см. главу 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), в которой хранится значение площади круга.
Чтобы определить переменную, которая будет представлять структуру, просто наберите
название структуры и затем название переменной.
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>
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++ вы к этому привык-
нете. Пока же для удобства, если хотите, можете приступить к чтению кодов с того места, где
начинается выполнение программы.
88 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава10
*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
оператором будет знак +.
Вычисление остатка от деления часто используется для того, чтобы не позволить значе-
ниям выходить из заданного диапазона. Например предположим, что вы создаете программу,
которая перемещает фигурку космического корабля вдоль нижней части экрана (как в игре
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. На данном этапе вы, наверное, могли бы обойтись и без них. Но по мере приобре-
тения опыта создания программ вы оцените эти операторы по достоинству и с удовольствием
будете их использовать. Некоторые из этих "сложных" операторов рассмотрены ниже в от-
дельных разделах этой главы. (Вы, наверное, подумаете: "Неужели они настолько сложные,
что для их описания нужно выделять отдельные разделы?!".)
Оператор »
Оператор сдвига очень полезен при работе с двоичными числами. Вот некоторые приме-
ры его использования:
16 >> 1 возвращает число 8;
16 >> 2 возвращает число 4;
16 >> 3 возвращает число 2;
15 >> 1 возвращает число 7;
15 >> 2 возвращает число 3.
Для получения результата первое число представляется в двоичном виде, а затем все биты
сдвигаются вправо на количество позиций, указанное вторым числом. Обратите внимание,
что при сдвиге битов вправо число уменьшается.
Для внесения ясности рассмотрим действие оператора сдвига подробнее. Число 16, на-
пример, в двоичном виде выглядит так:
10 0 0 0
Оператор «
Ниже приведены примеры использования оператора <<.
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 ;
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 , если
значение слева не равно значению справа. Например:
Оие/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++ бу-
дет с этим не согласен,
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 вопроса, требующих отве-
та "нет" или "да". Если вы используете для этого обычные логические значения, для которых
памяти нужно столько же, сколько для представления обычных чисел, программа потребует для
Условный оператор
Условным оператором называется оператор 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.
Приоритет Операторы
Низший приоритет
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.
радианах
#include "stdafx.h"
#using <mscorlib.dll>
102 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
#endif
Circlelnfo oCircle;
return 0;
второй
sin sin(.03)
Возвращает синус числа. Число представляется в радианах
sqrt sqrt(4)
Возвращает квадратный корень числа
tan tan(.03)
Возвращает тангенс числа. Число представляется в радианах
104 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Глава 11
В других ситуациях вам необходима возможность выбора. Обычно эта возможность фор-
мулируется так: если условие выполняется, сделайте это. Приведем примеры нескольких по-
добных ситуаций.
| /" Если профессор настаивает на выполнении домашнего задания, сделайте его. (В
| противном случае не делайте.)
Xf Если покупатель сделал более 3 000 покупок, предоставьте ему скидку.
| V Если цвет желтый — жмите на газ. Если красный — тормозите.
Условный оператор
Синтаксис условного оператора 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;
Другим приемом, облегчающим чтение кодов программы (который также использовался во всех приве-
денных выше примерах), является размещение закрывающей фигурной скобки (}} на одном уровне с со-
ответствующей ей открывающей скобкой:
if (fFoo)
} '
Благодаря этому легко увидеть, где начатый блок кодов заканчивается. Кроме того, хотя для оператора
i £ использовать фигурные скобки обязательно только в том случае, если он содержит более одной ин-
струкции для выполнения, постоянное их применение может предотвратить возникновение некоторых
тривиальных ошибок. Например, в следующем коде фигурные скобки не используются:
if (fFoo) ' ,
nVal++;
nCheck++;
Приведенный ниже код выполняет точно те же действия, но читается намного проще и позволяет в случае
необходимости без особых усилий добавить к оператору i f дополнительные инструкции для выполнения:
if (fFoo)
{
nVai++;
. nCheck++;
Visuaf C++ .NET форматирует коды вашей программы автоматически. Например, если вы выделите с по-
мощью отступа какую-то строку, набираемые после нее строки будут автоматически выравниваться по
: ней. Если вы наберете скобку }, она автоматически будет выровнена по соответствующей ей скобке {.
108 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
dblDiscount = 0.2;
}
//В противном случае скидка не предоставляется
else
{
dblDiscount = 0.0;
Оператор 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 ложно, выполнение цикла прекращается и программа
переходит к обработке следующих за этим циклом кодов.
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>
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]
Бесконечный цикл
Ниже приведен пример небольшой программы, выполнение шторой неминуемо приведет к зависанию
компьютера.
//Компьютер завис
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 без внешнего
вмешательства никогда не прекратится. Подобные циклы обычно называют бесконечными.
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"четыре");
п Результат
1 одиндватричетыренеизвестное число
2 дватричетыренеизвестное число
и т.д.
Оператор do
Выполнение цикла do во многом подобно выполнению цикла while. Отличие состоит в
том, что цикл while проверяет истинность выражения до того, как будет выполнена какая-
либо инструкция. Поэтому возможен вариант (если выражение сразу окажется ложным), при
котором ни одна из инструкций не будет выполнена. Цикл do, наоборот, вначале выполняет
первую итерацию и только затем проверяет истинность выражения. Если выражение истин-
но, выполняется следующая итерация и т.д. Когда выражение становится ложным, выполне-
ние цикла прекращается.
Do
s tin t;
while (expr) ;
Вот пример использование цикла do, который выполняется до тех пор, пока значение пе-
ременной i не сравняется со значением переменной п:
i n t i = 0;
do
Console::WriteLine(i.ToString{));
114 Часть II, Все, что вы хотели знать о C++, но о чем боялись спросить
51 Hi м , КАК 9Y*o t\pou^ouuh\/ но,
мои игрок Obi^w клюшкой wo
мою МАлемьки! комки
Внимание! Повышенная
функциональность
В э/Яой глабе...
> Что такое функция
> Создание функций
> Использование аргументов
>• Результат, возвращаемый функцией
> Рекурсия и значения аргументов, установленные по умолчанию
Создание функций
Определение функции начинается с набора ее имени и пары круглых скобок (). (Позже, ко-
гда вы познакомитесь с аргументами, в упомянутых скобках можно будет определить список
этих самых аргументов.) Далее следуют выражения, из которых собственно и состоит функция.
Правила присвоения функциям имен в точное™ совпадают с правилами присвоения имен пере-
менным, которые были рассмотрены в главе 8. Итак, вот код, которым определяется функция:
void f u n c t i o n n a m e ( )
Console::WriteLine(S"Hello World");
Теперь каждый раз, когда вы хотите, чтобы функция была выполнена, наберите ее имя и
сразу за ним пару пустых скобок. Это действие называется вызовом функции. Функция может
быть вызвана неограниченное количество раз.
Как и в случае со структурами, функции должны быть определены до того, как будут ис-
пользованы, что показано в приведенном ниже примере. Это программа HelloWorld, которую
вы уже видели ранее, но здесь код, заставляющий программу ожидать, пока пользователь не
нажмет на клавишу <Enier> и не закончит ее выполнение, выделен в отдельную функцию, на-
званную именем HangOut. Функция HangOut определена в начале программы и затем вы-
зывается функцией main.
//HelloWorld4
//Использование функции
#include "stdafx.h"
u s i n g manespace System;
118 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
//С этой точки начинается выполнение программы
#ifdef JJNICODE
i n t wmain(void)
#else
i n t main(void)
#endif
{
//Отображение текста на экране
Console::WriteLine{"Hello World");
Использование аргументов
Функция может требовать для своего выполнения передачи ей значений аргументов. Для
каждого аргумента (их называют также параметрами) должно быть определено имя и тип
данных. Функции, работа которых зависит от передаваемых значений аргументов, более уни-
версальны и могут многократно использоваться в процессе выполнения программы. Для каж-
дой фуикиии можно определить любое количество аргументов любого типа данных.
Вот как определяются аргументы:
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 , ...)
//Получение числа от п о л ь з о в а т е л я
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;
.' S при определении функции перед ее именем вместо слова v o i d указать тип данных,
I который будет иметь возвращаемое функцией значение;
| S использовать ключевое слово 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;
122 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Помните, что цикл while использует выражение как условие для выполнения или невы-
полнения следующей итерации. Итерация выполняется, если проверяемое выражение истин-
но. В нашем случае выражение вызывает вначале функцию GetNumber для получения числа
от пользователя. Затем полученное число присваивается переменной nNumber. Далее воз-
можны два варианта. Если пользователь набирает число 0, выполнение цикла прекращается,
поскольку этому числу соответствует логическое значение f a l s e и выражение воспринима-
ется как ложное. Если же пользователь набирает любое положительное число, оно присваи-
вается переменной nNumber и выполняется итерация цикла. Этот способ использования
цикла w h i l e довольно часто можно видеть в программах C++. (Если вы тестируете про-
грамму или просто любите создавать проблемы, можете ввести отрицательное число, тогда
программа выдаст неправильный результат. Чтобы избежать этого, можете добавить код, ко-
торый будет проверять вводимые пользователем числа и просить ввести число заново, если
оно окажется отрицательным.)
Итак, вот новый код программы, вычисляющей факториал:
//Вычисление факториала до тех пор, пока
//пользователь не наберет число О
#using <mscorlib.dil>
u s i n g manespace System;
i n t nNumber;
124 Часть If. Все, что вы хотели знать о C++, но о чем боялись спросить
Предположим, например, что необходимо отсортировать большой набор чисел. {Это
классическая задача, часто рассматриваемая в книгах по программированию. Возможно,
предлагаемое здесь решение с помощью использования рекурсии вам покажется не вполне
очевидным, но, в отличие от реальной жизни, в контексте программирования именно этот
подход считается одним из самых оптимальных.) Отсортировать большой набор чисел край-
не сложно. Самый простой способ сделать это — просмотреть весь список и найти наимень-
шее число, перенести его в новый список, который будет итоговым, и затем продолжать этот
процесс до тех пор, пока все числа не будут перенесены в этот новый список. Недостаток ме-
тода состоит в том, что один и тот же список приходится просматривать снова и снова, а это
занимает много времени. Так что проблема сортировки чисел не так проста, как это может
вначале показаться, и именно поэтому ей посвящены целые книги.
Чтобы ускорить процесс сортировки, используют рекурсию, разбивая при этом исходный
большой список на более мелкие. Предположим, например, что вместо сортировки одного
большого списка вам нужно правильно объединить вместе два небольших уже отсортирован-
ных списка. В действительности сделать это довольно просто.
Вы спросите, как это делается? Назовем список, который начинается с меньшего значения,
списком А. Второй список назовем списком Б. Итоговый список назовем В. Объединить списки
А и Б так, чтобы в итоговом списке все значения также были отсортированы, можно следую-
щим образом. Возьмите первый (наименьший) элемент списка А и перенесите его в список В.
Затем сравните второй элемент списка А с первым элементом списка Б. Если он также будет
меньше, чем наименьший элемент списка Б, перенесите его в список В. Продолжайте это до тех
пор, пока элемент списка Б не окажется меньше, чем элемент списка А. Тогда перенесите в спи-
сок В элемент списка Б. Теперь сравните следующий элемент списка Б с наименьшим (он будет
наименьшим из оставшихся) элементом списка А и снова меньший из этих двух элементов пе-
ренесите в список В. Продолжайте этот процесс до тех пор, пока все элементы из списков А и Б
не будут перенесены в список В. Возможно, на бумаге этот путь кажется несколько запутанным,
но поверьте, что это намного быстрее и эффективнее, чем снова и снова просматривать один и
тот же список. Попробуйте реализовать этот процесс на компьютере, и вы в этом убедитесь.
Теперь возникает вопрос, как из одного большого неотсортированного списка сделать два
меньших, но отсортированных (чтобы потом получить один общий отсортированный спи-
сок). Можно разбить большой список пополам и отсортировать каждую половину. Но как от-
сортировать половину списка? Ее можно также разбить пополам и отсортировать половину
от половины. Если продолжать делить списки пополам, рано или поздно они будут разбиты
на списки, состоящие из одного или двух элементов. Понятно, что такие списки отсортиро-
вать проще простого.
Далее, имея отсортированные списки из одного-двух элементов, двигайтесь в обратном
направлении, объединяя списки так, чтобы порядок сортировки не нарушался. На последнем
шаге у вас будут два больших отсортированных списка, объединив которые вы получите не-
обходимый результат. Таким образом, используя рекурсию, вы упрощаете задачу, разбивая ее
на небольшие подобные подзадачи.
Продемонстрируем сказанное на маленьком примере.
1. Вот список, состояший из чисел, которые нужно отсортировать:
1375927
2. Раюбьсм его на два списка поменьше:
1375 927
3. ЭТИ СПИСКИ по-прежнему большие, поэтому разобьем их на еще более мелкие:
13 75 92 7
4. Вот такие списки отсортировать очень просто:
13 5 7 29 7
#include "stdafx.h"
u s i n g rr.anespace System;
126 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
nResult *= i;
}
//Возвращение результата
return nResult;
128 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 13
Указатели
иа£е...
> Причины, по которым имеет смысл использовать указатели
> Как использовать указатели
> Рисование графических элементов с среде .NET
> Создание связанного списка
> Освобождение памяти
> Сборка мусора
> Строки в неуправляемых кодах
* / аверняка некоторые из вас думают, что понять принципы работы указателей очень и
* ** очень сложно. Возможно, это действительно было так в те времена, когда при изу-
чении процесса программирования больший акцент делался на теорию, чем не практику. Од-
нако сейчас положение изменилось. После того как у вас пройдет первый шок от соприкос-
новения с таким предметом, как указатели, вы увидите, что они весьма полезны и не так
сложны, как казалось раньше.
В этой главе рассматривается, как и зачем использовать указатели. Но, чтобы вам не показа-
лось, что все настолько уж просто (чуть ли не тривиально), в конце главы вы найдете несколько
отступлений, посвященных техническим деталям. Кроме того, вы сможете приступить к созда-
нию более интересных приложений .NET, снабженных некоторыми элементами графики.
Лочелсц цказсипели
В предыдущих главах вы познакомились с множеством технических приемов, позволяю-
щих сохранять и обрабатывать данные. Но поскольку сами данные становятся все более и бо-
лее сложными, представлять их с помощью именованных переменных становится все труднее
и труднее. Что, если вам необходимо, например, сохранить список, состоящий из произволь-
ных по объему фрагментов информации? Предположим, вам нужно отсканировать фотогра-
фию, но пока что вы не знаете, каких она будет размеров. Если использовать обычные пере-
менные, необходимо заранее указать, каких размеров фотография будет отсканирована. Если
же использовать указатели, программа приобретет большую гибкость и универсальность.
Другой пример: нужно создать программу, позволяющую пользователю рисовать графические
элементы. С обычными переменными нужно заранее точно знать, сколько именно фигур нарисует
пользователь (или наложить ограничения на количество фигур, которые пользователь может нари-
совать). Если в программе для хранения информации о каждой нарисованной фигуре будут исполь-
зоваться указатели, пользователь сможет нарисовать столько фигур, сколько захочет.
Еще одна причина, по которой указатели настолько полезны, заключается в том, что, хотя
сами по себе они невелики, ссылаться они могут на большие объемы информации. Предпо-
ложим, например, что есть большая база данных, содержащая информацию о пациентах не-
которой клиники, и каждая запись в этой базе данных может занимать объемы памяти, ис-
числяемые тысячами байтов. Если возникнет необходимость изменить порядок расположения
записей таким образом, чтобы они были отсортированы, например, по городам, и это можно
сделать только путем копирования каждой записи в новую позицию, эта процедура окажется
Указатели и переменные
Чтобы понять, в чем заключается преимущество указателей, нужно разобраться в том, как
работают переменные. Все данные, которые обрабатывает компьютер, хранятся в его памяти.
Когда переменной присваивается какое-то значение, оно заполняет собой некоторую часть
памяти. Когда значение переменной должно быть использовано, оно считывается из памяти.
Таким образом, каждая переменная — это всего лишь имя для определенного участка памяти.
Указатели — это те же обозначения каких-то отдельных фрагментов информации, запи-
санных в памяти. Указатель точно так же, как и переменная, указывает на участок памяти.
Поэтому каждый раз, когда вы используете переменную, по сути, вы используете указатель.
Различие между переменными и указателями заключается в том, что переменные всегда
указывают на один и тот же участок памяти, в то время как один и тот же указатель в разные
моменты может указывать на разные участки памяти.
На рис. 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 о
130 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
->'i"B действительности указатели— это один из самых полезных и мощных инструментов,
используемых при создании программ, поскольку они обладают большой гибкостью и пре-
доставляют разработчикам большую свободу действий.
Информация и ее адрес
Как только что было отмечено, указатели предоставляют доступ к двум различным фраг-
ментам информации.
Безымянные данные
Вторая причина, вызывающая трудности при работе с указателями, заключается в том,
что они могут указывать на данные, которые никак не обозначаются (т.е. не имеют никаких
имен). На рис. 13.1 вы видели, как используются указатели для получения доступа к значени-
ям различных переменных. Так, например, указатель b a z ссылался на значение переменной
b a r . Следовательно, вы могли разыменовать указатель b a z , чтобы получить значение пере-
менной bar. В данном случае указатель ссылается на участок памяти, который обозначен оп-
ределенным именем (этим именем является имя переменной, т.е. b a r ) .
Вы можете также попросить компьютер сохранить какую-то часть информации в памяти
без присвоения этой информации имени. К этой возможности очень часто обращаются в слу-
чае, если возникает необходимость в динамическом использовании памяти. В указателе мож-
но сохранить адрес информации, не обозначенной именем, и затем использовать его для дос-
тупа к этим данным.
жаемая на экране. Благодаря этому они могут в режиме реального времени показывать видеоизображе-
ния и сопровождать их специальными эффектами.
Но, с другой стороны, если вы заполните эту память, отведенную для специальных данных, разным ин-
формационным мусором, это может привести к возникновению всяких неожиданных проблем. Это со-
стояние называют трещингом компьютера (от слова trash - засорять), поскольку в действительности
специальная память засоряется ненужными данными. Последствия могут быть самыми разными, но в
любом случае нежелательными.
•• Windows пытается отслеживать корректность выполняемых программами действий. Она обычно может
• отличить "хорошие" указатели от "плохих" и останавливает выполнение программы до того, как она успе-
ет сделать какую-нибудь глупость. Более подробно эти вопросы рассматриваются ниже в главе.
Испугались? То-то. Будьте аккуратны при использовании указателей.
132 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Line Pointer •*- Linei
Next item Pointer
Рис. 13.2. Указатели могут указывать на большие и сложные структуры данных. Элемен-
ты этого связанного списка состоят из двух указателей; один указывает на информацию
о координатах линии, а второй - на следующий элемент списка
использование цказайьелей в С+ +
Указатель, ссылающийся на значения определенного типа данных, создается точно так
же, как и переменная того же типа, за исключением того, что перед именем указателя нужно
набрать звездочку (*).
Например, чтобы создать указатель, ссылающийся на значения типа integer, наберите та-
кой код:
int
134 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Далее;это Ш значение отображается еще раз, но теперь доступ к нему осуществляется по-
средством разыменовывания указателя pnNumber:
(*pnNumber).ToString()
//Point
//Объявление к разыменовывание указателя
#include "stdafx.h"
#using <mscorlib.dll>
• HangOut();
r e t u r n 0;
Использование стрелки
Поскольку набирать код (^указатель) .элемент не очень удобно, C++ пред-
лагает упрощенный вариант:
//Изменение значения элемента структуры
poFoo->dblValue = 7 . 6 ;
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 для отображения на экране графики.
138 Часть П. Все, что вы хотели знать о C++, но о чем боялись спроси
Окончание табл. 13.1
Метод Пример Описание
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.
Шрифты
Когда вы применяете метод 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.
Шрифт Пример
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;
//Отображение линии
poGraphics->DrawLine{poPen, 10, 10, 120, 150);
//Изменение свойств пера и
//отображение новой линии
poPen->Color = Color::FormArgb(100, 255, 200);
poPen->Wigth = 5;
poGraphics->DrawLine(poPen, 120, 150, 10, 150);
//Создание шрифта
Font *poFont = new Font("Helvetica", 22);
//Отображение текста этим шрифтом
poGraphics->DrawString(S"Hello", poFont, poBrush, 200, 200);
//Освобождение памяти
списки и графика
После того как вы немного поупражнялись с указателями (вся предыдущая программа
была построена с их использованием), перейдем к рассмотрению более типичных способов
их применения. В этом разделе внимание будет уделено вопросу создания с помощью указа-
телей связанных списков. Рассмотрено это будет на таком примере: пользователь набирает
координаты произвольного количества точек, для хранения которых используется связанный
список. Когда он закончит, эти точки соединяются между собой с помощью графических ко-
манд среды .NET.
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)
//Освобождение памяти
delete poLast;
Код программы
Ниже приведен код всей программы, работа которой обсуждалась в предыдущем разделе.
//Draw
//Применение связанного списка для сохранения
//координат произвольного количества точек
//и их отображение с использованием GDI +
#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;
/ 46 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
//Создание пера для рисования
Pen *poPen = Pen(Color::Red);
//Рисование линии от текущей точки к следующей
poGraphics->DrawLine(poPen, poPoint->nX, poPoint->nY,
роPoint->poNext~>nX, poPoint->poNext->nY);
//Освобождение памяти
d e l e t e poLast;
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;
Обратите внимание, что при обнулении указателя не набирается звездочка (*), поскольку
обнуляется значение самого указателя, а не участок памяти, на который он ссылается.
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.
Oefeug I
Ниже перечислен ряд причин, по которым работа с указателями может обернуться ма-
ленькой трагедией.
• 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
Вам наверняка будет интересно, какие библиотечные функции предназначены для работы
со строками и что они могут делать. Их будет легче найти, зная о том, что названия почти
всех из них начинаются с букв 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, как команды работы с графикой или методы обра-
ботки текстовой информации.
Но, даже если вам кажется, что вы уже стали экспертом по указателям, не обольщайтесь:
вам предстоит еще многому научиться.
0 32
1 10
2 17
3 -5
4 10
Как видите, первый элемент массива имеет нулевой индекс, второй — индекс 1 и т.д. И в
данном случае элемент с индексом 0 имеет значение 32, а злемент с индексом 4 — значение 10.
Чтобы создать массив, нужно просто указать тип данных, имя массива и в квадратных
скобках ( [ ]) количество элементов.
Например, чтобы создать массив типа integer, наберите такой код:
//Создание массиза типа integer, состоящего из 20
//элементов (индексы от 0 до 19)
;nt anFoo[20];
Помните, что первый элемент массива имеет нулевой индекс. Поэтому, если мас-
сив имеет п элементов, последний элемент будет иметь индекс (п - 1). Например,
для массива, показанного несколькими абзацами выше, п равнялось числу 5
(поскольку массив состоит из пяти элементов). Первый элемент имеет нулевой
индекс, а последний — индекс 4 (или 5 - 1 ) .
Те, кто только начинает использовать массивы, очень часто забывают об этом и думают,
что первый элемент имеет индекс 1, а потом долго не могут понять, почему они получают из
массива не те значения, которые им нужны.
Аналогичную ошибку совершают и в отношение последнего элемента массива: при обра-
щении к нему вместо индекса {п -\) используют индекс п. В результате этого либо будут по-
лучены совершенно нечитаемые данные, либо возникнет ошибка GPF (общее нарушение за-
щиты). Последнее случается из-за того, что C++ не может предотвратить обращение к памя-
ти, расположенной за пределами массива. Если вернуться к приведенному выше коду и
предположить, что вы ошибочно набрали a n F o o [ 2 0 ] , это будет обращение к участку памя-
ти, расположенному сразу после области памяти, выделенной для элементов данного масси-
ва. Последствия могут быть самыми непредсказуемыми.
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]);
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] ;
Чтобы обратиться к нужной ячейке, опять-таки необходимо использовать столько квад-
ратных скобок, сколько размерностей имеет массив:
/dасе
Самостоятельно создавать массивы — это здорово. Не менее увлекательное занятие — соз-
давать собственные связанные списки (что было продемонстрировано в главе 13). Но если вы не
в духе или просто не хочется напрягаться, среда .NET предлагает вам воспользоваться специ-
ально разработанными классами для представления структур данных. В действительности это
даже лучше, чем создавать массивы и списки самостоятельно. Пускай Microsoft выполнит за вас
,вск> рутинную работу, а также найдет и исправит все возможные стандартные ошибки. Вы же в
это время можете заняться более важными делами и создать ошибки посерьезнее.
г Одна из лучших структур .NET носит название A r r a y L i s t . Она похожа на массив в том
смысле, что к ее элементам можно обращаться непосредственно (т.е. они имеют порядковые
номера), и в то же время подобна списку, поскольку на количество ее элементов нет ограни-
чений (т.е. в любой момент в конец этой структуры может быть добавлен новый элемент).
Долее того, элементы этой структуры могут содержать значения любых типов, для которых
активизирована возможность сборки мусора (garbage collection).
^ При создании многомерного массива точно укажите объем каждой размерности. (Можно ука-
зать объем п - 1 размерности л-мерного массива, но, чтобы уменьшить вероятность возникновения
ошибок, укажите лучше объем для всех л размерностей.)
•/ Компьютеру совершенно все равно, какая именно размерность будет использована для
представления того или иного свойства. Например, если вы используете двухмерный массив для
представления объектов с координатами X и Y, можете использовать первую размерность как коор-
динату X, а вторую как координату Y. А можете наоборот: первую — как У, а вторую — как X. Глав-
ное, сами не запутайтесь,
•/ Индексы каждой размерности должны указываться отдельно, каждый в своей паре квадрат-
ных скобок. Например, [ 1 ] [ 7 ] — это не то же самое, что [ 1 , 7 ] . Индекс [ 1 ] [ 7 ] предостав-
ляет доступ к элементу двухмерного массива, а [ 1 , 7 ] — это то же самое, что и [ 7 ].
v Компилятор не проверяет, выходит ли указанный вами индекс за пределы массива. Так, если
вы объявили массив из 20 элементов и затем обращаетесь к 50-му элементу, компилятор без про-
блем вычислит "его" адрес и вернет то, что там записано. Поэтому сами проверяйте, принадлежат
ли используемые вами индексы к объявленному диапазону.
s Компилятор также не будет "возражать", если вы обратитесь к двухмерному массиву как к
одномерному. Компилятору индексы нужны только для того, чтобы вычислить нужный адрес. Вы
можете использовать этот факт обхода некоторых условностей и для написания более быстродей-
ствующих кодов.
При назначении индекса или при попытке использовать индекс для получения
значения элемента, убедитесь, что этот индекс не ссылается за пределы созданной
структуры.
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 . Методы, используемые при работе с нумератором
/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"Какая-нибудь с т р о к а " ) ;
типы
Вы только что узнали о том, как можно использовать нумератор для просмотра элемен-
тов, содержащихся в структурах 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 (Верхняя, Средняя или Нижняя), может быть
использовано в любом месте программы в качестве константы.
Circle,
Square
162 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
//Использование списка констант enum в качестве
//типа данных
Shapes oMyShape;
Немного tneofiuu
Программы становятся все больше и больше, и вместе с этим возрастает количество ис-
пользуемых ими функций и переменных. Большинство из этих переменных используются
только при решении какой-то одной небольшой задачи. Например, многие переменные ис-
пользуются только для отсчета количества итераций при выполнении циклов или для вре-
менного хранения значения, которое ввел пользователь. К счастью, не нужно каждой из таких
переменных присваивать какое-то уникальное имя. Ведь в противном случае вам пришлось
бы придумывать миллионы отличающихся друг от друга имен.
Итак, разные переменные могут быть названы одинаковыми именами. До тех пор пока та-
кие переменные используются разными функциями, они между собой не конфликтуют. Вы
можете, например, определить переменную к для функции foo. А потом определить пере-
менную к для функции baz. Хотя названия одинаковые, это разные переменные: одна ис-
пользуется только функцией foo, а вторая — только функцией baz.
Эти переменные разные, потому что имеют разную область видимости. Область видимо-
сти переменных— это место в программе, где эти переменные могут быть использованы.
Например, областью видимости переменной к, определенной внутри функции foo, является
функция foo. Это значит, что данная переменная может быть использована только в преде-
лах функции foo, а вне ее эта переменная не определена.
Все переменные могут быть разделены на две категории: глобальные и локальные. Гло-
бальные переменные— это те, доступ к которым имеет любая функция программы
(включая n a i n ) . Они определяются за пределами какой-либо функции. Такие переменные
нужны для тех случаев, когда некоторое значение должно быть доступным независимо от
того, какая из функций в данный момент выполняется. Имена всех глобальных переменных
должны быть разными.
Локальные переменные — это временные переменные, которые используются какой-
то одной функцией. Локальная переменная создается тогда, когда функция начинает ра-
ботать, используется с процессе выполнения функции, и уничтожается, как только
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 ) ;
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, компилятор выдаст сообще-
ние об ошибке.
170 Часть П. Все, что вы хотели знать о C++, но о чем боялись спросить
Глава 16
Через тернии к... работающей
программе
гла£е...
Инструкции просты и без труда могут быть выполнены кем угодно. Синтаксических ошибок
здесь нет. Но с другой стороны, если эти инструкции выполнить, микроволновая печь просто сго-
рит (черт с ней, с картошкой). Наверное, это не тот результат, который нужно было получить.
Чтобы решить такого рода проблему, нужно проанализировать все предпринимаемые ша-
ги и определить, какой из них приводит к плачевному результату. В этом и заключается про-
цесс отладки: строка за строкой просмотреть проблемный фрагмент кода и определить, что
именно работает неправильно.
Если программа довольно большая, устранить все логические ошибки очень сложно.
Причина в том, что некоторые ошибки могут проявляться только при стечении целого ряда
обстоятельств. Множество нестандартных ситуаций возникает тогда, когда программа попа-
дает в руки пользователей. Если с программой будет работать много людей, все логические
ошибки рано или поздно будут обнаружены. Пользователи, конечно же, сообщат вам об этом,
и вы сможете эти ошибки исправить.
172 Часть II. Все, что вы хотели знать о C++, но о чем боялись спросить
Watches (Просмотр). Отображение текущих значений переменных в процессе вы-
полнения программы. Это средство поможет вам проследить за жизненным цик-
лом интересующих вас переменных и увидеть, как изменяются их значения после
выполнения тех или иных инструкций. Это же средство позволяет просматривать
значения выражений, благодаря чему вы можете видеть, как они изменяются в ре-
зультате изменения значений переменных, входящих в эти выражения.
O/nuaqtUfc плюсfieqatanofi —
и €се в наших [гс/ках
Окно редактора кодов является также и окном отладчика. Другими словами, если вам
нужно совершить такие действия по отладке программы, как, например, определение точки
останова или просмотр значений переменных, сделать вы это можете прямо в окне редактора.
Процессы редактирования и отладки программы тесно связаны между собой. Иначе и
быть не может, поскольку при отладке программы необходимо просматривать исходные ко-
ды, чтобы видеть, выполнение каких из них приводит к тем или иным результатам. Так как
Visual C++ объединяет процессы редактирования и отладки, при отладке можно использовать
любые возможности редактора кодов (такие, как прокрутка окна, разделение окна или поиск
нужного слова). Вы можете даже при отладке сразу же вносить изменения в исходные коды.
Но не вся информация отображается непосредственно в окне редактора. Некоторые дей-
ствия, связанные с отладкой программ, сопровождаются открытием специальных окон. На-
пример, если вы захотите просмотреть значения переменных, для этого откроется отдельное
окно Watch. (Более подробно это описывается несколько ниже в главе.)
Оанановись, мгновение!
Точки останова позволяют остановить выполнение программы в нужном месте. Предпо-
ложим, например, что у вас есть подпрограмма, вычисляющая факториал числа. По какой-то
причине возвращаемый ею результат всегда равен нулю. Чтобы определить, в чем заключает-
ся ошибка, можно установить точку останова в начале этой подпрограммы. Затем, когда вы
запустите программу, ее выполнение будет остановлено в момент вызова процедуры, вычис-
ляющей факториал, и вы сможете использовать все средства отладчика для определения, что
именно в этой процедуре работает не так. Забегая вперед, скажем, что весь материал, изло-
женный далее в главе, посвящен решению подобных проблем.
Установить точку останова довольно просто. В окне редактора кодов щелкните правой
кнопкой мыши в том месте, где нужно установить точку останова, и в открывшемся меню
выберите команду Insert Breakpoint (Установить точку останова). Слева от выбранной стро-
ки появится значок точки останова. Ну хорошо, этим значком будет красный кружок, а не
ромбик или что-то еще, но главное, чтобы вы поняли общую идею,
174 Часть //. Все, что вы хотели знать о C++, но о чем боялись спросить
Если при пошаговом выполнении программы вы не видите желтой стрелки, зна-
чит, у вас, скорее всего, отключена возможность отображения дополнительной
1
информации на границе строк. Выберите команду Tools ^ Options, чтобы от-
крыть диалоговое окно Options. В категории Text Editor/General выберите Gen-
eral и затем опцию Selection Margin. Щелкните на кнопке ОК, и желтая стрелка
появится на экране.
Для пошагового выполнения с перешагиванием через процедуры нажимайте клавишу
<F10> или щелкайте на кнопке Step Over панели Debug (Отладка), показанной на рис. 16.1.
Если вызываемые функции также должны выполняться пошагово, нажимайте клавишу <F11>
или щелкайте на кнопке Step Into.
Обратите внимание, что команды Step Into и Step Over могут быть использованы без
предварительного определения точек останова. Если вы сделаете это, пошаговое выполнение
будет начато с самой первой строки программы.
Также при отладке программы автоматически отображается окно Locals (Локальные), по-
казанное на рис. 16.3. Как и в окне Autos, в нем автоматически отображаются названия пере-
менных и их значения. Но здесь уже отображаются все локальные переменные той функции,
коды которой выполняются в данный момент (т.е. переменные, объявленные внутри этой
функции).
Окна 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
176 Часть II. Все, что вы хотели знать о C++, но о чем боялись спроси
Эта возможность может вам пригодиться для того, чтобы проверить, правильно ли будет
работать вся остальная программа, если переменная будет иметь корректное значение. Когда
вы это проверите, вам нужно будет вернуться назад, чтобы определить, почему эта перемен-
ная принимает неверное значение.
I Пяти Value
nNunbw 5
Очень торопитесь?
Средства Watch и QuickWatch удобны, но все же, чтобы их использовать, вам придется
несколько раз щелкнуть кнопкой мыши. Представим, что вы этого делать не хотите. Тогда
Visual C++ может предложить другое средство, которое называется DataTips (Подсказка).
Оно позволяет увидеть значение переменной без единого щелчка кнопкой мыши.
Чтобы просмотреть значение переменной, используя средство DataTips, выполните сле-
дующее.
1. В окне редактора поместите курсор над той переменной, значение которой вы
хотите увидеть.
2. Подождите полсекунды.
Рядом с курсором появится подсказка, в которой будет показано текущее значение этой
переменной (рис. 16.6).
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;
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())
/ / 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.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 больше не будет прини-
мать нулевое значение, благодаря чему можно решить, что теперь программа работает
правильно.
Устранение ошибки
В программу нужно внести небольшое изменение так, чтобы начальное значение пере-
менной 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 ?
Последние штрихи
Теперь нужно удалить из программы точку останова и отменить отображение значения
переменной в окне 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 (Отладка О Остановить отладку),
чтобы завершить выполнение программы.
Теперь, когда вы снова запустите программу, ее выполнение не будет прерываться ника-
кими точками останова. (Если не верите, можете проверить сами.)
186 Часть И, Все, что вы хотели знать о C++, но о чем боялись спросить
Часть
Экскурс в объектно-
ориентированное
программирование
В э/ной час&и...
О языке C++ вы уже знаете достаточно много. Но вот об объектно-
ориентированном профаммировании (ООП) вы пока получили только
самое общее представление. Третья часть устранит этот пробел в вашем
образовании и познакомит вас с такими вещами, как представление,с
помощью объектов самых разнообразных явлений реального мира,
использование принципа наследования для передачи функциональных
возможностей одних объектов другим и т.п. Но главное — не забывайте
о том, что объектно-ориентированное программирование может
значительно упростить процесс создания, отладки и модификации
программ, а также облегчить их чтение и понимание.
Глава 17
озвольте огласить две новости: одну хорошую, другую не очень. Хорошая состоит в
том, что все основные идеи, изложенные в этой главе, довольно просты: вы научи-
тесь создавать классы настолько быстро, насколько это возможно. Более того, каждый раз,
когда вы использовали при написании программы встроенные функции .NET, в действитель-
ности вы использовали классы (так что некоторый опыт работы с классами у вас уже есть).
Новость похуже заключается в том, что для того, чтобы научиться создавать правильно орга-
низованные классы, нужно потратить немало времени и усилий. Именно поэтому большинст-
ву программистов, которые только начинают осваивать азы объектно-ориентированного про-
граммирования, приходится по нескольку раз возвращаться к уже созданным классам, чтобы
внести в них дополнительные изменения или вообще переписать их заново.
qe/палях
Сюрприз! Если вы читали предыдущие главы и повторяли приведенные там примеры,
значит, вы уже создали целую обойму классов. Это именно так, поскольку создание классов и
создание структур — по сути, одно и то же (а структуры вы создавали уже не раз). Единст-
венное отличие между классами и структурами в том, что классы включают в себя функции, а
структуры — нет.
В этом разделе вы найдете все, что нужно знать для создания полноценных классов. Вы
узнаете, что такое члены данных и функции-члены, как объявить класс и как ограничить дос-
туп к отдельным его элементам, а также каким образом объявляются функции-члены.
Члены данных
Переменные, которые принадлежат какому-то классу, официально называются членами
данных. (В терминологии Visual C++ они также обозначаются как переменные-члены, что, по
сути, одно и то же.) Когда вы анализируете реальную проблему и моделируете ее в виде объ-
екта, все то, что как-то описывает ее, представляется как члены данных.
Например, цвет, размер, цена, форма, вес, имя, исполнитель, продолжительность — это
характеристики, которые могут описывать объект. Всю эту информацию можно сохранить
как значения переменных и использовать в качестве членов данных различных классов.
Функции-члены
Функции-члены являются составной частью классов и используются для обработки хра-
нящихся там данных. При анализе реальной проблемы все действия, которые можно совер-
шать над объектом, моделируются в виде функций-членов.
Например, в отношении объектов могут быть предприняты такие действия, как настройка
цвета, вычисление размеров, определение общей цены, отображение названия трека или ис-
полнителя. Все это может быть смоделировано с помощью функций-членов.
Ограничение доступа
Вы можете защитить элементы данных отдельных классов от внешнего воздействия, сде-
лав их закрытыми для всей остальной программы. Доступ к таким элементам данных имеют
только функции-члены того же класса.
О том, что в классе 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 . Затем перечислите закрытые члены данных, которые предназначены для исполь-
зования исключительно внутри данного класса. (Можно создать также закрытые функции-
члены. Они могут быть вызваны только другими функциями-членами этого же класса. По су-
ти, это будут вспомогательные функции для открытых функций-членов, действия и возвра-
щаемые результаты которых тем не менее не предназначаются для непосредственного ис-
пользования за пределами этого класса.)
Защищенный доступ
До настоящего времени вы сталкивались только с двумя ключевыми словами, регули-
рующими права доступа: 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)
Обратите внимание, что при вызове функции DrawLine вам не нужно набирать
L i n e O b j e c t : :m_nXFrom, поскольку внутри функции-члена не требуется использовать
Статическое соответствие
В главе 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) ;
Немкою nftatanuicu
Теперь, когда вы имеете общее представление о классах, можно вернуться к программе,
отображающей линии на экране, и сделать ее объектно-ориентированной. Всего нужно будет
определить два класса. Объекты класса LineOb j e c t предназначены для хранения информа-
ции о каждой отдельной линии.
Сама функция main этой программы очень проста. Она создает новый класс
D i s p l a y O b j e c t , инициализирует его, и вызывает метод Draw. Итак, вся программа выгля-
дит следующим образом:
//Draw4
#include "stdafx.h"
//Рисование линии
void LineObject::Draw(Graphics *poG)
{
//Создание пера для рисования
Pen *poPen = new Pen(Color::Red);
//Рисование линии
poG->DrawLine(poPen, m__nXFrom, m_nYFrom, m_nXTo, m_nYTo)
//Рисование линий
poDisplay~>Draw(poGraphics);
poCur = t h i s ;
while (poCur|
{
pcTemp = poCur;
//Освобождение памяти, выделенной для объекта LineObject
delete poCur->poLine;
poCur = poCur->poNext;
//Освобождение памяти, выделенной
//для объекта LineObjectContainer
de-lete poTemp;
Обратите внимание, что необходимо освобождать память, выделенную как для объекта
LineObject (предназначен для хранения информации о координатах линии), так и для объ-
екта L i n e O b j e c t C o n t a i n e r (предназначен для создания связанного списка из объектов
LineObject).
Ниже показана неуправляемая версия объектно-ориентированной программы, рисующей
на экране линии. Поскольку здесь не могут быть использованы возможности .NET, автомати-
чески создающие связанные списки и освобождающие динамически выделяемую память, не-
управляемая версия будет несколько сложнее, чем управляемая.
//Draw5
//Неуправляемая программа
#include "stdafx.h"
#include <iostream.h>
public:
LineObject *poLine;
LineObjectContainer *poNext;
void Delete();
//Инициализация структуры
void ArrayList::Initialize
{
m_poFirst = 0;
m_poCur = 0;
mpoLast = 0;
if (!m_poFirst)
{
rn_poFirst = poLOC;
n poCur = m p o F i r s t ;
}
else
n_poLast->poNext = poLOC;
//Изменение указателя, ссылающегося на последний элемент списка
n^po^ast = poLOC;
}
//Переход к следующему элементу списка
LineObject *ArrayList::MoveNext ()
{
LineObject *poNext;
//Если элементов больше нет, функция возвращает нуль
if ( !m__poCur)
return 0;
//Освобождение памяти
void ArrayList::Delete
//Добавление линий
poDi:>pIay~>Add (> ;
//Отображение линий
poDiap!ay->Draw();
//Освобождение памяти
poDisplay->Delete{) ;
delete poDisplay;
retu-n 0;
Функции qoanifna
Вы только что увидели, насколько удобно держать члены данных закрытыми от осталь-
ной программы и использовать специальные функции для доступа к ним и для изменения их
значений. Visual C++ .NET оптимизирует этот процесс, позволяя автоматически создавать
функции доступа. (Это такие функции, которые присваивают значения членам данных и счи-
тывают их.) Вот как это делается:
property i n t get_X();
p r o p e r t y v o i d ser:__X(int i) ;
Этот код говорит о том, что у объекта есть свойство X. Если нужно прочитать значение
этого свойства, можно вызвать функцию get_X () или набрать такой код:
n = foo. X;
Если же нужно присвоить свойству X какое-то значение, можно вызвать функцию
set_X{) или набрать код
foo.X ~ п;
Если вы объявите только функцию get, свойство станет доступным только для чтения.
public:
___ property int get_X();
property void set_X(int i ) ;
property int get_Y();
property void set_Y(int J. ) ;
private:
int m_nX;
int m nY;
int PointObject::get_Y()
{
return m_nY;
}
void PcintObject::set_X(int i)
{
mnX = i;
void PointObject::set_Y(int i)
i
m nY = i ;
HangOut();
return 0;
^^r^ife, Если вы пишете неуправляемую программу, также можно создать подобный эф-
фект наличия свойств у объекта. Для этого можно использовать закрытые члены
данных и открытые функции, которые обеспечивают к ним доступ. При этом
нельзя будет ссылаться на сами свойства, как это делается в управляемых про-
граммах. Вместо этого нужно будет непосредственно вызывать функции-члены
get и set.
Если для какой-то переменной определить только функцию g e t , она станет доступной
только для чтения.
Ниже приведен код неуправляемой программы, в котором используются функции доступа.
//Accessors2
//Использование функций доступа
//Неуправляемая программа
#include "stdafx.h"
#include <iostream.h>
public:
int get_X{);
void set_X{int i ) ;
int get_Y() ;
void set_Y(int i ) ;
private:
int m_nX;
int m nY;
int PointObject::get_Y()
{
return m_nY;
}
void PointObject::set_Xfint i)
(
m_nX = i ;
}
void PointObject::set_Y(int i)
(
m_nY = i ;
}
return 0;
Заголовочные файлы
Если программа состоит из нескольких исходных файлов, вам нужно будет объявить классы в заголовоч-
ном файле. Если класс необходимо будет использовать в каком-то исходном файле, наберите в нем ди-
рективу # i n c l u d e и укажите название заголовочного файла, который должен быть включен в этот ис-
ходный файл.
Если вы добавляете в класс или удаляете из класса члены данных «ли функции-члены, не забудьте обно-
вить заголовочный файл. В противном случае вы получите сообщение наподобие такого: ' f о о ' : is
n o t a member of ' b a z '. Расшифровывается оно приблизительно так: "Вы забыли обновить за-
головочный файл для того, чтобы функция f o o была включена в класс b a z " . Или так: "Вы указали не-,
верный параметр, т.е. то, что было перечислено при объявлении класса, не совпадает с тем, на что вы
ссылаетесь при его использовании".
1. Проанализируйте проблему.
2. Отдельно выделите данные, которыми нужно будет манипулировать.
Что это за данные? Как вы будете их обрабатывать?
3. Разбейте данные и функции на группы, чтобы определить, из чего будет состо-
ять каждый объект.
4. Скройте детали выполняемых операций.
Для управления объектом используйте функции высокого уровня, так чтобы пользо-
ватель не знал, например, что имена сохраняются в массиве или что записи извлека-
ются из связного списка.
Приведем некоторые общие рекомендации, которыми следует руководствоваться присту-
пая к проектированию классов.
Конструкторы и деструкторы
Вэ/Кой главе...
> Для чего нужны конструкторы и деструкторы
> Создание нескольких конструкторов
> Советы о том, как правильно читать коды объектно-ориентированных npoi рамм
// ft // U
с}0 и после
Каждый из нас любит вкусно поесть, но вот процесс приготовления пиши и последующее
мытье посуды способны испортить общий "праздник жизни". В программировании та же
картина: создавать хорошие программы— одно удовольствие, если не учитывать всяческую
подготовительную работу и последующую уборку программного мусора.
Однако не стоит потакать своей лени, так как подготовительный и завершающий этапы имеют
при создании программы очень важное значение. Если, например, вы забудете инициализировать
переменную (т.е. не присвоите ей начальное значение), это может привести к таким последствиям,
как отображение на экране не того цвета, не тех данных или зависание программы. Аналогично,
если вы забудете написать коды, освобождающие выделяемую память, это может привести к тому,
что программа будет работать все медленнее и медленнее, пока вовсе не зависнет'.
К счастью (а вы ведь знали, что не может все быть настолько ужасно;, в С- • есть две
встроенные возможности— конструкторы и деструкторы, заботящиеся о том. чтобы пере-
менные инициализировались, а ненужная память вовремя освобождалась. Конструктор —
это процедура, которая автоматически вызывается в момент создания объекта (он ж е — эк-
земпляр класса). Вы можете помещать инициализирующую процедуру внутри конструктора,
чтобы иметь гарантию, что объект будет настроен должным образом, когда вы приступите к
использованию. Можно даже создать несколько конструкторов, чтобы иметь возможность
инициализировать объект разными способами.
Когда объект становится более не нужным (это происходит, если использующая его
функция заканчивает работу или вы набираете для него команду del-;^::п), автоматически вы-
зывается функция, называемая беструктором. Если по какой-то причине вас не устраивает
тот способ, которым среда .NET автоматически освобождает выделяемую намял., можх-ie
подкорректировать его, воспользовавшись деструктором.
э/нап
Конструктор — это функция, вызываемая каждый раз в момент создания HOBOJ^ оби.:1--: -.
Если объект содержит члены данных, которые должны быть инициаличнгмианы. .iooap.!.^ -
конструктор соответствующие коды.
Конструктор имеет такое же имя, как и соответствующий класс. Так, конструктор класса
D i s p l a y O b j e c t будет называться D i s p l a y O b j e c t : : D i s p l a y O b j e c t . Конструктор
класса Dog будет называться Dog : : Doc.
Конструктор никогда не возвращает значения в качестве результата.
Чтобы создать конструктор, объявите его вначале внутри класса, Ниже, например, пока-
зан код объявления класса D i s p l a y O b j e c " , для которого будет создан конструктор:
__gc c l a s s D i s p l a y O b j e c t
{
public:
void Add{);
void Draw(Graphics *poG);
DisplayObject: ( ) ;
private:
ArrayList *m__paLines;
};
Далее нужно написать коды для объявленного конструктора. В програм-
ме Draw4 класс DisplayObject имеет функцию-член Initialize, которая
присваивает указателю адрес списка ArrayList. Этот же код можно на-
брать внутри конструктора DisplayObject:
DisplayObject::DisplayObject ()
f
m__paLines = new ArrayList ();
}
Каждый раз при создании экземпляра класса D i s p l a y O b j e c t этот конструктор будет
вызываться автоматически. Теперь необходимость в функции I n i t i a l i z e отпадает, и более
не нужно беспокоиться о том, чтобы она вызывалась для каждого нового объекта. Этот про-
стой прием позволит вам избавиться от лишней головной боли.
"°—' Конструктор объявляется внутри класса как одна из его функций-членов. Он может
быть закрытым ( p r i v a t e ) , открытым (public) или защищенным ( p r o t e c t e d ) .
//Определение конструктора
DisplayObject::DisplayObject
{
m_paLines = new ArrayList
Замечание следов
Деструктор — это функция-член, автоматически вызываемая в момент ликвидации объекта.
Объекты могут ликвидироваться по разным причинам. Например объект, создаваемый и исполь-
зуемый какой-нибудь функцией, уничтожается тогда, когда эта функция заканчивает работу. Или
же объект, созданный командой new, может быть впоследствии удален командой d e l e t e . Или,
допустим, программа завершает свою работу и все созданные ею объекты подлежат удааению.
Компилятор вызывает деструктор автоматически. Вам вовсе не обязательно беспокоиться
о том, чтобы он был вызван, а также вы никак не сможете передать ему значения каких-то
параметров. Конструктор имеет то же имя, что и класс, перед которым стоит символ тильды
(~). Класс может иметь только один деструктор, и он должен быть открытым ( p u b l i c ) .
Вот как выглядят коды деструктора:
//Класс для создания связанного списка
class LineObjectContainer
(
public:
LineObject * p o L i n e ;
L i n e O b j e c t C o n t a i n e r *poNext;
-LineObjectContainer{);
//Удаление линии
delete poLine;
//Удаление следующего элемента списка
if (poNext)
delete poNext;
О
Если вы создаете управляемую программу (используя возможности .NET), среда
.NET автоматически освобождает выделяемую память или по крайней мере делает
это для тех объектов, для которых активизирована возможность сборки мусора. По-
этому в .NET не обязательно всегда собственноручно создавать деструкторы. Однако
ur = t h i s ;
(poCur)
" poCur;
/ /СсЕобожден'/G п а м я т и , вь:;:олекнсй ~ля о б ъ е к т
d e l e t e poCur->poI,ine;
poCur - poC^r->poNext;
/ ''ОсЕобсжде:;;!О п а м я т и , вь:дел ; якной
/ / д и к объекта LineObjoctCwHtainer
d e l e t e pcTernp;
Как вы помните, эта функция "ьпывается тогда, когда программа завершает работу. На-
зывается она вначале для первого элемента связанного списка, а затем поочередно оир;*!п;.ч.-[-
1
ся к каждому последующему элементу. (Ну хорошо, вы. скорее всего, об л о м не момпнн . :!а
самом деле, когда вы просматривали эти коды в главе 17. то наверняка думали о чемчо сгич:м
или просто считали овечек.)
С деструкторами все становится намного проще. Kouia вы удаляем обьек!. cooiueu:-
v
вуюший ему деструктор вызывается автоматически, а функцию I.ineObjecrtCor.rj.ii:!-. : : .
D e l e t e можно преобразовать к более совершенному виду:
/ / Д е с т р / к т с р : освоОождепие камчт^:, ьыде.генной д л я с п и с к а
L i n e O b j ^ c r C o n t a i n e r : : LineOb"', e c t C o n r a i n e r ( )
//Удаление линии
delete poLine;
//Удаление сленуюшего элемента списка
if (poNext)
delete coNext;
#include "stdafx.h"
u s i n g namespace System;
//Простой класс, состоящий из конструктора и деструктора
c l a s s bar
{
public:
bar();
-bar();