Книга: С++ — это просто. — 3-е изд.: Пер. с англ.
На главную: Предисловие
Дальше: Const-указатели

Яшавант Канеткар

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

Дальше: Const-указатели