




























Яшавант Канеткар
3-е издание
Санкт-Петербург
«БХВ-Петербург»
2023
УДК 004.43
ББК 32.973.26-018.1
К19
Канеткар Я.
К19
С++ — это просто. — 3-е изд.: Пер. с англ. — СПб.: БХВ-Петербург, 2023. —
336 с.: ил.
ISBN 978-5-9775-1194-0
Доступно изложены основы синтаксиса языка C++ и объектно-ориентированного программирования. Описаны функции С++, подробно рассмотрены классы
и объекты, инкапсуляция, наследование, полиморфизм, система ввода-вывода С++.
Даны советы и рекомендации по разработке современных приложений. Приведены
примеры использования шаблонов и библиотеки стандартных шаблонов, описана
обработка исключений. Отдельная глава посвящена расширенным возможностям
C++.
Для начинающих программистов
УДК 004.43
ББК 32.973.26-018.1
Группа подготовки издания:
Руководитель проекта
Павел Шалин
Зав. редакцией
Людмила Гауль
Перевод с английского
Сергея Гусева
Редактор
Анна Ардашева
Компьютерная верстка
Ольги Сергиенко
Оформление обложки
Зои Канторович
Copyright 2021 BPB Publications, India. All rights reserved.
First published in the English language under the title
Let Us C++. Third Edition, ISBN 9789388176644
by BPB Publications India ([email protected])
Russian translation rights arranged with BPB Publications, India
© 2021 BPB Publications, Индия. Все права защищены.
Впервые опубликовано на английском языке под названием
Let Us C++. Third Edition, ISBN 9789388176644
издательством BPB Publications India ([email protected])
Права на перевод на русский язык предоставлены издательством BPB Publications, Индия.
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20
ISBN 978-9-388-17664-4 (англ.)
© BPB Publications, India, 2021
ISBN 978-5-9775-1194-0 (рус.)
© Перевод на русский язык, оформление. ООО "БХВ-Петербург", ООО "БХВ", 2023
Оглавление
Об авторе ......................................................................................................................... 13
Благодарности ................................................................................................................ 15
Предисловие к третьему изданию .............................................................................. 17
Предисловие к первому изданию ............................................................................... 19
Глава 1. Введение в ООП ............................................................................................. 21
Истоки............................................................................................................................................. 23
Структурное программирование .................................................................................................. 24
Объектно-ориентированное программирование ........................................................................ 26
Характеристики объектно-ориентированных языков ................................................................ 28
Объекты .................................................................................................................................. 28
Классы .................................................................................................................................... 29
Инкапсуляция ......................................................................................................................... 29
Скрытие данных..................................................................................................................... 29
Наследование ......................................................................................................................... 30
Полиморфизм ......................................................................................................................... 31
Отношения включения .......................................................................................................... 31
Шаблоны................................................................................................................................. 32
Обработка исключений ......................................................................................................... 32
Многократное использование ............................................................................................... 32
Упражнения.................................................................................................................................... 33
Важное ............................................................................................................................................ 35
Глава 2. Переходим на C++ ......................................................................................... 37
Комментарии .................................................................................................................................. 39
Ввод и вывод в C++ ....................................................................................................................... 40
Динамическое объявление переменных ...................................................................................... 42
Динамическая инициализация ...................................................................................................... 43
Вывод типов ................................................................................................................................... 43
Синтаксис структуры (struct), объединения (union) и перечисления (enum) ........................... 43
Неименованные объединения и перечисления ........................................................................... 44
Приведение типов .......................................................................................................................... 45
Пустой указатель (void) ................................................................................................................. 46
Оператор :: ...................................................................................................................................... 46
Ссылки ............................................................................................................................................ 47
Типы обращений к функциям....................................................................................................... 48
6
Оглавление
Возвращение значения по ссылке ................................................................................................ 51
Спецификатор const ....................................................................................................................... 52
Const-указатели ...................................................................................................................... 53
Const-ссылки .......................................................................................................................... 54
Возврат значений const-переменных ................................................................................... 57
Функции-члены типа const .................................................................................................... 57
Логический тип данных (bool) ..................................................................................................... 58
Упражнения.................................................................................................................................... 59
Важное ............................................................................................................................................ 65
Глава 3. Функции .......................................................................................................... 69
Строгая проверка типов ................................................................................................................ 71
Исходные значения для аргументов функции ............................................................................ 72
Перегрузка функции ...................................................................................................................... 73
Разница в типе возвращаемого значения ............................................................................. 74
Можно ли задать разные типы данных при помощи typedef? ............................................ 75
Можно ли задать разные типы данных при помощи const? ............................................... 75
Разные задачи, одно имя ....................................................................................................... 76
Перегрузка операторов.................................................................................................................. 76
FAQ по перегрузке операторов ............................................................................................ 79
Встраиваемые функции ................................................................................................................. 79
Зачем полагаться на компилятор? ........................................................................................ 80
А где гарантия? ...................................................................................................................... 80
Когда ими пользоваться? ...................................................................................................... 81
Новый синтаксис возвращаемого типа ........................................................................................ 81
Функции instance, static, virtual и friend ...................................................................................... 81
Упражнения.................................................................................................................................... 82
Важное ............................................................................................................................................ 85
Глава 4. Классы и объекты ......................................................................................... 87
Структуры и классы ...................................................................................................................... 89
Классы и конструкторы ................................................................................................................ 92
Деструкторы ................................................................................................................................... 94
Класс Complex ................................................................................................................................ 95
Указатель this ................................................................................................................................. 97
Перегрузка унарных операторов .................................................................................................. 98
Объекты и память ........................................................................................................................ 100
Еще раз о структурах и классах ................................................................................................. 101
Идеальная организация программы ........................................................................................... 102
Упражнения.................................................................................................................................. 106
Важное .......................................................................................................................................... 112
Глава 5. Премудрости классов .................................................................................. 115
Статическое и динамическое выделение памяти ...................................................................... 117
Выделение памяти для массивов и структур .................................................................... 118
Выделение памяти для объектов ........................................................................................ 120
Статические члены класса .......................................................................................................... 122
Универсальный спецификатор const .......................................................................................... 124
Перегруженный оператор присваивания и конструктор копирования................................... 126
Оглавление
7
Преобразование данных .............................................................................................................. 129
Преобразование между встроенными типами .................................................................. 129
Преобразование между встроенными и пользовательскими типами .............................. 130
Преобразование между различными пользовательскими типами данных ..................... 132
Процедура преобразования в исходном объекте .............................................................. 132
Процедура преобразования в целевом объекте ................................................................. 134
Упражнения.................................................................................................................................. 137
Важное .......................................................................................................................................... 139
Глава 6. Наследование ................................................................................................ 141
Наследование ............................................................................................................................... 143
Еще один пример наследования ................................................................................................. 147
Варианты применения наследования ......................................................................................... 149
Наследование и конструкторы ................................................................................................... 151
Виды наследования ..................................................................................................................... 154
Одиночное наследование .................................................................................................... 154
Многоуровневое наследование .......................................................................................... 155
Множественное наследование ............................................................................................ 156
Предупреждение .......................................................................................................................... 157
Поэтапная разработка.................................................................................................................. 158
Упражнения.................................................................................................................................. 158
Важное .......................................................................................................................................... 160
Глава 7. Полиморфизм ............................................................................................... 163
Виртуальная функция ................................................................................................................. 165
Чистая виртуальная функция...................................................................................................... 167
Абстрактный класс ...................................................................................................................... 168
Связывание функций ................................................................................................................... 168
Анатомия виртуальных функций ............................................................................................... 170
Для чего применять виртуальные функции?............................................................................. 177
Срезание объекта ......................................................................................................................... 177
Виртуальные деструкторы .......................................................................................................... 179
Вызов виртуальных функций из конструкторов/деструкторов ....................................... 181
Виртуальные базовые классы ..................................................................................................... 181
Упражнения.................................................................................................................................. 183
Важное .......................................................................................................................................... 184
Глава 8. Система ввода-вывода в C++ .................................................................... 187
Требования к системе ввода-вывода .......................................................................................... 189
Решение с использованием потоков в C++ ............................................................................... 190
Предопределенные потоковые объекты .................................................................................... 191
Библиотека iostream .................................................................................................................... 191
Класс istream ................................................................................................................................ 192
Класс ostream ............................................................................................................................... 194
Вывод символов в кодировке Unicode ............................................................................... 195
Класс iostream .............................................................................................................................. 196
Манипуляторы потока ................................................................................................................. 196
Пользовательские манипуляторы .............................................................................................. 199
Пользовательские манипуляторы с аргументами ............................................................. 200
Работа с потоками ввода-вывода в файл ................................................................................... 202
8
Оглавление
Символьный ввод-вывод ............................................................................................................. 203
Открытие файла ................................................................................................................... 203
Чтение данных ..................................................................................................................... 204
Обнаружение конца файла (EOF) ....................................................................................... 204
Закрытие файла .................................................................................................................... 204
Программа копирования файлов ................................................................................................ 204
Ввод-вывод строк ........................................................................................................................ 205
Ввод-вывод записей .................................................................................................................... 206
Прямой доступ ............................................................................................................................. 208
Режимы открытия файла ............................................................................................................. 210
Строковые потоки ....................................................................................................................... 211
Работа с istrstream ................................................................................................................ 212
Ввод-вывод объектов .................................................................................................................. 213
Сериализация ............................................................................................................................... 214
Обработка ошибок ввода-вывода ............................................................................................... 215
Взаимодействие с файловой системой ...................................................................................... 217
Упражнения.................................................................................................................................. 220
Важное .......................................................................................................................................... 222
Глава 9. Расширенные возможности C++ ............................................................... 225
Отношения включения ................................................................................................................ 227
Дружественные (friend) функции и классы ............................................................................... 229
Еще одно применение дружественной функции ...................................................................... 231
Предупреждение .......................................................................................................................... 234
Ключевое слово explicit ............................................................................................................... 234
Ключевое слово mutable .............................................................................................................. 236
Пространство имен ...................................................................................................................... 237
Способы применения пространства имен ................................................................................. 240
Использование оператора разрешения контекста ............................................................. 240
Ключевое слово using .......................................................................................................... 241
Динамическая идентификация типа (RTTI) .............................................................................. 242
Приведение типов в C++ ............................................................................................................. 244
static_cast .............................................................................................................................. 245
dynamic_cast ......................................................................................................................... 246
const_cast .............................................................................................................................. 248
reinterpret_cast ..................................................................................................................... 248
Предупреждение .................................................................................................................. 249
Указатели на члены классов ....................................................................................................... 249
Упражнения.................................................................................................................................. 253
Важное .......................................................................................................................................... 254
Глава 10. Шаблоны ..................................................................................................... 257
Шаблоны функций ...................................................................................................................... 259
Что происходит во время компиляции?............................................................................. 261
Шаблоны функций для пользовательских типов ...................................................................... 261
Еще одна шаблонная функция ................................................................................................... 262
Явная специализация обобщенной функции..................................................................... 264
Функция с набором обобщенных типов .................................................................................... 264
Шаблоны и макросы .................................................................................................................... 265
Сортировка на основе шаблона .................................................................................................. 266
Оглавление
9
Шаблоны классов ........................................................................................................................ 267
Шаблон класса связного списка ................................................................................................. 271
Полезные советы по шаблонам .................................................................................................. 273
Вариативные шаблоны ................................................................................................................ 275
Области применения шаблонов ................................................................................................. 276
Упражнения.................................................................................................................................. 276
Важное .......................................................................................................................................... 278
Глава 11. Обработка исключений ............................................................................ 279
Обработка исключений в C++ .................................................................................................... 281
Работа с библиотечными классами исключений ...................................................................... 284
Библиотечные исключения при создании очереди ........................................................... 286
Еще один пример ................................................................................................................. 288
Работа с пользовательскими классами исключений ................................................................ 290
Полезные советы ......................................................................................................................... 292
Спецификация исключений ........................................................................................................ 293
Необработанные исключения ..................................................................................................... 294
Интеллектуальные указатели и динамические контейнеры .................................................... 295
Упражнения.................................................................................................................................. 297
Важное .......................................................................................................................................... 298
Глава 12. Стандартная библиотека шаблонов ...................................................... 301
Стандартная библиотека шаблонов ........................................................................................... 303
Компоненты STL ......................................................................................................................... 304
Контейнеры .......................................................................................................................... 304
Итераторы ............................................................................................................................. 305
Алгоритмы ............................................................................................................................ 306
Вектор (vector) ............................................................................................................................. 307
Другие операции .................................................................................................................. 308
Вектор объектов класса Point ..................................................................................................... 309
Список (list) .................................................................................................................................. 311
Множество (set) и мультимножество (multi-set) ....................................................................... 313
Отображение (map) и мультиотображение (multi-map) ........................................................... 317
Стек (stack) ................................................................................................................................... 319
Очередь (queue) ............................................................................................................................ 320
Объект-функция ................................................................................................................... 322
Упражнения.................................................................................................................................. 323
Важное .......................................................................................................................................... 324
Предметный указатель ............................................................................................... 327
10
Оглавление
Посвящается
Налини и Прабхакар Канеткар...
Об авторе
За последние два с половиной десятилетия книги Яшаванта Канеткара и его учеб-ные видеокурсы Quest Video Courseware по языкам C и C++, структурам данных, VC++, платформе .NET, встраиваемым системам и др. внесли весомый вклад
в развитие и подготовку многих первоклассных специалистов в сфере ИТ как в Индии, так и за ее пределами.
Книги Яшаванта обрели всемирное признание, став подспорьем для миллионов
программистов. Они переведены на многие языки и опубликованы в Индии, США, Японии, Сингапуре, Корее и Китае.
Будучи весьма востребованным лектором в области ИТ-технологий, Яшавант про-водил семинары и мастер-классы в TedEx, IIT, REC и других транснациональных
компаниях-разработчиках ПО.
За свои предпринимательские, профессиональные и научные достижения Яшавант
удостоен престижной награды «Выдающийся выпускник» Индийского технологического института Канпур. Эта награда была вручена 50 лучшим выпускникам
вуза, внесшим значительный вклад в развитие своей профессиональной сферы и
улучшение общества за последние 50 лет.
В знак признания его огромного вклада в ИТ-образование в Индии корпорация
Microsoft удостаивала его наград «Лучший технический вклад в .NET» и «Самый
ценный профессионал» в течение 5 лет подряд.
Яшавант имеет степень бакалавра технических наук Технологического института
Веермата Джиджабай и степень магистра технических наук ИИТ Канпур. В на-стоящее время Яшавант является директором компаний KICIT Pvt. Ltd. и KSET Pvt.
Ltd.
Благодарности
Упоение творчеством! Наверное, именно поэтому меня так привлекает написание
книг. Я верю, что на каком-то этапе создания книга начинает жить своей жизнью.
Как только это происходит, автору остается лишь пасть на колени, целовать землю
и, воздев руки к небу, молить о дожде. Дальше книга способна расти сама, и особой
заслуги писателя тут нет.
Многие люди помогали мне в создании этого трудоемкого книжного проекта.
Именно их и следует благодарить за все достоинства нашего совместного продукта, я же с готовностью приму все критические отзывы.
Шакил Али помогал в решении двух задач: вместе с ним мы исправляли все шеро-ховатости и неясности в тексте. Кроме того, он, как искусный повар, неустанно
мял, кромсал и препарировал исходный код примеров, пока не удовлетворился по-лученным результатом.
Нандита Хингве прорешала все упражнения в книге и позаботилась о том, чтобы
контрольные вопросы были понятны читателю. Все рисунки и таблицы тоже под-готовила она.
Я признателен студентам, посещавшим мои лекции и семинары по C++ и помогав-шим мне глубже постичь многие концепции этого языка. Именно от них я узнал, что если вы открыты для новых идей, то круг ваших друзей может стать куда шире
радиуса ваших ежедневных прогулок.
За годы я стал несколько привередлив в выборе обложки. Художник Винай Индо-рия настолько искусен в своем неподражаемом мастерстве, что создает подходя-щий вариант с первой попытки. Огромное ему спасибо за еще один выдающийся
шедевр.
И наконец, та единственная, что непоколебимо поддерживала меня во всех писа-тельских начинаниях, включая эту книгу, — моя жена Сима. Она помогла мне
гораздо больше, чем можно выразить словами.
Предисловие к третьему изданию
Многое изменилось за те десять лет, что прошло с момента первого издания этой
книги. В те времена C++ был еще новичком в своей отрасли. Сейчас это высоко-развитый, мощный и популярный инструмент программирования. В новом издании
я попытался отразить эти перемены.
Для этого я проверил все программы из корпуса примеров, работают ли они с по-пулярными сегодня компиляторами, такими как Visual Studio и GCC, внес необходимые изменения, и, наконец, представляю их на ваш суд. Учебный материал также
был изменен с учетом модификаций, внесенных в программный код.
Многие программисты переходят на C++ с C. Пожалуй, самая распространенная
среди них ошибка заключается в том, что по большей мере они так и продолжают
программировать на C, но уже при помощи компилятора C++. И мне хотелось бы
изменить эту тенденцию, поскольку, если этого не сделать, вы никогда не овладее-те теми возможностями, что дарит вам C++. С этой целью самую первую главу я
посвятил тому, как следует воспринимать любую проблему с точки зрения C++.
Очень рассчитываю на то, что вы оцените такой подход и не преминете им воспользоваться.
В ходе бесед со слушателями учебных курсов, семинаров и практикумов меня нередко спрашивают: «Как освежить в памяти все аспекты программирования на C++
перед важным выступлением или собеседованием?». Я ответил на этот вопрос, вве-дя раздел под названием «Важное» в заключение каждой главы. Надеюсь, что эта
идея придется вам по душе.
В настоящем издании я значительно переработал ряд глав, в частности, главы о наследовании, полиморфизме, шаблонах и обработке исключений. Поскольку большинство профессиональных программ на C++ используют стандартную библиотеку шаблонов для работы с наборами данных, я посвятил ей отдельную главу.
C++ претерпел множество редакций от C++98 до C++11, C++14 и C++17. В настоящий момент большинство компиляторов C++ полностью реализовали функции
C++14, чего не скажешь про версию С++17. По этой причине я протестировал все
программы этой книги на компиляторах C++14.
В целом, я бы сказал, что данное учебное пособие по C++ создано для того, чтобы
помочь вам в освоении этого замечательного языка при помощи новейших средств
разработки и с надлежащей точки зрения. Желаю вам всего наилучшего и надеюсь, что книга вас заинтересует.
Предисловие к первому изданию
Завершить работу над этой книгой оказалось едва ли не самым трудным делом мо-ей жизни. Мне потребовалось почти два года, чтобы придать ей сегодняшний вид.
Так, листая свой черновик, я всякий раз принимался переписывать текст заново. На
то было две причины: во-первых, С++ абстрактен, во-вторых, сложен. Как и большинство программистов на С++, я когда-то начинал с C. И потому я чувствовал, что не справлюсь со своей задачей до тех пор, пока не смогу сопоставить любое
новшество С++ с теми средствами, какими оно было реализовано в C, и продемонстрировать читателям преимущества использования С++.
Литература о С++ изобилует высокопарными жаргонизмами. Одна из задач этой
книги состояла в том, чтобы свести подобного рода риторику к минимуму, сосре-доточившись на основных концепциях языка. В большинстве случаев я пытался показать, как все это работает, и, что еще важнее, почему это работает именно так.
Попросите меня охарактеризовать эту книгу одним словом, и я отвечу — простота. Будь то программный код или текст, я стремился сделать его настолько простым, насколько это возможно. Что касается листингов, то я хотел снабдить своих
читателей простыми и наглядными примерами, которые легко редактировать, ком-пилировать и запускать. В этих программах нет ничего лишнего. Моя цель заклю-чалась не в том, чтобы продемонстрировать, насколько я хороший программист, а в том, чтобы проиллюстрировать конкретные концепции программирования.
Вы также заметите, что почти все примеры в этой книге представляют собой исполняе-мые программы. Я обнаружил, что законченная программа, пусть и небольшая, позволяет освоить предмет куда лучше, чем отдельные фрагменты исходного кода.
По моему опыту, несложные упражнения исключительно полезны для закрепления
пройденного материала. Подборку таких практических задач вы найдете в конце
каждой главы.
Однако прежде всего я мечтал написать книгу для программиста, покоряющего новый и сложный язык программирования. В эти страницы я вложил все свои знания
и опыт. И потому верю, что вы сочтете ее полезной.
Всего вам наилучшего и счастливого программирования!
Яшавант Канеткар
20
Глава 1

Введение в ООП
Объектно-ориентированные языки программирования,
такие как С++ и Java, считаются оптимальным выбором
при создании комплексных приложений для современного
цифрового мира. Прежде чем мы углубимся в тонкости
языка С++, важно понять, почему программисты
предпочитают ООП и С++

22
Глава 1
Истоки...
Структурное программирование
Объектно-ориентированное программирование
Характеристики объектно-ориентированных языков
• Объекты
• Классы
• Инкапсуляция
• Скрытие данных
• Наследование
• Полиморфизм
• Отношения включения
• Шаблоны
• Обработка исключений
• Многократное использование
Упражнения
Важное
Введение в ООП
23
то такое объектно-ориентированное программирование (ООП)? На этот вопрос несложно ответить, поскольку разработчики программного обеспече-Ч ния обожают сыпать терминами и модными выражениями. Еще не так давно
слова «искусственный интеллект», «WAP» и «Java» звучали не иначе как пропуск
в рай небесный. Похоже, что фразой «объектно-ориентированный» злоупотребляют
не меньше. Коль скоро было доказано, что объектно-ориентированные методы
позволяют создавать более качественные программы, каждый стремится наклеить
ярлык «объектно-ориентированный» на свои программные продукты.
Следовательно, нам важно понять, что такое ООП, для чего оно нужно, в чем его
преимущество перед традиционными языками программирования, такими как C, Pascal и Basic, и каковы основополагающие принципы ООП. Эти вопросы рассматриваются в настоящей главе, посвященной обзору характерных особенностей, которые мы подробно обсудим далее в книге. Здесь же мы ограничимся общим и
кратким экскурсом. Не переживайте, если вам не удастся усвоить весь материал
этой главы с первого раза; понимание комплексной структуры ООП требует времени. Мы еще раз вернемся к этим характерным особенностям объектно-ориентированного программирования в последующих главах.
Цель языка программирования — представить решение некой проблемы при помощи алгоритма (пошаговой процедуры). Удачность решения напрямую зависит от
того, каким образом оно моделирует (представляет) эту проблему. Для моделирования решений проблем на протяжении многих лет развивались различные под-ходы. Основными среди них являются модель структурного программирования
(также называемая моделью процедурного программирования) и модель объектно-ориентированного программирования. В последние годы модель объектно-ориентированного программирования вытесняет модель структурного программирования. Для понимания этих моделей нам необходимо начать с изучения истории
моделей программирования.
Истоки...
Программы для самых ранних компьютеров писались в двоичном коде. Для загруз-ки таких программ применялись механические коммутаторы. С появлением
устройств хранения данных и более дешевой памяти большей емкости возникли и
первые высокоуровневые языки программирования. С их появлением у программистов отпала необходимость мыслить в категориях битов и байтов; теперь они могли
написать ряд инструкций на языке, напоминающем английский, которые компилятор мог затем транслировать в двоичный компьютерный код.
Эти языки были просты по своему устройству и легки в использовании, поскольку
в то время программы в основном были связаны с относительно несложными задачами, такими как математические вычисления и расчеты. В результате программы
были короткими и в среднем насчитывали несколько сотен строк инструкций.
По мере того как мощность и потенциал компьютеров увеличивались, росла и спо-собность обрабатывать более сложные программы. Однако вскоре обнаружилось,
24
Глава 1
что ранние языки плохо справляются с комплексными задачами программирования.
У этих языков выявился ряд существенных недостатков:
Отсутствовали средства повторного использования существующего программного кода. Там, где требовался идентичный фрагмент кода, он просто дублиро-вался.
Для управления потоком инструкций использовался ненадежный оператор goto, превращавший программу в запутанный клубок переходов от одного фрагмента
кода к другому, зачастую в ущерб управляющей логике.
Все переменные в программах были глобальными. Отслеживание случайных
изменений глобальных данных в длинном запутанном коде было весьма утомительным делом.
Написание, понимание работы и сопровождение больших программ оборачивались для программистов сущим адом.
Иными словами, этот ранний метод разработки программ можно смело окрестить
неструктурным программированием.
Структурное программирование
Для преодоления вышеупомянутых ограничений начались разработки новых языков с новыми функциями, способствующими созданию более сложных приложений. Развитие в конце 1960-х — начале 1970-х годов структурного программирования стало настоящим прорывом. Длинные программы, с которыми программисту
было трудно разобраться, теперь можно было разбить на более мелкие блоки из
нескольких сотен инструкций. В этих языках появились специальные подпрограм-мы — функции и процедуры, позволяющие упростить программный код для
лучшего понимания его создателями. Теперь программа была разбита на функции, каждая из которых выполняла четко определенную задачу. Ниже показано, как
структурное программирование справилось с недостатками неструктурного программирования.
Повторное использование программного кода. В случаях, когда требуется ис-полнить идентичный фрагмент кода в разных частях программы, используется
функция, содержащая этот код. В результате нет необходимости многократно
копировать один и тот же фрагмент.
Злоупотребление оператором goto — необходимость в его применении была
сведена к минимуму благодаря мощным управляющим инструкциям, способным
управлять программным потоком в простой для понимания форме.
Случайные изменения в глобальных переменных — с введением функций потребность в глобальных переменных также была сведена к минимуму.
Сложность программ — комплексной структурой программного кода стало легче управлять, поскольку структурное программирование позволяло лучше организовать программу.

Введение в ООП
25
Структурно-организованная программа строится путем разбиения решения на более мелкие части (такой метод называется «разделяй и властвуй), которые затем
становятся функциями в рамках этой программы. Каждая функция может иметь
свои локальные переменные и свою логику. Выполнение начинается с одной функции, а затем, напрямую или косвенно, из этой функции вызываются все остальные.
Такая структура продемонстрирована на рис. 1.1.
Рис. 1.1. Структурное программирование
В структурном программировании потребность в глобальных переменных меньше, поскольку здесь они заменены локальными переменными с меньшей и более кон-тролируемой областью действия. Данные передаются между функциями посредством аргументов, а функции могут иметь свои локальные переменные, к которым
нельзя получить доступ за пределами области действия функции.
Изолируя процессы внутри функций, структурно-организованная программа сводит
к минимуму вероятность того, что одна функция повлияет на другую. Это также
упрощает обнаружение ошибок, если таковые имеются. Структурное программирование помогает создавать более прозрачный код и контролировать работу каждой
функции. Всё это делает разработку и сопровождение кода проще и эффективнее.
С возникновением структурного программирования появилось новое понятие —
абстракция. Абстракция позволяет программисту рассматривать задачу в обоб-щенном представлении, не вникая во внутренние детали. В структурно-органи-зованной программе достаточно знать, какую задачу выполняет та или иная функция. До тех пор, пока функция надежно справляется с задачей, программисту не
важно, каким образом эта задача выполняется. Это называется функциональной
абстракцией и является краеугольным камнем структурного программирования.
Структурное программирование господствовало в мире программного обеспечения
почти два десятилетия — с 1970 по 1990 год. С постоянным улучшением оборудо-вания и растущим спросом пользователей на многофункциональные программы
26
Глава 1
сложность программ возросла в несколько раз, и именно тогда стали обнаружи-ваться слабые места структурного программирования:
Основные компоненты структурного программирования — функции и структуры данных — не позволяли моделировать проблемы реального мира естественным, привычным образом.
Механизмы повторного использования программного кода оставались ограни-ченными.
Сопровождение, отладка и обновление больших программ по-прежнему были
нелегкой задачей.
То, каким образом преодолевались эти ограничения, мы обсудим в следующем разделе.
Объектно-ориентированное программирование
В нашем мире задачи и их решения не представляют собой набор отделенных друг
от друга значений и процедур. Те, кому приходится решать эти задачи, не воспри-нимают мир таким образом. Они работают в своих предметных областях, сосредо-тачиваясь на объектах и позволяя характеристикам этих объектов определять при-меняемые к ним процедуры. Чтобы построить дом, вырастить помидор или отре-монтировать двигатель, мы сначала думает о самом объекте, его назначении и
поведении. Затем мы выбираем инструменты и методы. Решение соответствует задаче. Объект можно обозначить с помощью двух основных компонентов: присутствующих у объекта свойств и характерного для объекта поведения.
Таким образом, мир вокруг нас «объектно-ориентирован», и методология объектно-ориентированного программирования позволяет строить компьютерные программы
способами, моделирующими восприятие людьми реальной действительности. Поскольку программисты тоже люди, то вполне естественно, что их подход к работе
над задачами реального мира отражает их ви´дение этого мира.
Объектно-ориентированная методика зиждется на фундаменте, заложенном кон-цепциями структурного программирования и абстрагированием данных. Абстрагирование данных позволяет делать с данными то, что функциональная абстракция
позволяет делать с операциями. Благодаря абстрагированию данных можно применять структуры данных, не заботясь о точных деталях реализации. Например, в языках программирования абстрагируются числа с плавающей точкой. Вам вовсе
не обязательно знать, как число с плавающей точкой представлено в двоичном
формате, когда вы присваиваете ему значение. Аналогичным образом вас не беспокоит, как происходит двоичное умножение при умножении чисел с плавающей
точкой. Абстракция для чисел с плавающей точкой уже давно существует в языках
программирования. Однако языки, позволяющие определять собственные абст-рактные типы данных, были разработаны лишь недавно.
Принципиальное новшество в ООП заключается в том, что программа разрабаты-вается для обработки данных, а не для совершения самих операций. Это и неудиви-

Введение в ООП
27
тельно, раз уж мы понимаем, что самой целью программы является доступ к данным или их использование. Основная задача объектно-ориентированного языка
состоит в том, чтобы объединить в единое целое как данные, так и функции, работающие с данными. Такая единица называется объектом.
Функции объекта, называемые функциями-членами (компонентными функциями) или методами в С++, обычно предоставляют единственно возможный способ доступа к его данным. Если вы хотите получить доступ к элементу данных в объекте, вы вызываете функцию-член в объекте. Она считает элемент и вернет вам его значение. Вы не можете получить доступ к данным напрямую.
Если вам необходимо изменить данные в объекте, вы точно знаете, какие функции
взаимодействуют с ним — компонентные функции в данном объекте. Никакие другие функции не могут получить доступ к этим данным. Это упрощает написание, отладку и поддержку программы.
Как правило, программа на языке С++ состоит из ряда объектов, которые взаимодействуют, вызывая функции-члены друг друга. Организация программы на С показана на рис. 1.2.
Рис. 1.2. Объектно-ориентированное программирование
Когда вы подходите к проблеме программирования в рамках объектно-ориентированного подхода, вы уже не задаетесь вопросом, как разделить задачу на функции, а думаете над тем, как разделить ее на объекты. Мышление в терминах объектов, а
не функций, удивительно способствует облегчению работы по созданию программ.
Это происходит в силу значительного сходства объектов в мире программирования
с объектами реальной действительности.
Близость объектов программирования объектам реальной действительности —
плод удачного сочетания данных и функций. Полученные в результате объекты
знаменовали подлинную революцию в разработке компьютерных программ. Ни
28
Глава 1
в одном процедурном языке не существует такого близкого соответствия между
конструкциями программирования и моделируемыми понятиями.
ООП — это больше, нежели просто объединение данных и функций в единое
целое. Ниже приведены некоторые из новых концепций, введенных в ООП: Инкапсуляция.
Скрытие данных.
Наследование.
Отношения включения.
Полиморфизм.
Шаблоны.
Обработка исключений.
Многократное использование.
И пусть этот список новых понятий вас не пугает. Их значение кратко объяснено
в следующем разделе. А в последующих главах мы подробно поговорим о каждом
из них.
Характеристики
объектно-ориентированных языков
Объектно-ориентированное программирование использует словарь, незнакомый
программисту на процедурных языках. Давайте кратко рассмотрим эти термины, сфокусировавшись на основных элементах объектно-ориентированных языков.
Объекты
В структурном программировании задача решается путем ее разделения на функции, тогда как в объектно-ориентированном программировании задача разбивается
на объекты. Мышление в терминах объектов, а не функций, упрощает разработку
программы. Ниже приведен ряд кандидатов на роль объектов в соответствующих
контекстах:
Сотрудники компании в системе расчета заработной платы.
Структуры данных, такие как связанные списки, стеки, очереди и т. п.
Элементы графического пользовательского интерфейса, такие как окна, меню, иконки и т. п.
Аппаратные устройства, такие как CD-или DVD-Rom, клавиатура, принтер
и т. п.
Различные элементы в компьютерных играх, такие как пушки, орудия, живот-ные и т. п.
Покупатели и продавцы в системе отслеживания продаж.
Компьютеры в сетевой модели.
Введение в ООП
29
Классы
Большинство языков предлагают элементарные типы данных, такие как int (целочисленный тип), long (длинный целый тип) и float (число с плавающей точкой). Их
представление данных и реакция на арифметическое присваивание, операторы присваивания и сравнения определяются как часть языка. Однако не вся информация
об объектах реального мира может быть представлена с помощью этих ограничен-ных встроенных типов данных. Программисту часто приходится создавать свои
собственные типы данных, выделяя их в отдельный класс.
К примеру, можно задать пользовательский тип данных для представления дат.
Компилятору и компьютеру ничего не известно о датах. Программисты должны
сами определить поведение дат, разработав класс Date. Этот класс описывает формат даты и операции, которые можно с ней выполнять. Подобно тому как можно
декларировать множество переменных элементарного типа int, мы можем задать
множество объектов класса Date. Класс служит чертежом, черновым планом или
шаблоном. Он указывает, какие данные и какие функции будут включены в объекты этого типа. Определение класса не создает никаких объектов, так же как и суще-ствование типа int само по себе не создает никаких переменных.
Инкапсуляция
Инкапсуляцией называется размещение значений данных и функций внутри объекта. Предположим, что мы определяем пользовательский тип (класс) с именем Box и
создаем объекты b1 и b2 этого пользовательского типа. Затем эти объекты могут
инкапсулировать элементы данных, такие как length (длина), breadth (ширина) и height (высота) объекта типа Box, а также компонентные функции calcVolume( ) (Вычислить объем), calcSurfaceArea( ) (Вычислить площадь поверхности) и
displayDimensions( ) (Показать габариты).
Другим примером могут послужить объекты пользовательского класса с именем
Patient. Объекты этого класса могут инкапсулировать такие данные, как patientId (идентификатор пациента), name (имя), age (возраст), height (рост), weight (вес), а
также такие функции, как RecordData( ) (Сохранить данные), displayData( ) (Показать данные) и т. п.
Скрытие данных
С++ располагает механизмами, при помощи которых можно разрешать или запре-щать доступ к членам данных и компонентным функциям в объекте. Как правило, данные недоступны извне объекта. Доступ к ним возможен лишь через функции-члены, присутствующие в самом объекте. Процедуру, запрещающую доступ к данным извне объекта, часто называют скрытием данных.
Вы спросите, что это — запретительный подход, т. е. «запрещено всё, что не раз-решено»? Какая польза от членов данных, к которым нельзя получить доступ или
изменить их извне объекта? Всё не так страшно. На самом деле, если потребуется

30
Глава 1
получить доступ или изменить данные, вы всегда сможете сделать это регламенти-рованным способом, через функции-члены объекта, что позволяет исключить не-преднамеренный доступ и манипуляции с данными.
Наследование
ООП позволяет создавать собственные типы данных (пользовательские классы), подобные тем, что изначально встроены в язык. Однако, в отличие от встроенных
типов данных, пользовательские классы могут использовать в качестве своей основы другие классы.
Благодаря наследованию новые классы могут создаваться поверх старых. Новый
класс, называемый производным (дочерним) классом, может наследовать данные и
функции исходного или базового (родительского) класса. Новый класс может добавлять собственные элементы данных и функции дополнительно к тем, которые
он наследует от своего родительского класса.
Например, мы можем создать набор классов, описывающих библиотеку публикаций. Существуют два основных вида публикаций — периодика и книги. Мы можем
создать общий класс Publication (Публикация), определив элементы данных для
имени издателя, количества страниц и инвентарного номера. Публикации можно
найти по поиску, сохранить и прочесть. Это можно сделать посредством функций
класса Publication.
Затем мы можем задать два класса с именами Periodical (Периодическое издание) и Book (Книга). Оба этих класса будут производными от родительского класса
Publication. И это интуитивно понятно, поскольку периодическое издание и книга
имеют общие свойства: название издательства, количество страниц и инвентарный
номер.
Кроме того, периодическое издание также имеет номер тома, номер выпуска и содержит статьи, написанные разными авторами. Элементы данных для этих свойств
Рис. 1.3. Пример наследования
Введение в ООП
31
должны быть включены в определение класса Periodical. Классу Periodical также
понадобится функция subscribe (Подписаться).
Элементы данных для класса Book будут включать имя автора, тип обложки (твер-дая или мягкая) и номер ISBN. У этого класса также будет функция с именем
subscribe. Как видите, классы Book и Periodical разделяют ряд характеристик
с классом Publication, имея при этом собственные уникальные атрибуты. Эта схема
отображена на рис. 1.3.
Полиморфизм
Развивая наш пример с классами Publication, Periodical и Book, давайте разберем
еще одно ключевое понятие. Наш родительский класс Publication определяет методы для хранения и извлечения данных. Периодическое издание может храниться
в папке для журналов, а книга обычно ставится на полку.
Кроме того, процедура поиска определенного периодического издания отличается
от поиска книги. Периодика ищется по указателю периодической литературы, а
книги — по картотечному каталогу. Основываясь на этом, мы можем разработать
функцию «поиск по периодической литературе» для периодики и функцию «поиск
по картотечному каталогу» для книг. Для решения подобных задач ООП располагает элегантным средством, именуемым полиморфизм.
В нашем примере метод поиска периодического издания отличается от метода поиска книги, хотя конечный результат одинаков. Полиморфизм позволяет нам задать
функцию для поиска публикации, которую можно применить как для периодики, так и для книг. При поиске периодики задействуется функция выборки, специфич-ная для периодического издания, а когда запрашивается книга, подключается
функция поиска по книжному каталогу. В итоге функция с одним именем может
использоваться для одной и той же операции, выполняемой над связанными производными классами, даже если реализация этой функции варьируется от класса
к классу. Понятие полиморфизма (одного явления, принимающего различные формы) может быть распространено даже на операторы, как мы увидим в следующих
главах.
Отношения включения
В обычном супермаркете каждый товар может быть представлен как класс. Товары, в свою очередь, относятся к разным категориям, таким как косметика, продукты
питания, прохладительные напитки, одежда, книги, электробытовая техника и т. п.
Взаимодействие категорий и классов может быть выражено посредством отношений включения. Например, такие объекты, как крем для лица, очищающий гель, шампунь, могут быть включены в объект категории с именем «косметика». Отношения включения можно обнаружить во многих практических задачах.
32
Глава 1
Шаблоны
Иногда при создании программы возникает необходимость в нескольких похожих
функциях или классах, код которых отличается лишь типом обрабатываемых данных. К примеру, вам может понадобиться набор функций abs( ), одна из которых
возвращает абсолютное значение целого числа (с типом int), другая — абсолютное
значение числа с плавающей точкой (с типом float) и т. д. Вместо того чтобы задавать и сопровождать этот набор функций, C++ позволяет в таких ситуациях воспользоваться инструментом под названием Template (Шаблон) для создания еди-ной универсальной функции. И тогда при необходимости компилятор будет генерировать различные версии этой функции для работы с данными разных типов.
Аналогичным образом вы можете представить универсальный шаблонный класс
с именем Stack (Стек). Из этого универсального класса компилятор может сгенерировать специальные классы — один для поддержки стека целых чисел, другой для
поддержки стека чисел с плавающей точкой и т. д.
Обработка исключений
Надежная программа должна быть способна предвидеть аппаратные и программные ошибки или непредусмотренные события, которые могут возникать во время
ее выполнения, и уметь их обрабатывать. Например, аппаратная ошибка может
возникнуть при доступе к файлу или базе данных, обращении к принтеру или другому устройству, а программная ошибка может возникнуть, если выполняется
деление на ноль. Это непредвиденные ситуации, и поэтому они удачно названы ис-ключениями.
С++ позволяет обрабатывать эти исключения средствами объектно-ориентированного программирования. Для этого мы создаем объект-исключение, помещаем
в него соответствующие данные об условиях ошибки, а затем генерируем этот объект-исключение (при помощи ключевого слова throw). Затем это сгенерированное
исключение может быть перехвачено в другой части кода (при помощи ключевого
слова catch) и соответствующим образом обработано.
Многократное использование
Объектно-ориентированные программы состоят из повторно используемых программных компонентов. После того как окончательный вариант класса создан и
протестирован, его можно передать другим разработчикам для использования в их
собственных программах. Это называется возможностью многократного (повторного) использования. Если другие программисты хотят добавить новые функции
или модифицировать уже имеющиеся, на основе существующих классов могут
быть созданы новые. Многократно испытанный на практике функционал базовых
классов не требует повторной разработки. Программисты могут посвятить свое
время написанию нового программного кода вместо того, чтобы тратить его на
переписывание существующего. В результате программное обеспечение легче тес-

Введение в ООП
33
тировать, поскольку ошибки программирования могут быть изолированы в новом
коде производных классов.
Представьте, что у вас есть написанный вами (или приобретенный у кого-то) класс, создающий систему меню. Вас устраивает работа этого класса и вы не хотите его
менять, но хотите добавить возможность отображения справки для каждого пункта
меню. Для этого вы просто создаете новый класс, который наследует все возможности существующего, но добавляет функцию справки. Эта простота многократного использования существующего программного обеспечения является основным
преимуществом ООП.
1. Укажите, верны ли следующие утверждения:
Объектно-ориентированное программирование позволяет многократно использовать существующий код.
В языках, предшествовавших процедурным языкам программирования, применялись только глобальные переменные.
В объектно-ориентированных языках программирования единственный способ
передать управление потоком инструкций из одной части кода в другую — это
использовать оператор goto.
Разрабатывать, понимать и сопровождать программы легче, если они используют модель объектно-ориентированного, а не структурного программирования.
В процедурном программировании данным не придается должного значения по
сравнению с процедурами.
Модель структурного программирования не позволяет сформулировать задачи
из реальной жизни настолько хорошо, как модель объектно-ориентированного
программирования.
Модель объектно-ориентированного программирования допускает функцио-нальную абстракцию, а также абстрагирование данных.
Класс позволяет создавать пользовательские типы данных.
Объекты относятся к классам так же, как переменные относятся к встроенным
типам данных.
Возможность создания нового класса из существующего способствует многократному использованию программного кода.
Инкапсуляция позволяет использовать функцию с одним именем для одной и
той же операции, выполняемой над связанными дочерними классами.
Благодаря полиморфизму реализация функций с одинаковыми именами может
варьироваться от класса к классу.
Может быть создано множество объектов, принадлежащих одному классу.
34
Глава 1
2. Заполните пропуски:
Двумя основными компонентами объекта являются ______ и ______.
Способность функции или оператора действовать по-разному с разными типами
данных называется ________.
Процесс создания новых классов из существующих известен как _______.
Если класс A наследует свои свойства от класса B, то A и B называются _______
класс и ______ класс соответственно.
Pascal и C — это _______ языки, а С++ — это _______ язык.
3. Установите соответствия между объектами:
(A) ООП
(1) Класс
(B) Структурное программирование (2) Универсальные функции/класс
(C) Пользовательский тип данных
(3) Базовый класс, производный класс
(D) C, Pascal
(4) Данные и функции
(E) Содержимое объектов
(5) Взаимодействие объектов
(F) Наследование
(6) Языки структурного программирования
(G) Шаблоны
(7) Языки ООП
(H) С++, Java
(8) Исключения
(I) Отношения
включения
(9) Взаимодействие
функций
(J) int, float
(10) Пользовательские типы данных
(K) Сотрудник, студент
(11) Стандартные типы данных
(L) Обработка ошибок в ООП
(12) Вложенные объекты
4. Ответьте на следующие вопросы:
В чем основное различие между моделью структурного программирования и
моделью объектно-ориентированного программирования?
В чем различие между классом и объектом?
Приведите не менее 5 примеров классов и объектов.
Что такое инкапсуляция?
Что означает наследование?
Что такое полиморфизм?

Введение в ООП
35
Структурное программирование ставит во главу угла взаимодействие функций.
Объектно-ориентированное программирование ставит во главу угла взаимодействие объектов.
Класс — это пользовательский тип данных, на основе которого создаются объекты.
Объект содержит определенные значения данных, а также функции, которые
могут обращаться к ним и/или манипулировать ими.
Члены данных и функции-члены инкапсулированы в объект.
Скрытие данных означает отказ в прямом доступе к данным извне объекта.
Наследование позволяет наследовать свойства базового класса производным
классом.
Полиморфизм позволяет использовать одну и ту же функцию для специфичных
реализаций в разных производных классах.
Отношения включения позволяют вкладывать объекты.
Шаблоны позволяют писать универсальные функции/классы, оставляя компилятору возможность генерировать их версии, соответствующие конкретным требованиям.
Исключения С++ позволяют обрабатывать ошибки при выполнении программы
средствами ООП.
С++ поддерживает многократное использование кода при помощи механизмов
наследования, отношений включения и шаблонов.

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

38
Глава 2
Комментарии
Ввод и вывод в С++
Динамическое объявление переменных
Вывод типов
Синтаксис структуры (struct), объединения (union)
и перечисления (enum)
Неименованные объединения и перечисления
Приведение типов
Пустой указатель (void)
Оператор ::
Ссылки
• Типы обращений к функциям
• Возвращение значения по ссылке
Спецификатор const
• Const-указатели
• Const -ссылки
• Возврат значений const-переменных
• Функции-члены типа const
Логический тип данных (bool)
Упражнения
Важное
Переходим на C++
39
ак и язык C, С++ — это детище корпорации Bell Labs. Он был разработан
Бьёрном Страуструпом в 1979 году. Страуструп взял C как основу в силу его
Ккраткости и широкой распространенности. Как следствие, любая программа
на C будет корректно выполняться на С++. И все типы данных, команды управления, функции, массивы, строки, структуры, объединения, перечисления enum и указатели работают в С++ точно так же, как и в C. Из чего следует, что знание C при-годится при переходе на С++.
С момента выхода первоначальной версии С++ претерпел ряд существенных редакций. Важными вехами в эволюции языка стали версии С++98, С++11, С++14 и
С++17. Все программы в этой книге работают с компиляторами, совместимыми
с С++17, такими как VisualStudio или GCC.
Основная цель создания С++ — сделать так, чтобы программисту стало легче и
проще создавать отличные программы. Для ее достижения Страуструп добавил
к языку C функции ООП без существенных изменений самого языка. Таким образом, С++ по сути является расширенной версией C. Для поддержки функций ООП
в С++ был введен ряд новых ключевых слов, представленных в табл. 2.1.
Таблица 2.1. Новые ключевые слова в С++
asm explicit
private
true
bool false
protected
try
catch friend
public
typeid
class inline
reinterpret_cast
typename
const_cast mutable
static_cast
using
decltype namespace
template virtual
delete new
this
wchar_t
dynamic_cast opertaor
throw
В последующих главах мы обсудим эти ключевые слова и объектно-ориентированные средства С++. А в этой главе мы рассмотрим необъектно-ориентированные расширения, предусмотренные в С++. И начнем с комментариев к коду
в С++.
Комментарии
В отличие от C, С++ поддерживает два типа комментариев. Программистам на
языке C известен стиль комментирования /* */. Все, что размещено внутри пар
символов /* и */, игнорируется компилятором. Кроме того, С++ поддерживает для
комментариев обозначение //.
К примеру:
c = 5.0 / 9 * ( f - 32 ) ; // формула преобразования
40
Глава 2
Все, что следует после // и до конца строки, интерпретируется как комментарий.
Как правило, стиль /* */ используется для того, чтобы закомментировать много-строчный блок программного кода, тогда как стиль // служит для создания однострочных комментариев.
Ввод и вывод в C++
Рассмотрим следующую инструкцию:
cout << "Боже, ниспошли мне толику здравомыслия!" ; Она выводит на экран фразу, заключенную в кавычки. Полное описание этой
команды требует понимания объектов, перегрузки операторов и других тем, которые мы рассмотрим далее, а здесь приведем лишь краткий обзор.
В действительности идентификатор cout (произносится как «си-аут») является
объектом. Он предопределен для стандартного потока вывода. Поток является
абстракцией, относящейся к потоку данных. Стандартный вывод обычно осуществляется на экран монитора, хотя его можно перенаправить на другие устройства вывода. Мы обсудим потоки в главе 9.
Символ << является оператором вывода данных. Он направляет содержимое переменной, расположенной справа, объекту слева от него. В нашем примере он передает строковую константу «Боже, ниспошли мне толику здравомыслия!» иденти-фикатору cout, который выводит ее на экран.
Если вам знаком язык C, вы без труда узнаете в << оператор сдвига влево и спросите в удивлении, разве он предназначен для прямого вывода? В С++ операторы
могут быть перегруженными. Иными словами, они могут выполнять различные
действия в зависимости от контекста. Мы рассмотрим перегрузку в главе 4.
Хотя концепции, лежащие в основе работы cout и <<, на данный момент могут показаться вам несколько туманными, их несложно применять. Они будут появляться
почти в каждом листинге этой книги. Теперь, когда мы знаем, как можно осуществить вывод на экран, посмотрим, как считать ввод с клавиатуры.
Для получения ввода с клавиатуры используется идентификатор cin. Слово cin (произносится как «си-ин») — это объект, предопределенный в С++ в качестве
стандартного потока ввода. Этот поток представляет данные, поступающие с клавиатуры (если они не были перенаправлены). Символ >> является оператором ввода
данных. Он берет значение из объекта потока, расположенного слева, и присваивает его переменной справа от него.
Следующий пример иллюстрирует работу cout и cin.
#include <iostream>
using namespace std ;
int main( )
{
char str[ 40 ] ;
int m1, m2, m3, avg ;
Переходим на C++
41
cout << "Введите ваше имя: " ;
cin >> str ;
cout << "Введите отметки по трем предметам: " ;
cin >> m1 >> m2 >> m3 ;
avg = ( m1 + m2 + m3 ) / 3 ;
cout << "Вас зовут " << str ;
cout << endl << "Ваш средний балл " << avg << endl ; return 0 ;
}
Ниже приведен пример работы с программой:
Введите ваше имя: Saurav Gupta
Введите отметки по трем предметам: 44 72 64
Вас зовут Saurav Gupta
Ваш средний балл 60
В первой строке программы мы подключили файл ‘iostream’. В отличие от C, заго-ловочные файлы С++ не имеют расширения .h.
Файл ′iostream′ содержит декларации, необходимые идентификаторам cout и cin, а также операторам << и >> . Без этих деклараций компилятор не распознает cout и
cin и посчитает, что << и >> используются некорректно.
Следующая строка, using namespace std;, присутствует во всех программах этой
книги. Чтобы разобраться со значением этой инструкции, нужно прежде всего понять, что такое пространство имен (namespace). Пространство имен есть не что
иное, как набор идентификаторов (имен переменных, а также имен некоторых других типов, которые мы еще не обсуждали), принадлежащих одной группе или се-мейству. К примеру, все идентификаторы в стандартной библиотеке С++ принадлежат пространству имен с названием std.
Существуют два способа сослаться на конкретный идентификатор, принадлежащий
некоему пространству имен:
Использовать команду using в начале программы, как мы сделали это в нашем
примере.
Указать перед идентификатором имя пространства имен в виде префикса, за которым следуют два двоеточия, как показано ниже:
std::cout << "Введите ваше имя: " ;
Естественно, первый способ куда лучше, чем предварять каждый cout, cin и endl префиксом std::.
Использование первой пары cout и cin в нашей программе достаточно прозрачно.
Так, cout выводит сообщение с запросом имени, а cin присваивает это имя строке
str[ ].
Обратите внимание на многократное использование оператора ввода >> во второй
команде cin. Это абсолютно корректная конструкция, известная как каскадирование операторов. Она избавляет нас от необходимости три раза повторять оператор
42
Глава 2
cin. Тем не менее ее использование исключает возможность отображения подсказ-ки в процессе ввода данных. Как вы должно быть уже догадались, также допускается каскадирование операторов вывода.
Взгляните на последнюю команду с cout. Вы обнаружите, что в этой инструкции
используется незнакомое слово, endl. Это манипулятор, вставляющий перевод
строки в поток вывода. Фраза, следующая за ним, начинается с новой строки.
Можно сделать то же самое, отправив символ новой строки "\n" в поток вывода, воспользовавшись любой из следующих инструкций:
cout << "\n" << "Ваш средний балл " << avg ; cout << '\n' << "Ваш средний балл " << avg ; cout << "\nВаш средний балл " << avg ;
Все эти команды взаимозаменяемы. Тем не менее использование манипулятора
endl является, пожалуй, самым прозрачным.
А сейчас вопрос на миллион! Дало ли нам что-либо применение cin и cout вместо
scanf( ) и printf( )? Безусловно! В scanf( ) и printf( ) необходимо указывать спецификаторы формата в строке формата (%d для целого числа, %s для строки и т. д.), чего можно полностью избежать, применив cin и cout. Кроме того, как мы узнаем
в главе 9, операторами cin и cout можно воспользоваться и для ввода-вывода пользовательских типов данных.
Динамическое объявление переменных
Языку C не хватает гибкости в том, что касается объявления переменных. В нем
требуется, чтобы все переменные были объявлены перед первой исполняемой инструкцией. Напротив, C++ позволяет задавать переменные в той части кода, где они
применяются. Это хорошо видно на следующем примере:
#include <iostream>
using namespace std ;
int main( )
{
int f ;cin >> f ;
int c = ( f - 32 ) * 5 / 9 ;
cout << c ;
for ( int j = 10 ;j <= 100 ;j++ ) cout << endl << j << endl ; return 0 ;
}
Заметьте, что переменные c и j объявлены не в начале функции main( ), а там, где
они используются для присвоения значения. Объявление переменных там, где они
используются, облегчает понимание программы, поскольку не требуется повторно
обращаться к началу функции, чтобы найти объявления переменных. Однако при-бегать к подобной практике следует с осмотрительностью. Возможно, те переменные, что используются в разных частях функции, лучше объявлять в ее начале.
Переходим на C++
43
Динамическая инициализация
В C++ есть целый ряд способов инициализации переменной. Продемонстрируем их
на примерах:
int age1 = 32 ;
int age2 ( 32 ) ;
int age3 { 32 } ;
Все три способа инициализации переменных, приведенные выше, абсолютно корректны и эквивалентны друг другу. Каждая из этих инструкций присваивает переменной значение 32.
Вывод типов
Компилятор C++ также может определить тип переменной по присваиваемому ей
значению. Для этого необходимо указать auto в качестве спецификатора типа для
переменной, как показано ниже:
auto age1 = 32 ;
auto age2 = age1 ;
Поскольку 32 — это целочисленный тип (int), предполагаемым типом для переменной age1 станет int. По аналогии коль скоро age1 имеет значение с типом int и
присваивается переменной age2, то предполагается, что age2 также является целым
числом.
Необходимо ли нам инициализировать переменную, чтобы компилятор мог корректно определить ее тип? Нет, инициализировать переменную не нужно. Мы можем сделать это следующим образом:
char ch ;
decltype ( ch ) dh;
Здесь тип переменной dh будет таким же, как тип переменной ch.
Функции auto и decltype были добавлены в язык не так давно. Мы привели их
в весьма упрощенных примерах. Их ценность обнаруживается в тех случаях, когда
тип данных не может быть установлен другими способами.
Синтаксис структуры (struct),
объединения (union) и перечисления (enum)
Рассмотрим пример:
struct employee
{
char name[ 20 ] ;
int age ;
} ;
44
Глава 2
union data
{
char ch[ 2 ] ;
int i ;
} ;
enum status { married, unmarried, divorced } ;
employee e ;
data d ;
status s ;
Обратите внимание на то, как заданы переменные e, d и s. В отличие от C, при объявлении переменных e, d и s язык C++ не нуждается в ключевых словах struct, union и enum. Таким образом, формат для объявления структурной переменной
точно такой же, как формат, используемый для переменной встроенного типа данных, такого как int:
int i ;
employee e ;
Сходство отнюдь не случайно. Одна из задач C++ — организовать работу с пользовательскими типами данных по аналогии со встроенными типами данных.
Есть еще одно существенное различие между структурами и объединениями в C++
и C. Структуры и объединения C++ могут содержать в качестве своих элементов не
только различные типы данных, но и функции. Мы рассмотрим этот аспект структур и объединений в главе 5, когда начнем изучать классы в С++.
Неименованные объединения
и перечисления
C++ позволяет использовать неименованные объединения (union) и перечисления
(enum). У неименованного объединения нет имени (тега), и к его элементам можно
обращаться напрямую, без использования переменной объединения. Проиллюстрируем это на следующем примере:
union
{
int i ;
char ch[ 2 ] ;
} ;
Переменная i и массив ch[ ] используют одни и те же ячейки памяти, и к ним можно обратиться напрямую, просто написав:
i = 10 ;
ch[ 0 ] = 'A' ;
Аналогичным образом можно создавать неименованные перечисления:
enum { sleeper, acthreetier, actwotier, acfirstclass } ;
int bogietype = actwotier ;
Переходим на C++
45
Неименованное объединение полезно, когда оно вложено в структуру, как на примере ниже:
struct employee
{
char name[ 20 ] ;
union
{
int emptype ;
char grade[ 4 ] ;
} ;
} ;
int main( )
{
struct employee e ;
e.emptype = 3 ;
return 0;
}
В этой декларации emptype и grade занимают одни и те же ячейки памяти и допускают прямой доступ. Он возможен благодаря неименованному объединению. Если
бы объединение было именованным, то для обращения к emptype или grade нам
пришлось бы использовать несколько операторов.
Классы потокового ввода-вывода, представленные в главе 9, используют целый ряд
неименованных перечисляемых типов.
Приведение типов
Если мы выполняем операцию между целым числом (int) и числом с плавающей
точкой (float), то перед ее выполнением целое число конвертируется в число с плавающей точкой. Это преобразование происходит автоматически. В языках C и C++
возможен и ряд других автоматических преобразований. В качестве своего рода
противовеса для выполнения необходимых программисту преобразований данных
используется приведение (или явное преобразование) типов. В C++ поддерживают-ся две разновидности синтаксиса приведения типов. Проиллюстрируем их на примере:
int y = 1001, j = 365, n ;
n = ( y - 1 ) * ( long ) j ; // Стиль приведения типов C, также поддерживаемый в C++
n = ( y - 1 ) * long ( j ) ; // Новый стиль приведения типов в C++
C задействует лишь первый стиль приведения типов, тогда как в C++ применимы
оба. Вместо явного преобразования типов мы могли бы также объявить j как переменную типа long. В данном фрагменте кода это вполне уместно. Однако если
в дальнейшем мы намереваемся использовать j как переменную типа int, альтернативы приведению типов нет.
46
Глава 2
Пустой указатель (void)
Ключевое слово void может использоваться в качестве указателя на обобщенный
термин (т. е. объект неизвестного типа). В отличие от языка C, в C++ необходимо проявлять особую осторожность при присваивании указателей void другим
типам указателей. Приведем пример возможной ошибки:
void *p ;
char *s ;
p = s ;
s = p ;
В этом фрагменте кода второе присваивание выдаст ошибку, указывающую на несоответствие типа. Вы можете присвоить указатель любого типа указателю void, однако, чтобы сделать наоборот, необходимо явным образом привести указатель
к соответствующему типу, как показано ниже:
s = ( char * ) p ;
Оператор ::
Оператор расширения области :: разрешает конфликт в области видимости, когда
существуют локальные и глобальные переменные с одинаковыми именами. Посмотрим, как это выглядит на практике:
#include <iostream>
using namespace std ;
int num = 10 ;
int main( )
{
int num = 15 ;
cout << "Локальная num = " << num << endl ; cout << "Глобальная num = " << ::num << endl ;
::num = 20 ;
cout << " Локальная num = " << num << endl ; cout << " Глобальная num = " << ::num << endl ; return 0 ;
}
Результат работы программы:
Локальная num = 15
Глобальная num = 10
Локальная num = 15
Глобальная num = 20
Обратите внимание, что у нас есть две переменные с одинаковым именем num.
Одна из них глобальная, а другая — локальная. Благодаря оператору расширения
области :: обеспечивается гибкость доступа к обеим переменным.
Переходим на C++
47
Так, в приведенном выше листинге при помощи num мы получаем доступ к локальной переменной num, тогда как ::num позволяет обратиться к глобальной
переменной с тем же именем. Результат работы программы показывает, что мы
можем не только обратиться к глобальной переменной и получить ее значение, но
даже присвоить ей новое значение.
Ссылки
Ссылки весьма схожи с указателями. Есть, разумеется, и отличие, но с ним мы разберемся чуть позже. А сначала напишем программу с использованием ссылки:
#include <iostream>
using namespace std ;
int main( )
{
int i = 10 ;
int &j = i ;
cout << "i = " << i << " j = " << j << endl ; j = 20 ;
cout << "i = " << i << " j = " << j << endl ; i = 30 ;
cout << "i = " << i << " j = " << j << endl ; i++ ;
cout << "i = " << i << " j = " << j << endl ; j++ ;
cout << "i = " << i << " j = " << j << endl ; cout << "Адрес i = " << &i << "адрес j =" << &j << endl ; return 0 ;
}
Результат исполнения кода:
i = 10 j = 10
i = 20 j = 20
i = 30 j = 30
i = 31 j = 31
i = 32 j = 32
Адрес i = 61342
Адрес j = 61342
В этом листинге j является ссылкой на переменную i. Ссылка объявляется при помощи оператора & (амперсанд) подобно тому, как оператор * (звездочка) применяется для объявления указателя. Ссылка содержит адрес переменной, на которую
она ссылается. Ссылка отличается от указателя лишь тем, что для доступа к значению, на которое указывает ссылка, не нужно использовать оператор *. Иными словами, ссылка автоматически разыменовывается.
48
Глава 2
Поэтому всякий раз, когда мы обращаемся в нашей программе к j, на самом деле
используется *j, с того самого момента, как ссылка j связывается с переменной i.
Поскольку j содержит адрес i, *j возвращает значение i. Вот почему, когда мы пытаемся присвоить j значение 20, оно присваивается *j, т. е. переменной i. Аналогичным образом, когда мы инкрементируем (увеличиваем) j, происходит приращение *j, т. е. переменной i. Более того, когда мы пытаемся вывести адрес j, выводится адрес *j, т. е. адрес i. Сопоставьте это с результатом исполнения кода, приведенного выше.
Ниже несколько важных моментов использования ссылок:
Ссылка должна быть инициализирована.
Поэтому, например, следующий набор инструкций приводит к ошибке:
int i = 4 ;
int &j ; // ошибка
j = i ;
После связывания ссылки с определенной переменной она не может ссылаться
ни на какую другую переменную.
Иными словами, с момента инициализации ссылка и переменная неразрывно
связаны друг с другом.
Можно создать ссылку на целочисленный тип данных (int), тип данных с плавающей точкой (float) или символьный тип данных (char), а также ссылку на
указатель. Объявление такой ссылки выглядит следующим образом:
char *p = "Привет" ;
char *&q = p ;
Допускается множество ссылок на одну переменную. Изменение значения
любой из них влечет за собой изменение всех остальных.
В С++ допустим массив указателей, а вот массив ссылок — нет (то самое отличие, о котором говорили в начале раздела).
Типы обращений к функциям
В программировании на C возможны два типа обращений к функциям — вызов по
значению и вызов по адресу. В C++ существует еще один тип обращений к функциям. Он известен как вызов по ссылке. Он работает так же, как и вызов по адресу.
В обоих случаях мы можем изменять значения фактических аргументов при помощи формальных аргументов, используемых в вызываемой функции. В то же время
код вызова по ссылке намного прозрачнее, что делает его более читабельным:
# include <iostream>
using namespace std ;
// объявления прототипов
void swapv ( int, int ) ;
Переходим на C++
49
void swapr ( int &, int & ) ;
void swapa ( int *, int * ) ;
int main( )
{
int a =10, b = 20 ;
swapv ( a, b ) ; // вызов по значению
cout << endl << a << "\t" << b ;
swapa ( &a, &b ) ; // вызов по адресу
cout << endl << a << "\t" << b ;
swapr ( a, b ) ; // вызов по ссылке
cout << endl << a << "\t" << b << endl ; return 0 ;
}
void swapv ( int i, int j )
{
int t ;
t = i ;
i = j ;
j = t ;
cout << i << "\t" << j ;
}
void swapa ( int *i, int *j )
{
int t ;
t = *i ;
*i = *j ;
*j = t ;
}
void swapr ( int &i, int &j )
{
int t ;
t = i ;
i = j ;
j = t ;
}
В этом листинге функция swapv( ) вызывается по значению. Обмен значениями i и j в этой функции не влияет на значения a и b в глобальной функции main( ).
Функция swapa( ) демонстрирует обращение по адресу, где при помощи указателей
мы изменяем значения a и b в вызывающей функции. Функция swapr( ) выполняет
задачу, аналогичную функции swapa( ), с той лишь разницей, что эта функция вызывается по ссылке.
Согласитесь, что применение ссылок для обмена значениями выглядит более элегантным и приятным для глаз. Кроме того, указатель должен быть разыменован, прежде чем вы сможете с его помощью получить доступ к значению, в то время как
50
Глава 2
для ссылки это не обязательно. В этом смысле ссылка действует более прямоли-нейно. Синтаксис вызова функции swapr( ) также намного проще в сравнении
с вызовом swapa( ).
Таким образом, ссылки представляют собой ясный, элегантный и эффективный
способ передачи аргументов функциям, в которых предполагается изменение их
значений.
Вызов функции по ссылке полезен еще в одном случае. При вызове по значению
в вызываемой функции создается копия передаваемых аргументов. Если размер
аргументов велик, это приводит к пустой трате драгоценного пространства памяти.
Тогда как при обращении по адресу или по ссылке копия передаваемых аргументов
не создается. Это связано с тем, что в обоих случаях передаются лишь адреса. Ниже
приведена программа, демонстрирующая три типа обращений к функциям.
#include <iostream>
using namespace std ;
struct employee
{
char name[ 20 ] ;
int age ;
float salary ;
} ;
void displayVal ( employee ) ;
void displayAddr ( employee * ) ;
void displayRef ( employee & ) ;
int main( )
{
employee e = { "Sanjay", 32, 3200.00 } ;
displayVal ( e ) ;
displayAddr ( &e ) ;
displayRef ( e ) ;
return 0 ;
}
void displayVal ( employee e )
{
cout << e.name << endl << e.age << endl << e.salary << endl ;
}
void displayAddr ( employee *p )
{
cout << p->name << endl << p->age << endl << p->salary << endl ;
}
void displayRef ( employee &p )
{
cout << p.name << endl << p.age << endl << p.salary << endl ;
}
Переходим на C++
51
Из трех вызовов функций, используемых в этом примере, наилучшим является обращение по ссылке, т. к. оно позволяет избежать копирования аргументов, а также
использования указателей.
Возвращение значения по ссылке
Мы уже знаем, как передать ссылку функции в качестве ее аргумента. Мы также
умеем возвращать ссылку из функции. Когда функция возвращает ссылку, вызов
функции может существовать в любом контексте, где может существовать ссылка, в том числе в левой части присваивания. Поясним это на следующем примере:
#include <iostream>
using namespace std ;
struct emp
{
char name[ 20 ] ;
int age ;
float sal ;
} ;
emp e1 = { "Амол", 21, 2345.00 } ;
emp e2 = { "Эджей", 23, 4500.75 } ;
emp &fun( ) ;
int main( )
{
fun( ) = e2 ;
cout << e1.name << endl << e1.age << endl << e1.sal << endl ; return 0 ;
}
emp &fun( )
{
cout << e1.name << endl << e1.age << endl << e1.sal << endl ; return e1 ;
}
В этой небольшой программе мы объявили структуру emp и инициализировали две
глобальные переменные e1 и e2, присвоив им некоторые значения. Выведя в функции fun( ) значения, хранящиеся в e1, мы вернули их по ссылке. Необычен здесь
сам вызов функции fun( ). Он происходит в левой части оператора присваивания: fun( ) = e2 ;
Функция fun( ) возвращает значение переменной e1 по ссылке. Этой ссылке присваивается значение e2. Поскольку ссылка автоматически разыменовывается, e2
присваивается e1. Вывод оператора cout в main( ) подтверждает, что присваивание
и правда имело место.
52
Глава 2
В результате выполнения программы мы получим:
Эджей
23