Академический Документы
Профессиональный Документы
Культура Документы
Второе издание
А Beginner's Guide
Second Edition
HERBERT SCHILDT
МсGга\\'-Нill/0sЬогпе
J'\C\\' \"OJ'k
Cllicago SaIl r:raIlCISCO
Lj,IЮII 1\1;JIJI'i(\ Мсхн;о Cil\' Мilап
L,1'11I\01l
;-\l'\" Dcll11 ~ап.lШJl SCOII\ Sll1gal"JI'(" SY(\IlC\' ')urUlllU
РУКОВОДСТВО дпя начинающих
Второе издание
ГЕРБЕРТ шилдт
•
!\10СКВ(\ • C<J.hkt-ПетсрGург
2005
• KIlt'B
ББК 32.973.26-018.2.75
Ш57
УДК 681.3.07
Шилдт, Герберт.
Ш57 С++: руководство для начинаlOЩИХ, 2-е издание. : Пер. сангл. - М. : Издатель-
ский дом "Вильямс", 2005. - 672 с. : ил. - Парал. тит. аш·л.
в этой книге описаны ocHoBныe средства языка С++, которые необходимо осво
ить начинающему программисту. После рассмотрения элементарных поWJТИЙ
(переменнblX, операторов, инструкций управления, функций, классов и объеК10В)
читатель легко перейдет к изучениlO таких БОJtее сложных тем, как перегрузка опе
раторов, механизм обработки исключительных ситуаций (искточений), наследова
ние, полиморфизм, виртуальные функции, средства ввода-вывода и шаблоны. Aв'rop
справочника - общепризнанный авторитет в области программирования на языках
С и С++, Java и С# - включил в свою книry множество тестов для саМОКОНТРОJIJl,
которые ПОЗВОJIJlЮТ быстро проверить степень освоеиия материала, а также раздcлы
"воnpосов и ответов", способствующие более глубокому изучения основ програм
мирования даже на начальном этапе.
ББК 32.973.26-018.2.7S
Об авторе 15
Введение 17
Модуль 1. Основы С++ 21
Модуль 2. Типы данных и операторы 69
Модуль З. Инструкции управления 109
Модуль 4. Массивы, СТрОI<И и указатели 157
Модуль 5. Введение в фУНI<ЦИИ 211
Модуль 6. О функциях подробнее 261
МОДУЛЬ 7. Еще о типах данных и операторах ЗОЗ
Модуль 8. I<лассы и объекты З49
Модуль 9. О классах подробнее З97
Модуль 10. Наследование 465
МОДУЛЬ 11. С++-система ввода-вывода 521
Модуль 12. Исключения, шаблоны и кое-что еще 571
Приложение А. Препроцессор 639
Приложение Б. Использование устаревшего С++-компилятора 65з
Предметный указатель 656
Содержание
Об авторе 15
Введение 17
Как организована эта книга 17
Практичсские навыки 1.8
Тсст для самоконтроля 18
Вопросы для текущего контроля 18
Спросим у опытного программиста 18
Учсбные проекты 18
Никакого предыдущего опыта в области программирования не требуется 18
Требуемое программное оБССllсчение 19
Программный код - из Web-пространства 19
Для далЫlсйшего изучения программирования 19
Модуль 1. Основы С++ 21
1.1. Из истории создания С++ 22
Язык С: начало эры современного программирования 23
Предпосылки ВОЗIШКlЮВСНИЯ языка С++ 24
Рождение С++ 25
Эволюция С++ 26
1.2. Связь С++ с языками Java и С# 27
1.3. Объектно-ориентированное программирование 29
Инкапсуляция 30
Полиморфизм 31
Наследование I
32
1.4. Создание, компиляция и выполнение С++-программ 32
Ввод текста программы 34
Компилирование программы 34
Выполнение программЬ! 35
Построчныii "разбор полетов" первого ПРИl\fера IJporpaMMbl 36
Обработка синтаксических ошибок 38
1.5. Использование нсремснных 1\0
1.6. Использование операторов 41
1.7. Сt"пываllие данных с клавиатуры 44
Вариации на тему вывода данных 46
Познакомимся еше с одним ТIlIЮМ даJlНЫХ 47
1.8. Использование IfНСТРУКЦИЙ управления if и (ог 52
С++: PYI<OBOACTBO для. начинающих 7
Инструкция if 52
Цикл for 54
1.9. Использование блоков кода 56
Точки с запятой и расположение инструкций 58
Практика отступов 59
1.10. Понятие о функциях 62
Библиотеки С++ 64
1.12. Ключевые слова С++ 65
1.13. Идентификаторы 66
Модуль 2. Типы данных и операторы 69
Почему типы данных столь важны 70
2.1. Типы данНЫХ С++ 70
Целочисленный тип 72
Символы 75
Типы данных с плавающей точкой 77
Тип данных Ьооl 78
Тип void 80
2.2. Литералы 83
Шестнадцатеричные и восьмеРИЧllые литералы 84
Строковые литералы 84
Управляющие символьные последователыfстии 85
2.3. Со:щаиие инициализироваlfНЫХ переменных 87
Инициализация псременной 87
Динамическая инициалllзация переменных 88
Операторы 89
2.4. Арифметические операторы 89
Инкремент и декремент 90
2.5. Операторы отношений и логические операторы 92
2.6. Оператор присваинания 98
2.7. Составные операторы приспаивания 99
2.8. П реобраэопание типов в операторах присваиваиия 100
Выражения 101
2.9. Прео6раэование типов в оыраЖСIlИЯ-Х 101
Пре06разооаIlИЯ, связанные с типом bool 1()2
2.10. Приведение тшlOВ 102
2.11. Использование пробелов If КРУГЛЫХ скобок 104
8 Содержание
учебными проектами, поэтому, про работав весь материал этой книги, вы получи
те глубокое понимание основ С++-программирования.
Я хочу подчеркнуть, что эта книга - лишь стартовая площадка. С++ - это
большой (по объему средств) и не самый простой язык программирования. Не
обходимым условием успешного программирования на С++ является знание не
только ключевых слов, операторов и синтаксиса, определяющего возможностн
пройденный материал.
18 Введение
Практические навыки
Все модули содержат рюдслы (отмеченные n и KTO!l)aM мой" Важно! "), которые
важно не ПРОПУСТИТЬ при lIзучеllИИ материала юшги. ИХ ЗШ\ЧИМОСТI. ДЛЯ IJОСЛ~
доватсльного освоения lюдчеркивается тем, что они про~умерованы и Ilеречис
Учебные проекты
Каждый модуль включает ОДИН [lJ111 нескоЛ\.ко просктов, которые 1l0ЮlзЫВ;t
ют, как Шl праКТlше можно прнмеНИТf> ИЗJlожеlIныi, здесь материал. ЭТII учебвые
Ilроекты представляют собой реальные примеры, КОТОРЫС можно IIСIЮЛI>ЗОВ"ТЬ JJ
качестве стартовых вариантов для ваших программ.
Требуемое программное
обеспечение
Чтобы СКОМrII1;IIIРol),l1'Ь и 8ЫПОЛIIIIТl. программы, приведенные в этой книге,
вам ПОllадобится любой 1'13 таких современных компиляторов, как Visual С++
(MicrosQft) JI С++ Builder (Borland).
Те, КТО желает подробнее изучить ЖIЫК С++, могут обратиться к следую
ЩИМ книгам.
• Полный сnравочнuк по С,
• Освой самостолmелыlO С.
Если вам нужны четкие ответы, обращ8ЙТеСЬ к ГербеJ7IY Шилдту, общепризнанво
му авторитету в области ПрОf1>аммирования.
От издательства
Вы, читатель этой КIIИrn, и есть главный ее критик и комментатор. Мы ценим
ваше мнение и хотим знать, что было сделано нами правильно, что можно было сде
лать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно УС.1Ы
шать и любые другие замечания, которые вам хотелось бы высказать в наш адрес.
Мы ждем ваших комментариев и надеемся t: а них. Вы можете при слать нам
бумажное или электронное письмо, либо просто посетить наш Web-сервер и
оставить свои замечания там. Одним словом, любым удобным для вас способо.\{
дайте нам знать, нравится или нст вам эта книга. а также выскажите свое мнение
о том, как сделать наши книги более интересными ДЛЯ вас.
Посылая письмо или сообщение, не забудьте указать название книги и ее авто
ров, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением
и обязательно учтем его при отборе и подготовке к изданию последующих книг.
Наши координаты:
E-таН: info@williamspublishing.c:)m
WWW: http://www.williamspublishing.com
Информация для писем из:
России: 115419, Москва, а/я 783
Украины: 03150, Киев, а/я 152
МОДУЛЬ 1
ОСНОВЫ С++
ВАЖНОI
,ММ ИЭИСТnРИId СО3ДnWИАu-С+* /
Рождение С++
Язык С++ был создан Бьерном Страуструпом (Bjame Stroustrup) в 1979 году
в компании ВеН Laboratories (г. Муррей-Хилл, шт. Нью-Джерси). Сиачала но
вый язык получил имя "С с классами" (С with Classes), но в 1983 году он стал
называться С++.
Страуструп построил С++ на фундаменте языка С, включающем все его
средства, атрибуты и основные достоинства. Для него также остается в силе
принцип С, согласно которому программист, а не язык, несет ответственность
за результаты работы своей программы. Именно этот момент позволяет понять,
что изобретение С++ не было попыткой создать новый язык программирова
ния. Это было скорее усовершенствование уже существующего (и при этом
весьма успешного) языка.
Большинство новшеств, которыми Страуструп обогатил язык С, было предна
значено для поддержки объектно-ориентированного программирования. По сути,
С++ стал объектно-ориентированной версией языка С. Взяв язык С за основу,
Страуструп подготовил плавный переход к ооп. Теперь, вместо того, чтобы из
учать совершенно новый язык, С-программисту достаточно было· освоить только
ряд новых средств, и он мог пожинать плоды использования 06ъектно-ориенти
рованной технологии программироваиия.
Создавая С++, Страуструп понимал, иасколько важно, сохранив изначальную
суть языка С, Т.е. его зффективность, гибкость и принципы разработки, внести в
него поддержку объеКТlю-ориеllТИРОВaJlНОl"O орограммирования. К счастью, эта
цель была достигнута. С++ по-прежнему предоставляет программисту свободу
действий и власть над компьютером (которые были присущи языку С), значи
тельно расширяя при этом его (программиста) возможности за счет использова
ния объектов.
26 Глава 1. Основы С++
Эволюция С++
С момента изобретения С++ претерпел т~,и крупные l1ереработки, причем
каждый раз язык как ДОПОJlllЯЛСЯ новыми средствами, так и в чем-то изменялся.
Первой ревизии он был подвергнут в 1985 году. второй - в 1990, а третья произо
шла в процессе стандартизации, который актюшзировался в начале 1990-х. Спе
циально для этого был сформирован объединенный АNSI/ISО-комитет (я был
его членом), который 25 января 1994 года принял первый проект предложенного
на рассмотрение стандарта. В этот проект БЫЛII включены все средства, впервые
определенные Страуструпом, и добавлены новые. Но в целом он отражал состоя
ние С++ на тот момент времени.
Вскоре после завершения работы над первым проектом стандарта С++ про
изошло событие, которое заставило значительно расширить существующий
стандарт. Речь идет о создании'Ллександром Степановым (Alexander Stepanov)
стандартной библиотеки шаблонов (Standar<\ Template Library - STL). Как
вы узнаете позже, STL - это набор обобщенных функций, которые можно ис
пользовать для обработки данных. Он доволыio большой по размеру. Комитет
ANSI/ISO проголосовал за включение STL в спеllИфикauию С++. добавление
STL расширило сферу рассмотрения средств С++ далеко за пределы исходнuго
определения ЯЗblка. Однако ВlUlючение STL. помимо прочего, замедлило процесс
стандартизации С++, причем довольно сущест)}еllllO.
Помимо STL, в сам ЯЗblК было добавлено н('сколько новых средств и внесено
множество мелких изменений. Поэтому верси.il С++ после рассмотрения коми
тетом по стандартизацЮ1 стала намного больше и сложнее по сравнению с ис
ходным вариантом Страуструпа. Конечный результат работы комитета датиру
ется 14 ноября 1997 года, а [lеалыlO ЛNSI/ISО-стандарт языка С++ увидел Сllет
в 1998 году. Именно эта спецификация С++ ОnЫЧJlО называется Standard С++.
и именно она описана в данной книге. Эта версия С++ поддерживается всеми
ОСНОВJlЫМИ C++-компилятораМII, включая Vistl<ll С-Н (Мiсrоsоft) и С++ Buildct·
С++: руководство для начинающих 27
ВАЖНОI
58 0 бьектнn-nр и еЫZИРОВQ нн ое
программирование
Основополагающими для разработки С++ стали принципы объектно-ори
ентированного программирования (ООП). Именно ООП явилось толчком для
создания С++. А раз так, то, прежде чем мы напишем самую простую С++-про
грамму, важно понять, что собой представляют принципы ООП.
Объектно-ориентированное программирование объединило лучшие идеи
структурированного с рядом мощных концепций, которые способствуют более
эффективной организации программ. В самом общем смысле любую программу
можно организовать одним из двух способов: опираясь на код (действия) или на
данные (информация, на l<ОТОРУЮ направлены эти действия). При использова
нии лишь методов структурированного программирования программы обычно
опираются на код. Такой подход можно выразить в использовании "кода, дей
ствующего на данные".
Механизм объектно-ориентированного программирования основан на выборе
второго способа. В этом случае ключевым принципом организации программ яв
ляется использование "данных, управляющих доступом !( коду". В любом 06ъек
тно-ориентированном языке программирования определяются данные и процеду
ры, которые разрешается применить к этим данным. Таким образом, тип данных в
точности определяет, операции какого вида можно примешпь к этим данным.
ИнкапсуМlЦИЯ
Инкапсуляция - это такой механизм программирования, который связывает
воедино код и данные, им обрабатываемые, чтобы обезопасить их как от внешне
го вмешательства, так и от неправильного исrlOJlьзоваиия. В объекпю-ориеНnf
рованиом языке код и данные могут быть связаны способом, при котором созда
ется самодостаточный черный ЯЩlJК. В этом "яшике" содержатся все необходимые
(для обеспечения самостоятельности) данные If код. При таком связывании кода
и данных создается объект, Т.е. 06ьекm - это КО:iСТРУКЦИЯ, котuрая поддерживает
инка.псу ляцию.
Внутри объекта код, данные или обе эти составляющие могут быть закРЫТЫ\fИ
в "рамках" этогообъекта или открытыми. Закрытый код (или данные) известен и
доступен только другим частям того же объекта. Другими словами, к закрытому
коду или данным не может получить доступ та часть программы, которая суще
ствует вне этого объекта. Открытый код (или данные) доступен любым другим
частям программы, даже если они определены [. других объектах. Обычно откры
тые части объекта используются для предоставления управляемого интерфейса
с закрытыми элементами объекта.
Базовой единицей инкапсуляции является класс. Класс определяет новый тип
данных, который задает формат 06ьекта. Класс' включает как данные, так и код,
предназначенный ДЛЯ выполнения над этими данными. Следовательно, класс
связы8em данные с "адом. В С++ спецификация класса используется для по-
С++: PYI(OBOACTBO ДЛ~ начинающих 31
ПолиморфИЗМ
Полиморфизм (от греческого <:Л.ова polym01p11ism, означающего "много форм") -
это свойство, позволяющее исnользовать один интерфейс для целого lUIacca дей
ствиii. В качестве простого примера полиморфизма можно привести руль автомоби
ля. Лля руля (т.е. шперфейса) безразлично, какой пш рулеоого механизма исполь
зуется u автомобиле. Другим словами, руль работает одинаково. не:iависимо от того,
оснащеli ли автомобиль рулевым управлением прямоro деЙСТВIIЯ (без усилителя),
рулевым управлением с УСИЛИТeJlем или механизмом реечной lIсредачи. Если вы
знаете, как обращаться с рулем, вы сможете вести автомоБИЛI, любого ТИП8.
ТОТ же IIРИНЦИП можно применить к программировClНИJO. Рассмотрим, напри
мер, стек, или список, добавление и удаление элементов к !{оторому осуществля
ется по ПРИIiЦИПУ "последним прибыл - пеРПl)IМ обслужен". У "ас может быть
программа, А которой используются три различных типа стека. Один стек пред
назначен ДЛЯ цеЛОЧJ1СЛСIШЫХ значений, второй - для значений с плавающей точ
кой и третий - для символов. AлrОРlfТМ реализации всех стеков - один и тот
же. несмотря на то, что 1:1 них хранятся данные различных типов. В необъектно
ориентированном языке программисту пришлось бы создать три различных ,.. а
бора подпрограмм обслуживания с.тека, причем подпрограммы должны были бы
иметь различные имена, а каждый набор - собственный интерфейс. Но благо
даря полиморфизму в С++ можно создать одии общий набор подпрограмм (один
интерфейс), который подходит для всех трех конкретных СИ1'УациЙ. Таким обра
зом, зная, как использовать один стек, вы можете использовать все остальные.
НаслеАование
Ншледованue - это процесс, благодаря которому один объект может при обретать
свойства дpyt·oгo. Благодаря наследованию ПОJl.ll.ерживается концепция иерархи
ческой классификации. В виде управляемой иерархической (нисходящей) класси
фикации организуется большинство областей знаний. Например, яблоки Красный
Делишее являются частью классификации яблоки, которая в свою очередь является
частью класса фрукты, а тот - частью еще большего класса пища. Таким образом,
класс пища обладает определеtШЫМИ качествами (съедобность, питательность и пр.),
которые применимы и к подклассу фрукты. Помимо этих качеств, класс фрукты
имеет специфические характеристики (сочность. сладость и пр.), которые отличают
их от других пищевых продуктов. В классе я6JЮКU определяются качества, специ
фичные для яблок (растут на деревЬЯХ, не тропические и пр.). Класс Красныйде:ш
шее наследует качества всех предыдущих классов и при этом определяет качес1'ва,
и выполнение C++-прОграмм
Пора приступить к программированию. Для этого рассмотрим первую про
стую С++-программу. Начнем с ввода текста, а затем перейдем к ее компиляции
и выполнению.
1*
Это простая С++-программа.
#include <iostrearn>
using namespace std;
return О;
}
Итак, вы ДОЛЖНЫ выполнить следующие действия.
2. Скомпилировать ее.
3. Выполнить.
34 Глава 1. Основы С++
Компилирование программы
Способ компиляции программы Sample. срр зависит от используемого ком
пилятора и выбранных ОIЩИЙ. Более того, многие компиляторы, например ViStlal
С++ (Мiсrоsoft) и С++ Builder (Borland), предоставляют два различных способа
компиляции программ: с помощью компилятора командной строки и интегриро
ванной среды разработки (lntegrated Development Environment - IDE). Поэтому
для компилирования С++-программ невозможно дать универсальные инструк
ции, которые подойдут для всех компиляторов. Это значит, что вы должны сле
довать инструкциям, приведенным в СОПРОВОДIfТельной документации, прилага
емой к вашему компилятору.
Но, если вы используете такие популярные компиляторы, как Visual С++
и С++ Builder, то проще всего в обоих случаях компилировать и выполнять про
граммы, приведенные в этой книге, с использованием компиляторов командной
С++: PYI<OBOACTBO для начинающих 35
Выполнение программы
Скомпилированная программа готова к выполнению. Поскольку результатом
работы С++-компилятора является выполняемый объектный код, то для запу
ска программы в качестве команды достаточно ввести ее имя в режиме работы
по приглашению. Например, чтобы выполнить программу Sample. ехе, исполь
зуйте эту командную строку:
/*
Это простая С++-программа.
#include <iostream>
С++: PYI<OBOACTBO Mt;\ начинающих 37
в языке С++ определеtI ряд зшоловков (header), которые обычно содержат ин
формацию, необходимую для программы. В нашу программу включен зaroловок
<iostream> (он используется ДЛЯ поддержки С++-системы ввода-вывода), ко
торый представляет собой внешний исходный файл, помещаемый компилятором
в начало программы с помощью директивы # incl ude. Ниже в этой книге мы
•
: +
: +
:U
::0
'111
:0
::1:
ближе познакомимся с заголовками и узнаем, почему они так важны.
:и
:0
Рассмотрим. следующую строку программы:
int main ()
Как сообщается в только что рассмотренном комментарии, именно с этой строки
и начинается выполнение программы.
функции. При нормальном завершении (т.е. без ошибок) все ваши программы
должны возвращать значение О.
Закрывающая фиrypная скобка в конце програММbJ формально завершает ее.
#include <iostream>
using namespace std;
int main ()
{
int length; // ~ Здесь об~.етCR переиеина•.
return О;
+
Как упоминалось выше, для С++-программ можно выбирать любые имена. +
u
Тогда при вводе текста этой программы дадим ей, скажем, имя VarDemo. срр. 2i
ID
О
Что же нового в этой программе? Во-первых, инструкция I:
(J
int length; // Здесь объявляется переменная. О
+ Сложение
Вычитание
* Умножение
/ Деление
// Использование оператора.
#include <iostream>
using narnespace std;
int rnain ()
{
int length; // Здесь объявляется п~ременная.
int width; / / Здесь объявляется В'l'орая переменная.
int area; // Здесь объявляется третья переменная.
return О;
#include <iostream>
using namespace std;
int main ()
{
int length; 11 Здесь объявляется переменная.
int width; /1 Здесь объявляется вторая переменная.
Хочу 06ратИ1:Ь ваше внимание на то, что с помощью одной и той же инструк
ции можно объявить не одну, а сразу несколько перемеНIiЫХ. Для этого достаточ
но разделить их имена запятыми. Например, переменные length, width и area
можно было бы объявить таким образом.
int length, width, area; 11 Все переменные объявлены в
// одной инструкции.
cin » vpr;
Здесь cin - еще ОДИН встроенный идентификаl0Р. Он составлен из частей слов
console input и автоматически померживается средстоами С++. По умолчанию
идентификатор cin связывается с клавиатурой, хотя ero можно перенаправИ1Ь
и на другие устроЙства. Элемент var означает переменную (указанную с правой
стороны от оператора "»"), которая принимает вводимые данные.
Рассмотрим новую версию программы вычисления площади, которая позво
ляет пользователю вводить размеры сторон прямоугольника.
/*
Интерактивная программа, которая вычисляет
площадь прямоугольника.
*/
iinclude <iostream>
int main () +
{
u+
:JS
11:1
int length: // Здесь объявляется переыеииая. О
:I:
Здесь объявляется вторая переме~ная.
int width: // 8
cout « "Введите длину прямоугольника: ":
cin » length: // ВВОД sначеВИR дnивu прсиоyr~RИXа
// (~.e. значеИИR аерекеввой lenqth)
/ / с 1UIaвKa~yp...
return О:
/*
в этой программе демонстрируется и,::пользование кода \п,
который генерирует переход на новую строку.
*/
finclude <iostream>
using namespace std;
int main ()
{
cout « "один\п";
cout « "два\п";
cout « "три";
cout « "четыре";
return О;
С++: руководство ДЛЯ начинающих 47
один
+
два
u+
тричетыре :i5
~
Символ новой строки можно размещать в любом месте строки, а не только в :r
конце. Вам стоит на практике опробовать различные варианты применения кода 8
\n, чтобы убедиться в том, что вам до конца понятно его назначение.
double result;
Здесь resul t представляет собой имя переменной, которая имеет тип
double.
Поэтому перемеииая resul t может содержать такие значеиия, как 88,56, 0,034
или -107,03.
Чтобы лучше понять разницу между типами дaJmыx iлt и double, рассмотрим
следующую npoграмму.
/*
Эта лрограмма иллюстрирует различия между типами
liлсludе <iostream>
using namespace std;
int mаiл () {
int ivar; // Здесь объявляется переменная типа int.
double dvar; // Здесь объявляется переменная типа double.
return О;
Последовательность действий
1. Создайте новый С++-фзйл с именем Ftc.M. срр. (Конечно, вы можете вы
брать для этого файла любое другое имя.)
#include <iostream>
using namespace std;
3. Начните определение функции main () с объявления переменных f и!'.1.
int main () {
double f; / / содержит длину в футах
double т; // содержит результат преобразоваНИR в метрах
6.
7.
Завершите программу следующим образом.
return О;
#include <iostrearn>
using narnespace std;
int rnain () {
double f; // содержит длину в футах
double т; // содержит результат преобразования в ме
трах
I
8. СJ,(омпилируйте и выполните программу. Вот как выглядит один из воз
можных результатов ее выполнения.
ИНСТРУКЦИЯ if
Можно избирательно выполнить часть программы, используя инструкцию
управления условием i f. Инструкция i f в С -\ + действует подобно инструкции
"IF", определенной в любом другом языке программирования (например C.Java
и С#). Ее простейший формат таков:
if(условие) инструкция;
Здесь элемент условие - это выражение, которое при вычислении может ока
заться равным значению ИСТИНА ИЛИ ЛОЖЬ. В С++ ИСТИНА представля
ется ненулевым значением, а ЛОЖЬ - нулем. Если условие, или условное вы
ражение, истинно, элемент инструкция выполнится, в противном случае - нет.
Равно
!= Не равно
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно
#include <iostream>
using namespace stdi
int main ()
int а, Ь, С;
а = 2;
Ь З;
cout « "\п";
cout « "\п";
return О;
а меньше Ь
Переменная с содержит 1.
Значение снеотрицательно.
ЦИКЛfоr
Мы можем организовать повторяющееся выполнение одной и той же после
дователыюсти инструкций с помощью специальной конструкции, именуемой
ЦUКЛОМ. В С++ предусмотрены различные ВИДЫ циклов, и одним из них являетея
цикл for. Для тех, кто уже не новичок в программировании, отмечу, что в С+ +
цикл for работает так же, как в языках С# или Java. Рассмотрим простейший
формат использования цикла for.
fоr(инициализация; условие; инкременr) инструкция;
внимание на то, что все эти элементы цикла for должны отделяться точкой с
запятой. Цикл for б~/:tет выполняться до тех пор, пока вычисление элемента
+
условие дает истинный результат. Как только это условное выражение ста
нет ложным, цикл завершится, а выполнение программы продолжится с ин
u+
25.
m
струкции, следующей за циклом for. О
1:
()
Использование цикла for демонстрируется в следующей программе. Она вы О
водит на экран числа от 1 до 100.
iinclude <iostream>
using hamespace std;
int main ()
{
int count:
return О;
count=count+1,
или подобной ей, поскольку в С++ существует специальный оператор инкре
мента, который выполняет операцию увеличения значения на единицу более эф
фективно. Оператор инкремента обозначается в виде двух знаков "плюс" (+ +).
Например, инструкцию for из приведенной выше программы с использованием
оператора "++" можно переписать так.
Одним из ключевых элементов С++ является бло" "ода. Блок - это логически
связанная группа программных инструкций (т.е. одна или несколько инс-rрук
ций), которые обрабатываются как единое целое. В С++ программный блок соз
дается путем размещения последоватеЛЬНОСТIf инструкций между фигурными
(открывающей и закрывающей) скобками, После создания блок кода становится
логической единицей, которую разрешено использовать везде, где можно приме
нять одну инструкцию. Например, блок кода может составлять тело инструкций
if или for. Рассмотрим такую инструкцию if'.
i f (w < h) {
v w * h;
w = О;
#include <iostream>
using namespace stdi
int main ()
double result, n, di
« "\n".i
result = n I di
cout « n « " I " « d « " Р".О " « resulti
}
return О;
Введите делимое: 10
Введите делитель: 2
Значение d не равно О, деление осуществимо.
10 / 2 равно 5
В этом случае инструкция i f управляет целым блоком кода, а не просто одной
ИliструкциеЙ. Если управляющее условие инструкции if истинно (как в нашем
примере), будут выполнены все три инструкции, составляющие блок. Поnробуй-
58 rлава 1. Основы С++
те ввести нулевое значение для делителя и узнайте, как выполнится наша про
грамма в этом случае. Убедитесь,.что IIpИ вводе нуля if-блок будет опущен.
как будет показано ниже, блоки кода имеют дополнительные свойства и спо
собы IIpименения. Но основная цель их существования - создавать логически
связанные единицы кода.
у = y+l:
cout « х « " " « у;
Верно ли это? •
Проект 1.2.
Последовательность действий
1. Создайте новый файл с именем Fto~Table. срр.
2. Введите 8 этот файл CJlед)'ющую программу.
/*
Проект 1.2.
iinclude <iostrearn>
using narnespace std;
int rnain () {
double f; // содержит длину, выраженную в футах
double т; // содержит результат преобразования в метры
int counter; // счетчик строк
+
u+
1i
ID
return О; О
1:
U
О
3. Обратите внимание на то, как используется переменная counter для вы
вода пустой строки после каждых десяти строк. Сначала (до входа в цикл
for) она устанавливается равной нулю. После каждого пре06разования
переменная counter инкРементируется. Когда ее значение становится
равным t О, выводится пустая строка, после чего счетчик ~ТPOK снова обну
ляется и процесс повторяется.
'ШОI
:1111 []ablSlIId" а ф~ЫКI ~ldSl;l
Любая С++-программа составляется из "строительных блоков", именуемых
функциями. И хотя более детально мы будем рассматривать функции в MOДY:le 5,
сделаем здесь краткий обзор понятий и терминов, связанных с этой темой. Функ
ция - это подпрограмма, которая содержит одну или несколько С++-инструк
ций и выполняет одну или несколько задач.
Каждая функция имеет имя, которое используется для ее вызова. Чтобы вы
звать функцию, достаточно в исходном коде l1porpaмMbl указать ее имя с парой
круглых скобок. Своим функциям программист может давать любые имена, за
исключением имени mаiл (), зарезервированного для функции, с которой начи
нается выполнение программы. Например, мы назвали функцию именем МуРи
лс. Тогда для вызова функции MyFunc достаточно записать следующее.
МуFuлс();
#include <iostream>
#include <cstdlib>
using namespace std;
int main ()
64 Глава 1. Основы С++
int result;
cout « result;
return О;
Библиотеки С++
как упоминалось выше, функция аЬs () не является частью языка С++. но ее
"знает" каждый С++-компилятор. Эта функция, как и множестводругих, входит
в состав стандартной 6u6лuотeICU. В примерах этой книги мы подробно рассмо
трим использование многих библиотечных функций С++.
С++: руководство для начинающих 65
u+
встречающихся задач, включая операции ввода-вывода, математические вы
ВАЖНОI
"'1 КАючевь'е САОва С++
В стандарте С++ определено 63 ключевых слова. Они показаны в табл. 1.1.
Эти ключевые слова (в сочетании с синтаксисом операторов и разделителей)
образуют определение языка С++. В ранних версиях С++ определено ключевое
слово overload, но теперь оно устарело. Следует иметь в виду, что в С++ раз
личается строчное и прописное написание букв. Ключевые слова не являются ис
ключением, Т.е. все они должны быть написаны строчными буквами.
• Идентификаторы
в С++ идентификатор представляет собой имя, которое присваивается функ
ции, переменной или иному элементу, 'определенному пользователем. 'Иденти
фикаторы могут состоять из одного или нескольких символов. Имена перемен
ных должны начинаться с буквы или символа подчеркивания. Последующим
символом может быть буква, цифра и символ подчеркивания. Символ подчер
кивания можно использовать для улучшения читабельноети имени перемеююй,
IJ;lIlример line_count. В С++ прописные и строчные буквы воспринимаются
t-.:ш, различные символы, Т.е. myvar И MyVar - это разные имена. В С++ нельзя
использовать в качестве идентификаторов ключевые слова, а также имена стан
дартных функций (например, abs). Запрещено также использовать в качестве
пользовательских имен встроенные идентификаторы (например, cout).
Вот несколько примеров допустимых идентификаторов.
Test х у2 Maxlncr
ир _top my_var simplelnterest23
Помните, что идентификатор не должен начинаться с цифры. Так, 980К
недопустимый идентификатор. Конечно, вы вольны называть переменные и
другие программные элементы по своему усмотрению, но обычно идентифика
тор отражает назначение или смысловую характеристику элемента, которому
он принадлежит.
С++: PYI<OBOACTBO ДЛЯ начинающих 67
3. Слова
катор?
..
index21 и Index21 представляют собой один и тот же идентифи
А. count
В. count
С. count27
D_ 67count
Е. if
1. Ключевым словом эдесь является вариант (от. В С++ все ключевые слова ПШlIутся
С ИСПОЛЪЗ0ванием СТРОЧНЫХ букв.
2. С++-идентификатор может содержать буквы, цифры И символ подчеркивания.
З. Нет, в С++ прописные и строчные буквы воспринимаются как раэличные симвоJIы.
68 Глава 1. Основы С++
14. Год Юпитера (т.е. время, за которое Юпитер делает один полный оборот
вокруг СОЛf!.ца) составляет приблизительно 12 земных лет. Напишит(' [IPO-
грамму, которая бы выполняла преобразовани.я значений, выраженных в
годах Юпитера, в значения, выраженные в годах Земли. Конечно же, здесь
допустимо использование нецелых ЗliачениЙ.
ные. Вы научитесь создавать такие типы данных, как классы, структуры и перс
числения, 110 при этом помните, что все (даже очень сложные) новые типы дан
ных состоят из встроенных.
С++: РУI<ОВОДСТВО ДNI начинающих 71
Тип Название
char Символьный
wchar t Символьный двубайтовый
int целочисленный
float С плавающей ТОЧКОЙ
double С плавающей точкой двойной точности
bool Лоrnческий (или булев)
void Без значения
В С++ перед такими типами данных, как char, int и double, разрешается
использовать .модифU1Шmоры. Модификатор служит для изменения значения ба
зовоro типа, чтобы он более точно соответствовал конкретной ситуации. Пере
чиелим возможные модификаторы типов.
signed
unsigned
long
short
Модификаторы signed, unsigned, long и short можно примеitять к це
лочисленным базовым типам. Кроме того, модификаторы signed и unsigned
можно использовать с типом
char, а модификатор long - с типом double. Все
допустимые комбинации базовых типов и модификаторов приведены в табл. 2.1.
В этой таблице также указаны гарантированные минимальные диапазоны пред
ставления для каждоro типа, соответствующие С++-стандарту ANSI/ISO.
Минимальные диапазоны, представленные в табл. 2.1, важно понимать именно
как .мUНUМШlЫtыe дuanаЭ()7fЫ и никак иначе. Дело в том, что С++-компилятор может
расширить один или несколько из этих минимумов (что и делается в большинстве
случаев). Это означает, что диапазоны представления С++-типов данных зависят от
конкретной реализации. Например, ДЛЯ компьютеров, которые используют арифме
тику дополнительных кодов (т.е. почти все COBpeMeННhle компьютеры), целочислен
НЫЙ тип будет иметь диапазоli представлеlШЯ чисел от -32 768 до 32 767. Однако во
всех случаях диапазон представления типа short int является подмножеством
диапазона типа in t, диапазон представления KOТOPOro в свою очередь является под
множеством диапазона типа long int. Аналогичными отношениями связаны и
типы float, double и long double. В этом контексте термин noдмножеC11l8O озна
чает более УЗЮiЙ или такой же диапазон. Таким образом, типы int и long int
MOryт иметь одинаковые диапазоны, но диапазон типа int не может быть шире диа
пазона типа long int.
72 Модуль 2. Типы данных и операторы
~Ти~п~____________________~~~и~н~и~м~ал~ЬН~Ыf1~АИ~а_п
__о_эо_н
________________
char -128-127
unsigned char 0-255
signed char -128-127
int -32768-32767
unsigned int 0-65 535
signed int АналоrиЧентипу int
short int -32768-32767
unsigned short int 0-65 535
signed short int Аналоrnчентилу short int
10ng int -2147483648-2147483647
·signed 10ng int Аналоrnчен типу 10ng int
unsigned 10ng int 0-4294967295
floa t 1Е-37 -1 Е +37. с шестью значащими цифрами
double 1Е-37-1Е+З7. (" дссятьюзначащими цифрами
_1_0_п-=g__d_о_u_Ь_1_е______________1_Е_-_3_7_-_1_Е_+_37--'-,с десятью значащими циФрами
Целочисленный тип
как вы узнали в модуле 1, переменные типа iI\ t предназначены для хранения lJ,e-
лочисленных значений, которые не содержат др06ной части. Переменные этого nma
часто используются для управления циклами и условными инструкциями. Опера
ЦИИ с iпt-значениями (поскольку они не имеют дробной части) выполняются на
много быстрее, чем со значениями с плавающей точкой (вещественного типа).
А теперь подробно рассмотрим каждый тип в отдельности.
Поскольку целочисленный тип столь важен для программирования, в С++
определено несколько его разJfовидностеЙ. Как показано в табл. 2.1, существуют
короткие (short int), обычные (int) и длинные (long int) целочисленные
значения. Кроме того, существуют их версии со знаком и без. Переменные uело
численного типа со знаком MOryт содержать как положительные, так и отрица-
С++: PYI(OBOACTBO для начинающих 73
*/
int main ()
{
short int i; // короткое iпt-значение со знаком
short unsigned int j; // короткое iпt-значение без знака
return О;
-5536 60000
Дело в том, что битовая комбинация, которая представляет число 60000 как короткое
(short) целочисленное значение без знака, интерпретируется в качестве короткого
iпt-значения со знаком как число -5536 (при 16-разрядном представлении).
С++: РУКОВОДСТВО ДЛЯ начинающих 75
Символы
Переменные типа char предназначены для хранения ASCIl-СИМDОЛОВ (напри
мер А, z или G) либо иных В-разрядных величин. Чтобы задать символ, необхо
димо заключить ero в одинарные кавычки. Например, после выполнения следу-
I
char ch;
ch = 'х' i
tinclude <iostream>
76 МОДУЛЬ 2. Типы данных и операторы
int main ()
{
char letter; // Перекеква_ letter ~a char
// иcnоnьзуетса дn_ упрaanекия цихпок for.
J.
for(letter = 'А'; letter <= 'Z'; letter++)
cout « letter;
return О;
Если ЦИКЛ for вам покажется несколько cTpaнным. то учтите. что символ' А'
представляется в компьютере как число 65. а значения от 'А' до 'Z' являются
последовательными и расположены в возрастающем порядке. При каждом про
ходе через ЦИКЛ значение переменной let ter инкрементируется. Таким обра
зом. после первой итерации переменная lette:r будет содержать значение I Е'.
ТИП wchar_ t предназначен для хранения символов. входящих в состав t:юль
ших символьных наборов. Вероятно. вам известно, что в некоторых естественных
языках (например китайском) определено очень большое количество символов,
для которых В-разрядное представление (обеспечиваемое типом char) вес[.ма
недостаточно. Для решения проблем такого рода в язык С++ и был добавлен тип
wchar _ t. который вам пригодится. если вы планируете выходить со своими про
граммами на международный рынок.
3. Да, переменные типа char можно ИСПОЛЬЗОЕ·атъ для представления небольmиx це
лыхчисел.
С++: РУКОВОДСТВО ДЛЯ начинающих 77
..................................................................................................................................
/*
Здесь используется теорема Пифагора для вычисления
длины гипотенузы по заданным длинам двух других
сторон треугольника.
*/
78 МОДУЛЬ 2. Типы данных и операторы
.................................................................................................................................
finclude <iostream>
#include <cmath> // Это~ saroловох аеобходим дn_
/ / 8101S08. ФУRJC.ЦИИ 8qrt () .
using namespace std;
int main () (
double х, у, z;
х = 5;
У 4;
return О;
tinc1ude <iostream>
using nаmеэрасе std;
int main ()
Ьоо1 Ь;
ь = fa1se;
cout« "Значение переменной Ь равно" « Ь « "\n";
ь = true;
cout« "Значение переменной Ь равно" « Ь « "\п";
ь fa1se;
=
if(b) cout« "Это не выполнимо.\n";
return О;
10 > 9 равно 1
В этой программе необходимо отметить три важных момента. Во-первых, как
вы могли убедиться, при выводе на экран Ьоо1-значения отображается число О
80 МОДУЛЬ 2. Типы данных и оперспоры
или 1. Как будет показано ниже в этой книге, вместо чисел можно вывести слова
"false" и "true".
Во-вторых, для управления i f-инструкциеj;( вполне достаточно самого значе
ния Ьооl-переменной, Т.е. нет необходимости в испольэовании инструкции, 110-
добной следующей.
if (Ь == true) ...
В-третьих, результатом выполнения оперг.ции сравнения. Т.е. применсния
оператора отношения (например, "<") являетСJl6улево значение. Именно поэ'~о
му вычисление выражения 10 > 9 дает в результате число 1. При этом необхо
димость в дополнителыilхx круглых скобках в чнструкции вывода
cout« "10 > 9 равно" « (10 > 9) « "\п";
объясняется тем, что оператор "«" имеет более высокий приоритет, чем опера
тор "<".
Типvоid
Тип void испольэуется для задания выражений, которые не возвращают зна
чений. Это, на первый взгляд, может показаlЬСЯ странным, но подробнее 1ИП
void мы рассмотрим ниже о этой книге.
................------------------_..------------------------..
3. Что такое void?·
2. Переменные типа Ьооl могут иметь значения tгue либо false. Нуль преобразу
ется в значение false.
3. void - это ТШI данных, характеризующий ситуацию отсутствия значения.
С++: руководство ДЛ~ начинающих 81
Проеl<Т 2.1.
- . - --1 В положении, когда Марс tlаходится к Земле ближе всего, рассто-
Mars.cpp ,
яние между ними составляет приблизительно 34000000 миль.
Предположим, что на Марсе есть некто, с кем вы хотели бы ПОI'()ворить. Тогда
возникает вопрос, какова будет задержка во времени между моментом отправ
ки радиосигнала с Земли и моментом его прихода на Марс. Цель следующеro
проскта - создать программу, которая отвечает на этот вопрос. Для этоro вспом
ним, что радиосигнал распространяется со скоростью света, т.е. приблизительно
186000 миль в секунду. Таким образом, чтобы вычислить эту задержку, необхо
димо расстояние между планетами разделить на скорость света. Полученный ре
зультат следует отобразить в секундах и минутах.
Последовательность действий
1. Создайте новый файл с именем Mars. срр.
2. Для вычисления змержки необходимо использовать значения с плавающей
. точкой. Почему? Да просто потому, что искомый временной интервал будет
иметь дробную часть. Вот переМСliные, которые используются в программе.
double distancei
double lightspeed;
double delaYi
double delay_in_min;
3. Присвоим перемеНIlЫМ distance и lightspeed начальные значения.
delay = distance /
(8 секундах) разделим значение
1 igh tspeed. Присвоим вычисленное част
delay и отобразим результат.
lightspeedi
пере
1.
cout« "Временная задержка при разговоре с Марсом: "
«
delay « " секунд.\п";
5. Для получения задержки n минутах разделим значение переменной delay
на 60 и отобразим результат.
/*
Проект 2.1.
"ПоговорИЪо! с Марсом"
*/
#include <iostream>
using namespace std;
int main () {
double distance;
double lightspeed;
double delay;
double delay_in_min;
«
delay « " секунд.\п";
return О;
Все литеральные значения имеют некоторый тип данных. Но, как вы уже зна
ете, есть несколько разных целочисленных типов, например int, short int и
unsigned 10ng int. Существует также три различных вещественных типа:
f1oat, double и 10ng double. Интересно, как же тогда компилятор определяет
тип литерала? Например, число 123.23 имеет тип float или double? Ответ на
этот вопрос состоит из двух частей. Во-первых, С++-компилятор автоматически
делает определенные предположения насчет литералов. Во-вторых, при желании
программист мож~т явно указать ~п литерала.
Строковы е литералы
Язык С++ поддерживает еще один встроенный тип литерала, именуемый
строковым. Строка - это набор символов, заключенных в двойиые кавычки,
например "это тест". Вы уже видели примеры строк в некоторых соut-ии
струкциях, с помощью которых мы выводили текст на экран. При этом обра
тите внимание вот на что. Хотя С++ позволяет определять строковые литера
лы, он не имеет встроенного строкового типа данных. Строки в С++, как будет
показано ниже в этой книге, поддерживаются в виде символьных массивов.
(Кроме того, стандарт С++ поддерживает строковый тип с помощью библио
течного класса string.)
С++: РУКОВОДСТВО МЯ начинающих 85
Вопрос. Вы nоказШlIl., как определить СШlВlJЛ6НЫ;; литерал (дmr эmozо СIIJНВlJЛ нео6хо
дllJtlо JQключит6 tI одинарные IШtlычки). Надо nOHlIJНtlln6,
"mo pe116 ш)ет О ли
тep~ax тим char. А КJU(:Jaдamь лumeptlll (m.е. константу) "'II11II W_ char?
Ответ. Для определения двубайтовой константы, Т.е. константы типа w char, не-
обходимо использовать префикс L. ВОТ пример. - .
w char wc;
~ = L'A';
Здесь переменной WC присваивается двубайтовая константа, эквивалеlП
ная символу А. Вряд ли вам придется часто использовать "широкие" сим
волы, но в этом мОЖет возникнуть необходимость при выходе ваших за
казчиков на меЖдУНародный рынок.
Jinclude <iostrearn>
using narnespace std;
int rnain ()
{ ,
cout « "one\ttwo\tthree\n";
cout « "12З\Ь\Ь45"; // Последовательность \Ь\Ь
// обеспечит возврат на две позиции
// назад. Поэтому цифры 2 и 3 будут
// "стерты".
return О;
тип имя_переменной;
Здесь элемент тип означает допустимый в С++ тип данных, а элемент имя_пе
ременной- имя объявляемой переменной. Создавая переменную, мы создаем
экземпляр ее типа. Таким образом, возможности переменной определяются ее
типом. Например, переменная типа Ьоо 1 может хранить только логические зна
чения, и ее нельзя использовать для хранения значений с плавающей точкой. Тип
переменной невозможно изменить во время ее существования. Это значит, что
iпt-переменную нельзя превратить в dоublе-переменную.
Инициализацияпеременной
При объявлении переменной можно присвоить некоторое значение, Т.е. ини
циQJluзироваmь ее. Для этого достаточно записать после ее имени знак равенства
1. Для литерала 10 по умолчанию используется тип int, а для литерала 10.0 - тип
double.
2. Для того чтобы константа 1О О имела тип данных lопg int, необходимо записать
100L. ДЛЯ того чтобы константа 1 О() имела тип данных unsigned int, необ
ходимо записать 1 О О U.
З. Запись \Ь представляет собой управляющую последовательность, которая означа
ет возврат на одну позицию.
88 Модуль 2. Типы данных и операторы
1/ значение 10.
char ch = 'х' ; // Инициализируем nE!ременную ch буквой х.
float f = 1.2F // Переменная f инициализируется
// значением 1,2.
При объявлении двух или больше переменных одного типа в форме списка
можно одну из них (или несколько) обеспечигь начальными значениями. При
этом все элементы списка, как обычно, разделяются запятыми. Вот пример.
int а, Ь = 8, с = 19, d; // Переменные Ь и с
// инициализируются при объявлении.
Динамическаяинициализацияпеременных
Несмотря на то что переменные часто инициализируются-константами (как
показано в предыдущих примерах), С++ ПОЗВОllяет инициализировать перемt'н
ные динамически, т.е. с помощью любого выраж ения, действительного на момент
инициализации. Например, рассмотрим не60лыuую программу, которая вычис
ляет объем цилиндра по радиусу его основания и высоте.
#include <iostream>
using namespace std;
int main () {
double radius = 4.0, height = 5.0;
return О;
С++: руководство ДЛ'il начинающих 89
Операторы
в С++ определен широкий набор операторов. Оператор (operator) - это
символ, который указывает компилятору на выполнение конкретных матема
тических действий или логических манипуляций. В С++ имеется четыре общих
класса операторов: арифметические, поразрядные, логuческие и операторы отно
шений. Помимо них определены другие операторы специального назначения. В
этом модуле мы уделим внимание арифметическим. логическими и операторам
отношений, а также оператору присваивания. Поразрядные и операторы специ
ального назначения рассматриваются ниже в этой книге.
;В Арифмехические~ПQРnТОРЫ
в С++ определены следующие арифметические операторы.
* Умножение
/ Деление
% деление по модулю
Декремент
++ Инкремент
#include <iostrearn>
using narnespace std;
int rnain ()
(
int х, У;
х =,10;
У = 3;
cout « х « " / " « у « " равно " « х / у «
" с остатком" « х % у « "\п";
х = 1;
У = 2;
cout « х « " / " « у « " равно " « х / у « "\п" «
х « " % " « у « " равно " « х % У;
return О;
Инкремент и декремент
Операторы инкремента (++) и декремента (--). которые уже упоминались в
модуле 1. обладают очень интересными свойствами. Поэтому им следует уделить
особое внимание.
Вспомним: оператор инкремента выполняет сложение опер~нда с числом 1. а
оператор декремеftrа вычитает 1 из своего операнда. Это значит. что инструкция
х = х + 1;
С++: руководство дI\'iI начинающих 91
++Х;
:а
а.
А инструкция о
х=х...,/l;
6
а.
ф
с:
аналогична такой инструкции: о
:s:
--Х; х
:а
1:
Операторы инкремента и декремента могут стоять как перед своим операн 1:
дом (nрефU1€СНая форма), так и после него (постфиксная форма). Например, ин ~
J5
с:
струlЩИЮ
~
Х = Х + 1;
можно переllИсать в виде префиксной
Х = 10;
У = ++Х;
В этом случае переменная у будет установлена равной 11. Но если в этом коде
прсфиксную форму записи заменить постфиксной, переменная у будет установ
лена равuой 10:
Х = 10;
У = Х++;
В обоих случаях переменнэя Х получит значение 11. Разница состоит лишь в том,
в какой момент она станет равной 11 (до присвоения ее значения перемеиной у
или после). Для программиста очень важно иметь возможность управлять време
нем выполнения операции инкремента или декремеlПа.
92 МОДУЛЬ 2. Типы данных и операторы
Приоритет Операторы
Наивысший ++ --
- (унарный минус)
* / %
Низший + -
Операторы одного уровня старшинства вычисляются компилятором CJJeBa
направо. БезуCJJОВНО, ДЛЯ изменения порядка ВЫЧИCJJений можно использовать
круглые скобки, которые обрабатываются D С1-+ так же, как практичеСЮi во всех
других языках программирования. Операции IIЛИ набор операций, заключенных
в круглые скобки, приобретают более высокий приоритет по сравнению с други
ми операциями выражения.
Ответ. Даl как вы знаете, С++ построен на фу.даменте языка С, к которому добав
лено множество усовершенствований, tiольшинство из которых предна;ша
чено ДЛЯ поддержки объектно-ориеНТlfJЮВС1ННОro Iтрограммирования. Та-'
ким образом, С++ представляет собой '''''''крем.ентное усовершенствовани~
языка С, а результат добавления симвслов "++" (оператора инкремеНТd) к
имени С оказался вполне подходящим liМeHeM для нового языка.
и логические операторы
Операторы отношений и логические (булевы) операторы, которые часто
идут ирука 06 руку", ИСПОЛЬЗУЮТСЯ для получения результатов в виде значений
ИСТИНА/ЛОЖЬ. Операторы отношений ОU,енивают по "двухбалльной систе
ме" отношения Между ДВУМЯ значениями, а лоrnческие определяют различные
С++: РУКОВОДСТВО ДЛЯ начинающих 93
р q р И q р или q НЕ Р
ложь ЛОЖЬ ложь ЛОЖЬ ИСТИНА
ЛОЖЬ ИСТИНА ЛОЖЬ ИСТИНА ИСТИНА
ИСТИНА ИСТИНА ИСТИНА ИСТИНА ЛОЖЬ
ИСТИНА ЛОЖЬ ЛОЖЬ ИСТИНА ЛОЖЬ
#include <iostream>
using патезрасе std;
int main () (
int i, j;
bool Ы, Ь2:
i = 10:
j = 11:
if(i < j) cout « "i < j\n";
if(i <= j) cout « "i <= j\n":
if(i != j) cout « "i != j\n";
if(i j) cout « "Это не вьmолняется!\п";
if(i >= j) cout « "Это не выполняется!\п":
if{i > j) cout « "Это не выполняется!\п":
Ы = true:
Ь2 = false:
if(bl && Ь2) cout « "Это не выполняется!\п":
if(! (Ы && Ь2» cout «
" ! (Ы & & Ь2) равно значению ИСТИНА \п" ;
if (Ы I I Ь2) cout « "Ы I I Ь2 равно значению ИСТИНА \п";
return О:
i < j
i <= j
С++: PYI(OBOACтaO д.л~ начинающих 95
.................................................................................................................................
i != j
! (Ы && Ь2) равно значению ИСТИНА
л
Ы 1 1 Ь2 равно значению ИСТИНА а.
о
>-
Как операторы оmошений, так и логические операторы имеют более низкий Q
...... ._",
2.2.
_.. __ ...... - - .. ,
"исключающее ИЛИ"
..
s
~
ЛОЖЬ
q
ЛОЖЬ
р
ЛОЖЬ
XOR q
Ig
ЛОЖЬ ИСТИНА ИСТИНА
ИСТИНА ЛОЖЬ ИСТИНА
ИСТИНА ИСТИНА ЛОЖЬ
С++. Другие же придерживаются иного мнеьия: мол, это избавляет язык 01 из
быточности, поскольку логический оператор "исключающее ИЛИ" (XOR) не
ТРУДНО "создать" на основе встроенных операторов. И, ИЛИ и НЕ.
В настоящем проекте мы создадим операпию "исключающее ИЛИ", исполь
зуя операторы &&, II и !. После этого вы са.\fИ рещите, как относиться к тому
факту, что этот оператор не является встроеЮIЫМ элементом языка С++.
Последовательность действий
1. Создайте новый файл с именем XOR. ср::).
(р II q) && ! (р && q)
Рассмотрим это выражение. Сначала над операндами р и q выполняется
операция логического ИЛИ (первое подвыражеН1-\f), результат которой
будет равен значению ИСТИНА, если по крайней мере один из операн
дов будет иметь значение ИСТИНА. Загем над операндами р и q ВЫПОЛ
няется операЩIЯ логического И, результат которой будет равен значению
ИСТИНА, если оба операнда будут иметь значение ИСТИНА. Последний
результат затем инвертируется с помощью оператора НЕ. Таким обра
зом, реэультат второго подвыражения !:р && q) будет равен значению
ИСТИНА, если один из операндов (или оба сразу) будет иметь значение
ЛОЖЬ. Наконец, этот результат логичеСl:И умножается (с помощью опера
цИИ И) на результат вычисления первого подвьrpажения (р II q). Таким
образом, полное выражение даст значение ИСТИНА, если один из операн
дов (но не оба одновременно) имеет знач.~ние J:fСТИНА.
3. Ниже приводится текст всей программы XOR. срр. При ее выполнении де
монстрируется результат применения ош~рации "исключающее ИЛИ" дДЯ
всех четырех возможных комбинаций ИСТИНА/ЛОЖЬ-значений к двум
ее операндам.
1*
Проект 2.2.
*/
С++: руководство ДЛЯ начинающих 97
#include <iostrearn>
#include <crnath>
р false;
q true;
р true:
q false;
.§
cout « р « " XOR " « q « " равно " « ~
( (р 11 q) && ! (р && q) ) « "\n":
q
false:
false:
!
cout «
( (р
р
11
«
q)
" XOR " «
&& ! (р
q
&& q)
«
) «
" равно
"\п";
" « I
.~
I
J
return О:
98 МОДУЛЬ 2. Типы данных и операторы
................................................................................................................................
1 XOR 1 равно О
О XOR 1 равно 1
1 XOR О равно 1
О XOR О равно О
леременная = выражение;
присваивания
в С++ предусмотрены специальные cocmaвHыe операторы nрuсвauванu,Я. в
которых объединено присваивание с еще одной операцией. Начнем с примера и
рассмотрим следующую инструкцию.
х = х + 10:
Используя составной оператор присваивания, ее можно переписать в таком виде.
х += 10;
Пара операторов += служит указанием компилятору присвоить переменной х
сумму текущего значения переменной х и числа 10.
А вот еще один пример. Инструкция
х = х - 100;
аналогична такой: .
х -= 100:
Обе эти инструкции присваивают переменной х ее прежнее значение, уменьшен
ное на 100.
100 Модуль 2. Типы данных и операторы
................................................................................................................................
присваивания.
ВАЖНОI
'1:8 "реобрnзовпние ТИEJав
воператорахприсваивания
Если переменные одного типа смешаны с переменными другого типа, про
исходит nрео6разованuе тиnов. При выполнении инструкции присваивания
действует простое правило преобразования типов: значение, расположенное с
правой стороны от оператора присваивания, преобразуется в значение типа, со
ответствующего переменной, указанной слева от оператора присваивания. Рас
смотрим пример.
int Х;
char ch;
f10at f;
ch = Х; /* строка 1 */
Х = f; /* строка 2 */
f ch; /* строка 3 */
f Х; /* строка 4 */
С++: PYI<OBOACтeO д,лfl начинающих 101
Вblражения
Операторы, литералы и переменные - это все составляющие выражений. Ве
роятно, вы уже знакомы с выражениями по предыдущему опыту npoграммиро
в Вblражениях
Если в выражении смешаны различные типы литералов и переменных, ком
пилятор преобразует их в один тип. Во-первых, все char- и short iпt-значе
ния автоматически преобразуются (с расширением "типоразмера") в тип int.
102 Модуль 2. Типы данных и операторы
ВАЖНОI
tll•1 ПРИВРАрние типов L •
в С++ предусмотрена возможность установить для выражения заданный тип.
Для этого используется операция nриведенuя типов (cast). С++ определяет пять
видов таких операций. В этом разделе мы рассмотрим только один из них (самый
универсальный и единственный, который поддерживается старыми версиями
С + + ), а остальные четыре описаны ниже в этой книге (после темы создания объ
ектов). Итак, общий формат операции приведеllИЯ типов таков:
(тип) выражение
(float) х / 2
С++: РУКОВОДСТВО ДМ! начинающих 103
i1nclude <1ostream>
us1ng патеэрасе std;
1пt та1п () (
1nt i;
11 2 равно: 0.5
21 2 равно: 1
ЗI 2 равно: 1.5
41 2 равно: 2
51 2 равно: 2.5
61 2 равно: 3
71 2 равно: 3.5
81 2 равно: 4
91 2 равно: 4.5
101 2 равно: 5
Без оператора приведения типа (floa t) выполнилось бы только целочислен
ное деление. Приведение типов в данном случае гарatПирует, что на экране будет
отображена и дробная часть результата.
104 Модуль 2. Типы данных и операторы
ВАЖНОI
"1' Использование прОбелов
и круглых скобок
Любое выражение в С++ дЛЯ повышения читабельности может включать про
белы (или символы табуляции). Например, следующие два выражения совер
шенно одинаковы, но второе прочитать гораздо легче.
х=10/у*(127/х);
х = 10 / у * (127/х);
Круглые скобки (так же, как в алгебре) повышают приоритет операций, содер
жащихся внутри них. Использование избыточных или дополнительных круглых
скобок не приведет к ошибке или замедлению вычисления выражения. Другими
словами, от них не будет никакого вреда, но зато сколько пользыl Ведь они помо
гут прояснить (для вас самих в первую очередь, не говоря уже о тех, кому придет
ся разбираться в этом без вас) точный порядок вычислений. Скажите, например,
какое из следующих двух выражений легче понять?
х = у/З-З4*tеmр+127;
Х = (у/З) - (З4*tеmр) + 127;
Проект 2.3.
IВ_Э1"ОМ проекте ~ы создадим программу, которая вычисляет размер регулярных
RegPay. срр ! платежей по займу, скажем, на покупку автомобиля. По таким
данным, как основная сумма займа, срок займа, количество вы
плат в год и процентная ставка, программа вычисляет размер платежа. Имея дело
с финансовыми расчетами, нужно использовать типы данных с плавающей точ
кой. Чаще всего о подобиых вычислениях применяется тип double, вот и мы не
будем отступать от общего правила. Заранее отмечу, что в этом проекте исполь
зуется еще одна библиотечная С++-Функция pow ( ) .
Для вычисления размера регулярных платежей по займу мы будем использо
вать следующую формулу.
е = -(РауРеrУеаr * NumYears);
Ь (IntRate / PayPerYear) + 1;
#include <iostream>
iinclude <cmath>
using namespace std;
int main () {
double Principal; // исходная сумма займа
double IntRate; // процентная ставка в виде
// числа (например, 0.075)
double PayPerYear; // количество выплат в год
double NumYearsi // срок займа(в годах)
double Payment; // размер регулярного платежа
double numer, denom; // временные переменные
double Ь, е; // аргументы для вызова
11 функции Pow ( )
е = -(РауРеrУеаr * NumYears);
Ь (IntRate / PayPerYear) + 1;
return О;
I
108 Модуль 2. Типы данных и операторы
............................................................................................................................. - ..
х = х + 12;
13. Что такое приведение типов?
3.1. Инструкция if
3.2. Инструкция switch
3.3. Цикл for
3.4. Цикл while
3.6. ИСПОЛl>Зование инструкции break для выхода ШJ цикла
3.7. Использование ИIIСТРУКЦИИ continue
3.8. Вложенные llИКJIbl
3.9. Инструкция goto
110 Модуль З. ИНСТРУКЦИИ управления
..................................................................................................................................
модуле.
if(выражение) ИНСТРУКЦИЯ;
else инструкция;
i f (выражение)
{
последовательность инструкций
else
последовательность ИНСТРУКЦИЙ
iinclude <iostream>
•
finclude <cstdlib>
using namespace std;
int main ()
{
int magic; // магическое число
int guess; // вариант пользователя
return О;
'include <iostream>
iinclude <cstdlib>
using namespace stdi
if(guess == magic)
cout « "** Правильно **".,
else
cout « " ... Очень жаль, но вы ошиблись."; 11 Индикатор
11 неправильного ответа.
return О;
Условное выражение
Иногда новичков в С++ сбивает с толку тот факт, что для управления if-ин
струкцией можно использовать любое деЙствите.'IЪное С++-выражение. Другими
словами, тип выражения необязателыю ограничивать операторами отношений и
логическими операторами или операндами типа bool. Главное. чтобы результат
вычисления условного выражения можно было интерпреrnровать как значение
ИСТИНА или ЛОЖЬ. Как вы пьмните из предыдущего модуля, нуль автомати
чески преобраэуется в false, а все ненулевые значения - в true. Это означает,
что любое выражение, которое дает в результате нулевое или ненулевое значе
ние, можно использовать для управления if-ИlfструкциеЙ. Например, следую
щая про грамма считывает с клавиатуры два целых числа и отображает частное
от деления первого на второе. Чтобы не допустить деления на нуль, в программе
используется if-инструкция.
#include <iostream>
using namespace std;
int main ()
С++: РУКОВОДСТВО ДМl начинающих 113
int а, Ь;
return О;
Введите числитель: 12
Введите знаменатель: 2
Результат: 6
Введите числитель: 12
Введите знаменатель: О
На нуль делить нельзя.
Вложенные if-ИНСТРУКЦИИ
Вложенные if-ИНСТРУКЦИИ образуются в том случае, если в качестве элемен
та ИНСТРУКЦИЯ (СМ. полный формат записи) используется другая if-ИНСТРУК
ция. Вложенные if-инструкции очень поnyлярн'ы в программировании. Главное
здесь - помнить, что еlsе-инструкция всегда относится к ближайшей if-ин
струкции, которая находится внутри того же программноro блока, но еще не свя
зана ни с какой другой else-инструкциеЙ. Вот пример.
if(i) (
if(j) result = 1;
if(k) result = 2:
else result = 3; // Эта else-ветвь связана с if(k).
finclude <iostream>
#include <cstdlib>
int main ()
(
int magic; // магическое число
int guess; // вариант пользователя
if (guess == magic)
cout « ,,** Правильно **\n";
cout « magic
« " и есть то самое магическое число.\n";
else
cout « " ... Очень жаль, но вы ошиблись.";
if(guess > magic)
cout « " Ваш вариант больше магического числа.\n";
else
cout « " Ваш вариант меньше магического числа.\n";
return О;
if (условие)
инструкция;
else if (условие)
инструкция;
else if(условие)
инструкция;
else
инструкция;
tinclude <iostream>
using namespace std;
int main ()
(
int Х;
return О;
х не попадает в диапазон от 1 до 4.
х равен единице.
х равен двум.
х равен трем.
х равен четырем.
х не попадает в диапазон от 1 до 4.
Как видите, последняя еlsе-инструкция выполняется только в том случае,
если все предыдущие i f-условия дали ложный результат.
С++: PYI(OBOACTBO ДЛЯ начинающих 117
ВАЖНОI
case KOHCTaHTal:
последовательность инструкций
break;
саsе константа2:
последовательность инструкций
break;
case константа3:
последовательность инструкций
break;
2. Ветвь else всегда связана с ближайшей i f-ветвью, которая находится в том же про
граммном блоке, но еще не связана ни с какой другой else-инструкциеЙ.
3. ~Лес1'НИца" if-else-if - это последовательность вложенных i f-еlsе-инстрYJЩИЙ.
118 Модуль З. ИНСТРУКЦИИ управлени~
default:
последовательность инструкций
#include <iostream>
С++: PYl<OBOACтeo ДЛЯ начинающих 119
return О;
Введите число от 1 дО З: 1
Один пирог два раза не съешь.
Введите число от 1 дО З: 5
Вы должны ввести число 1, 2 или з.
Формально инструкция break не06язательна, хотя в большинстве случаев
использования switсh-КОНСТРУКЦИЙ она присутствует. Инструкция break, сто
ящая в последовательности инструкций любой саsе-ветви, приводит к выходу
из всей 5wi tсh-КОНСТРУКЦИИ и передает управление инструкции, расположен
ной сразу после нее. Но если инструкция break в саsе-ветви отсутствует, будут
выполнены все инструкции, связанные с данной саsе-ветвью, а также все после
дующие инструкции, расположенные за ней, до тех пор, пока все-таки не встре-
120 МОДУЛЬ З. ИНСТРУI<ЦИИ управления
*include <iostrearn>
using паrnезрасе std;
int rnain ()
{
int i;
cout « '\п' ;
return О;
меньше 1
меньше 2
меньше 3
меньше 4
меньше 5
меньше 2
меньше 3
меньше 4
меньше 5
меньше 3
С++: PYI<OBOACTBO ДЛЯ начинающих 121
меньше 4
меньше 5
меньше 4
меньше 5
меньше 5
Как ВИДНО по результатам, если инструкция break в одной саsе-ветви отсут
ствует, выполняются инструкции, относящиеся к следующей саsе-ветви.
Как показано в следующем примере, в swi tсh-КОНСТРУКЦИЮ можно включать
"пустые" саsе-ветви.
switch(i)
case 1:
саsе 2: // +- Пустые саsе-ветаи.
саsе З:
cout « "Значение переменной i меньше 4.";
break;
саsе 4:
cout « "Значение переменной i равно 4.";
break;
Если переменная i в этом фрагменте кода получает значение 1,2 или З, ото
бражается одно сообщение.
Значение переменной i меньше 4.
Если же значение переменной i равно 4, отображается другое сообщение.
Значение переменной i равно 4.
Использование "пачки" нескольких пустых са 5 е-ветвей характерно для случаев,
когда при выборе нескольких констант необходимо выполнить один и тот же код .
switch(chl) (
case 'А': cout « "Эта д-ветвь - часть внешней switch";
switch(ch2) { // +- ВnО8евкая ИRструхци. switch.
122 Модуль З. ИНСТРУI<ЦИИ управлени~
case 'А':
cout « "Эта А-ветвь - часть внутренней switch";
break;
case '8': // _.,
break;
саБе '8': // __ .
ПослеАовательность Аействий
1. Создайте файл Help. срр_
2. Выполнение программы должно начинаться с отображения следующего
меню_
Справка по инструкциям:
1. if
2.switch
Выберите вариант справки:
cin » choice;
4. Получив вариант пользователя, программа применяет инструкцию swi t-
ch для отображения синтаксиса выбранной инструкции_
switch(choice)
саsе '1':
cout « "Синтаксис инструкции if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "е1sе инструкция;\п";
break;
124 Модуль З. Инструкции управления
саэе '2':
cout « "Синтаксис инструкции switch:\o\o":
cout « "switсh(выражение) (\о":
cout « " саэе константа:\о":
cout « " последовательность инструкций\о":
cout « " break:\o";
cout « " // ~ .. \o";
cout « "}\о":
break:
defau1t:
cout « "Такого варианта нет.\о";
/*
Проект 3.1.
*/
#ioc1ude <iostream>
usiog оатезрасе std:
iot maio ()
char choice:
cout « "\n":
switch(choice)
саэе '1':
cout « "Синтаксис ИНСТРУКЦИИ if:\n\n":
cout « "if (условие) ин(:трукция: \0":
cout « "е1зе инструкция;\п";
С++: руководство ДЛЯ начинающих 125
break;
case '2':
cout « "Синтаксис инструкции switch:\n\n";
cout « "switсh(выражение) (\n";
cout « " case KOHcTaHTa:\n";
cout « " последовательность инструкций\n";
return О;
Справка по инструкциям:
1. i f
2. switch
Выберите вариант справки: 1
if(условие) инструкция;
else инструкция;
ВАЖНОI
#inc1ude <iostream>
#inc1ude <cmath>
using namespace std;
int main ()
{
int пит;
double sq_root;
return О;
изCBoero аргумента. Аргумент должен иметь тип double, и именно поэтому при
вызове функции sqrt () параметр пит приводится к типу double. Сама функ
г
ция также возвращает значение типа double. Обратите внимание на то, что в :s:
:r
Ф
программу включен заголовок <ста th>, поскольку этот заголовочный файл обе .;;:
также может быть любой. Например, следующая программа выводит числа в диа
пазоне от 50 до -50 с декрементом, равным 10.
// Использование в цикле for отрицательного приращеНИR.
#include <iostrearn>
using narnespace std;
int main()
int i;
return О;
Этот цикл Iiикогда не выполнится, поскольку уже при входе в Hero значение его
управляющей переменной count больше пяти. Это делает условное выражение
(count < 5) ложным с самого начала. Поэтому даже одна итерация этоro цикла
не будет выполнена.
tinclude <iostream>
tinclude <cstdlib>
using namespace std;
С++: PYI<OBOACTBO ДЛ91 начинающих 129
int main ()
{
int i;
int r;
• 10:
:s:
:1:
Ф
<
са
R
5-
:s:
r = rand () ; ~
~
>-
е-
u
for(i=O; r <= 20000; i++) // В этом уcnо.вом awp. . . вии .:1:
:S:
/ / уа:рaJШ-nцe- переllеlUl&Jl ЦИIUI&
// не ИCDоnъsуетс •.
r = rand () ;
return О;
#include <iostream>
using паmеsрасе std;
int main ()
130 МОДУЛЬ З. ИНСТРУJ<ЦИИ управлени~
int Х;
return О;
for ( ; х<10;
cout « х « I ,
1.
++Х;
// ...
С++: руководство ДЛЯ начинающих 131
Этот ЦИКЛ будет работать без конца. Несмотря на существование некоторых за
дач программирования (например, KOMaMHbIX процессоров операционных си
стем), которые требуют наличия бесконечного цикла, большинство "бесконеч
ных циклов" - это просто циклы со специальными требованиями к завершению.
Ближе к концу этого модуля будет показано, как завершить ЦИКЛ тaкoro типа.
(Подсказка: с помощью инструкции break.)
#include <iostream>
#include <cstdlib>
using namespace std;
int main ()
int i;
int зит О;
return О;
зит += i++
132- Модуль З. ИНСТРУI(ЦИИ управлени~
sum = эит + i;
i++;
#include <iostream>
using патеэрасе stdi
int main () {
int эит = О;
int fact = 1;
return О;
1. да. Пустыми MOryr быть все три раздела заголовка инструкции for: ииициалиэация,
выражение и инкремент.
2. for ( ; ; )
3. Область видимости переменной, объявленной в заголовке инструкции for, ограничи
вается рамками цикла for. Вне его она неизвестна.
134 Модуль З. ИНСТРУI<ЦИИ управления
ВАЖНОI
*1
#include <iostrearn>
using narnespace std;
int main ()
{
unsigned char ch;
ch = 32;
while(ch) ( 1/ Начало цикла while.
cout « ch;
ch++;
С++: руководство ДЛЯ начинающих 135
return О;
#include <iostream>
using namespace std;
int main ()
int len;
rеturл О;
}
Если значение переменной len не "вписывается" в заданный диапазон
(1-79), то цикл while не выполнится даже один раз. В противном случае его
тело будет повторяться до тех пор, пока Зliачение переменной len не станет
равным нулю.
136 Модуль З. ИНСТРУI<ЦИИ управления
while(rand() != 100)
Этот цикл выполняется до тех пор, пока случайное число, генерируемое функци
ей rand (), не окажется равным числу 100.
ВАЖНОI
&. Ilиl(Adn-wЬjlе
•
Настало время рассмотреть последний из С++-циклов: do-while. В отличие
от циклов for и while, в которых условие проверяется при входе, цикл do-wh-
ile проверяет условие при выходе из цикла. Это значит, что цикл do-while
всегда выполняется хотя бы один раз. Его общий формат имеет такой вид.
do {
инструкции;
} while (выражение);
int rnain ()
{
int пит;
return О;
С++: РУКОВОДСТВО дl\Я начинающих 137
#include <iostream>
#include <cstdlib>
using паmеsрасе std;
int main ()
{
int magic; // магическое число
int guess; // вариант пользователя
do (
cout « "Введите свой вариант магического числа: ";
cin » guess;
if(guess == magic)
cout « "** Правильно ** ".,
cout « magic «
" и есть то самое магическое число.\п";
else
cout « " ... Очень жаль, но вы ошиблись.";
if(guess > magic)
cout «
" Ваше число больше магического.\п";
else cout «
" Ваше число меньше магического.\п";
while(guess != magic);
rеturп О;
И еще. Подобно циклам for и while, тело цикла do-while может быть пу
стым, но этот случай редко применяется lIa практике.
l,iЩ·МSЕ,,меовеpuJВJlC1В08ОНИrмm,.вQЮICi)Й
системыС++
1. В цикле while условие проверяется при входе, а в цикле do-while - при выходе.
Это значит, что цикл do-wh i 1 е всегда выполняется хотя бы ОДЮI раз.
2. Да, тело цикла while (как и любоl·О другого С++-цикла) может быть пустым.
С++: руководство ДЛ~ начинающих 139
................................................................................................................................
Последовательность действий
1. Создайте файл Help2 . срр.
2. Измените ту часть программы, которая отображает команды меню, причем
так, чтобы был использован цикл do:....while.
do
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";
cout « " 2. switch\n";
cout « " З. for\n";
cout « " 4. whilе\л";
cout « " 5. dо-whilе\л";
cout « "Выберите вариант справки: ";
cin » choice;
break;
case 131:
cout « "Инструкция for:\n\n";
cout « "fоr(инициалиэация; условие; инкремент)";
cout « 11 инструкция;\п";
break;
case 141:
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case 151:
cout « "Инструкция do-while:\n\n";
cout « "do {\n";
cout « " инструкция;\п";
cout « "} while (условие);\п";
break;
#include <iostream>
using namespace std;
int main ()
char choice;
do {
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";
С++: PYI<OBOACTBO ДЛЯ начинающих 141
cin » choice;
cout « "\п\п";
switch(choice)
саsе '1':
cout « "Инструкция if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) I\п";
cout « " case KOHcTaHTa:\n";
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \п";
cout « "}\п";
break:
case '3':
cout « "Инструкция for:\n\n";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкuия;\п";
break:
case '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case '5':
cout « "Инструкция do-while:\n\n":
cout « "do {\n":
cout « " инструкция;\п";
142 МОДУЛЬ З. ИНСТРУ1<ЦИИ управлени~
............................................................................................................................................................
return О;
Вопрос. Ра1tьше было nоказаuо, 'tтo nеремен.ную можно объявить в разделе uнициа
лuзацuи ЗOlоловка цикла for. Можно ли оБЬSUJJUlmь neре.меНII.ые внутри лю
бых других управляющux С + + -инструкций?
Ответ. Да. В С++ можно объявить nepeMeHH)lO в условном выражении if- ИJПI
sw i tсh-ииструкции, в УСЛОВНОМ выражеllИИ цик.ла whi le либо в разделе
инициализации цикла for. Область видимости переменной, объявлеююй
в ОДНОМ ИЗ таких мест. ограничена блоком кода, управляемым соответ·
ствующей инс.трукциеЙ.
if (int х = 20)
х = х - У;
ВАЖНО!
iinclude <iostream>
using namespace std;
int main ()
int t i
return О;
О 123 4 5 б 7 8 9
как подтверждают эти результаты, программа выводит на экран числа от О
до 9, а не до 100, поскольку инструкция break при значении t, равном 10, обе
спечивает немедленный выход из цикла.
При использовании вложенных циклов (т.е. когда один цикл включает дру
гой) инструкция break обеспечивает выход только 113 наиболее глубоко вложеll
ного цикла. Рассмотрим пример.
#include <iostream>
using namespace std;
int main ()
144 МОДУЛЬ З. ИНСТРУКЦИИ управления
int t, count;
cout « '\п';
return О;
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 б 7 8 9
1 2 3 4 5 б 7 8 9
1 2 3 4 5 6 7 8 9
Как видите, эта программа вьшодит на экран числа от 1 до 9 десять раз. Каж
дый раз, когда во внутреннем цикле встречается инструкция break, управление
передается во внешний ЦИКЛ for. Обратите внимание на то, что внутренняя for-
инструкция представляет собой бесконечный ЦИКЛ, для завершения которого ис
пользуется инструкция break.
Инструкцию break можно использовать для завершения любого цикла. Как
правило, она предназначена для отслеживания специального условия, при насту
ВАЖНОI
. . ИСПОАЬЗQвание иwструк'",ии"
continue
в С++ существует средство "досрочного" выхода из текущей итерации цикла.
Этим средством является инструкция continue. Она принудительна выполня
ет переход к следующей итерации, опуская выполнение оставшегося кода в те
кущей. Например, в следующей программе инструкция continue используется
для "ускоренного" поиска четных чисел в диапазоне от О до 100.
linclude <iostream>
using narnespace std;
int main ()
(
int Х;
return О;
н~iрЗ ~ срр -1, Этот проект завершает построение справочной системы С++ .
. _-_._- ..! Настоящая версия пополняется описанием синтаксиса инструк
ций break, continue и goto. Здесь пользователь может делать запрос уже не
только по одной инструкции. Эта возможность реализована путем использова
ния внешнего цикла, который работает до тех пор. пока пользователь не введет
для выхода из программы команду меню q.
Последовательность действий
1. Скопируйте файл Help2 . срр в новый файл и назовите его НеlрЗ. срр.
3. Нет, если инструкция break применяется в инструкции switch, которая вложена в не
КОТОРЫЙ ЦИХЛ. ТО она повлияет только на данную инструкцию ~witch. а не на цикл.
С++: PYI<OBOACTBO ДМI начинающих 147
1
break;
5. Вот как выглядит полный текст программы Help3. срр.
/* 5
I
Проект 3.3.
1
. позволяет обрабатывать несколько запросов пользователя.
*/
#include <iostream>
using namespace stdi
148 Модуль З. ИНСТРУКЦИИ управления
int rnain ()
char choice;
for(;;)
do {
cout « "Справка по ииструкциям:\п";
cout « " 1. if\n";
cout « " 2 . switch\n";
cout « " З. for\n";
cout « " 4 • while\n" ;
cout « " 5. do-while\n";
cout « " 6. break\n" ;
cout « " 7 • continue\n";
cout « " 8 . goto\n";
cout « "Выберите вариант еправки (q для выхода) : " ,.
cin » choice;
while( choice < '1' II choice > 'В' && choice !=
'q' ) ;
cout « "\n\n";
switch(choice)
case '1':
cout « "ИНСТРУ1Щия if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) {\n"i
cout « " case KOHcTaHTa:\n"i
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \n";
cout « "}\n";
breaki
С++: РУКОВОДСТВО ДI\~ начинающих 149
сазе '3':
cout « "Инструкция for:\n\n";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкция;\п";
break;
сазе '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case '5':
cout « "Инструкция do-while:\n\n";
cout « "do {\n";
cout « " инструкция;\п";
cout « "} while (условие);\п";
break;
сазе '6':
cout « "Инструкция break:\n\n";
cout « "break;\n";
break;
сазе '7':
cout « "Инструкция continue:\n\n";
cout « "continue;\n";
break;
case '8':
cout « "Инструкция goto:\n\n";
cout « ~goto MeTKa;\n";
~
]1
break;
I
u
'3:
О
cout « "\n"; :о:
§
8.
с
u
I
return О;
граммы.
!r
:о:
CD
Справка
1. if
2. switch
по инструкциям:
1
з. for
150 МОДУЛЬ З. ИНСТРУКЦИИ управления
4. while
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): 1
Инструкция if:
if(условие) инструкция;
e1se инструкция;
Справка по инструкциям:
1. if
2. switch
З. for
4. whi1e
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): б
Инструкция break:
break;
Справка по инструкциям:
1. if
2. switch
З. for
4. while
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): q
С++: РУКОВОДСТВО ДМI начинающих 151
ВАЖНОI
G:
:s:
:J:
Как было продемонстрировано на примере одной из предыдущих программ, ф
<
ID
один цикл можно вложить в другой. Вложенные циклы используются для реше
ния задач самого разного профиля и часто используются в программировании.
8.
~
Например, в следующей программе вложенный цикл for позволяет найти мно :s:
~
жители для чисел в диапазоне от 2 до 100.
/* ~u
:J:
Использование вложенных циклов для отыскания s:
множителей чисел в диапазоне от 2 до 100
*/
#include <iostream>
using namespace std;
int main ()
cout « "\n";
return О;
Множители числа 2:
Множители числа З:
Множители числа 4: 2
Множители числа 5:
Множители числа 6: 2 3
Множители числа 7:
Множители числа 8: 2 4
Множители числа 9: 3
152 Модуль З. ИНСТРУf<ЦИИ управления
ВАЖНОI
",.• 14WCTPVK' ,ИЯ got-o
Инструкция goto - это С++-инструкция безусловного перехода. При ее вы
полнении управление программой передается и НСТРУIЩИИ, указанной с помощью
метки. Долгие годы эта инструкция находилась в немилости у программистов,
поскольку способствовала, с их точки зрения, созданию "спагетти-кода". Однако
инструкция goto по-прежнему используется, 11 иногда даже очень эффективно.
В этой книге не делается попытка "реабилитаиии" законных прав этой инструк
ции в качестве одной из форм управления программой. Более того, необходимо
отметить, что в любой ситуации (о области программирования) можно обойтись
без инструкции goto, поскольку она не является элементом, обеспечивающим
полноту описания языка программирования. Вместе с тем, в определенных ситу
ациях ее использование может быть очень полезным. В этой книге было решено
ограничить использование инструкции goto рамками этого раздела, так как, по
for ( ... ) {
for ( ... )
while ( ... )
if( ... ) goto stop;
stop:
cout « "Ошибка в программе.\п";
count = i;
7. Поясните назначение инструкции break.
8. Что будет отображено на экране после выполнения инструкции break в
следующем фрагменте кода?
11. Код строчных букв ASCII отличается от кода прописных на 32. Таким об
разом, чтобы преобразовать строчную букву в прописную, необходимо вы
честь из ее кода число 32. Используйте эту информацию при написании
программы, которая бы считывала символы с клавиатуры. Перед отобра
жением результата обеспечьте преобразование всех строчных букв в про
писные, а всех прописных - в строчные. Другие же символы никаким из
менениям подвергаться не должны. Организуйте завершение программы
после ввода пользователем символа "точка". Перед завершением програм
ма должна отобразить количество выполненных преобразований (измене
ний регистра).
ВАЖНОI
Здесь с помощью элемента записи тип объявляется базовый тип массива. Базо
вый тип определяет тип данных каждого элемента, составляющего массив. Коли
чество элементов, которые будут храниться в массиве, определяется элементом
размер. Например, при выполнении приведеиной ниже инструкции объявляет
ся iпt-массив (состоящий из10 элементов) с именем sample.
int sample[lOJ;
С++: РУКОВОДСТВО для начинающих 159
#iлсludе <iostream>
usiлg лаmеsрасе std;
iлt mаiл ()
(
iлt sample[10); // Эта инструкция резервирует область
// памяти для 10 элементов типа iлt.
iлt t;
« sample[t] « "\л";
rеturл О;
О 2 з
4 I
Массивы часто используются в программировании, поскольку ПQ.1ВОЛЯЮТ
легко обрабатывать большое количество СВЯ;Iанных переменных. Например, в
следующей программе создается массив из де(:яти элементов, каждому элементу
присваивается некоторое число, а затем вычисляется и отображается среднее от
этих значений, а также минимальное и максимальное.
/*
Вычисление среднего и определение минимального
* 1-
#include <iostream>
using namespace std;
int main ()
nums[O] 10;
nums[l) 18;
nums[2) 75;
пums[З) О;
nums[4] 1;
С++: руководство ДЛЯ начинающих 161
nums[S] 56;
nums[6] 100;
nums[7] 12;
nums[8] -19;
nums [9] 88;
return О;
1/ ...
а = Ь; // Ошибка!!!
нерушимосги границ.
ВАЖНОI
с'· дs.yмермымассивbl
в С++ можно использовать многомерные массивы. Простейший многомер
ный массив - двумерный. Двумерный массив, по сути, представляет собой спи
сок одномерных массивов. Чтобы объявить двумерный массив целочисленных
значений размером 10х20 с именем twoD, достаточно записать следующее:
int twoD[10] [20];
Обратите особое внимание на это объявление. В отличие от MHomx других
ЯЗЫI<ОВ программирования, в которых при объявлении массива значения раз-
int main ()
cout « '\n';
return О;
в этом примере элемент numS [О] [О] получит значение 1, элемент nums I О]
[ 1] - значение 2, элемент n uтз [ О] [2] - значение 3 и т.д. Значение элемента
nums [2] [3] будет равно числу 12. Схематически этот массив можно предста
вить следующим образом.
о 2 з ~ Прааый индекс
о 1 2 3 4
1 5 6 ;!) 8
2 9 10 ~1 12
t
Левый индекс nums(1)[2)
С++: PYI(OBOACTBO дl\fI начинающих 165
в байтах
ВАЖНОI
рв МWQtQМepNЫ8..I4QCCИВЫ
В С++, помимо двумерных, можно определять массивы трех и более измере
ний. Вот как объявляется многомерный массив.
.,.•t8i§ЦI'Мс'РDJpQII&8IUIOCGИ8a
BUbble. ~~p I Поскольку одномерный массив обеспечивает организацию дан
L ных в виде индексируемого линейного списка, то он представляет
собой просто идеальную структуру ланных для сортировки. В этом проекте вы по
знакомитесь с одним из са\1ЫХ простых способоfl сортировки массивов. Существует
много различных алгоритмов сортировки. Широко применяется, например, сорти
ровка перемешиванием и сортировка методом Шелла. Известен также алгоритм t)w-
строй сортировки. Однако самым пpocrым счит ..ется алгоритм сортировки ny3bl,CJb-
КOбbI.М методо,м. Несмотря на то что пузырьковая сортировка не отличается высокой
эффективностью (его производительность неприемлема для больших массивов), его
вполне успешно можно применять для сортировки массивов малого размера.
Последовательность действий
1. Создайте файл с именем ВиЬЫе. срр.
/*
Проект 4.1.
Демонстрация метода пузырьковой сортировки
(ВuЬЫе sort).
*/
#include <iostre~m>
'include <cstdlib>
using namespace std;
int main ()
168 Модуль 4. Массивы, строки и указатели
int пиmз[10];
int а, Ь, t;
int size;
return О;
Исходный массив:
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
Отсортированный массив:
41 6334 11478 15724 18467 19169 24464 26500 26962 29358
С++: руководство дl\t;J начинающих 169
.................................................................................................................................
~
>-
одну из версий этого алгоритма. Но, прежде чем использовать ее, вам не :s:
:s:
обходимо изучить больше средств С++. ~
о
е-
u
]j
ВАЖНОI 111
:s:
u
u
.0
Чаще всего одномерные массивы используются для создания символьных строк. :~
В С++ поддерживается два типа строк. Первый (и наиболее популярный) пред
полагdет, что строка определяется как символьный массив, который 3аВершается
нулевым символом ( , \ О '). Таким образом. строка с завершающим нулем состоит
из символов и конечного нуль-символа. Такие строки широко используются в про
граммировании, поскольку они позволяют программисту выполнять самые разные
char str[l1];
Заданный здесь размер (11) позволяет зарезервировать место для нулевого сим
вола в конце строки.
170 Модуль 4. Массивы, СТРОI(И и УI<азатели
................................................................................................................................
м 8 r s о I
Считывание строк с клавиатуры
Проще всего считать строку с клавиатуры, создав массив, который примет эту
строку с помощью инструкции cin. Считывание строки, введенной пользовате
лем с клавиатуры, отображено в следующей программе.
11 Использование сiп-инструкции для считывания строки
11 с клавиатуры.
#include <iostream>
using namespace stdi
int main ()
{
char str[80]i
rеturп О;
gеts(имя_массива);
1/ с клавиатуры.
#include <iostream>
#include <cstdio>
using патеэрасе std;
int main ()
{
char str[80);
return О;
На этот раз после запуска новой версии программы не! 8ыполнеtlие и ввода с
клавиатуры текста "Это простой тест" строка считывается полностью, а затем 1ак
же полностью и отображается.
cout « str;
Здесь (вместо привычного литерала) используется имя строкового массива. Эа
помните: имя символьного массива, который содержит строку, можно испоЛ1,:Ю
вать везде, где допустимо применение строкового литерала.
При этом имейте в DИДУ, что ни оператор "»" (В инструкции cin), нифункщ{я
gets () не выполняют граничной проверки (на отсутствие нарушения границ
массива). Поэтому, если пользователь введет строку, длина которой преВЫШilСТ
размер массива, возможны неприятности, о которых упоминалось выше. Из cl\a-
занного следует, что оба описанных здесь варианта считьшания строк с клавиату
ры потенциально опасны. Однако ПOCJIе подробного рассмотрения С++-возмож
ностей ввода-вывода мы узнаем способы, позволяющие обойти эту проблему.
ВАЖНОI
, • • HeKOlOpb1e бибАиQxQчныe
функции обработки строк
Язык С++ подцерживает множество функций обработки строк. Самыми рас
пространенными из них являются следующие.
strcpy ()
strcat ()
strlen()
strcmp ()
Для вызова всех этих функций в программу необходимо включить заголовок
<cs tr ing>. Теперь познакомимся с каждой ФУlfкцией в отдельности.
ФУНКЦИЯ s trcpy ( )
Общий формат вызова функции strcpy () TalCOB:
strcpy(to, from);
ФУНlщия s t rcpy () кuпирует содержимое строки from о строку to. Помните,
что массив, используемый для хранения строки to, должен быть достаточно
БОЛЬUIIlМ, чтобы в него МОЖIIО было пом~стнть строку из массива from. В про
ТlШIЮМ случае массив to IIсреI10ЛНИТСЯ. ·Г.С. произойдет выход за его границы,
что может привести к разрушению програММhI.
ФУНКЦИЯ s trca t ( )
Обращение к функции s t rca t () имеет следующий формат.
strcat(sl, s2);
ФУНlщия strcat () присоеДИlJяет строку s2 к концу строки з1; при этом строка
s2 не изменяется. Обе строки должны завершаться нулевым символом. Резуль
тат lJЫ30lJa этой ФУНКIIИИ, T.~. результирующая строка s1 также будет завершать
ся нулевым символом. Программист должен позаботиться о том, чтобы строка 51
была достаточно большой, и в нее поместилось, кроме ее исходного содержимого,
содержимое строки 52.
ФУНКЦИЯ strcmp ()
Обращение к функции s t rcmp () имеет следующий формат:
strcmp(sl, 52);
Функция strcmp () сравнивает строку 52 со строкой 51 и возвращает значение О,
если они равны. Если сч>ока s 1 лексикографичесlСИ (т.е. в соответствии с алфаJJИТ-
174 Модуль 4. Массивы, строки и указатели
HbIМ порядком) больше строки 52, возвращается положительное число. Если строка
51 лексикографичесЮf меньше строки 52, возвращается отрицательное число.
При использовании функции strcmp () важно помнить, что она В03вращает
число О (т.е. значение fa1se), если сравниваемые строки равны. Следовательно,
если вам необходимо выполнить определенные действия при условии совпаде
ния строк, вы должны использовать оператор НЕ (!). Например, условие, управ
ляющее следующей if-инструкцией, даст истинный результат, если строка str
содержит значение "С++".
Функция strlen ( )
Общий формат вызова функции str1en () таков:
str1en(5);
Здесь 5- строка. Функция 5tr1en () возвращает длину строки, указанной ар
гументом 5.
int rnain ()
{
char 51[80], 52[80];
5trcpY(51, "С++");
5trcpY(52, " - это мощный язык.");
if(!strcrnp(sl, 52»
cout « "Эти строки равны.\п";
С++: Pyt<OBOACTBO ДЛЯ начинающих 175
5trcat(51, 52):
cout « 51 « '\п':
5trcpY(52, 51):
cout « 51 « " и n « 52 « "\п":
if(!5trcmp(51, 52»
cout « "Строки 51 и 52 теперь одинаковы.\п":
return О:
Длины строк: 3 19
Эти строки не равны.
int main ()
char 5tr[BO]:
int i:
5trcpY(5tr, "abcdefg");
176 Модуль 4. Массивы, строки и указатели
// J.
for(i=Oi str[i]i i++) str[i] = toupper(str[i])i
cout « str;
return О;
ABCDEFG
Здесь используется библиотечная функция t.oupper (), которая возвращает
прописной эквивалент своего символьного аргумента. Для вызова функции t)-
upper () необходимо включить в проrpамму заголовок <cctype>.
Обратите внимание на то, что в качестве УСЛQf:ИЯ завершения цикла for ИСПО."IЬ
зуется массив s tr, индексируемый управляющей переменноil i (str [i ) ). Такой
способ управления циклuм вполне приемлем, IJOCKOJ1bKY за истинное значение в
С++ прюrимается любое ненулевое значение. Вспомните, что все печатные СЩf[-:Q
лы представляются значениями, не равными нулю, Ii только символ, завершаЮIlНfЙ
строку, равен нулю. Следовательно, этот цикл работает до тех пор, пою\ Иlщекс не
укажет на нулевой признак конца строки, Т.е. пока значение str [i ) не станет ну
левым. Поскольку нулевой символ отмечает конец строки, цикл останавлинаеl::Я
в точности там, где нужно. В дальнеiiшем вы УВИ.НJТе множество примеров, в КО10-
ph1X нулевой признак КOllЦа строки используется подобным образом.
З. strlen (mystr) .
С++: PYI<OBOACTBO ДЛЯ начинающих 177·
~
Вопрос. J/оддержuвает Лll С++ другие, nOMIL'fO фующuu toupper (), фунxцuu 06-
работ"" сu.мволов?
о
м
Ответ. Да. ПОМИМО ФУНКЦИИ toupper () , стандартная библиотека С++ содер
жит много ДРУПlХ фуюсций обработки символов. Например, ФУНКЦИЮ ~S
toupper () дополняет ФУНКЦИЯ tolower (), КОТQРая 80ЗВращает строч S
:..:
О
ный эквивалент своего символьного арryмеlПа. Например, perncтp буквы а.
(т.е. ПРОШIСНая она ИЮI строчная) можно определить с ПОМОЩЬЮ функции u
isupper (), которая возвра.щпет значение ИСТИНА, если исследуемая iCII
буква является прописной, и функции islower (), которая возвращает s
u
значение ИСТИНА, если исследуемая буква является строчной. Часто
u
о
используются такие функции, как isalpha (), isdigi t (), isspace () ~
и ispunct (), которые ПРИlшмаlOТ символьный аргумент и определяют,
принадлежит ли он к соответствующей категории. Например, функция
isalpha () во:шращает значение ИСТИНА, если ее аргумеlПОМ является
буква алфавита.
ВАЖНОI
а. ИНI4ЦI4ШИ3С1ЦkU1.,.м,QCСИВОВ
в С++ предусм()трена возможность инициализации массивов. Формат ини
циализании массивов подобен формату ининиализации других переменных.
тип имя_массива [раэмер) = {список_значений};
Здесь элемент список_ эна чений представляет собой список значений ишщиа
ЛИ:l3ЦИИ элемеНТОIJ массива, разделенных запятыми. Тип каждого значения ини
циализаЩIII должен быть совместим с базовым типом массива (элементом тип).
Первое значение инициализации будет сохранено в первой позиции масс~ша,
IJторое значение - во второй 11 т.Д. ОбраТlIте внимание на то, что точка с запятоii
ставится после закрывающей фюурной скобки ( ) ).
Например, в следующем примере 10-элементнblЙ целочисленный массив ини
циализируется числаМJI от 1 до 10. '
int i[lO) = {l, 2, 3, 4, 5, б, 7, 8, 9, lO}i
После вьшолнения этой инструкции элемент i [О) получит значение 1, а эле
мент i [9) - значение 10.
Для символьных маССИRОП, предназначенных для хранеllИЯ строк, предусмо
трен сокращенный вариант Iшициализации, который имеет такую форму.
char имя_массива[размер) = "строка";
178 МОДУЛЬ 4. Массивы, строки и указатели
{ 6, 36} ,
(7, 49) ,
{8, 64 } ,
{9, 81) ,
{10, 100}
};
о 1 + - - ПраВblА индекс
о 1 1
1 2 4
2 Э 9
Э 4 16
4 5 25
5 6 эв
6 7 49
7 8 64
8 9 81
9 10 100
i
Левый индекс
#include <iostrea~>
using narnespace std;
(8, 64},
{9, 81},
{10, 100}
};
int main ()
int i, j;
// Поиск значения i.
for(j=O; j<10; j++)
if(sqrs[j] [O]==i) break;
cout « "Квадрат числа" « i « " равен ";
cout « sqrs[j] [1];
return О;
после чего создаст массив, размер которого будет достаточным для хранения всех
значений инициализаторов. Например, при вы полнении этой инструкции
int nums[] = { 1, 2, З, 4 };
будет создан 4-элементный массив nums, соД(·ржащиЙ значеШiЯ 1,2, 3 и 4. По
скольку здесь размер массива nums не задан явным образом, его можно назвать
"безразмерным" .
Массивы "безразмерного" формата иногда бывают весьма полезными. Напри
мер, предположим, что мы используем следующий вариант инициализации мас
сивов для построения таблицы !пtегпеt-адресов.
С++: РУI<ОВQДСТВО Mt;I начинающих 181
ВАЖНО!
, . . МаССИВbI строк
Существует специальная форма двумерного символьного массива, которая
представляет собой массив строк В использовании массивов строк нет ничего
иеобычного. Например, в программировании баз данных для выяснения кор
ректности вводимых пользователем команд входные данные сравниваются с со
int main ()
{
int i;
char str[80];
char nuтbers[10] [80]
С++: РУКОВОДСТВО ДМ) начинающих 183
"Том", "555-3322",
"Мария", "555-8976",
"Джон" , "555-1037",
"Раиса", "555-1400",
"Шура", "555-8873"
};
return О;
ВАЖНОI
char 5 tr [6] = { ' П', 'р', 'и', 'Б', 'е', 'т', '\ О' } ;
з. Перепишите следующий вариант инициализации в "безразмерном"
формате.
Здесь элемент тип означает базовый тип указателя, причем он должен быть до
пустимым С++-типом. Базовый тип определяет, на какой тип данных будет ссы
латься объявляемый указатель. Элемент имя_ переменной представляет собой
имя переменноЙ-указателя. Рассмотрим пример. Чтобы объявить переменную
ip указателем на iпt-зна'lеиие, используйте следующую инструкцию.
int *ip;
1. int 1ist(] = { 1, 2, 3, 4 };
2. char str(] "Привет";
ВАЖНОI
с указателями
с указателями используются два оператора: "*" и "&". Оператор "&" - унар
ный. Он возвращает адрес памяти, по !<оторому расположен его операнд. (Вспом
ните: унарному оператору требуется только один операнд.) Например, при вы
полнении следующего фрагмента кода
ptr = &totali
в переменную ptr помещается адрес переменной total. Этот адрес соответству
ет области во внутренней памяти компьютера, которая принадлежит переменной
total. Выполнение этой инструкции uuкак не повлияло на значение переменной
total. Назначение оператора" &" можно "перевести" на русский язык как "адрес
переменной", перед которой он стоит. Следовательно, приведенную выше инструк
цию присваивания можно выразить так: "перемеllная ptr получает адрес пере
менной total". Чтобы лучше понять суть этого присваивания, предположим, что
переменная total расположена в области памяти с адресом 100. Следовательно,
после выполнения этой инструкции переменная ptr получит значение 100.
Второй оператор работы с указателями (*) служит дополнением к первому
(&). Это также унарный оператор, но он обращается к значению переменной, рас
положенной по адресу, заданиому его операндом. Другими словами, он ссылается
на значение переменной, адресуемой заданным указателем. Если (продолжая ра
боту с предыдущей инструкцией присваивания) перемеНl:lая ptr содержит адрес
переменной total, то при выполнении инструкции
val = *ptri
переменной val будет присвоено значение переменной total, на которую ука
зывает переменная ptr. Например, если переменная total содержит значение
3200, после выполнения последней инструкции переменная val будет содержать
значение 3200, поскольку это как раз то значение, которое хранится по адресу
100. Назначение оператора "*" можно выразить словосочетанием "по адресу".
В данном случае предыдущую инструкцию можно прочитать так: "переменная
val получает значение (расположенное) по адресу ptr".
I
186 Модуль 4. Массивы, СТРОI<И и YI<аэатели
#include <iostrearn>
using narnespace std;
int rnain ()
{
int total;
int *ptri
int val;
return О;
int *р;
double f;
11 ...
р = &f; 11 ОШИБКА!
1. Указатель - это объект, который содержит адрес памяти некотороro другого 06ъекта.
2. long int *valPtr;
3. Оператор" * " позволяет получить значение, хранимое по адресу, заданному ero
операндом. Онератор \\ &" возвращает адрес объекта. по которому расположен етО
операнд.
188 'МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели
Несмотря на то что, как было заявлено выше, при присваивании два указателя
должны быть совместимы по типу, это серьезное ограничение можно преодолеть
(правда, на свой страх и риск) с помощью операции приведения типов. Напри
мер, следующий фрагмент кода теперь формально корректен.
int *р
double f;
/ / ...
р = (int *) &f; // Теперь формально все ОК!
дующую I1рограмму.
!i.nt main ()
{
double Х, У;
int *р;
Х = 123.23;
Р (int *) &Х; / / Используем операцию приведения типов
// для присваивания dоublе-указателя
// iпt-указателю.
// CnеД)'DЦИе две инструкции не ДaJD'J.' _enаекых резуm.'2!а'2!ОВ.
У = *р; // Что происходит при выполнении этой инструкции?
cout « У; // Что выведет эта инструкция?
return О;
С++: РУI<ОВОДСТВОДЛЯ начинающих 189
Вот какой результат генерирует эта программа. (У вас может ПОЛУЧ~ТI3СЯ дру
гое значение.)
1.31439е+ОО9
*р = 101;
число 101 присваивается области памяти, адресуемой указателем р. Таким об
разом, эту инструкцию можно прочитать так: "по адресу р помещаем значение
101". Чтобы инкрементировать или декрементировать значение, расположенное
в области памяти, мресуеМШI указателсм, можно ИСfIOЛI>ЗО[]ЗТЬ IШСТРУIЩIfIО, по
добную следующей.
(*р)++;
Круглые скобки здесь обязательны, поскольку оператор "." имеет более низкий
приоритет, чем оператор "++".
Присваивание эначений с использованием указателей демонстрируется D сле
дующей программе.
#include <iostream>
иsiлg namespace std;
int rnain ()
{
int *р, пurn;
р = &пит;
// через ухазатen. р.
cout « пит « ' ';
(*р) ++; / / t- Ивхреиев'1'ИрУек значение переке_ой пшn
// через ухазатenъ р.
cout « пит « ' ':
(*р)--: // t- Дехрекеи'1'ИрУеи значение перекеиной num
// через указатеnь р.
cout « пит « '\n':
return О:
ВАЖНОI
1'111 ИСПОАIИQВQМ'48 ука3Qr8 А ей
в выражениях
Указатели можно использовать в большинстве допустимых в С++ выражений,
Но при этом следует при менять некоторые снециальные правила и не забывать,
что некоторые части таких выражений необходимо заключать в круглые скобки,
чтобы гарантированно получить желаемый [)(':'Iультат.
pl++:
содержимое переменной-указателя р 1 станст равным 2 004, а не 2 0011 Дело в
том, что при каждом инкрементировании указатель pl будет указывать на сле
дующее int-значение. Для операции декрементирования справедливо обратное
утверждение, Т.е. при каждом декрсментирооаш/И значение рl будет уменьшать
ся на 4. Например, после выполнения инструкции
рl--:
указатель рl будет иметь значение 1 996, еслн до этого оно было равно 2000.
С++: руководство ДЛfJ начинающих 191
рl = рl + 9;
заставляет рl ссылаться на девятый элемент 6азового типа указателя рl относи
тельно элемента, на который рl ссылался до выполнения этой ИНСТРУКЦЮI.
Несмотря на то что складывать указатели нельзя, один указатель можно вы
честь из другого (если они оба имеют один и тот же 6азовый тип). Разность пока
жет количество элементов базового типа, которые разделяют эти два указателя.
Помимо сложения указателя с целочисленным значением и вычитания его из
указателя, а тЗI<ЖС вычисления разности двух указателей, над указателями ника
кие другие аРИф~fетичеСКllе операции не выl1лняются•. Например, с указателями
нельзя складывать Цоа t - или dоublе-зиачения.
Чтобы понять, как формируется результат выполнения арифметических опе
раций нал указателями, выполним следующую короткую программу. Она выводит
реальные физические адреса, которые содержат указатель 11а int-значение (i) и
указатель на dоublе-значение (f). Обратите внимание на каждое изменение адре
са (зависящее от базового типа указателя), которое происходит при повторении
цикла. (Для большинства З2~разрядных компиляторов значение i будет увеличи
ваться на 4, а значение f - на 8.) Отметьте также, что при использовании указателя
в соut-инструкции его адрес автоматически отображается 8 формате адресации,
применяемом для текущего процессора и среды выполнения.
#include <iostream>
using namespace std;
int main ()
int Ч, j [10];
192 Модуль 4. Массивы, СТРОI<И и УI(азатели
................................................................................................................................
i j;
f Ч;
Сравнение указателей
Указатели можно сравнивать, использун операторы отношсния ==, < И >.
Однако для :roro, чтuGы результат сравнения указателей поддавался интер
претации, сравниваемые указатели должны быть каким-то образом связаны.
Например, если указатели ссылаются на две отдельные и никак не связанные
переменные, то любое их сравнение в общем случае не имеет смысла. Но если
они указывают на пеrемснtIые, мсжду которыми существует не которая связь
ВАЖНОI
111' Vказ nТМ l4.14ма ссивы
в С++ указатели и массивы тесно связаны между собой, причем настолько,
что зачастую понятия "указатель" и "массив" взаимозаменяемы. В этом разделе
мы попробуем проследить эту связь. Для начала рассмотрим следующий фраг
мент программы.
рl = str;
Здесь str предстапляе·г соБОl1 имя массива, содержащего 80 символов, а рl -
УI<а:1атель на ТIШ char. Особыii интерес представляет третья строка, при выпол
неllИИ которой переменной р 1 присваивается адрес первого элемента массива
str. (Другими словами, после этого присваивания рl будет указывать на эле
мент str [О].) Дело в том, что в С++ использование имени массива без индекса
генерирует указатель из первый элемеит этого массива. Таким обра.10М, при вы
полнеllИИ присваивания
рl = str
адрес str[Q] присваивается указателю рl. Это и есть ключевой момент, кото
рый необходимо четко понимать: неиндексированное имя массива, использован
ное в выражении, означает указатель на начало этого массива.
str[4]
или
* (p1+4)
В обоих случаях будет выполнено обращение к пятому элементу. Помните,
что индексирование массива начинается с нуля, поэтому при индексе, равном че
_include <iostream>
_include <cctype>
using namespace stdi
int main ()
С++: PYI(OBOACTBO дl\Я начинающих 195
int i;
char 5tr[80] = "Thi5 I5 А Test";
return О;
#include <iostrearn>
tinclude <cctype>
using narnespac'e std;
int main ()
196 Модуль 4. Массивы, СТРОI<И и указатели
char *р;
char str[80] = "This I5 А Test";
while (*р)
if (isupper (*р))
*р = tolower(*p);
else if(islower(*p)) // Дoc~ к массиву _tr через
// указатeJП..
*р = toupper(*p);
р++;
return О;
Индексирование указателя
Как было показано выше, доступ к массиву можно получить путем выполне
ния арифметических действий над указателями. Интересно то, что в С++ указа-
.а
С++: РУI<ОВОДСТВО ДЛЯ начинающих 197
....•...••....•.......••.••....•.................•............................................................................
тель, который ссылается tla массив, можно индексировать так, как если бы это
было имя массива (это говорит о тесной связи Между указателями и массивами).
Рассмотрим пример, который представляет собой третью версию программы из
менения регистра букв.
// Индексирование указателя подобно массиву.
#include <iostream>
#include <cctype>
using namespace std;
int main ()
{
char *р;
int i;
char str[80] = "This Is А Test";
// now, index р
for(i = О; p(i); i++) (
if(isupper(p[i]))
p[i] = tolower(p[i)); / / f - Используем р !Сак еcnи бы
else if(islower(p[i])) // это было имя массива.
p[i) = toupper(p[i]);
return О;
Строковые константы
Возможно, вас удивит способ обработки С++-компиляторами строковых KOIf-
стант, подобных следующей. I
cout « strlеп("С++-компилятор");
#include <iostream>
using namespace std;
int main()
char *ptr;
cout « ptr;
return О;
:- s·"t-··R"- ..._""-
: r ev.cpp
1 Выше отмеча.lЮСЬ, что сравнение указателей имеет смысл,
06
если
пример массив. Допустим, даны два указателя (с именами А и В), которые ссыла
ются на один и тот же массив. Теперь, когда вы lIонимаете, как могут быть связа
ны указатели и массивы, можно применить сравнение указателей для упрощения
некоторых алгоритмов. В этом проекте мы рассмотрим один такой пример.
Разрабатываемая здесь программа реверсирует содержимое строки, Т.е. меня
ет на обратный порядок следования ее СИМВОЛОIl. Но вместо копирования СТРOJ~и
в обратном порядке (от конна к началу) о ltpyroii массиu, она реверсирует содер
Жll:\юе строю! DHYTPIl JlI<\СС!ШU, l<UTopbli'( ее СОДСРЖIIТ. ДЛЯ реализации Dышеска
заlllЮГО программа llспользует дnе переменныс типа у«азаТель. Одна указывает
на начало массива (еl"О пероыi1 элемент), а вторая - на еl"О конец (его последний
элемент). UИl<Л, используеl\(Ы(~1 в прогрuмме, продолжается до тех пор, пока ука
затель на начало строки меньше указателя на ее конец. На каждой итерации ЦИК
ла символы, на I<OTOPbIC ссылаются эти указатели, меняются местами, после че,'о
Последовательность действий
1. Создайте файл с именем StrRev. срр.
/*
Проект 4.2.
Реверсирование содержимого строки "по месту".
*/
tinclude <iostream>
tinclude <cstring>
using namespace stdi
С++: РУКОВОДСТВО дl\Я начинающих 201
int main ()
{
~
~
char str[] "это простой тест"; о
с')
int len;
5-
s
s
char t; ::.::
О
len = strlen(str);
start = str i
end = &str[len-l];
ОбраТIIТС ВШlМаllIlС lIа то, что у[(~атCJlЬ end ссылается на последний сим
вол строки, а не на признак ее завершения (нулевой символ).
•
4. Добавьте код, который обеспечивает реверсирование символов в строке .
*/
#include <iostream>
#include <cstring>
using namespace std;
int main () .
(
char str [) "это простой тест";
char *start, *end;
int len;
char t;
len = strlen(str);
start = str;
end = &str[len-l];
п" ;
rеtцrп О;
Массивы указателей
Указатели, подобно ДillШЫМ других типов, могут храниться в массивах. Вот,
например, как выглядит объявление 10-элементного массива указателей на int-
значения.
int *pi[10];
Здесь каждый элемент массива pi содержит указатель на цело численное значение.
Чтобы присвоить адрес i n t -перемеи ной с именем v а r третьему элементу это
го массива указателей, запишите следующее.
int var;
рН2] = &var;
Помннте, что здесь pi - массиu указатслсi1на цеЛО'lислеlШЫС значения. Элемен
ты этого массива могут содержать только значения, которые представляют собой
адреса персмеНIIЫХ ЦСЛОЧИСЛСIl1IОГО ТllПа, а не сами значення. Вот поэтому пере
меннаяvаr предваряется оператором" &".
Чтобы узнать Зllачение lIеремеНIIОЙ var С 110МО~ЬЮ массива pi, используйте
такой синтаксис.
*pi[2]
Поскольку адрес переменной var хранится n элементе pi [2] , применение опе
ратора "*" к этой индексированиой персменной и позволяет получить значение
переменной var.
Подобно другим массивам, массивы указателей можно инициализировать.
Как правило, инициализированные массивы указателей используются для хра
нения указателей на строки. Рассмотрим пример исполъзования двумерного мас
сива символьных указателей для СОЗДilllИЯ неболъшого толкового словаря.
#include <iostream>
#include <cstring>
204 МОДУЛЬ 4. Массивы, СТРОI<И и указатели
int main ()
11 Д-укерlDlЙ ка.ссив chаr-укаsатenей ДnK адресации
1/ пар строк.
char *dictionary[] [2] = {
"карандаш", "Инструмент для письма или рисования.",
"клавиатура", "Устройство ввода данных.",
"винтовка", "Огнестрельное оружи.=.",
"самолет", "Воздушное судно с неподвижным крылом.",
"сеть", "Взаимосвязанная группа компьютеров.",
" " , ",.
};
char word[80];
int i;
if(!*dictionary[iJ [О])
cout « word « " не наЙдено.\п";
return О;
*dictionary[i] [О]
ВАЖНОI
~_~~!LМног.о.v:РDвне,вая.иenр.ЯМая
адресация
Можно создать указатель. который будет ссылаться на другой указатеЛJ"
а тот - на коне'шое значсние. Эту ситуацию назыпают цепочкой указателей,
лmОlOУРО611саой lIеnрямой адресацией (multiple iJldirection) или использовани.ем
указа.теля иа указатель. Идея многоуровневой непрямой адресации схематично
ПРОIlЛЛЮСТрIlроn:ша на рис. 4.2. Как DНДIIТС, значение обычного указатсля (при
ОДllOУРОDIlспоilнспрямоii а..'l[)есаЦlШ) предстаDляет собой адрес псремешIOЙ, ко
торая содсржнт HCI<OTOPOC Зllа'IСНИС. В случае применеиия УI<азателя на указа
тель псрвыii содсржит адрес оторого, а тот ссылается на переменную, содержа
щую определенное значение.
свАоАТАЩ:"
int main ()
{
int х, *р, **q;
х = 10;
return О;
Очень простые программы (как представлеНllые здесь до сих пор) содержат толь-
1<0 одну функцию main () , в то время I<aк большинство других включает нссю)лько
фуrшпнii. ОltJ[~ШО IЮ~fмсrЧl'СIClIС ПJюграммы ""СЧllТывают СОТНII ФУlllщшUr.
ВАЖНОI
~.,z,О_бщиЙ~ормаr_С:.+_~"фу,.нl{ЦИЙ
Всс С++-Функции имеют такой общий формат.
тил_возвращаемого_значения имя (список_лараметров)
11 тело функции
J
111 Создание фVНКII.kU4
Функцию создать очень просто. Поскольку все функции исполъзуют один И
тот же общий формат, их структура подобна структуре фуикции main (), с кото
рой нам уже приходилось иметь дело. Итак, начнем с простого примера, который
содержит две фушщии: main () и myfunc (). Еще до выполнения этой програм
мы (или чтения послеДУlощего описания) внимательно изучите ее текст и попы
тайтесь предугадать, что она должна отобра.lИТЬ иа экране.
int main ()
return О;
В ФУНКЦИИ main().
В ФУНКЦИИ myfunc().
Снова в функции main().
в общем случае при каждом вызове функции Ьох () будет вычислен объем
параллелепипеда путем Уl\шожения значений, переданных ее параметрам leng-
th, width и height. Обратите внимание на то, что объявлены эти параметры в
виде списка, заключеНIIОГО в круглые скобки, расположенные после имени ФУНК
ции. Объявление каждого параметра отделяется от следующего запятой. Так объ
являются параметры для всех функций (если они их используют).
При вызове функции необходимо указать три аргумента. Вот примеры.
Ьох (7, 2 О, 4);
Ьох (50, З, 2);
#include <iostream>
using namespace std;
216 МОДУЛЬ 5. Введение в фУНI<ЦИИ
................................................................................................................................
int main ()
{
Ьох(7, 20, 4); // Передаем apryмeH~ ФУНКЦИИ Ьох().
Ьох (50, 3, 2);
Ьох (8, 6, 9);
return О;
return
в предыдущих примерах возврат из ФУНКЦIIИ к инициатору ее вызова проис
ходил при обнаружении закрывающей фиryрной скобки. Однако это приемлемо
С++: PYI(OBOACTBO для начинающих 217
не для всех ФУНКЦИЙ. Иногда требуется более гибкое средство управления воз
вратом из функции. Таким средством служит инструкция return.
:s:
~
#include <iostream>
using namespace stdi
void f () i
int main ()
(
cout « "До вызова функции.\п";
f();
return О;
До вызова функции.
В функции f().
После вызова функции.
#include <iostream>
using паmезрасе std;
int main ()
(
power(10, 2):
power(10, -2);
С++: PYI<OBOACTBO ДМ1 начинающих 219
return О;
i = 1;
void f ()
(
// ...
switch(c)
case 'а': return;
саэе 'Ь': // ...
саэе 'с': return;
Возврат значений
Функция может возвращать значение инициатору своего вызова. Таким об
разом, возвращаемое функцией значение - это средство получения информации
из фушщии·. Чтобы вернуть значение, используйте вторую форму инструкции
return.
return значение;
// Возврат значения.
#include <iostrearn>
using narnespace std;
int main ()
(
int answer;
return О;
:s:
~
// Эта функция возвращает значение.
int box(int length, int width, int height)
(
1
ID
Ф
:s:
return length * width * height ; // Bos.p~e~CR об~и.
#include <iostream>
using namespace stdi
int main ()
{
double answer;
return О;
BA~
fJ ИС П О,дЫQВQНи еТФVI:JIЩI414
В выражениях
в предыдущем примере значение, возвращаемое функцией Ьох (), присваи
валось переменной, а затем значение Этой перемешlOЙ отображалось с помощью
инструкции cout. Эту программу можно было бы переписать в более эффектив
ном варианте, используя возвращаемое функцией значение непосредственно в
инструкции cout. Например, функциюmаiП () из предыдущей программы мож
но было бы представить в таком виде.
int main ()
(
// Непосредственное использование значения,
11 возвращаемого функцией Ьох().
cout « "Объем параллелепипеда равен "
« box(lO.l, 11~2, 3.3); 11 Испonъзоваиие зкачеНИR,
С++: руководство дAr;) начинающих 223
// .оs.ращаеиоrо функцией
// Ьох(), непосредст••иво
:s:
// • инструхции cout.
~
х
return О; !
са
ф
:s:
При выполнении этой инструкции
ция Ьох ()
cout автоматически вызывается функ
для получения возвращаемого ею значения, которое и выводится на I
со
a:I
экран. Выходит, совершенно необязательно присваивать его сначала некоторой
переменной.
В общем случае vоid-функцию можно использовать в выражении любоro
типа. При вычислении выражения функция, которая о нем содержится, автома
тически вызывается с целью получения возвращаемого ею зна~ения. Например,
J] следующей программе суммируется объем трех параллелепипедов, а затем ото
бражапся среднее значение объема.
//Использование функции Ьох() в выражении.
finclude <iostream>
using namespace std;
int main ()
(
double sum;
return О;
L. АQКQАьыая оБАQСТhВИДИМОСIИ
Локальная область видимости создается блоком. (Вспомните, что блок на
чинается с открывающей фигурной скобки и завершается закрывающей.) Таким
ID
Ф
s
j
образом, каждый раз при создании нового блока программист создает новую об
ласть видимости. Переменная, объявленная внутри любого блока кода, называет
ся локалыюй (по отношению к этому блоку).
Локальную переменную могут ИСПОJII,зовать лишь ИНСТРУКЦИИ, включенные
D блок, в котором эта переменная объявлена. дРУЛ1МИ словами, локальная пере
мснная нсизвестна за пределаl\Ш собствснного блока кода. Следовательно, ИН
СТРУЮIИИ внс блока не могут получить доступ к объекту, определенному внутри
блока. По сути, объявляя локальную переменную. вы защищаете ее от несанкци
онироваНIIОГО J1.0ступа н/или модифиющии. Более того, можно сказать, что пра
вила действия областеii DIIДIIМОСТИ обеспечивают фундамент для инкапсуляции.
Важно понимать, что локальные переменные существуют только во время вы
полнения программного блока, u котором они оБЪЯDлены. Это означает, что ло
кальная псременная создается при входе в "свой" блок и разрушается при выходе
И;J него. А поскольку ЛОl<aJIЬНая пере~енная разрушается при выходе из "своего"
блока, се значеНllе теряется.
Самым распрос-граненным программным блоком является функция. В С++
каждая функция определяет блок кода, который начинается с ОТl<рьшающей фи
гурной скоБЮI этой функции 11 заuершается ее закрывающей фигурной скобкой.
Код функнии и ее данные - это ее "частная собственность", и к ней не может по
ЛУ'IИТЬ доступ liИ ОJl.на инструкция нз любой другой функции, за исключением
инструкции ее вызова. (Например, невозможно использовать инструкцию goto
для перехода в середину кода другой функции.) Тело ФУНКЦИИ надежно скрыто
от остальной части программы, и она не может оказать никакого влияния на дру
гие части программы, равно, как и те на нее. Таким образом, содержимое одной
функции совершенно "езависимо от содержимого другой. Другими словами, код
и данные, определенные в одной функции, Jje могут взаимодействовать с кодом
и данными, определенными в другой, поскольку две функции имеют различные
области видимости.
Так как каждая функция определяет собственную область видимости, пере
менные, объявленные в одной функции, не оказывают никакого влияния на пере-
226 Модуль 5. Введение в функции
менные, объявленные в другой, причем даже в том случае, если эти переменные
имеют одинаковые имена. Рассмотрим, например, следующую программу.
tinclude <iostream>
using namespace std;
void f1 () ;
int main ()
{
int val = 10; // ~a пsреке_. . val nОК8J'П8аа по
return О;
void fl ()
/11
Локальная переменная инициализируется
*/
#include <iostream>
using namespace std;
void f();
int main ()
return О;
99
99
99
Содержимое неинициализированной локальной переменной будет неизвест
но до тех пор, пока ей не будет присвоено некоторое значение.
#include <iostream>
using namespace std;
int main ()
int х 19; // Переменная х иэвестна всему коду функции.
if(x == 19)
int у 20; 11 nepeкeRИa. у похan.ва дик if-бпоха.
поэтому доступна ДЛЯ осеl"О последующего кода этой функции. В блоке if-ин
струкции объявляется переменная у. Поскольку рамками блока и определяется
область видимости, переменная у видима только для кода внутри этого блока.
j
Поэтому строка
у = 100;
(она находится за пределами этого блока) отмечена как комментарий. Если
убрать символ коммснтария (/1) в началс этой строки, компилятор отреагирует
сообщением об ошибке, поскольку персменная у невидима вне этого блока. Вну
три i f-блока вполне можно использовать переменную х, поскольку код в рамках
блока имеет доступ к переменным, объявленным во включающем блоке.
Несмотря на то что локальные ПСРСl\Iснные обычно объявляются в начале сво
его блока, это не ЯАляется обязательным требованием. Локальная переменная
может быть объявлена в любом месте блока кода - главное, чтобы это произошло
до ее использования. Например, слеЛУЮIJЩЯ программа абсолютно допустима.
#include <iostrearn>
using namespace stdi
int rnain ()
{
cout « "Введите первое число: ";
int а; /1 Объявляем одну переменную.
cin » а;
return О;
230 Модуль 5. Введение в функции
Сокрытие имен
Если имя переменной, объявленной во внутреннем блоке, совпадает с именем
переменной, объявленной во внешнем блоке, то "внутренняя" переменная скры
вает, или переопределяет, "внешнюю" в пределilX области видимости внутренне
го блока. Рассмотрим пример.
linclude <iostream>
using namespace stdi
int main ()
{
int i, j;
i 10;
j 100;
if(j > О) {
int ii // Эта леременная i отделена от
// внешней леременной i.
i = j / 2;
cout « "Внутренняя леременная i: " « i « '\п';
return О;
Внутренняя леременная i: 50
Внешняя леременная i: 10
Здесь переменная i, объявленная внутри if~лока, скрывает внешнюю пере
менную i. Изменения, которым подверглась внутренняя переменная i, не ока-
С++: руководство МЯ начинающих 231
параметры функции i
ID
Ф
:s
Параметры функции существуют В пределах области ВИДИМОСТИ функции.
Таким образом, они локзльны по отношению к функции. Если не считать получе !
ния значений аргументов при вызове функции, то поведение параметров ничем не !
отличается от поведения любых других локзльRых перемеlПlblX внутри функции.
Ответ. Действительно, язык С++ содержит ключевое слово а uto, которое можно
использовать для объявления локальных переменных. Но поскольку все
локальные переменные являются по умолчанию аutо-переменными, то
#include <iostream>
using narnespace std;
void funcl () ;
void func2();
int rnain ()
return О;
void funcl ()
func2();
s
void func2() ~
!
са
int count; / / Э~О JlOKaJIIoBёUI пер....IUI&JI. ф
S
count: О
... count: 2
... count: 4
... count: 6
... count: 8
· .. count: 10
· .. count: 12
· .. count: 14
... count: 16
· .. count: 18
При внимательном изучении этой программы вам должно быть ясно, что как
ФУНКЦИЯ main (), так и функция func1 () используют глоба.тIl:.НУЮ переменную
count. Но в ФУНКЦИИ func2 () объявляется локальная l1еременная соип t. Поэ
ТОМУ здесь при использовании имени count подразумевается именно локальная,
2. да, локальную переменную можно объявить в любом месте блока, 110 до ее исполь
зования.
ВАЖНОI
181;8 ПеРААача~азаlfМей.I4МOССИ808
••
в качестве аргументов функций
в предыдущих примерах функциям передавались значения простых перемен
НhlX типа int или double. Но возможны ситуации, когда в качестве аргумен
тов необходимо использовать указатели и массивы. Рассмотрению особенностей
передачи аргументов этого типа и посвящены следующие разделы.
#inc1ude <iostream>
using namespace stdj
11 на iпt-значение.
int main ()
int ii
int *р;
return О;
*j == 100;
переменная i получает значение 100. Поэтому программа отобразит на экране
число 100. В общем случае Ilрипеденная здесь функция f () присваипаеl' ЧИС.ilо
100 переменной, адрес «оторой был передан э10Й функции в качестве аргумента.
В предыдущем примере необяззтелыю было использовать переменную-ука
затель р. Вместо нее при вызове функции f () ,'{остаточно взять переменную i.
предварив ее оператором" &" (при этом, как вы знаете, генерируется адрес пе
рсмешюй i). После внесения огопорешюго ИЗI\IСНfrшя предыдущая программа
приобретает такой вид.
// Передача указателя функции (исправленная версия).
#include <iostream>
using namespace std;
int main ()
int i;
cout « i;
return О;
#include <iostream>
using namespace stdi
int main ()
{
int t[lO],ii
return О;
int i;
int i;
int i;
#include <iostream>
using namespace std;
int main ()
{
int i, nums[10];
return О;
while(num)
·п = ·п * *п * *п; 11 Э~О в~а.евие измеRRе~
// зва.еиие эnеиеи~а массива,
240 Модуль 5. Введение в функции
// aдpecyeMO~O yxasa~eneм n.
цит--;
п++;
tinclude <iostream>
#include <cstring>
#include <cctype>
using паmеsрасе stdi
int main ()
(
char str[80];
strlnvertCase(str);
return О;
tНIS iS а tEST
3. Массивы как таковые функции передать нельзя. Но можно передать фyнI<ЦИи ука
затель на массив. В этом случае изменения, вносимые в массив кодом функции,
окажут влияние на массив, испольэуемый в качестве apryмeнтa.
242 Модуль 5. Введение в функции
указателей
Функции могут возвращать указатели. Указатели возвращаются подобно эна
чениям любых других типов данных и не соэдают при этом особых проблем. Но,
поскольку укаэатель представляет собой одно иэ самых сложных (или не6еэопас
ных) средств яэыка С++, имеет смысл посвятить этой теме отдельный раздел.
Чтобы вернуть указатель, функция должна объявить ero тип в качестве типа
возвращаемого эначения. Вот как, например, объявляется тип воэвращаемого эна
чения для функции f ( ) • которая должна воэвращать указатель lIа целое число.
int *f()i
ЕСЛIJ функция [юзвращает указатель, то зна'[ение, используемое 8 ее инструк
[ЩИ return, тш,же должно быть указателем. (Как и ДЛЯ всех функций, rеt\lr-п
значение ДОЛЖНО быть совместимым с типом возвращаемого значения.)
В следующей программе демонстрируется использование указателя в каче
стве типа возвращаемого значения. Функция get_substr () возвращает ук.ыа
тель на первую подстроку (найденную в строке), которая совпадает с заданной.
Если заданная подстрок<I. не найдена, возвращается нулевой указатель. Напри
мер. если в строке "Я люблю С++" вьmолияется поиск подстроки "люблю", ТО
ФУНlЩИЯ get_substr () возвраnп укаэатель на букву л в слове "люблю".
11 Возвращение функцией указателя.
#include <iostream>
using namespace'stdi
int main ()
(
char *substri
return О;
С++: руководство ДМ1 начинающих 243
р2++;
ФУНКЦИЯ main ( )
как вы уже знаете, ФУНlЩИЯ main () - специальная, поскольку это первая
I
ВАЖНОI
fl ll' Передача аргумрнтnвкомаWДNОЙ
строки функции main ( )
Иногда возникает необходимость передать информацию программе при ее
запуске. Как правило, это реализуется путем передачи аргументов ко.манд1l0Й
строки функции main () . ApГYMCIIT командной строки представляет собой IIH-
формацию, указываемую в команде (командной строке), предназначенной для
ВЫПОЛIIСIIIIЯ oIlepaЦllOllllOii системоii, после ЮlеШJ программы. (В Windo\vs 1<0-
манда "Rtш" (Выполнить) т.шже использует командную строку.) Например, с--+
программы можно компилировать путем выполнения следующей команды.
cl рrоg_лате
Параметр
argv представляет собой указатель на массив символьных указате
лей. каждый указатель в массиве argv ссылается на строку, содержащую аргу
мент командной строЮf. Элемент а rgv [ О) указывает на имя программы; элемент
argv [1] - на первый аргумент, элемент argv [2] - на второй и Т.д. Все аргументы
С++: PYI<OBOACTBO МЯ начинающих 245
#include <iostream>
using namespace std;
return О;
ComLine
one
two
three
В С++ точно не оговорено, как должны быть представлены аргументы команд
ной строки, поскольку сред.ы выполнения (операционные системы) имеют здесь
большие различия. Однако чаще всего используется следующее соглашение:
каждый аргумент командной строки должен быть отделен пробелом или симво
лом табуляции. Как правило, запятые, точки с запятой и тому подобные знаки не
являются допустимыми разделителями аргументов. Например, строка
*/
#include <iostream>
finclude <cstdlib>
using namespace std;
double а, Ь;
s
~
if(argc!=3) {
cout « "Использование: add
return 1;
число число\п"; !
CII
ф
S
}
1/
1/
Иcnonъзование функции atof() дпя: npеобразовaииR
строховых аргументов командной строки в чиcnовuе
~
CII
a::I
1/ звачения: типа double.
а = atof(argv[l]); // Преобразование второго аргумента
// командной строки.
Ь atof(argv[2]); // Преобраэование третьего аргумента
// командной строки.
cout « а + Ь;
return О;
Чтобы сложить два числа, используйте командную строку такого вида (пред
полагая, что эта программа имеет имя add).
C>add 100.2 231
1. Два naраметра функции main () обычно называются argc и argv. Параметр argc
предназначен для хранеЮUI количества apryмeНТOB командной строки. Параметр argv
представляет собой указатель на массив символьных указателей, а каждый указатель
в массиве argv ссылается на строку, содержащую соответствующий apryмeнт команд
ноЙстроки.
• тип ее параметров;
• количество параметров.
Прототипы позволяют компилятору выполнить следующие три важные опе
рации.
*/
С++: PYI<OВOACTBO ДМI начинающих 249
int main ()
(
int х;
х = 10;
// Бnаrодари прототипу КОИПИnRТор вЫRВИТ иесоответствие
/ / типов. Ведъ функции sqr_ i t () дoпlCКa принимать
/ / yxasa'l'enь, а она IWSвана с ЦeJIОЧ:ИCneJUDal арryиен'1'ОК.
sqr_it(x); /1 Ошибка! Несоответствие типов!
return О;
*i = *i '* '*i;
#include <iostream>
using namespace std;
int main ()
250 Модуль 5. Введение в функции
return О;
ВАЖНОI
flffРеКУРСИ9
Рекурсия - это последняя тема, которую мы рассмотрим в этом модуле. Ре
"урсu.л, которую иногда называют ЦU1ClluчeCКUAC определением, представляет собой
процесс определения чего-либо на собственной основе. В области программиро
вания под рекурсией пони мается процесс вызова функцией самой себя. Функ
цию, которая вызывает саму себя, называют ре,,:урcuвноЙ.
С++: руководство #\S1 начинающих 251
// Демонстрация рекурсии.
#include <iostream>
using патеsрасе stdi
int main ()
return О;
// Рекурсивная версия.
int factr(int п)
{
int answeri
if(n==l) return(l)i
answer = factr(n-1)*ni // ВЫполнение рекурсивного вызова
// фунхции factr().
return(answer)i
// Итеративная версия.
int fact(int n)
{
int t, answer;
answer = 1;
for(t=li t<=ni t++) answer answer*(t);
return(answer) ;
Когда функция вызывает сама себя, в системном стеке выделяется память для
новых локальных переменных и параметров, и код функции с самого начала вы
полняется с этими новыми переменными. При возвращении каждого рекурсив
ного вызова из стека извлекаются старые локальные переменные и параметры, и
finclude <iostream>
using namespace std;
reverse(str) ;
return О;
if{*s)
rever5e(s+1);
else
return;
cout « *э;
компаранда или равны ему, помещаем в одну часть, OCTaJIblThle (которые меш,шс
компаранда) - в другую. Этот процесс затем повторяется для ка.ждоЙ части до
тех пор, пока массив не станет полностью отсортированным. Например, воз.ьмем
массив fedacb I{ используем значение dn качестве КОЫl1араl!да. Тогда в реЗУ"'lu'
тате первого прохода алгоритма QuickSогt lIаш массив Gудет реорганшюшш СЖ~
дующим образом.
Последовательность действий
1. Создайте файл QSDemo. срр.
i lefti j =oright;
х ~ items[( left+right) / 2 ];
do (
while«items[i] < х) && (i < right» i++;
while«k < items[j]) && (j > left» j--;
if(i o<= j)
у = items[i]i
items[i] = items[j];
items[j] = У;
i++; j--;
*/
finclude <iostream>
#include <cstring>
int main () {
quicksort(str, strlen(str));
return О;
258 МОДУЛЬ 5. Введение в функции
.................................................................................................................................
i left; j = right;
х = items [( left+right) / 2 ];
do (
while«items[i] < х) && (i < right» i++;
while«x < items[j]) && (j > left)) j--;
i f (i <= j)
у = items[i];
items[i] = items[j];
Неmз [j] = У;
i++; j--;
Вопрос. Н СЛWШIlJl, чmo сущесm.уеm nptl_ШlО "IID умолчаНIIЮ тlln in t ". В '101 0110
сосmoll", 11 пplLtleHlIelllCll ли к С++?
1/ ...
return х;
• JeCTnA4Sl.CQMQКD~OASI QQ.МОАJfAю.5
1. Представьте общий формат Функr.rIIИ.
2. Создайте функцию с именем hypot (), которая вычисляет длину гипоте
нузы ПРЯМОУГОЛЬНОI'О треугольника, заданного длинами двух противопо
ВАЖНОI
f._йДsа..с,lX1COfSa.pереДШlI4,
аргументов
н языках I1рограММИРОВ<lIIИЯ. как правило, IIреДУсматривается два способа.
которыс l1031ЮЛЯЮТ персдавать аргументы в подпрограммы (функции, методы,
ПРОIIСДУf>Ы). Первыii назыrшстся оы.ЭООО_~1 по 31lаченuю (call-by-value). В этом
случае 31ЮЧС1luе аргумснта копируется в фОР\1алЫIЫЙ параметр подпрограммы.
Следовательно. изменения. внесенные в парамстры подпрограммы. не влияют на
аргументы. используемые при ее вызове.
ВАЖНОI
#include <iostream>
using namespace stdi
int main ()
(
double t = 10.0;
« reciprocal(t) « "\n";
return О;
return х;
Х = 1 / Х;
.~~.....~~~r~~.~:. <?P.Y.~~~~~~.~?~P.~~!:I.~~ .................................................
// по адресу х.
*у temp; // Помещаем значение, которое раньше
// хранилось по адресу х, по адресу у.
-б
*х = *у: о
#include <iostream>
using namespace std:
int main ()
{
int i, j;
i 10;
j 20;
return О:
}
266 Модуль 6. О ФУНJ(ЦИ~Х подрОбнее
- ч -
З. I<ц:ой механизм передачи параметров используется в С++ по умолчанию?·
..
1. При передаче функции параметров ПО значению (т.е. при вызове по значению) зна
чения аргументов копируются 8 параметры подпрограммы.
2. При передаче фующии параметров по ссылке (т.е. при вызове по ссылке) в naраме
тры подпроrpаммы КОШiРУIOТСЯ адреса аргументов.
ф
ф
:I:
Несмотря на возможность "вручную" организовать вызов по ссылке с помощью \о
О
оператора получения адреса. такой подход не всегда удобен. Во-первых, он вынужда
ет про~миста въmолнять все операции с использованием ука:iате..lеЙ. Во-вторых,
g
~
#inc1ude <iostream>
using namespace std;
int main ()
(
int val = 1;
cout « "Старое значение переменной "а1: .. « va1
« '\п';
f(va1); 1/ Передаем адрес переменной va1 ФУНКЦИИ f().
cout « "Новое значение переменной val: " « val
« '\п';
return О;
268 МОДУЛЬ 6. О функциS1Х подробнее
i = 10;
(в данном случае она одна составляет тело функции) не присваивает перемешюй
i значение 10. В действительности значение 10 присваивастся переменной, на
которую ссылается переменная i (в нашей ПрОl'раммс ею является персмсшшя
val). Обратите внимание на то, что в ЭТQJ':'I инструкции нет оператора "*". ко
торый используется при работе с указателями. Применяя ссылочный llараМС1'Р,
вы тем caMbiM уведомляете С++-компилятор о передаче адреса (т.с. указателя),
и компилятор автоматически разымеиовьшаст его за вас. Более тоl'о. если бы вы
попытались '·помочь" компилятору, использовав оператор "*". то сразу же nO.'IY-
чили бы сообщение об ошибке.
Поскольку перемснная i была объявлена как ссылочный параметр, КОМПП.'Iя
тор автоматически передает функции f () адрес аргумента, с которым вызывает
ся эта функция. Таким образом, в фушщии та:..п () инструrщия
iinclude <iostream>
using namespace stdi
int main ()
{
il1t i, ji
i 10;
j 20;
return О;
*/
void swap(int &х, int &у)
[
int tempi
З. Нужно ли Ilрел:варять параметр символами "." или .. &" при выполнеЮ-il'i опе
раций Над ними в nнкции, которая принимает ССbUlОЧНЫЙ параметр?*
Ответ. Если кратко ответить, то никакой разницы нет. Например, вот как выгля х
с;:
:s:
дит еще один способ записи прототипа функции swap () . :::r
:z:
I
vo1d swap(int& х, int& у);
~
Нетрудно заметить, что в этом объявлении символ" &" ПРllлс("аf"Т вплот О
ную к имени ТJma 1nt, ане к имени переменной х. Более того, некоторые
программисты опрсделяют в таком с.тилс 11 указатсли, беJ пробела СВЯ:JЫ
вая символ "." с. ТJшом, а не С переМСlllюii, как в этом нрнмсре.
float* р;
1nt* а, Ь;
// Возврат ссылки.
#include <iostream>
using паmеsрасе std;
int main ()
(
double Х;
return О;
100
100
99.1
Рассмотрим эту программу подробнее. Судя по прототипу функции f ( ) , она
должна возвращать ссылку на dоuЫе-эначение. За объявлением функции f ()
следует объявление глобальной переменной val, которая инициализируется
значением 100. ~ри выполнении с;ледующей инструкции в функции rnain () вы
водится исходное значение переменной val.
cout « f() « '\n'; 1/ Отображаем значение val.
llосле вызова функция f () при выполнении инструкции return возвращает
ссылку на l1еременную val.
return val; 1/ Возвращаем ссылку на val.
TaKIIM оGразом, I1pll ВЫllOЛIIСIllШ ПРIIВСДСШlOii выше строки автоматически 80З
вращается ССЫЛ1<а на глобальную переменную val. Эта ссылка затем использует
ся инструкцией сои t для отображения значения val.
При выполнеtlИИ строки
#include <iostream>
using narnespace std;
int main ()
(
int i;
return О;
double &change_it(int i)
#include <iostream>
using namespace std;
276 Модуль 6. О ФУНI(ЦИSlХ подробнее
int main ()
{
int j, k;
int &i j; // веsавиCИК&R ccкnxa
j 10;
k 121;
i k; 11 Копирует в переменную :j значение
11 переменной k, а не aдp~c переменной k.
return О;
10 10
121
Адрес, который содержит ссылочная перемснная, фиксирован и его измешrть
нельзя. Следовательно, при выполнении инструкции
i : k;
8 переменную j (адресуемую ССblЛкой i) копируется значение псременной k,
а не ее адрес.
ВАЖНО!
!
о
#include <iostream>
using namespace std;
int main ()
(
f (10) ; // вызов функции f (int)
return О;
#include <iostream>
using namespace std;
int main ()
return О;
return -n;
neg (-10): 10
neg(9L): -9
neg(11.23): -11.23
При выполнении эта программа создает три 110хожие, но все же различные функ
ции, вызываемые с использованием "общего" (одного на всех) имени neg. Каждая
из них возвращает результат отрицания своего аргумента. Во всех ситуациях вызова
компилятор "знает'~, какую именно фуНlЩИЮ ему использовать. Для принятия реше
ния ему достаточно "взглянуть" на тип аргумента, передаваемого функции.
Принципиальная значимость перегрузки состоит в том, что она позволяет об
ращаться к связанным функциям посредством одного, общего для всех, имени.
Следовательно, имя neg представляет общее действие, которое выполняется во
всех случаях. Компилятору остается правильно выбрать "оu"рemн.ую версию при
конкретных обстоятельствах. Благодаря полиморфизму программисту нужно
помнить не три различных имени, а только одно. Несмотря на простоту приве
денного примера, он позволяет понять, наскоЛ1,КО перегрузка способна упростить
процесс программирования.
Еще одно достоинство перегрузки состоит в том, что она позволяет определить
версии одной функции с небольшими разлИЧIIЯМИ, которые зависят от типа об
рабатываемых этой функцией данных. Рассмотрим, например, функцию min (),
которая определяет минимальное из двух значений. Можно создать версии этой
функции, поведение которых было бы различным для разных тИпов данных. Ilри
сравнении двух целых чисел функция min () [JQзвращала бы наименьшее из llИХ,
•
С++: PYI(OBOACTBO д,ля начинающих 281
а при сравнении двух букв - ту, которая стоит по алфавиту раньше другой (неза
висимо от ее реrn:стра). Согласно АSСП-кодам символов прописные буквы пред
ставJUПOТCЯ значениями, которые на 32 меньше значений, которые имеют строчные
6уквы. Поэтому при упорядочении букв по алфавиту важно игнорировать способ
их начертания (регистр букв). Создавая версию функции rnin () , принимающую в
качестве аргументов указатели, можно обеспечить сравнение значений, на которые
они указывают, и возврат указателя, адресующего меньшее из них. Теперь рассмо
трим программу, которая реализует эти версии функции rnin ( ) .
// Создание различных версий функции min().
#include <iostream>
using narnespace std;
1/ на int-значения
int rnain ()
(
int i=10, j=22;
return О;
else return Ь;
/*
Функция rnin() ДЛЯ указателей на int-значения.
Сравнивает адресуемые ими значения и возвращает
*/
int * rnin(int *а, int *Ь)
rnin ( I Х 1 , I а I ) : а
rnin(9, З): 3
*rnin(&i, &j): 10
Каждая версия перегруженной функции может выполнять любые действия.
Другими словами, не существует правила, которое бы обязывало программиста
связывать перегруженные функции общими действиями. Однако с точки зрения
стилистики перегрузка функций все-таки подразумевает определенное "род
ство" его версий. Таким образом. несмотря на то. что одно 1'1 то же имя можно
использовать для перегрузки не связанных общими действиями функций. этого
делать не стоит. Например, в принципе можно использовать имя sqr для ('оз
дания функции, которая возвращает квадрат целого числа, и функции, котоrая
возвращает значение каадраm1l0г0 корня из вещественного числа (типа doub1e).
Но, поскольку эти операции фундаментально rазличны, применеllие мсхаНlпма
перегрузки методов в этом случае сводит на нет его первоначальную цель. (Тшсой
стиль программирования, наверное, подошел бы лишь для того, чтобы ввести в
заблуждение конкурента.) На практике перегружать имеет смысл только Te'~HO
связанные операции.
/*
Автоматические преобразования могут оказать влияние на
*/
iinclude <iostream>
using namespace std;
int main () (
int i = 10;
double d = 10.1;
short 5 = 99;
float r = 11.5Fi
return О;
void f (int х) (
cout « "в функции f(int): " « х « "\п";
void f(double х) (
cout « "В функции f(double): " « х « "\п";
в функции f(int): 10
В функции f(double): 10.1
284 Модуль 6. О фУНКЦИЯХ подробнее
в функции f(int): 99
В функции f(double): 11.5
В этом примере определеиы две версии функции f (): с int- и double-napa-
метром. При этом функции f () можно передать 5hort- или f10аt-значение. При
передаче 5hоrt-параметра С++ автоматически прео6разует его в iпt-значение, и
поэтому вызывается версия f (! n t) . А при передаче f10a t -параметра его значение
пре06разуется в dоublе-значение, и поэтому вызывается версия f (double).
Однако важно понимать, что автоматичеСlсие пре06разования применяются
толы<О в случае, если tleT непосредственного совпадения п типах параметра It ар
#include <iostream>
using namespace std; ,
int main () (
int i = 10;
double d = 10.1;
short s = 99;
float r = 11.5F;
1/ .ерс:ик f (short) .
return О;
void f(int х) {
С++: руководство ДЛЯ начинающих 285
void f(short х) {
cout « "В функции f(short): " « х « "\п";
void f(double х) {
cout « "В функции f(double): " « х « "\п";
В функции f(int): 10
В функции f(double): 10.1
В функции f(short): 99
В функции f(double): 11.5
wщ·тЦ"МаЗДClНИfВIеPlШPv-eнн&JDфу.н_Й
вывода
lX
Пере грузка ФУНКЦИЙ - мощное средство! 18.22
В этом проектефункции print1n () и print () перегружаются для данных типа
Ьоо1, char, int, 1ong, char* и double, но вы сами можете добавить ВОЗ~ОЖ
насть вывода данных и друrnх типов.
Последовательность действий
1. Создайте файл с именем Р r i n t . срр.
2. Начните проект с таких строк кода.
/*
Проект 6.1.
*/
#include <iostream>
using narnespace std:
3. добавьте прототипы функций println () и print ().
// Эти функции обеспечивают после вывода аргумента
// переход на новую строку.
С++: руководство МЯ начинающих 287
void println(int i)
cout « i « "\п";
void println(long i)
cout « i « "\п";
cout « ch « "\п";
288 Модуль 6. О функциях подробнее
..................................................................................................................................
void println(double d)
cout « d « "\п";
Обратите внимание на то, что каждая функция из этого набора после i:lP-
гумента выводит си:\шол новой строки. Кроме того, отметьте, что версия
print 1п (bool) при выводе значения булевого пша отображает слопа
"ложь" ИЛИ "истина". П[)и: жслашш эти слопа можно выводить ПРОПИСIIЫ
ми бу/(вами. Это ДOI<азьшает, насколько легко можно настроить ФУНКЦИИ
вывода под конкретные требования.
5. Реализуйте функции print () следующим образом.
1/ Набор пере груженных функций print().
void print(bool Ь)
{
if(b) cout « "истина";
e1se cout « "ложь";
void print(int i)
cout « i;
void print(long i)
cout « i;
cout « ch;
С++: PYI<OBOACTBO ДЛЯ начинающих 289
void print(double d)
cout « d;
Создание
для
пере груженных
отображения данных
функций
различного
print()
типа.
и
.
println()
*/
#include <iostream>
using namespace std;
int main ()
println(true);
println(lO);
рriпtlп("Это простой тест");
println ( 'х' ) ;
println(99L);
рriпtlп(12З.2З);
println(" Выполнено!");
return О;
void println(int i)
cout « i « "\п";
void println(long i)
С++: PYI<OBOACTBO М9. начинающих 291
cout « i « "\n";
ф
ф
:I:
void println(char ch) 10
о
cout « ch « "\п";
g
с:
х
СА
:s:
::r
:.:'
:I:
void println(char *str)
~
{ О
cout « str « "\n";
void println(double d)
cout « d « "\n";
void print(int i)
cout « i;
void print(long i)
cout « i;
cout « ch;
292 Модуль 6. О фУНI<ЦИ~Х подробнее
void print(double d)
cout « d;
ис'rина
10
Это простой тест
х
99
123.23
Вот несколько значений: ложь 88 100000 100.01 ВыnолнеНJ!
ВАЖНОI
rf:8 Apгyмeыzы"..aapeдaвaaмыe
функции по умолчанию
в С++ мы може~ придап) параметру некоторое значение, которое будет ав
томатически ИСПОЛЬЗOlЗ<lIЮ, если при lIbl30Be функции не задается аргумент, ':0-
ответствующий этому параметру. Аргументы, передаваемые функции по у.МОЛ
чанию, можно использовать, чтобы упростить ()бращение к сложным ФУНКIlИЯМ,
а также в качестве "сокращенной формы" перегрузки ФУНКЦИЙ.
Задание аргументов, перелаваемых ФУНIЩIIИ по умолчанию, синтаксически
аналогично инициализаЦIIИ переменных. PacC-\ютрим следующий пример, n ко
тором объявляется функция myfunc (), приннмающая два аргумента типа int
с дейстоующими по умолчанию значениями О 11 100.
// по умолчанию.
#include <iostream>
using namespace std;
myfunc(10) ;
myfunc();
return О;
х: 1, у: 2
х: 10, у: 100
х: О, у: 100
294 МОДУЛЬ 6. О функциS1Х подробнее
#include <io5tream>
#inc1ude <cstring>
using namespace 5td;
return О;
Это теСТ.01234
Это тест.О12345б789
H01
tl
СМ Оересрузка функ' '.иl4
и неоднозначность
Прежде чем завершить этот модуль, мы должны исследовать вид ошибок, уни
кальный дЛЯ С++: неоднозначность. Возможны ситуации, в которых компилятор
не способен сделать выбор между двумя (или более) корректно перегруженными
функциями. Такие ситуации и называют lIеодUОЗUQЧllЫМU. Инструкции, создаю
щие неоднозначность, являются ошибочными, а программы, которые их содер
жат, скомпилированы не будут.
Основной причиной нсоднозначности в С++ является автоматическое преоб
разопание типов. В С++ делается попытка автоматичеС1Ш преобразовать тип ар
гумеllТОП, IIспользуемых для Dызоuа функции, 11 ТШl параметроп, определснных
функциеif. Рассмотрим пример.
#include <iostream>
using namespace std;
int main ()
(
// Неоднозначности нет, вызывается
// функция myfunc(double).
cout « rnyfunc(10.1) « " ";
return О;
float myfunc(float i)
return i;
double myfunc(double i)
return -i;
#include <iostream>
using namespace std;
300 Модуль 6. О фУНI<ЦИ~Х подробнее
int rnain ()
(
cout « rnyfunc('c'); // Здесь вызывается myfunc(char).
cout « rnyfunc(88) « " "; // ~бка! Здесь вноситCJI
// Rеоднозначвос~ъ, поскольку нексно, в
// значение JCaKoro типа cneдYe~ преобразова~ъ
// чиcnо 88: char или unsiqned char?
return О;
return ch-l;
return ch+l;
#include <iostream>
using паmеэрасе std;
int myfunc(int i ) ;
int myfunc(int i, int j=l);
С++: руководство дl\Я начинающих 301
int main ()
return О;
int myfunc(int i)
return i;
return i*j;
char ch = 'х';
int i = 10;
4. Создайте vоid-функцию round (), которая оКрУГЛЯет значение своего dou-
Ыe-apryмeHтa до ближайшего целого числа Позаботьтесь о том, чтобы ФУНК
цИЯ round ( ) использовала ссьuючный параметр и возвращала результат
округления в этом параметре. Можете исходить из предположения, что все
округляемые значения положительны. Продемонстрируйте использование
функции round () в программе. Для решения этой задачи используйте стан
дартную библиотечную функцию modf () . Вот как выглядит ее прототип.
double modf(double пит, double ~i);
Иvоlаtilе
в С ++ определено два спецификатора Т1!па, Jюторые оказывают l3J1июше на )'0,
каким образом МОЖНО получить доступ" переМСIIIIЫМ или МОдllфИЦИРОВа1Ъ ИХ. ::.tTO
спсцпфикаторы const и volatile. Официально они именуются CV-С11ецuфuкаmо
РaJ\ПL и должны предшесгвоваТI, базовому типу при объяnлешlИ псременноЙ.
#include <iostream>
using namespace std;
:i.nt main ()
{
int empNums[num_employees]i // Имевоваивая хоис~~а
double salary [n1..1m_employees]; 11 num_employees З~8СЪ
char *names[num_employees]i 11 испоnъsуе~ся дnк задаИИR
11 размеров массивов.
/ /' ...
Если n этом при мере понадобится использовать новый размер для массивов,
вам потребустся И3МСИИТЬ только объявление переменной пит _ employees и
перекомпилировать программу. В результате все три массива автоматически по
лучат новый размер.
Спецификатор const также используется для защиты объекта от модифика
ции посредством указателя. Например, с помощью спецификатора const можно
11С ДОПУСТИТЬ, чтобы функция изменила значение объекта, адресуемого ее пара
метром-указателем. Для этого достаточно объявить такой параметр-указатель с
использош:шием спеЦИфИЮlТора const. Другими словами, если параметр-указа
тель предваряется КJIЮЧСIJЫМ словом cons t, пикаЮiЯ ИНСТРУIЩИЯ этой фушщии
не может модифицировать переменную, адресуемую этим параметром. Напри
мер, функция nega te () в следующей КОРОТКОЙ программе возвращает результат
отрицания значения, на которое указывает параметр. Испольэо"ание специфи
катора const в объявлешlИ параметра не позволяет коду ФУIIКЦИИ модифициро
вать объект, адресуемый этим параметром.
/1 Использование сопst-параметра-указателя.
#include <iostream>
using namespace std;
int main ()
(
int result;
int v = 10;
result = negate(&v);
return О;
return - *val;
auto
extern
register
static
mutable
С помощью этих ключевых слов компилятор получает информацию о том, как
должна ХРШlИться переМСllllая. Спецификатор классов памяти необходимо ука
зьшать в начале объявления переменной.
Спецификатор mutable применяется TOJl),KO к объектам классов, о которых
I)(;'ЧI) вперСЮI. OCT,Ulbllble спенифшсаторы мы расоютрим в этом раЗД~J1С.
~
файле, а в других использовать ехtеrп-06ъявления, как показано на рис. 7.1.
а.
Файл :Е1 ФайnF2 ф
t:
О
int х, у: extern int х, у: S
char ch; extern char ch; х
::о
з:
з:
{ { О
t:
If ... х = у/ТО; ~
О
} } Ф
~
ш
extern "язык" (
ПротоТИПЫ_функций
Локальные s ta tiс~переменные
Если к локальной переменной применен мqдификатор static, то для нее вы
деляется постоянная область памяти практически так же, как и для глобальной
переменной. Это позволяет статической переменной поддерживать ее значеlше
между вызовами функций. (Другими словами, в отличие от обычной локальной
переменной, значение stаtiс-переменной не теряется при выходе из функции.)
Ключевое различие между статической локальной и глобальной переменными
С++: руководство ДЛЯ начинающих 311
объявлены.
Возможность использопаШIЯ статических локальных перемеНlIЫХ важна для
создания функций, которые должны сохранять их значения между вызовами.
Если бы статические переменные не были предусмотрены в С++. пришлось бы
использовать вместо них глобальные, что открыло бы "лазейку" для всевозмож
ных побочных эффектов.
Рассмотрим пример использования static-переменноЙ. Она служит для
хранения текущего среднего значения от чисел, вводимых пользователем.
*/
#include <iostream>
using namespace std;
int main ()
int num;
do
cout « "Введите числа (-1 означает выход): ";
cin » num;
if (пит != -1)
cout « "Текущее среднее равно: "
312 МОДУЛЬ 7. Еще о типах данных и операторах
« running_avg(num);
cout « '\п';
while(num> -1);
return О;
sum = sum + i;
count++;
Глобальные s ta tic-перемеННbIе
Если модификатор static применен к глобальной переменной, то компиля
тор создаст глобальную переменную, которая будет известна только для файла,
в котором она объявлена. Это означает, что, хотя эта переменная является J ло
бальной, другие функции в других файлах не имеют о ней "нн малейшего поня-
С++: руководство ДЛЯ начинающих 313
// ---------------------
#inc1ude <iostream>
Первый файл ---------------------
*
ш
int main ()
{
int пит;
do
cout «
"Введите числа (-1 для выхода, -2 для сброса): ";
cin » пит;
if (num==-2) . (
reset () ;
continue;
if(num != -1)
cout « "Среднее значение равно: "
« running_avg(nurn)i
cout « '\n':
while(num != -1);
return О:
314 Модуль 7, Еще о типах данных и операторах
/ / они об'ЪJl8JlеНJ.r,
int running_avg(int i)
{
surn = surn + i;
count++;
void reset()
surn = О;
count = О;
~
ш
ВАЖНОI
M-ееr.иаравыe.nеpe.rJJ.eJiU;lblе
Возможно, чаще всего IIСПОЛI,ЗУСТСЯ спецификатор класса памяти register.
Для компилятора модификатор register означает предписание обеспечить та
кое хранение соответствующей переменной, чтобы доступ к ней можно было по
лучить максимально быстро. Обычно переменная в этом случае будет храниться
либо в регистре цеитралыюго процессора (ЦП), либо в кэш-памяти (быстродей
ствующей буферной памяти небольшой емкости). Вероятно, вы :~HaeTe, что до
ступ к регистрам ЦП (или к кэш-памяти) принципиалыю быстрее, чем доступ
к основной памяти компьютера. Таким образом, l1еремеtшая, сохраняемая в ре
гистре, будет обслужена гораздо быстрее, чем переменная, сохраняемая, напри
мер, 8 оперативной памяти (ОЗУ). Поскольку скорость, с которой к переменным
можно получить доступ, определяет, по сути, скорость выполнения вашей про
rpa.I\1MbI; для получения удовлетворительных результатов программирования
Поскольку быстрый доступ можно обеспечить на самом деле только для огра
ниченного количества переменных, важно тщательно выбрать, к каким из них
применить модификатор register. (Лишь правильный выбор может повысить
быстродействие программы.) Как правило, чем чаще к перемеl"lНОЙ требуется до
ступ, тем большая выгода будет получена в результате оптимизации кода с помо
щью спецификатора register. Поэтому объявлять регистровыми имеет смысл
управляющие переменные цикла или перемснные, к которым выполняетс~ до
#include <iostream>
using namespace std;
int main ()
int vals[] { 1, 2, З, 4, 5 };
int result;
return О;
return зит;
R
о
6
а.
ф
с:
Здесь переменная i, которая управляет циклом for, и переменная зит, к ко о
:s:
торой осуществляется доступ 6 теле цикла, определены с использованием спе )(
:о
цификатора register. Поскольку обе они используются внутри цикла, можно :I:
:I:
рассчитывать на то, что их обработка будет оптимизирована для реализации бы ~
строго к ним доступа. В этом примере предполагалось, что реально для получе ~
с:
ния скоростного эффекта будут оптимизированы только две переменные, поэто ~
О
му nums и n ие были опрсделсны как regis ter-пеРСМСl-lllые, ведь доступ !( ним
реализуется не столь часто, как к переменным i и эит. Но если среда позволяет ~
ш
Однако использование ключевого слова епиm здесь излишне. В языке С (ко &
торый также поддерживает перечисления) обязательной была вторая форма, по §
Q.
этому в некоторых программах вы можете встретить подобную запись. Ф
1:
О
С учетом предыдущих объявлений при выполнении следующей иистру/щии
:s:
переменной how присваивается значение airplane. х
21
1:
how = airplane; 1:
~
Важно пони мать, что каждый символ списка перечисления означает целое х
О
число, причем каждое следующее число (представленное идентификатором) I-/а 1:
~
единицу больше предыдущего. По УМОЛLJalШЮ значение перноl'O символа пере О
Ф
чпслспия равпо НУЛЮ, слеДОВ~lТелUllU, :шачснне второго-- СЛШIJЩС Н т.Л. ПnЭТО~[У ::f
ш
при выполнении этой инструкции
how = 1; // ошибка
cout « "Automobile";
break;
case truck:
cout « "Truck";
break;
case airplane:
cout « "Airplane";
break;
case train:
cout « "Train";
break;
case boat:
cout « "Boat";
break;
#include <iostream>
using namespace stdi
а
о
о
Q.
Q)
еnиm transport ( car, truck, airplane, train, boat ); t:
О
S
int main ()
transport hOWi
how = car;
cout « name[how] « '\n';
how = airplane;
cout « name[how] « '\n';
how = traini
cout « name[how] « '\n';
return О;
Automobile
Airplane
Train
Использованный в этой программе метод пре06разования значения пере
числения в строку можно применить к перечислению любого типа, если оно не
содержит инициализаторов. Для надлежащего индексирования массива строк
перечислимые константы должны начинаться с нуля, быть строго упорядочен-
322 Модуль 7. Еще о типах данных и операторах
iJi
• КAIoиевое САnВО twsde;E
в с ++ разрешается определять новые имена типов дaHНblx с помощью ключе
вого слова t уре de f. П rш IfспользоnаlПШ t yperle f -имени не создается НОВЫЙ тип
Д<lШ/ЫХ, а ЛIIШЬ определяется "овое IIМЯ дЛЯ уже существующего типа. Благодаря
tуреdеf-имеllам можно сделать машинозависимые программы более переноси
мыми: ДЛЯ этого иногда достаТОЧIlО изменить tуреdеf-ИIIСТРУКЦИИ. Это сред
ство таюкс ПОЗDоляет улучшить читабельность кода, поскольку ДЛЯ стандартных
типо/З данных с его помощыо можно использовать описательные имена. Общий
формат записи инструкции typedef таков.
typedef тип ИМЯ;
Здесь элемент тип означает любой допустимый тип данных, а элемент ИМЯ - но
вое имя для этого типа. При этом заметьте: новое имя определяется вами в каче
стве дополнения к существующему имени типа, а не для его замены.
ВАЖНОI
ММ Dо,розрядNь1едаерnторы
Поскольку язык С++ сориентирован так, чтобы позволить полный доступ к
аппаратным средствам компьютера, важно, чтобы он имел возможность непо-
С++: РУКОВОДСТВО МЯ начинающих 323
bool, f1oat, double, long double. void или других еще более сложных типов ~
о
данных. Поразрядныс операторы (табл. 7.1) очень часто используются для реше- :s:
х
Jj
ния широкого круга задач программирования системного уровня, например, при :I:
:I:
опросе информации о состоянии устройства или ее формировании. Теперь рас- О
~
смотрим каждый оператор этой группы в отдельности. х
О
с:
~
~
о
IСОИСТант.
*
ш
р q р & q р I q р л q -р
о о О О О 1
о О О
о О
1 1 о о
1101 0011
& 1010 1010
1000 0010
Использование оператора" &" демонстрируется в следующей программе. Она
преобразует любой строчный символ в его прописной эквивалент путем установ
ки шестого бита равным значению О. Набор символов ASCIl определен так, что
строчные буквы имеют почти такой же код, что и прописные, за исключением
того, что код первых отличается от кода вторых ровно на 32. Следовательно, как
показано в этой программе, чтобы из строчной буквы сделать прописную, доста
точно обиулить ее шестой бит.
// Получение прописных букв С использованием поразрядного
// оператора ~И".
#inc1ude <iostream>
using namespace std;
int main ()
(
char ch;
ch = 'а' + i;
cout « ch;
~
а.
// Эта инструкция Обнуляет 6-й бит. о
// прописная буква. ~
о
:s:
х
return О; ~
ш
ад ЬВ се dD еЕ fF gG hH iI jJ
Значение 223, используемое в ННСТРУIЩИИ поразрядного оператора "И", явля
ется десятичным представлением ДDОИЧIЮГО числа 11 О 1 1111. Следовательно, эта
операция "И" оставляет все биты в переменной ch нетронутыми, за исключением
шестого (он сбрасьшается в IIУЛЬ). •
Оператор "И" также полезно использовать, если нужно определить, установ
лен интересующий вас бит (т.е. равен ли он значению 1) или нет. Например, при
выполнении следующей инструкции вы узнаете, установлен ли 4-й бит в пере
менной status.
if(status & 8) cout « "Бит 4 установлен";
int t;
cout « "\п";
1101 0011
I 1010 1010
1111 1011
Оператор "ИЛИ" можно использовать для превращения рассмотренной выше
программы (которая преобразует строчные символы в их ПРОIIисные эквивален
ты) в ее "противоположность", Т.е. теперь, как показано ниже, она будет преоб
разовывать прописные буквы в строчные.
#include <iostream>
using nатезрасе std;
int main ()
{
,
char ch;
cout « "\n";
return О;
Аа ВЬ Сс Dd Ее Ff Gg Hh Ii Jj
И так, вы убеДИJIНСЬ, что УСТaIЮlша шестого бита превращает прописн.ую букву
В ее СТРОЧIIЫЙ эквивалент.
Поразряднос l1сключающее "ИЛИ" (XOR) устанавливает бит результата рап·
ным единице "I:ОЛl)КО в том случае, если СООТllСТСТВУlOщие биты операндов отли·
чаются один от другого, т.е. не равны. Вот при мер:
0111 1111
л 1011 1001
1100 0110
Оператор "исключающее ИЛ И" (XOR) Q6ладает интересным свойством, кото
рое дает нам простой способ кодирования сообщений. Если применить операцию
"исключающсе ИЛИ" к не которому значению Х и зарансе известному значению
У, а затем проделать то же самое с рсзультатом предыдущей операции и значени
ем У, то мы снова получим значение х. Это означает, что после выполнения этих
операций
R1 = Х л У;
R2 = Rl " У;
#include <iostream>
using namespace std;
int main ()
(
char msg[) = "Это простой тест";
char key := 88;
return О;
iinclude <iostrearn>
using патезрасе std;
)(
о
Q.
void shоw_Ьiлаrу(uлsigпеd int и):
@
Q.
Ф
int main () с:
о
s
х
j]
unsigned и: :r
:r
~
cout « "Введите число между О и 255: "; ~
с:
cin » и; s
~
cout « "Исходное
*
число в двоичном коде: ";
ш
show_binary(u);
return О;
cout « "\п";
ВАЖНОI
р;8 Оаераюры СДВl4,са
Операторы сдвига, "»" и "«", сдвигают нсе биты в значении псремеllНОЙ
вправо или влево. Общий формат использования оператора сдвига вщ>аво вы
глядиттак.
переменная » ЧИСЛО_БИТОВ
леременная « числО_битов
Здесь эле~fСllТ число_битов у,,:азывает, l1а сколько· позиций должно быть сдви
нуто ~шачение элемента переменная. При каждом сдвиге влево все биты, с::о
стаВЛЯЮЩllС 3llачеНllе, сдвигаются плево на одну позицию, а в младший ра.1РЯД
записывается нуль. При каждом СД8иге вправо все би'FЫ сдвигаются, соотпет
ствешlO, вправо. Если СДll\lГУ вправо подвергаегся значение без знака, в старший
разряд записывается нуль. Если же сдвиry вправо подвергается значение со зна
ком, значение ЗlIакового разряда сохраняется. Клк вы помните, отрицательные
LLелыc числа преДСТ<lВЛЯIOТСЯ установкой cTapu;ero разряда числа равным едини
це. Т~ким образом, ссли сдвигаемое значение отрицательно, при каждом сдонге
вправо в старший разряд записывается еДИlllща, а если положительно - нуль. Не
забывайте: СДDИl·, выполняемый операторами сдоига, не является циклическим,
Т.е. при сдвиге как вправо, так и влево крайние биты теряются, и содержимое по
терянного бита узнать невозможно.
Операторы сдвига работают только со значеНlIЯМИ целочислеНllЫХ типов, напри
мер, символами, целыми числа.\1И и дЛинными целыми числами (int, char, long
int или short int). Они не применимы к знаЧ~IiИЯМ с плавающей точкой.
Побитовые операции сдвига могут оказаться весьма полезными для декоди
рования входной информации, получаемой от внешних устройств (например,
LLифроаналоговых преобразователей), и обработки информации о состоянии
устройств. Поразрядные операторы сдвига можно также использовать для вы
полнения ускоренных операций умножения и д.еления целых чисел. С помощью
сдвига влево можно эффективно умножать на два, сдвиг вправо позволяет не ме
нее эффективно делить на два.
Следующая программа наглядно иллюстрирует результат использования опе
раторов сдвига.
С++: руководство ДЛЯ начинающих 331
х
tinclude <iostream> О
Q.
using namespace std; о
~
&
ф
show_binary(i) ;
i = i « 1; !! ~ Сдвиг влево перекекной i на 1 позицию.
cout « "\п";
// Сдвиг вправо.
for(t=O; t < 8; t++)
i = i >> 1; / / +- Сдвиг вправо перемеиной i на 1 позицшз.
show_binary(i) ;
return О;
cout « "\п";
332 МОДУЛЬ 7. Еще о типах данных и операторах
О О О О О О О 1
О О О О О О 1 О
О О О О О 1 О О
О О О О 1 О О О
О О О 1 О О О О
О О 1 О О О О О
О 1 О О О О О О
1 О О О О О О О
1 О О О О О О О
О 1 О О О О О О
О О 1 О О О О О
О О О 1 О О О О
О О О О 1 О О О
О О О О О 1 О О
О О О О О О 1 О
О О О О О О О 1
-
Проект 7.1. eClA3nовивrфyiпщИЙПОРОЗРЯАИОг.а
циклического сдвига
i rota te . срр--! Несмотря на то что язык С++ обеспечивает два оператора сдвига,
в нем не определен оператор цuмuчecкozо Cдвuza. Оператор ци-
З. х « 2
С++: PYI(OBOACTBO ДЛЯ начинающих 333
клическоro сдвига отличается от оператора обычноro сдвига тем, что бит, выдвигае
мый с одноro конца, "вдвигается" в другой. Таким образом, выдвинутые биты не те
х
ряются, а просто перемещаются "по кpyry". Возможен циклический сдвиг как вправо, О
а.
о
так и влево. Например, значение 1О 1О 0000 после циклическоro сдвига влево на один
разряд даст значение 0100 0001, а после сдвига вправо - число О 101 0000. В каждом ~
ф
с:
случае бит, выдвинутый с одного конца, "вдвигастся" в другой. ХОТЯ отсутствие опе о
раторов циклического сдвига может по казаться упущением, на са.\юм деле это не так,
s
х
Ji
поскольку их очень легко создать на основе других поразрядных операторов. ::I:
::I:
В этом проекте предполагается создаtlИе двух функций: rrotate () и lro- ~
которые сдвигают байт впр,шо или влево. Каждая функция принима
х
ta te ( ) , О
с:
ет два параметра и возвращает результат. Первый параметр содержит значение, ~
о
подлежащее сдвигу, второй - количество сдвигаемых разрядов. Этот проект
щ,лючаст ряд действш':'( над битами 11 отображает ВОЗМОЖIЮСП, IIРНI\(СНСНИЯ по ~
ш
разрядных операторов.
Последовательность действий
1. Создай:те файл с именем rota te . срр.
2. Определите функцию lrota te () , предназначенную для выполнения ци·
клического СДВИl'а влево.
t = val;
t = val;
бит
2i
1:
1:
равен единице, то нужно установить крайний слева
~
бит. */ )(
о
с:::
i f (t & 128)
~
t = t I 32768; 11 Устанавливаем "1" с левого кон- о
ф
ца. .:3"
: UJ
return t;
\
Циклический сдвиг вправо немного сложнее ЦИКЛItческого сднш"а влево,
поскольку значение. переданное в параметре val. должно быть пеРВОJlа·
чально сдвинуто во второй байт значения t. чтобы "не упустить" биты,
сдвигаемые вправо. После завершения циклического сдпига значение не
обходимо сдвинуть назад в младший байт значения t. подготовив его тем
самым для возврата из функции. Поскольку сдвиrасмый вправо бит стано
вится седьмым битом, то для проверки его значения используется следую
rчaя инструкция.
i f (t & 128)
В десятичном значении 128 установлен ТОЛЬКО седьмой бит. Если анализи
руемый бит оказывается равным еДИНlще, то в значении t устанаJlливается
15-й бит путем применения к значению t операции "ИЛИ" с числом 32768.
В результате выполнения этой операции lS-й бит значения t устанавлива
ется равным 1, а все остальные не меняются.
4. Приведем ПWIНhlй текст программы, которая демонстрирует исполъзоваште
функций rrotate () и lrotate (). Для отображения результатов в~олне
кия каждого циклическоroсдвиra испольэуетсяФункция show_binary ().
336 Модуль 7. Еще о типах данных и операторах
/*
Проект 7.1.
*/
*include <iostream>
using патезрасе std;
iлt main ()
{
char ch 'Т';
во:\п";
for(int i=O; i < 8; i++)
ch = rrotate(ch, 1);
show_binary(ch);
return О;
t = val;
п)
- )(
о
g....
о
Q.
Ф
§
for (int 1=0; i < п; i++) { :s:
)(
7i
t = t « 1; :r
:r
~
/* Если выдвигаемый бит (8-й разряд значения t) )(
о
с::
содержит единицу, то устанавливаем младший бит. *1 ~
if (t & 256) О
t = val;
return ti
cout « "\п";
О 1 О 1 О 1 О О
Результат 8-кратного цикличес~ого сдвига вправо:
О О 1 О 1 О 1 О
О О О 1 О 1 О 1
1 О О О 1 О 1 О
О 1 О О О 1 О 1
1 О 1 О О О 1 О
О 1 О 1 О О О 1
1 О 1 О 1 О О О
О 1 О 1 О 1 О О
Результат 8-кратного циклического сдвига влево:
1 О 1 О 1 О О О
О 1 О 1 О О О 1
1 О 1 О О О 1 О
О 1 О О О 1 О 1
1 О О 1) 1 О 1 О
О О О 1 О 1 О 1
О О 1 О 1 О 1 О
О 1 О 1 О 1 О О
С++: PYl<080ACT80 дI\Я начинающих 339
rjo
· аерnтар "Зl:lак вопроса"
ОДНИМ И3 самых замечательных операторов С++ является оператор "?". Опе
ратор "?" можно ИСПОЛI,зовап.l D качестве замены if-еlsе-инструкций, употре
бляемых в следующем общем формате.
i f ( условие)
переменная выражение 1 ;
else
переменная = выражение2;
#include <iostream>
using паmезрасе stdi
int main ()
int i, j, resul t;
return О;
"
Не менее интересным, чем описанные выше операторы, является такой оператор
С++, как "запятая". Вы уже видели несколько примеров его исполъэования D цикле
f о r, где с его помощью была организована инициализация сразу нескольких пере
мещIых.. Но оператор "запятая" также может составлять часть любого выражения.
С++: руководство ДЛЯ начинающих 341
finclude <iostream>
using namespace stdj
int main ()
{
int i, j;
j 10;
cout « i;
return О;
Эта программа выводит на экран число 1010. И вот почему: сначала перемеJl
ной j присваивается число 1О, затем переменная j инкрементируется до 11. По
сле этого вычисляется выражение j+ 100, которое нигде не при меняется. Нако
нец, выполняется сложение значения переменной j (оно по-прежнему равно 11)
с числом 999, что в результате дает число 1010.
По сути, назначение оператора "запятая" - обеспечить выполнение задан
ной последовательности операций. Если эта последовательность используется
в правой части инструкции присваивания, то переменной, указанной в ее левой
342 МОДУЛЬ 7. Еще о типах данных и операторах
х = 10 > 11 ? 1 : О;
ВАЖН<jI
t1'COCTQ8NbI8.oaepaTopbl
присваивания
в С++ предусмотрены специальные состапные операторы присваивания,
D которых объединено ПРlIсваипаНl1е с еще одной операцией. Начнем с примера
и рассмотрим следующую инструкцию.
х = х + 10;
х += 10;
Пара операторов += служит указанием компилятору присвоить переменной
х СУ:\IМУ текущего значения переменной х и числа 10. Составные версии опера
торов присваивания сущестnуют для всех БИllарных операторов (т.е. для всех
-
:а
:0.
je
:0
:0.
:Ф
.с::
х
А вот еще один llример. ИJlСТРУJЩИЯ
= ;ос - 100;
:ш
*
аналогична такой:
х -= 100;
Обе эти rшструкцнн ПРIIСВ311В3ЮТ переменной х ее прежнее значение, уменьшен
ное на 100.
Эти примеры служат иллюстрацией того, что составные операторы при св а
ивания упрощают программирование определенных инструкций присваивания.
Кроме того, они позволяют компилятору сгенерировать более эффективный код.
Составные операторы ПРИСDaI·lDания можно очень часто встретить в професси
онально написанных С++-программах, поэтому каждый С++-программист дол
жеtl БЫТJ, с ними на "ты" .
ВАЖНОI
tltА14СООАЬЗ0ваыир к ыnчево[о
слова sizeof
Иногда полезно Зllать размер (в баЙта..х) одного из типов данных. Поскольку
раЗ:\fСРЫ BCTpoeHlIbIx С++-ТИПОВ данных в разных вычислителыlхx средах могут
быть различными, знать заранее размер переменной во всех ситуациях не пред
ставляется возможным. Для решения этой проблемы в С++ включен оператор
sizeof (действующий во время компиляции программы), который использует
ся в двух следующих форматах.
sizeof (type)
sizeof имя_переменной
344 Модуль 7. Еще о типах данных и операторах
Первая версия возвращает размер заданного типа данных, а вторая -.: размер
заданной переменной. Если вам нужно узнать размер некоторого типа данных
(например, int), заключите назваtш.е этого типа в круглые скобки. Если же вас
интересует размер области памяти, занимаемой конкретной переменной, можно
обойтись без круглых скобок, хотя при желании их можно использовать.
Чтобы понять, как работает оператор sizeof, испытайте следующую корот
кую программу. Для многих 32-разрядных сред она должиа отобразить значения
1,4,4 и 8.
// Демонстрация использования оператора sizeof.
#include <iostream>
using namespace stdi
int main ()
(
char Chi
int i;
cout « sizeof ch « , ,.
, /1 размер типа char
cout « sizeof i « I , .
,. /1 размер типа int
cout « sizeof (Поа t) «
, 1.
, // размер типа float
cout « sizeof (double) «
, , ,. // размер типа double
return О;
int nums[4Ji
cout « sizeof пuтэ; // Будет выведено число 16.
Для 4-байтных значений типа int при выполнении этого фрагмента кода на
экране отобразится число 16 (которое получается в результате умножения 4 байт
на 4 элемента массива).
Как упоминалосъ выше, оператор s i z ео f действует во время компиляции
программы. Вся информация, необходимая для вычисления размера указанной
переменной или заданного типа данных, известна уже во время компиляции.
Оператор sizeof главным образом используется при нarrnсании кода, который
зависит от размера С++-типов данных. Помните: поскольку размеры типов дан-
С++: PYI(OBOACTBO МЯ начинающих 345
R
~ПРОСЫ ДЛЯ текущего IЮНТРОЛЯ
о
5Q.
Ф
-------- с:
о
з. Оператор s i z ео f
•
возвращает размер заданной переменной или типа в
=
О
~
ш
1. t1 = t2 = tЗ = 10;
2. х +с 1ОО;
3. Оператор sizeof возвращает размер заданной переменной или типа D байтах.
346 Модуль 7. Еще о типах данных и операторах
................................................................................................................................
.* ->*
* / %
+ -
« »
< <= > >=
!=
&
&&
11
?:
+= *= 1= %= »= «= &= Л= 1=
Низший
• ТеСТА49СnМQI<OнтролqММQДУАЮ Z
1. Покажите, как объявить iпt-переменную с именем test. I<ОТОРУЮ lIСIЮЗ
можно изменить в программе. Присвойте ей начальное значение 100.
2. Спецификатор vola ti le сообщает компилятору о том, что значснис перс
менной может быть изменено за прсделами программы. Так ли это?
З. Какой спецификатор примеllЯСТСЯ в одном из файлов Мllогофайловой JlРО
граммы, чтобы сообщить об использовании глобальной псремешюй, объ
явленной в другом файле?
int myfunc ()
{
int Х;
int У;
int Z;
С++: РУКОВОДСТВО ДЛЯ начинающих 347
z = 10;
у
for(x=z;
О;
х <15; х++)
-
.. х
а
Q.
о
1-
а
Q.
ф
с::
у += х;
О
s:
х
]j
rеturл У; r
r
о
<1
х
Какую переменную следовало бы объявить с использованием специфика а
с::
тора register с точки зрения получения наибольшей эффективности? ~
о
1. Чем оператор" &" отличается от оператора" & &"?
х *= 10;
:ш
*
9. Используя функции rrotate () н 1rotate () из ПРОСI<та 7.1, можно за
кодировать и ДСl\Одировать строку. Чтобы закодировать строку, выплtlитеe
циклический сдвиг влево каждого символа на некоторое количество раз
рядов, которое задается I(ЛЮЧОМ. Чтобы декодировать строку, выполните
цю(лический сдвиг вправо каждого символа на то же самое количество раз
рядов. Используйте ключ, который состоит из некоторой строки символов.
Существует множество способов для вычисления количества сдвигов по
ключу. Проявите свои способности к творчеству. Решение, представленное
в разделе ответов, является лишь одним из многих.
ВАЖНОI
1:1- Q6"'.ИЙ формоХ-ОБЪЯ8АеI:U4Sl
класса
Класс создается с помощью ключевого слова class. Общий формат объяuле
ния класса имеет слеДУ10ЩИЙ вид.
claS5 имя_класса {
закрытые данные и ФУ],!КЦИИ
public:
открытые данные и ФУНКЦИИ
список объектов;
С++: руководство ДЛ';1 начинающих 351
Здесь элемент имя_класса означает имя класса. Это имя становится именем
нового типа, которое можно использовать для создания объектов класса. Объ
екты класса можно создать, указав их имена непосредственно за заКрЫВaIOщейся
фигурной скобкой объявлеиия класса (В качестве элемента СПИСОК_Объектов),
но это необязательно. После объявления класса его элем~нты ]'.Н)ЖНО создавзп,
-
:• 1i
.
:0>
:\0
:0
>-
v
.,.1)
::!;;
по мере необходимости. 1i
U
Любой класс может содержать как закрытые, так и OTKPblTLIC 'IJlellbl. По умол ()
а
чанию все элементы, определенные в классе, являются закрытыми. Это означает, .<
:::::!
что к ним могут получить доступ только другие члены их класса; никакие другие
части программы этого сделать не могут. В этом состоит одно И~'I llрОЯ13лений ин
капсуляции: программист В полной мере может упраВЛЯТ1> ДОСТУПО\I к определен
ным элементам данных.
Чтобы сдежlТЬ части класса ОТЩ)Ь!ТЫМlI (т.с. дОСТУПIlЫЮ'1 JIЛЛ l(РУПIХ частсii
программы), необходимо оБЪЯnИТ1> их 1I0CJJC J(J]\О'IСnОГО СЛОlJа PI.Jbl i с. Всс пере
MeJ-llibJе или функции, определенные после спецификаТор<t риЬ2. ic, ЛОСТУПШ..J
для всех других функций I1рограммы. Обычно в программе организуется достуr
к закрытым членам класса через его открытые функции. Обратите внимание 1-1'
то, что после ключевого слова риЫ ic стоит двоеточие.
Хорошо разработанный класс должен определять одну и ТОЛЬКО одну логиче
скую СУЩНОСТЬ, хотя т,шое синтаксическое правило отсутствует в "конституции"
С++. Например, клш;с, в котором хранятся имена лиц и их телефонные номера, не
стоит использовать для хранения информации о фондовой бирже, среднем количе
стве осадков, циклах соmlеЧIЮЙ активности и прочих l:Ie связанных с lCою<ретными
лицами данных. Основная мысль здесь состоит в том, что хорошо разра60ТШШЫЙ
класс должен включать логически связаlШУЮ информацию. Попытка поместить не
связанные между собой данные в один класс быстро деструктуризнрует ваш код!
Подведем первые итоги: в С++ класс создает новый тип данных, который
можно использовать для создания объектов. В частности, класс создает логиче
скую конструкцию, которая определяет отношения между ее членами. Объявляя
переменную класса, мы создаем объект. Объект характеризуется физическим су
ществованием 11 является ко/шретным экземпляром класса. Другими словами,
объект занимает некоторую область памяти, а определение типа - нет.
объектов
Для иллюстрации мы создадим класс, который инкапсулирует информацию о
транспортных средствах (автомобилях, автофургонах и грузовиках). В этом клас-
352 МОДУЛЬ 8. l<лассы и обьекlы
class Vehicle (
public:
int passengers; 11 количество пассажиров
int fuelcapi 11 вместимость топливных резервуаров
1/ в литрах
int rnpgi 1/ расход горючего в километрах на литр
1.
J'
тип имя_перемеННОЙi
Здесь элемент тип определяет тип переменной экземпляра, а элемент имя_ .'1е
ременной - ее имя. Таким образом. переменная экземпляра объявляется так же,
как локальная перемеIiНая. В классе Vehicle все переменные экземпляра объ
явлены с использованием модификатора доступа public, который, как упоми
налось выше, позволяет получать к ним доступ СО стороны кода, расположенного
#inc1ude <iostream>
using narnespace std;
int rnain ()
Vehicle rninivan; // Соэдание объех~а minivan (ЭХ8eкnn~а
// класса Vehicle).
int range;
return О;
Прежде чем идти дальше, имеет смысл вспомнить основной принцип про
граммнроваllИЯ классов: каждый объект класса имеет собственные копии пере
менных экземпляра, определенных в этом классе. Таким образом, содержимое
переменных в одном объекте может отличаться от содержимого аналогичных
переменных о другом. Между двумя объектами нет связи. за исключением того,
что ОНИ являются объектами одного и того же типа. Например, если у вас есть
доа объекта типа
Vehicle и каждъп':i объект имеет свою копию переменных ра
ssengers, fuelcap и mpg, то содержимое соответствующих (одноименных)
перемеНI-IЫХ этих двух экземпляров может бы'гь разным. Следующая программа
демонстрирует это.
#include <iostream>
using namespace std;
// в литрах
int mpg; // расход горючего в километрах на литр
};
int main ()
Vehicle minivani // Создаем первый объект типа Vehicle.
Vehicle sportscar; // Создаем второй объект типа Vehicle.
// Теперь O~K~ шinivаn и sportscar имеют собс~еaкwе
/ / КОПИИ переиеlUDlX реamr8ации xnасса Vehi.cle.
return О;
356 Модуль8.l<лассы и объеl<ТЫ
sportscar
"" passengers 2
fuelcap 14
mpg 12
членов
Пока класс Vehicle содержит только данные и ни одной функции. И хотя
такие "чисто информационные" классы "полне допустимы, большинство классов
все-таки имеют фУlIкции-члены. Как правнло, функции-члены класса 06рабаты
пают данные, определенные в этом классе, и во многих случаях обеспечивают до
ступ к этим данным. Другие части программы обычно взаимодействуют с клас
сом через его функции.
Чтобы ПРОНЛЛlOстрировап> ИСПОЛl>зооанне функций-членов, добавим функ
цию-член D IUшсс Vehicle. ВСIIОМl!ите, что функция main () вычисляет рас
СТОЯl1ие, которое мож~т просхап, заданное транспортное средство после заливки
//
iлt
OCS~._
rалgе();
ФУВICЦИИ-чn_а
// функция
ranqe () .
вычисления максимального пробега
);
358 МОДУЛЬ 8. Классы и объекты
щему объекту.
В телс функции range () обращсние к перемснным mpg И fuelcap проис"о
дит напрямую, Т.е. без указания имени об"екта и оператора "точка". Если функ
ЦШI-ЧJlСII использует переменвую экземпляра, которая определена в том же клас
се. не нужно де;:rать явную ссылку на объект (с участием оператора "точка"). Ведь
компилятор в этом случае уже точно знает, какой объект подвергается обработке,
поскольку функция-член всегда вызывается относительно некоторого объекта
"ее" класса. Коль вызов функции произошел, :шачит, объект известен. Поэтому
в функции-члене нет необходимости указывать объект еще раз. Следовательно,
чле1'lЫ mpg и fuelcap в функции range () нсявно обращаются к копиям этих
переменных. относящихея к объекту, который вызвал функцию range (). Одна-
С++: PYI<OBOACTBO ДЛ~ начинающих 359
rangel = minivan.range()i
Быражение minivan. range () означает вызов Фуикции range (), которая
будет обрабатывать копии переменных объекта minivan. Поэтоr<IY фУНJ(ция
range () [ЮЭDратит ма"г.ИМ~ЫIУ\О дальность пробега для объекта mini van.
Во-вторых, ФУJJКЦНIO-ЧЛ('II МОЖI-IО вызвать \IЗ другой Функции-члеllа того же
класса. Б этом случае вызов также ПРОIIСХОДИТ напрямую, без оператора "точка",
ПОСIЮЛЬКУ КОМПИЛЯТОРУ уже известно, каl<ОЙ объект подвергается обработке.
И только тогда, когда Функция-член вызывается кодом. который не принадлежит
классу, необходимо использовать имя объекта и оператор "точка".
Использование класса Vehicle и функции range () демонстрируется в при
веденной ниже программе (для этого объединены все уже знакомые вам части
кода и добавлены недостающие детали).
#include <iostream>
using namespace stdi
// Об~.RИе фунхции-чnена,ranqе().
int range()i // функция вычисления максимального пробега
};
int ·main ()
Vehicle minivan: // Создаем первый объект типа Vehicle.
Vehicle sportscar; // Создаем второй объект типа Vehicle
return.O:
~
о
_ _ _ _ _ _ _ __ :s:
:ti
u
U
1. Как называется оператор": : "1 о
<
:::.::
2. Каково назначение оператора": :"?
З. Если функция-член вызывается извне ее класса, то обращение к ней
должно быть организовано с использованием имени объекта и оператора
"точка". Правда ли это? •
00ЗД'tJНИ8JКf(Q.сса~справочноЙ
информации
Последовательность действий
1. Создайте НОВЫЙ файл с именем HelpClass. срр. Скопируйте фай]! из
ПРОСI<та 3.3, НеlрЗ . срр, D файл HelpCl ass. срр.
2. Чтоuы IIреобраЗОП<lТl-, справочную с"стему D класс, Сllачала необходимо
ТО'lНО определить ее составляющие. Оапример, файл НеlрЗ . срр содержит
код, который отображает меню, вводит команду от пользователя, ПрОБ('рЯ
ет ее допустимость и отображает информацию о выбранной инструкции.
Кроме того, в программе создан цикл, выход из которого организован носле
ввода пользователем буквы "q". ОчеВИДIJО, что меню, проверка допустимо
СПI пведенного запроса и отображение запрошенной информации явля
ются неотьемлемыми частями спраВОЧIIОЙ системы, чего нельзя сказать о
приеме l<ОМШlДы от пользователя. Таким образом, можно уверенно создать
юшсс, который будет отображать справочtlУЮ информацию, меню и про
верять допустимость введенноro запроса. Назовем функции, "отвечаюшие"
за эти действия, helpon (), showmenu () и isvalid () cootbetctbeJ-IНО.
switch (what) ( ~
о
саве '1': :s:
cout « "Инструкция if:\n\n"; 2i
()
()
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п"; ~
break;
сазе '2 ':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) (\п";
cout « " case константа:\п";
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \п";
cout « "}\п";
break;
case t З' :
cout « "Инструкция fоr:\п\л";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкция;\л";
break;
сазе '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) ИНСТРУКЦИЯi\П";
break;
сазе '5':
cout « "Инструкция do-whil~:\n\n";
1:s
.~
break; ~
саsе '6':
cout « "Инструкция break:\n\n";
cout « "break;\n";
J
break;
саве '7':
cout « "Инструкция continue:\n\n";
~~.....~~.~X~~.~:..~~~~~I..~.~.?~.~~.I............................................................
cout « "солtiлuе;\л";
break;
сазе '8':
cout « "Инструкция goto:\n\n";
cout « "goto метка;\л";
break;
cout « "\л";
else
return true;
1*
Проект 8.1.
класс Help.
*/
iinclude <iostream>
using namespace std;
j
о
::s:
2i
u
// Класс, который инкапсулирует справочную систему. U
class Help ( ~
public:
void helpon(char what);
void showmenu();
bool isvalid(char ch);
};
сазе '5':
cout « "ИНСТРУКЦИЯ do-whi1e:\n\n";
cout « "do {\n";
cout « " инструкция;\п":
cout « "} whi1e (условие);\п";
break;
сазе '6':'
cout « "ИНСТРУКЦИЯ break:\n\n":
cout « "break;\n";
break;
case '7':
cout « "Инструкция continue:\n\n":
cout « "continue:\n";
break;
case '8':
cout « "Инструкция goto:\n\n";
cout « "goto метка;\п";
break;
cout « "\п";
return falsei
else
J5
return truei !;;:
(1)
~:s:
int main () J5
u
U
Q
char choicei <
~
hlpob.helpon(choice);
return О;
ВдЖi
';1 кокстру.к.т.а,рьL,иrдеару,кI.оры
в предыдущих при мерах перемеННblе каждого Vеhiсlе-объекта устанавли
вались "вручнуюn С помощью следующей последооательности инструкций:
minivan.passengers = 7;
rninivan.fuelcap = 16;
minivan.mpg = 21;
368 МОДУЛЬ 8. l<Лассы и объекты
имя_класса ()
// код конструктора
#include <iostream>
using патеsрасе stdi
class MyClass
public:
int Х;
MyClass(); // конструктор
-МуСlаss(); // деструктор
};
int main ()
MyClass оЫ;
MyClass оЬ2;
return О;
10 10
Разрушение объекта .. .
Разрушение объекта .. .
Б этом примере конструктор для класса МуС 1 а s s выглядит так.
MyClass оЫ;
370 МОДУЛЬ 8. Классы и объеl<ТЫ
ВАЖНОI
1:.._ ПаРQмеtРИ30ВQNНhl8, КQI;ICTpYКZOPb l
в предыдущем примере использовался конструктор без параметров. Но чаще
приходится иметь дело с конструкторами, которые принимают один или несколь
MyClass оЫ = MyClass(lOl)i
В этом объявлении создается объект класса MyClass (с имеflем оЫ), кото
-
1;~~
;0
::!>:
;~
рому передается значение 1О 1. Но эта форма (в таком контексте) используется :()
.()
редко, поскольку второй способ имеет более короткую запись и удобнее для ис :0
~~
пользования. Во втором способе аргумеит (или аргументы) должен следовать
за именем объекта и заключаться в круглые скобки. Например, следующая ин
струкция эквивалентна предыдущему объявлению.
MyClass obl(lOl)i
Это C<1J\fblil распростраJJС/ШЫН Сllособ объявления парзметризованных объек
тов. Опираясь на этот метод, Ilриnедем общий формат передачи аргументов кон
структорам.
#include <iostream>
using namespace stdi
class MyClass
public:
int Х;
};
int main ()
MyClass tl(S); // Передаем арry.меиты в конструктор NyClass().
MyClass t2(19);
return О;
int main ()
// Передаем дaнкue о ~анспортвок средстве
1/ конструктору Vehicle().
Vehicle minivan(7, 16, 21);
374 Модуль 8. Классы и объекты
return О;
#include <iostream>
С++: PYl<OBOACтaO ДЛЯ начинающих 315
........................................................................................................................................
class MyClass
public: ~
~
int Х; о
s:
2i
U
// Объявляем конструктор и деструктор. U
iлt rnain () {
MyClass оЬ 5; // Вызывается конструктор MyClass(5).
rеturл О;
Здесь конструктор ДЛЯ объектов класса MyClass принимает только один па
раметр. Обратите внимание на то, как в функции main () объявляется объект оЬ.
Для этого используется такой формат объявления:
MyClass оЬ = 5;
В этой форме инициализации объекта число 5 автомаТически передается napа
метру i при вызове конструктора MyClass () . Другими словами, эта инструкция
объявления обрабатывается компилятором так, как если бы она была записана
следующим образом.
myclass оЬ (5);
376 Модуль 8. I<лассы и объеl<ТЫ
в общем случае, если у вас есть конструктор, который принимает только ОДИН
аргумент, для инициализации объекта вы можете использовать либо вариант
оЬ (х) , либо вариант оЬ = х. Дело в том, что при .создании конструктора с од
НИМ аргументом неявно создается преобразование из типа этого аргумента в тип
этого класса.
Test оЬ = Test(lO);
З. Как еще МОЖНО переписать объявление из второго вопроса?·
-
Спросим у опытного программиста
Ответ. Да, можно. В этом случае создается аложеllНЫЙ класс. Поскольку объяв
ление класса, по сути, определяет область uидимости, вложенный класс
действителен только в рамках области ШlДимости внешнего класса. Спра'
педливости ради хочу сказать, что благодаря богатству и гибкости других
средств С++ (например, наследованию), о которых речь еще впереди, не
обходимости в соэдании вложенных классов пракrnчески не существует.
ВАЖНОI
1:1- ВС%pQивае.мые Ф\O:lIЩl4J4
Прежде чем мы ПРОДОЛЖИМ освоение класса, сделаем l1е60льшое, но важное от
ступление. Оно не относится конкретно к объектно-ориенrnрованному програм
мированию, но является очень полезным средством С++, которое довольно часто
inline int f () ~
// ...
Модификатор inline должен предварять все остальные аспекты оqъявлсtll1Н
функции.
Причиной существования встраиоаемых функций является эффективность.
Ведь при каждом вызове обычной функции должна быть выполнена целая по
следовательность инструкций, связанных с обработкой самого выэова, включа
ющего помещение ее аргументов в стек, и с возвратом из функции. В некоторых
случаях значительное количество циклов центрального процесс ара используется
#include <iostream>
using namespace std;
class cl (
int i; // Этот член данных закрыт (private) по умолчанию.
риЬНс:
int get_i () ;
void put_i(int j);
;
return i;
i = j;
int main ()
cl 5;
s.put_i(lO) ;
cout « s.get i();
return О;
#include <iostream>
using патеэрасе std;
class cl (
int i; // Этот член данных закрыт (private) по умолчанию.
public:
/ / АвIJ!Оll&тичесUJ ВС'1'рaиJlа8lAlе фYlUЩИИ, посIcoJIысy
// они оnpе~enеDl ВВУ'1'рИ cвoe:t'o x.nacca.
int get_i () { return i; }
void put_i(int j) { i = j;
int main ()
cl 5;
s.put i(10);
cout « s.get_i():
return О:
class cl {
int i; // закрытый член по умолчанию
public:
// встраиваемые функции
int get_i ()
{
return i;
}
void put_i(int j)
380 МОДУЛЬ 8. Классы и объекты
i "" j;
};
Небольшие функции (как представлеиныс в этом примере) обычно опреде
ляются в объявлеиии класса. ИСПОЛЪЗ0ваиие "внутриклассовых" встраиваемых
рubliс-функций - обычная практика в С++-программировании, поскольку с
ИХ помощью обеспечивается доступ к'закрытым (private) переменным. Такие
функции называются аксессорны.ми или фУIlКЦUЯМU доступа. Успех объектно
ориентированного программирования зачастую определяется умением управ
#include <iostream>
using патезрасе std;
// .~аиааеlCаSl.
int range() { return mрч * fuelcap; }
int main () {
// Передаем значения в конструктор класса Vehicle.
Vehicle minivan(7, 16, 21);
Vehicle sportscar(2, 14, 12);
return О;
.............................................................._-
3. Что представляет собой функция доступа?·
Проект 8.2.
! Q~~ue-~~pp' Возможно, вы ЗН:::lсте, что под структурой данных ПОJlимается
! ... ш средство оргаШlЗ,ЩIШ nallHblX. В качестве llримсра caMoii llPO-
стой структуры данных можно привести .массив (апау), предстаВЛЯЮЩllЙ собой
линейный список, в котором поддеР)Jj:ивается проиэвольный доступ К его элемен
там. Массивы часто используются как UфУllдамент", на который опираются та
кие более сложные структуры, как стеки или очереди. Стек (stack) --: это список,
доступ к элементам которого возможен только по принципу "первым прибыл,
последним обслужен" (first in Last Out - FILO). Очередь (чиеие) - это список,
доступ к элементам которого возможен толыю по ПРИllЦИПУ Uпервым прибыл,
первым обслужен" (Ftrst in first Out - FIFO). Вероятно, очередь не нуждается
в примерах: вспомните, как вам приходилось стоять к какому-нибудь окоше'-{ку
кассы. А стек можно представить в виде множества тарелок, поставленных О.1,на
на друГУIО (к первой, или самой нижней, можно добраться только в самом конце,
когда будут сняты все верхние).
В стеках и очередях интересно то, что в них объединяются как средства хра
неиня информации, так и функции доступа к этой информации. Таким образом,
стеки и очереди представляют собой специаЛЫlые механизмы обслуживаllИЯ
данных, в которых хранение и доступ к ним обеспечивается самой структурой,
а не программой, в которой они используются.
Как правило, очереди поддерживают две основные операции: чтение и запись.
При каждой операции записи новый элсмент помещается в консц очереди, а при
каждой операции чтения считывается следующий элемент с начала очереди. При
этом считанный элемент снова сtlИтать нельзя. Очередь может быть заполненной
"до отказа", если для приема нового элемента нет свободного места. Кроме того,
очередь может быть пустой, если из нее удалены (считаны) все элементы.
Существует два основных типа очередей: циклические инециклические.
В циклической очереди при удалении очередного элемента повторно использу
-
:~
~ffi
:~
:0
::s:
ется "ячейка" базового массива, на котором построена эта очередь. В нецикличе :2j
:u
ской повторного использования элементов массива не предусмотрено, и потому
:u
:0
такая очередь в конце концов исчерпывается. В нашем проекте с целью упроще ~:;2
ния задачи рассматривается нециклическая очередь, но при не60льших дополни
тельных усилиях ее легко превратить в циклическую.
Последовательность действий
1. Создайте файл с именем Queue. срр.
2. В качестве фуtщамента для нашей очереди МbI берем массив (хотя возмож
ны и другие варианты). Доступ к этому массиву реализуется с помощью двух
индексов: ИНдекса записи (рut-индекса) и индекса считывания (gеt-индекса).
ИНдекс записи определяет, где будет храниться следующий элемент данных,
а индекс считывания - с какой позиции будет считан следующий элемент
данных. Следует помнить о невоэможности считывания одного и того же эле
мента дважды. Хотя очередь, кОО'орую мы создаем в проекте, предназначена
для хранения символов, аналогичную логику можно применить для хранения
class Queue {
char q[maxQsize)i // Массив, на котором строится
очередь.
size len;
putloc = get10c = О;
putlOC++i
q[putloc] = ch;
return О;
getloc++;
return q[getloc);
*/
#include <iostream>
using namespace std;
class Queue {
char q[maxQsize]; // Массив, на котором строится
// очередь.
int size; // Реальный (максимальный) размер
// очереди.
int putloc, getloc; // put- и gеt-индексы
public:
size len;
putloc = getloc О;
putloc++;
q [putloc] ch;
getloc++;
return q[getloc);
};
cout « "\n\n";
smallQ.put('Z' - i);
cout « "\п";
cout « "\п";
cout « "\n";
388 МОДУЛЬ 8. Классы и объекты
Попытка сохранить Х
Попытка сохранить W
Попытка сохранить V -- очередь заполнена.
ВАЖНОI
~!АмаССИ8ЬI 06ьеgD.8
Массивы объектов можно создавать точно так же, как создаются массивы зна
чений других типов. Например, в следующей программе создается маССllВ для
хранения объеI<ТОВ типа MyClass, а доступ !( объектам, которые являются эле
ментами этого массива, осуществляется с по.\ющью обычной процедуры индек
сирования массива.
#include <iostream>
using namespace std;
class MyClass
int Х;
public:
void set_x(int i) { х = i;
int get_x() { return Х;
};
int main ()
С++: руководство ДЛr;! начинающих 389
return О;
оЬs[О] .get_x(): О
obs[l] .get_x(): 1
obs[2] .get_x(): 2
оЬs(З] .get х(): 3
ВАЖНОI
1:1:"'~ЫL1ЦI4.аАk1~ацL4SLМaCC14ВОВ
объектов
Если класс включает llараметризоваШIЫЙ конструктор, то массив объектов
TaKol'O класса можно инициализировать, Например, в следующей программе ис
полыуется параметризооанный класс MyClass и инициализируемый массив
оЬs объектов этого класса,
// Инициализация массива объектов класса.
#include <iostream>
using namespace std;
class MyClass
int Х;
public:
MyClass (int i) ( Х = i;
int get_x() { return Х;
};
390 Модуль 8. Классы и обьеl<ТЫ
int main ()
{
MyClass obs[4] { -1, -2, -3, -'1 };
int i;
return О;
оЬэ[О] .get_x(): -1
оЬэ[l] .get_x(): -2
obs[2] .get_x(): -3
оЬs[З] .get_x(): -4
В действительности синтаксис инициализации массива, выраженный строкой
#include <iostream>
using namespace std;
class MyClass
int х, у;
public:
MyClass(int i, int j) ( х = i; У j;
int get_x() { return х;
С++: РУКОВОДСТВО ДЛJ\ начинающих 391
..................................................................................................................................
int main () ~
~
о
:s:
MyClass obs[4] [2] = ( 1i
MyClass(1, 2), MyClass(3, 4), / / \' Дm«ннЧЙ" формаТ u
u
MyClass(5, 6), MyClass(7, 8), / / ИlUЩИ8JlИзации. ~
MyClass(9, 10), MyClass(11, 12),
MyClass(13, 14), MyClass(15, 16)
};
int i;
return О;
BAII
1:1% Vka-1атеАI4 на обыU<;ты
Доступ к объекту можно получить напрямую (как во всех предыдущих примерах)
или через указатель на этот объект. Чтобы получить доступ к отдельному элементу
(члену) объекта с помощью указателя tш этот объект, необходимо исполъзовать опе
ратор "стрелка" (он состоит из знака "минус" и знака "больше": "->").
Чтобы объявить указатель на объект, используется тот же синтакСИС,lсак и в
случае объявления указателей на значения других типов. В следующей npoграм
ме создается простой класс Р _ example, определяется объект этого класса оЬ и
объявляется указатель на объект тина Р _ example с именем р. В этом при мере
показано, как можно напрямую получить доступ к объекту оЬ и как использовать
для этого указатель (В этом случае мы имеем дело с l<Освенным доступом).
#include <iostream>
using namespace std;
class P_example
int num;
public:
void set_num<int val) {пит = val;}
void show_num() { cout « num « "\n";
};
int main ()
return О;
С++: руководство ДМI начинающих 393
этого массива.
#include <iostream>
usiлg латеsрасе std;
class P_example
int лum;
public:
void sеt_пит(iлt val) {лиm = val;}
void show_num() { cout « пит « "\л";
};
iлt main ()
return О;
10
20
10
Как будет ПОJ«lЭШIO ниже в этоil книге, ука:JaТСЛИ на объекты играют главную
роль 8 реализации одного из важнейших прющипов С++: полиморфизма.
.~
лен указатель на этот объект?*
fC" ~~~:-.J.~:;г.i __ ...
;;iiJor._~., АР .-
• .lеСТ..дАЯжСомок.он:z:p.oЛЯ.QQ.М.ОAVАю.8
1. Каково различие между классом и объектом?
2. Какое ключевое слово используется для объявления класса?
class Sample
int i;
public:
Sample (int х) {i х;
// ...
};
Покажите, l<aK объявить объект класса Sarnple, который инициализирует
перемен"ую i значением 10.
7. Какая оптимизация происходит автоматически при определении функции
члена в объявлении класса?
8. Создайте класс Triangle, в котором для хранения длины основания и
высоты прямоугольного треугольника исполъзуются две переменные эк
ВАЖНОI
tII!IJ]"ер.е.tPVа3ка.кOJ:iC.Т~к"Т..Q,рав
Несмотря на Dы!oлнениеe конструкторами уникальных деiiствий, они не СИЛЬ
но отличаются от функций других типов и также могут подвергаться переГРУ:llre.
Чтобы перегрузить конструктор lU1acca. достаточно объявить его во всех нужных
форматах и определить )(аждое действие, связанное с соответствующим фОР\1а
том. Например, в следующей программе определяются три конструктора.
#include <iostream>
using namespace stdi
class Sample
public:
int Х;
int У;
// Переrpу~ха конструктора:
// конструктор по умолчанию
Sample () { х = у == О; }
int main ()
Sample t; // вызываем конструктор по умолчанию
ф
Sample t1(5); // используем вариант Sample(int) ф
~
О
return О;
t.x: О, t.y: О
tl.x: 5, t1.y: 5
t2.x: 9, t2.y: 10
В этоii программе создается три КОНСТРУlCтора. Первый - конструктор без па·
раметров - иtIJщиализирует члены данных х и у нулевым» значениями. Этот
КОIIСТРУКТОР играет роль конструктора по умолчанию, который (в случае его
ОТСУТСТОIIЯ) был бы пре!l<1ставлеll средстоами С++ автоматически. Второй кон
cтpYJaop ГJflJlШJмает ОДИН ГJapa~I(~Tp, значение ((оторого прнсваl1вается обоим
'1ЛСJJЮ1 даНIIЫХ х п у. TpCTJJii КОIIСТРУКТОР принимает два параметра, которые IIС
пользуются для индивидуальной инициа.JJИЗации переменных х и у.
Ilерегрузка конструкторов полезна по нескольким причинам. Во-первых,
она делает J\лаССh1, определяемые программистом, более гибкими, позволяя соз
давать объекты различными способами. Во-вторых, перегрузка конструкторов
предоставляет удоБС'гва ДЛЯ пользователя класса, поскольку он может выбрать
самый подходящий способ построения объектов для каждой конкретной задачи.
В-третьих, определение конструктора как без napa.\ofeТPOB, так и параметри30Ван-
1101'0, позволяет создавать как шшциализироваllные объекты. так и неиющиали
зированные.
ВАЖНОI
#include <iostrearn>
using narnespace std;
class Test {
iлt а, Ь;
public:
void setab (int i, int j) ( а i, Ь j;
void showab ()
cout « "а равно" « а « '\п';
cout « "Ь равно 11 « Ь « '\п';
};
iлt rnain ()
obl.setab(lO, 20);
ob2.setab(0, О);
cout « "Объект оЬ! до присваивания: \п";
оЬ! . showab () ;
return О;
а равно 10
Ь равно 20
Объект оЬ2 до присваивания:
а равно О
Ь равно О
Ь равно 20
Объект оЬ2 после присваивания:
а равно 10
Ь равно 20
Объект можно передать функции точно так же, как значение любого друго
го типа данных. Объекты передаются функция~[ путем использования обычного
С++-соглзшения о передаче парщtетров по значению. Тюшм образом, ФУНКЦ(IИ
передастся не сам объект, а его кт/.llЯ. Следователыю, измеНСIIИЯ, внссенные в
объект при вып()лнении фУI\IЩИИ, не оказываю'l" Нlшакого ВЛИЯНИЯ на объект, IIС
пользуемый в качестве аргумента для функции. 3тот механизм демонстрируется
в следующей программе.
#include <iostream>
using паmезрасе std;
class MyClass
int va).;
public:
MyClass (int i) (
val = i;
int main ()
MyClass а(10):
change(a);
cout « "Значение объекта а после аызова функции сhалgе():
" ,.
display(a);
return О:
#include <iostream>
404 Модуль 9. О классах подробнее
class MyClass
int val;
public:
MyClass (int i) {
va1 = i;
cout « "Внутри констружтора.\п";
~MyC1ass()
int main ()
tv!yC1ass а (10) ;
return О;
Внутри конструктора.
временного 06ъекта.
Теперь рассмотрим пример передачи объекта по ссылке.
finclude <iostream>
using namespace std;
class MyClass
int val;
public:
MyClass (int i) {
val = i;
cout « "Внутри конструктора.\п";
cout « ob.getval()
ob.setval(100);
int main ()
MyClass a(lO);
С++: РУКОВОДСТВО M~ начинающих 407
change(a) ;
.\п"; -
:ф
:ф
:!:
:10
·0
i~
:0
cout « "После вызова функции change() .\п"; :С::
display(a); :~
:о
:о
:0
return О; ~;2
:0
Внутри конструктора.
#include <iostream>
using namespace stdi
class MyClass
int vali
public:
// Обычный конструктор.
MyClass(int i)
val = i i
cout « "Внутри KOHCTpYKTopa.\n"i
-МуСlаss()
cout « "Разрушение объекта.\П"i
С++: РУКОВОДСТВО для начинающих 409
ф
int getval() ( return val; ф
];
10
О
// Возвращение объекта. ~
О
MyClass mkBigger() ( // Функция mkВiqqer() возвращает 1:
х
// об~хт типа МУСlазз. О
()
MyClass o(val * 2); ()
~
х
О
return О;
}
};
int main ()
return О;
Внутри конструктора.
Разрушение объекта.
Разрушение об~хта.
После возвращения из функции mkBigger () .
Разрушение объекта.
ВАЖНОI
Et1LС.Q.эдаНИe..иJ4CLI.QАЬ.за.ваwие.
конструктора копии
Как было показано в предыдущих llрю.Jер,ах, при передаче объекта фушщии
или возвратс оБЪСI(та 113 ФУIIКЦИИ создастся «опия объекта. По умолчанию копия
представляет соGой побнтовыi1 клон орипшального объекта. ВО МНОl11Х случаях
такое поведение "по УМОЛ'Iанию" вполне прнемлемо, но в ОСПL71I,НЫХ Сlпуациях
желательно бы уточнить, 1(3К ДОЛЖШlбыть с.оздана копия объекта. Это можно сде
лать, определив явным образом конст[)у1<'ГО[) коПl-п-J ДЛЯ класса. К01lсmруктор "о
пии представляет собоii специалЫlыii пш I1срегружешюго I<OIICТPYl<Topa. который
автоматически вызывается, когда в копии объекта В03НИ1<ает необходимость.
Для начала еще раз сфОРМУЛИ(1уем проблемы, ДЛЯ решения которых мы хо
тим определиТl) конструктор копии. При передаче объекта функции создается
побитовая (т.е. точная) I(ОIlIlЯ этого объекта, которая передается параметру этой
функции. Однако возможны ситуации, когда такая идентичная копия нежела
тельна. Например, ссли ОРИПfШlЛЫIЫЙ объект использует такой ресурс, как от-
крытый файл, то копия объекта будет исполыовать тот же eшtый ресурс. Сле
довательно, если копия внесет изменения в этот ресурс, то изменения коснyrся
также оригинального объектаl Более того, при завершении функции копия будет
разрушена (с вызовом деструктора). Это может освободить ресурс, который еще
нужен оригинальному объекту.
Аналогичная ситуация возникает при возврате объекта из функции. Компи
лятор генерирует временный объект, который будет хранить копию значения,
возвращаемого функцией. (Это делается автоматически, и без нашего иа то со
гласия.) Этот временный объект выходит за пределы области видимости сразу
же, как только инициатору вызова этой функции будет возвращено "обещанное"
значение, после чего ·незамедлительно вызывается деструктор временного объек
та. Но если этот деструктор разрушит что-либо нужное для ВЫllOлняемого далее
кода, последствия будут печальны.
В центре рассматриваемых проблем лежит создание побитовой копии обl,ек
та. Чтобы предотвратить их возникновение, необходимо точно определить, что
должно происходить, когда создается копия объекта, и тем самым избежать не
желательных побочных эффектов. Этого можно добиться путем создания кон
структора копии.
MyClass Х;
MyClass у;
#include <iostream>
using namespace stdi
class MyClass {
int val;
int copynumЬer;
public:
// Обычный конструктор.
MyClass(int i) t
val = ii
copynumber = О;
cout « "Внутри обычного KOHCTpYKTopa.\n";
// KOKCТPYK~OP копии.
MyClass(const MyClass &0) {
414 МОАуль9.01~ассаХПОАробнее
................................................................................................................................
val = o.val;
copynumber = o.copynumber + 1;
cout « "Внутри конструктора копии.\n";
-МуСlаss ()
if(copynumЬer == О)
cout « "Разрушение оригинал~ного объекта. \n";
else
cout « "Разрушение копии " «
copynumber « "\n";
int main ()
MyClass a(lO);
return О;
10
Разрушение копии 1
Разрушение оригинального объекта ..
При выполнении этой программы здесь происходит следующее: когда в функ
ции main () создается объект а, "стараниями" обычного конструктора значение
С++: PYI<OBOACTBO ДЛ9i начинающих 415
ВАЖНОI
. , . <Il\lLllЩl4Иr"
~r-"
А р,у3Ья.:
••
в оБЩБ1 случае только 'Iлены класса имеют доступ к эш,рытым членам этого
I<Jlacca. ОДВШ<О п С++ сущсстnуст DО3~ЮЖИОСТЬ разрешить доступ к закрытым
членам класса ФУНlщиям, которые не нвляются членами этого класса. Для этого
достаточно объявить эти функции "дружественными" (или "дР!lЗЫШU") по от
ношению к рассматриваемому Iслассу. Чтобы сделать функцию "другом" класса,
включите ее прототип в риЫ i.с-раздел объявления класса и предварите его клю
чевымсловом friend. Например, в этом фрагменте кода функция frnd () объяв
ляется "другом" класса MyClass.
class MyClass
/ / ...
public:
friend void frnd(MyClass оЬ);
/ / ...
};
Как видите, ключевое слово friend предваряет остальную часть ПРОТОТJmа
функции. ФуНlЩИЯ может быть "другом" нескольких классов.
Рассмотрим короткий пример, о котором функция-"друг" используется для
доступа к заI<РЫТЫМ члеНаМ Ю1асса MyClass, чтобы определить, имеют ли OJШ
общий множитель.
#include <iostream>
using паrnезрасе std;
class MyClass
int а, Ь;
public:
MyClass(int i, int j) { a=ii b=ji }
friend int соmDеrюm (MyClass х); / / ФУИICЦИ_ comDenom() -
// "друг" lCJIacca MyClass.
};
int comDenom(MyClass х)
{
/* Поскольку функция comDenom () -- "друг" класса MyClasS',
она имеет право на прямой доступ к его членам
данных а и Ь. */
int тах = Х.а < х.Ь ? Х.а : х.Ь;
return О;
С++: руководство ДЛЯ начинающих 417
int main ()
return О;
#include <iostream>
using namespace stdi
class СиЬе (
colors color;
public:
Cube(colors с) { color = с; }
friend bool sarneColor(Cube х, Cylinder У); 11 ~-"дpyr"
11 1CJtacca сиье.
11
J;
class Cylinder
colors color;
public:
Cylinder(colors с) ( color= с; J
friend bool sarneColor(Cube х, Cylinder У); 11 ФУНКЦИR-
// "дpyr" и
11 1CJt&cca
/1 Cylinder.
11
};
int main ()
(
СиЬе cubel(red);
СиЬе cube2(green);
Cylinder cyl(green);
if(sameColor(cubel, cyl) )
cout « "Объекты сиЬеl и су! одина:кового цвета.\п";
else
cout « "Объе:кты cubel и су! разного цвета.\п";
if(sameColor(cube2, cyl) )
cout « "Объекты сuЬе2 и су! одинакового цвета.\п";
else
cout « "Объекты сuЬе2 и су! разного цвета.\п";
return О;
tinclude <iostream>
using namespace std;
420 Модуль 9. О классах подробнее
...............................................................................................................................
class СиЬе (
colors color;
public:
Cube(colors с) { color= с; }
Ьооl sameColor(Cylinder у); // Функция sameColor() теперь
// R8ЛRетCR членом класса Cube.
//
};
class Cylinder
colors colori
public:
Cylinder (colors с) { color '" с; }
friend Ьооl Cube::sameColor(Cylinder у); // Функция
// Cube::sameColor() -
/ / "дpyr" xnасса Cylinder.
//
};
bool Cube::sameColor(Cylinder у) (
if(color == y.color) return true;
else return false;
int main ()
СиЬе cubel(red);
СиЬе cube2(green);
Cylinder cyl(green);
if(cubel.sameColor(cyl»
cout « "Объекты сиЬеl и су! одного цвета.\п";
else
cout « "Объекты сиЬеl и су! разного цвета.\п";
С++: руководство ДЛЯ начинающих 421
if(cube2.sameColor(cyl))
cout « "Объекты сиЬе2 и су! одного цвета.\п";
else
cout « "Объекты сиЬе2 и су! разного цвета.\п";
return О;
_.
ратор "точка"?
Структур".
Структуры "достались в наследство" от языка С и объявляются с помощью
ключевого слова struct (struсt-о6ъявление синтаксически подобно class-
объявлению). Оба объявлсния создают "классовый" тип. В языке С структура
#include <iostream>
using namespace stdi
struct Test {
int get_i () { return i; // Эти члены открыты
void put_i (int j) ( i = j; // (public) по умолчанию.
private:
int ii
};
int main()
Test S;
s.put_i(lO)i
cout « s.get i() i
rеturп О;
class Test {
int ii // закрытый член по умолчанию
public:
С++: РУКОВОДСТВО ДЛЯ начинающих 423
s.put_i(lO); ~
О
cout « s.get_i();
return О;
Вопрос. Если структура и класс так nОXIJЖlI, то почему в С++ существуют оба
BiqJllaHma оfiьявлеllll1l "классового" nlll1lа?
Ответ. С ОДНОЙ стороны, действительно, D существовании структур и классов
присугствует определенная избьгго'!ность, и МИОП1е нач~rnающие про
граммисты УДl18J1ЯЮТСЯ этому. Нередко можно даже услышать предложе
ния mП<Видировать одно нз этих средств.
Объединения
Обьедuне1luе - это область памяти, которую разделяют несколько различных
перемеННbJХ. Объединение создается с помощью ключевого слопа uлiол. Его
объявление, как lIетрудно убедиться па следующем примере, подобио объявле
нию структуры.
union utype {
short int i;
char Chi
utype u_vari
В переменной объединения u _ var как переменная i типа short int, так и сим
вольная переменная ch занимают одну и ту же область памяти. (Безусловно, пере
менная i занимает два байта, а символьная переменная ch использует только один.)
Как переменные i и ch разделяют одну область памяти, показано на рис. 9.1.
Согласно Uидеологии" С++ объединение, по сути, является классом, в котором
все элементы хранятся в одной облаcrи памяти. Поэтому объединение может вклю
чать конструкторы и деструкторы, а таюке функции-члены. Поскольку объединение
унаследовано от языка С, его члены открыты (а не закрыты) по умолчанию.
Рассмотрим программу, в которой объединение используется для отображения
символов, составляющих младший и старший байты короткого целочисленного
значения (в предположении, что "размер" типа short int составляет два байта).
С++: РУКОВОДСТВО ДЛЯ начинающих 425
~
I
I
I
m
:I:
10
~
ch О
1:
Х
Рис. 9.1. Пере.менuые i и ch О
U
вместе используют 06ьедине U
иие u var ~
О
// Демонстрация использования объединения.
#include <iostrearn>
using паrnезрасе stdi
union u_type (
u_type(short int а) { i = а; };
u_type(char х, char у) { ch[Q] = Х; ch[l] Yi}
void showchars() {
cout « ch[Q] « " ".,
cout « ch[l] « "\п";
} i
int rnain ()
u type u(lOOO)i
u_type и2('Х', 'У');
return О;
Анонимные объединения
в С++ предусмотрен специалЬtlый тип объединения, который называется шю
ншtныAI. Анонимное объединение не имеет наименования типа, и поэтому объект
С++: PYI<OBOACTBO ДМI начинающих 427
~
Рассмотрим такой пример.
int main ()
{
/ / ОпредеJUl811 авОllИlOlое об'Идивеиие.
union (
10ng 1;
double d;
char 5[4];
return О;
как видите, к элементам объединения можно получить доступ так же, как к обыч
ным переменным. Несмотря на то что ОНИ объявлены как часть анонимного объ
единения, их имена наХодятся на том же уровне области видимости, что и другие
локальные переменные ТОГО же блока Тахим образом, чтобы избежать конфликта
имен, член анонимного объединения не должен иметь ИМЯ, совпадающее с именем
любой другой переменной, объявленной в той же области видимости.
Все описанные выше ограничения, налагаемые на использование объедине
ний, применимы и к анонимным объединениям. К ним необходимо добавить
428 Модуль9.01~ассахподробнее
ВАЖНОI
t.I:-----КАючевое САово thj.s
Прежде чем переходить J( теме пере1"РУ3КИ операторов, необходимо рассмо
треть ключевое слово this. При каждом obl30fle функции-члена ей автоматиче
ски передается указатель, именуемый this, на объект, для которого оызьшзет
СЯ эта функция. Указатель this - это lIеявныu параметр, принимаемый всеми
функциями-членами. Следовательно, в любой функции-члене указатель t h i 5
можно использовать для ссылки на вызывающий объект.
Как вы знаете, функция-член может иметь прямой доступ к 3ЗI<рытым (pr i '1-
ate) членам дшшых своего класса.
Например, у нас определен такой класс.
c1ass Test {
int i;
void f () . .. };
/ / .,.
};
в ФУНКЦИИ f () для присnзиnания члену i значения 10 можно использовать сле
дующую инструкцию.
i = 10;
В действительности предыдущая ИНСТРУКЦИЯ представляет собой сокращен
ную форму следующей.
this->i = 10;
Чтобы понять, как работает указатель this. рассмотрим следующую корот
кую программу.
tinc1ude <iostream>
using narnespace std;
c1ass Test {
С++: руководство д1\Я начинающих 429
int ii
public:
void load i(int val) { т:I:
this->i = val; // то же самое, что i val ID
О
int get i () (
g
1:
Х
return this->i; 11 то же самое, что return i О
U
U
о
<
~
int main ()
Test о;
o.load_i(lOO);
cout « o.get_i();
return О;
~-
r.Y&I:IepeCpV3KQ оаераroров
Остальная часть модуля посвящена одному из самых интересных средств -
nереzрузке операторов. В С++ операторы можно перегружать для "классовы.х"
типов, определяемых программистом. ПРИНЦИI1Иальный ВЫИL"рыш от перегруз
ки операторов состоит в том, что она позволяет органично интегрировать нов ые
дом "Вильяме". g
с::
х
О
U
ВАЖНОI U
ПереГРV3КQ.оаераZQрав
о
tlJl' ;2
О
с использованием функций-членов
Начнем с простого примера. В следующей программе создается класс ThreeD,
который обеспечивает поддержку координат объекта в трехмерном пространстве.
Для класса Th reeD здесь реализована перегрузка операторов" +" 11 "=". и TaI<, рас
смотрим внимательно код этой программы.
#include <iostream>
using namespace std;
class ThrecD (
int х, у, z; // 3-мерные координаты
public:
ThreeD () ( х = у = z = О; )
ThreeD(int i, int j, int k) х = i; У = j; z = k; )
void show() ;
};
// Отображение координат Х, У, Z.
void ThreeD::show()
(
cout « х « " " ,.
cout « у « ", ";
cout « z « "\n";
int main ()
cout « "\n";
с = а + Ь; // сложение объектов а и Ь
cout « "Значения координат объекта с после с а + Ь: ";
С++: руководство дl\Я начинающих 433
c.show();
Q)
cout « "\п" i ф
~
с = а +
cout « "Значения
Ь + С; 1/ сложение объектов а,
координат объекта с после
Ь и с
с = а + Ь + с: "; ~
с::
х
с. show () ; О
U
U
cout « "\п"; ~
О
return О;
тора "=". Это позволит выполнить любую цепочку присваиваНИЙ. Операция при
сваивания - это одно из самых важных применений указателя this.
В предыдущей программе не было насущной необходимости переrpужать опе $
ратор "=", поскольку оператор npисваива.ния, по умолчанию предоставляемый ~
языком С++, вполне соответствует требованиям класса ThreeD. (Как разъяс
иялось выше в этом модуле, при выполнении операции ПРИСDаивания по умол
g
t:
х
Llанию создается побитовая КОПИЯ объекта.) Здесь OllepaTOp "=" был перегружен О
(J
(J
исключительно в демонстрационных целях. В общем случае оператор "=" следу о
temp.x = - ор2.х;
х
temp.y у
- ор2.у;
temp.z = z - Op2.Zi
return tempi
Можно также перегружать такие унарные операторы, как "++", "--", или унар
ные "-" и "+". Как УПОМИllалось выше, при перегрузке унарного оператора с помо-
436 МОДУЛЬ 9, О I<ЛОССОХ подробнее
#include <iostream>
using namespace std;
class ThreeD {
int х, у, Z; // З-мерные координаты
.public:
ThreeD () { х = у = Z = О; )
ThreeD(int i, int j, int k) {х = i; У = ji Z = k; }
void show() ;
у++;
z++;
return *this; // Возвращаем инкрементированный объект.
// Отображение координат Х, У, z.
void ThreeD::show()
{
cout « х « ", ";
cout « у « ", ";
cout « Z « "\n";
int main ()
С++: РУКОВОДСТВО MfI начинающих 437
.ThreeDa(1,2,·3);
ф
ф
а. show () ; ~
О
return О;
++х;
или
х++; .
Как отмечено в комментариях к предыдущей программе, функция operat-
or++ () определяет префиксную форму оператора "++" ДЛЯ класса ThreeD. Но
нам ничего не мешает перегрузить и постфиксную форму. Прототип постфикс
ной формы оператора
"++" для класса three_d имеет следующий ВИД.
// на использование
// параметра notused.
У++;
Z++i
return temp; // возврат исходного значения
Обратите внимание на то, что эта функция сохраняет текущее значение опе
ранда путем выполнения такой инструкции.
iinclude <iostream>
using namespace stdi
class ThreeD {
int х, у, Zi // З-мерные координаты
public:
ThreeD() х = у = z = О; }
ThreeD(int i, int j, int k) {х = ii У = j; Z = k; }
void show ()
};
С++: PYI<OBOACTBO дl\Я начинающих 439
Z++;
return temp; // возвращаем исходное значение объекта
// Отображение координат Х, У, Z.
void ThreeD::show()
{
cout « х « ", ";
cout « у « ", ";
cout « z « "\п";
int main ()
cout « "\п";
440 Модуль 9. О I<ЛОССОХ подробнее
..................................................................................................................................
cout « "\п";
cout « "\п";
return О;
Помните, что если символ "++" стоит перед операндом, вызывается операторная
функция operator++ (), а если после операнда - операторная фуmщия operat-
ф
or++ (int notused). Тотжеподходисполыуется и для переГРУЗЮ1 префиксной и ф
:r
постфиксной форм оператора декремента для любого класса. Б качестве упражне 10
о
ВАЖНi
'1' Переrрvзка операторов
с использованием функций
не членов класса
llерегрузку оператора для класса можно реализовать и с использованием
функции, не являющейсн члеl-JOМ этого класса. Такие функции часто определяют
ся "друзьями" класса. Как упоминалось выше. функции-не члены (В том числе и
функции-"друзья") не имеют УЮlЗателя this. Следовательно. если для переГРУЗI<И
бинарного оператора исrюльзуется функция-"друг". явным образом передаются
оба операнда. Если же с помощью Функции-"друга" перегружается унарный опера
тор, операторной функции передастся один оператор. С использованием функций
не членов класса нелыя перегружать такие операторы: =. (). [] и ->.
#include <iostream>
using паmезрасе std;
class ThreeD {
int х, у, Zi // 3-мерные координаты
public:
ThreeD () х = у = z = О; }
ThreeD(int i, int j, int k) { х = ii У j; , = k; }
void show () i
// Отображение координат Х, У, Z.
void ThreeD: :show()
{
cout « х « ", ";
cout « у « ", ";
cout « Z « "\п";
С++: PYI(OBOACTBO ДМI начинающих 443
int main ()
ф
{ ф
Ь. show () ; ~
О
cout « "\n";
с = а + Ь; // сложение объектов а и Ь
cout « "Значения КООРдИНат объекта с после с а + Ь: ";
с. show () ;
cout « "\n";
с = а + Ь + с: // сложение объектов а, Ь и с
cout « "Значен·ия координат объекта с после с = а + Ь + с: ";
с. show () ;
cout « "\n":
return О;
i
// Перегрузка операторной функции operator+() ДЛЯ таких
// наборов операндов: (int + объект) и (объект + int).
#include <iostream> 1:
Х
О
using namespace stdi U
U
~
class ThreeD ( х
О
int х, у, Z; // З-мерные координаты
public:
ThreeD () { х = у = Z = О; }
ThreeD (int i, int j, int k) { х = i; У = j; z = k; }
void show ()
return tempi
return temp;
// Отображение координат Х, У, Z.
void ThreeD::show()
{
cout « х « ", ";
cout « у « ", ";
cout « z « "\п";
int main ()
cout « "\п";
cout « "\п";
return О;
унарных операторов ~
О
с помощью Функuий-"друзей" можно перегружать и YHapllbIe nператоры. Но
это потребует от программиста ДОllОлнитеЛl,НЫХ усилий. Если вы хотите пере
грузить операторы инкремента (++) или декремента (--), то ТaJюjj операторной
функции необходимо передать объект по ссылке. ПОСКОЛhКУ ссылочный пара
метр представляет собой неЯ8НЫЙ указатель на аргумент, то изменения, внесен
ные в lJapaMeтp, повлияют и на аргумент. Таким образом, применение ССЫ.'10'lНОГО
пара метра позволяет функции успешно инкрементировать или деl<рементиро
вать объект, используемый в I<ачестве операнда.
Если для перегрузки операторов инкремента или декремента ИСПОЛiJзуется
Функция-"друг", ее префиксная форма принимает один паРdметр (КОТОf>ЫЙ и
является операндом), а постфиксная форма - два параметра (вторым является
целочисленное значение, которое не используется).
Ниже приведен код перегрузки двух форм инкремента объекта трехмерных
координат. в котором используется операторная функция-"друг" operator++ ()
для класса ThreeD.
1* Перегрузка префиксной формы оператора "++"
с помощью функции-"друга". Для этого
необходимо использование ссылочного лараметра. ~/
ор1.х++;
ор1.у++;
opl.z++i
return temp;
~
с:
Спросим у опытного программиста х
О
()
()
Вопрос. Существуют ли особенности nерегРУ1ки операторов оmношенlUI?
ОтвеТ. Операторы отношений (например, "==" или "<") также можно перегружать,
~
О
причем делаn, это совсем нетрудно. как правило, перегружеЮlЗЯ операторная
функция возвращает объект класса, для которого она перегружается. Однако
iIерегружешrый оператор отношения возвращает одно из двух возможных
значений: true или false. Это соответствует обычному применеиию этих
операторов и позволяет использовать их R условных выражениях. ВЬПlIеска
занное справедливо и в отношении логических операторов.
* ?
Оператор". *" - это оператор специального назначения, рассмотрение кото
рого выходит за рамки этой книги.
{А,В,С)
совпадает со множеством
{А, С, В}
Множество может быть пустым.
М ножества поддерживают некоторый набор операций. В нашем проекте мы
реализуем такие:
• объединение множеств;
• получение разности множеств.
Такие операции, как внесение элемента в множество и удаление элемента из
него него, не требуют дополнительных разъяснений. О других же необходимо
сказать несколько слов.
Последовательность действий
1. Создайте новый файл с именем Set. срр.
2. Начните создание типа множества с объявления класса Set.
const int MaxSize = 100;
class Set
int len; // количество членов
char members[MaxSize); 1/ На этом массиве и будет
// построено множество.
public:
return -1;
cout « "}\п";
~
О
в множество.
Set так, чтобы он
Iа
7~ Перегружаем оператор "-" так, чтобы с его помощью можно было удалить
элемент из множества.
return newset;
// Разность множеств.
Set Set::operator -(Set оЬ2)
Set newset = *this; // Копируем первое множество.
~
newset = newset - ob2.memЬers[i);
/*
Проект 9.1.
*/
#include <iostream>
using namespace std;
class Set
int len: // количество членов
char members[MaxSize]: // На этом массиве и будет
// построено множество.
public:
return -1;
cout « "}\п";
if(len == MaxSize)
cout « "Множество заполнено.\п";
return *this; // возвращает существующее множество
458 МОДУЛЬ 9. О классах подробнее
return newseti
// Объединение множеств.
Set Set::operator +(Set оЬ2)
Set newset = *thisi 1/ Копируем первое множество.
// Разность множеств.
ф
Set Set::operator -(Set оЬ2) { ф
:I:
Set new5et = *thi5i // Копируем первое множество. \о
О
Н
// "Вычитаем элементы из второго множества.
g
с::
х
for(int i=O; i < оЬ2.1еп; i++) О
U
U
new5et = new5et - ob2.member5[i]i о
~
О
return new5et; // Возвращаем модифицированное множество.
51 sl + ;
'А'
sl 51 + 'В' ;
51 51 + 'С' ;
СИМВОЛОВ А В С: ";
sl.5howset() i
cout « "\п";
isMember () . \п" i
if(sl.isMember('B'»
cout « "в - член множества sl.\n"i
else
cout « "в - не член множества sl.\n";
if(sl.isMember('T') )
cout « "т - член множества sl.\n"i
else
460 Модуль 9. О классах подробнее
cout « "\п";
51 = 51 - 'В';
cout « "Множество 51 после 51 51- 'В':";
51.5how5et();
51 = 51 - 'А';
cout « "Множество 51 после sl 51 - 'А': ";
51.5how5et() ;
51 = 51 - 'С';
cout « "Множество ~1 после 51 51- 'С':";
51.5how5et();
cout « "\п";
51 51 + 'А';
51 51 + 'В';
51 51 + 'С';
cout « "Множество 51 после добавления А В С: ";
51. 5how5et () ;
cout « "\п";
52 52 + 'А';
52 52 + 'Х';
52 52 + 'W';
cout « "\п";
53 = 51 + 52;
cout « "Множество 53 после 53 51 + 52: ";
53.5hOW5et();
С++: PYI<OBOACTBO ДЛ91 начинающих 461
53 = 53 - 51;
cout « "Множество 53
5З.5hОW5еt();
cout « "\п";
после 53 53 - 51: ";
-
:Ф
:Ф
:1:
:10
1§
:0
:с:
:х
cout « "Множество 52 после 52 52 - 52: "; :0
:u
52 = 52 - 52; //·очищаем 52 :u
:0
52.5how5et(); ~~
:0
cout « "\п";
return О;
• JQСТААЯ самоКОНТРОASLдА.МOAV~
1. Что представляет собой кон.структор копи И И В каких случаях он вызы вает
ся? Представьте общий формат конструктора к~пии.
2. Поясните, что происходит при возвращении объекта функцией? В частно
сти, в каких случаях l1ызьшается его деструктор?
class Т (
int i, j;
public:
int sum()
return i + j;
};
10. Для класса Set, определенного в проекте !).1, определите операторы "<" и
">", чтобы с их помощью можно было узнать, является ли одно множеств()
подмножеством или супермножеством другого. Пусть оператор "<" DОЗ
вращает значение true, если левое МНОЖI~(ТRО является подмножеством
правого, и значение false 8 противном случае. ПУС,ть оператuр ">" 130:1-
вращает значение true, если левое множество является супермножеством
11. Для класса Set определите оператор "&", чтобы с его помощью можно было
получить пересечение двух множеств.
ф
ф
12. Самостоятельно попытайтесь добавить в класс Set другие операторы. :t:
ID
Например, попробуйте определить оператор "1" для получеliИЯ строгой О
ВАЖНОI
1("'Поыяж ие О наСАААО'ОЫИИ
..
в стандартной терминологии языка С++ класс, который наследуется, называ
ется базовым. Кnacc, который наследует 6азовый класс, называется nроuзводным.
Следовательно, производный класс предстаВ.'lЯет собой специализированную
версию 6азового класса. Производный класс наследует все члены, определенные
. в базовом, и добавляет к ним собственные уникальные элементы.
Язык С++ поддерживает механизм наследоваllИЯ, который предусматривает
возможность в объявление одного класса встраивать другой класс. Для этого
достаточно при объявлении пронзводного класса указать базовый. Лучше вее
го это пояснить на примере. В следующей ПР(lграмме создается базовый класс
TwoDShape, в котором хранятся основные параметры (ширина и высота) дву
мерного объекта, и ПРОИЗВОДНЫЙ класс Triangle, создаваемый на основе класса
Two DShape. Обратите особое внимание на то, как оБЪШ1ляется класс т r iang 1 е.
11 Простая иерархия классов.
#include <iostream>
#include <cstring>
using namespace stdi
void showDim ()
ф
cout « "Ширина и высота равны " « :s:
:I:
width « " и " « height <~ "\п"; о
a::I
}; ~
U
о
::r:
// ,Класс Trianqle ВЫВОДИТСR из класса ТWoDShape.
class Triangle : public TwoDShape ( // Обратите внимание на
// синтаксис объявления.
,
public:
char style[20);
double area() {
return width * height / 2; // Класс Trianqle может
// обращаТЬСR к членам xnасса
// TwoDShape так, как если бы они
// были частью класса Trianqle.
void showStyle () {
cout « "Этот треугольник" « style « "\п";
};
int main ()
Triangle tl;
Triangle t2;
Shape.
strcpy(tl.style, "равнобедренный");
t 2•• wi d t h = В. О ;
t2.height = 12.0;
strcpy(t2.style, "лряыIугольны'');;
468 Модуль 10. Наследование
cout « "\n";
cout « "Информация о треугольнике t2:\n";
t2.showStyle();
t2 . showDim () ;
cout « "Площадь равна" « t2.area() « "\n";
return О;
~Sh~{
width
helght
showDimO
Trlangle
styte
areaO
showSlyleO
class TwoDShape
// Теперь это рrivаtе-чnены.
double width;
double height;
public:
void showDim ( )
С++: руководство ДМ! начинающих 471
};
cout « "Ширина
width «
и высота
" и
равны
" «
" «
height « "\п";
-
:Ф
:::;;:
:::t
:~
:0
:<[
:Ф
// Класс Triangle выводится из класса TwoDShape. .<
:u
:0
class Triangle : public TwoDShape { ::!:
public:
char style [20] ;
double area () {
return width * height / 2; // Ошибка: кenъЗR поn~
/ / ПРIDIОЙ дос'l'УП IC за!cpы'l'tolн
// чnевак базового xnасса.
void showStyle ()
cout « "Этот треугольник" « style « "\п";
};
#include <iostream>
472 Модуль 10. Наследование
#include <cstring>
using namespace std:
void showDim ( )
cout « "Ширина и высота равны " «
width « " и " « height « "\n":
void showStyle() {
cout « "Этот треугольник" « style « "\n":
};
int main ()
Triangle tl:
С++: руководство ДЛЯ начинающих 473
Triangle t2;
ф
tl.setWidth(4.0); :s:
:r:
tl.setHeight(4.0) ; О
ID
strcpy(tl.style, "равнобедренный");
~
u<
о
t2.setWidth(8.0); :I:
t2.setHeight(12.0) ;
strcpy(t2.style, "прямоугольный") ;
cout « "\п";
cout « "Информация о треугольнике t2:\n";
t2.showStyle();
t2.showDirn();
cout « "Площадь равна" « t2.area() « "\п";
return О;
}
1. Имя базового класса указывается после имени производного. Имена этих классов
должны разделяться двоеточием.
Вопрос. Я СЛ6lшtlll, что "pozpa.wнllcmw 11" Jr:JЫI(J! Java IIСnOllьзуюm mepмШI.' "cynер
класс" u "nодкласс". Как зmu ItU!pAfШl.' сооmносяmCJI с C++-термUНOIIozuеu?
ВАЖНОI
1[1'1 Xnравдеыие А OCтyaQМК ЧАенам
базового класса
..
Как разъяснялось выше, если один класс наследует другой, <!Лены базового
класса становятся членами производиого. Однако статус доступа членов базо
вого класса в производном классе определяется спецификатором доступа, ис
пользуемым для наследования базового класса. Спецификатор доступа базового
класса выражается одним из ключевых слов: public, private или pr;otectl~d.
Если спецификатор доступа не указан, то по умолчанию используется специфи
катор pri vate, если речь идет о наследовании типа class. Если же наслсдуе7СЯ
тип s t ruct, то при отсутствии явно заданного с.пецифш\атора доступа по умол
чанию используется спецификатор public. Рассмотрим рамификацию (ра.lвет
вление) использования СI1СЦИфИl(аторов publ ic или pri vate. (Спецификатор
protected описан J) следующем разделе.)
Если базовый класс наследуется как рuЫ i с- класс, все его publ i с-члены с ra-
НО8ЯТСЯ publ ic-членаМI1 производного класса. Во всех случаях pr i va tе-члены
базового класса остаются закрытыми в рамка,'{ этого класса и не доступны ДЛЯ
членов производного. Например, н следующей программе рuЫ i с-члены класса
В становятся рubliс-членами класса D. Следовательно, они будут доступны и
для других частей программы.
// Демонстрация рubliс-наследования.
#include <iostream>
using namespace stdi
class В {
int i, ji
С++: руководство д,л>1 начинающих 475
public:
};
void set(int а, int Ь) ( i а; j
void show() { cout « i « " " «
=
j «
Ь;
"\п" i }. -
:ф
:s
.::t:
:0
·ID
:0
:~
:Ф
class D : public В ( // ~ Здесь класс В ваcnедуетс. :<
OTкpЫТWN способом.
:~
// :I
int ki
public:
D(int х) ( k Х; }
void showk () cout « k « "\п"; }
int rnain ()
D оЬ (3) ;
return О;
#include <iostream>
using namespace std;
class В {
int i, j;
public:
void set (int а, int Ь) {i а; j Ь;
void show () {cout « i « " " « j « "\п";
};
int main ()
D оЬ(3);
return О;
Итак. если базовый класс наследуется как рrivаtе-класс, ero открытые чле
ны становятся закрытыми (р r i v а t е) членам и производного класса. Это означа-
С++: PYI<OBOACTBO ДЛ)! начинающих 477
ет, что они доступны для членов производного класса, но не доступны для других
частей программы.
ВАЖНОI
- ф
:s::
:J:
о
ID
О
af
I-ИсI:lОАЬ308aWИе защI4IJ~"hI"ых <
u
·0
членов ::!:
Как упоминалось выше, ~акрытый член базового класса недоступен для про
изводного класса. Казалось бы, это означает, что, если производному классу ну
жен доступ к члену базового класса, его следует сделать открытым. При этом
придется смириться с тем, что открытый член будет доступным для любого дру
гого кода, что иногда нежелательно. К счастью, таких ситуаций можно избежать,
поскольку С++ позволяет создавать защuще1/1/ые 1iле1/Ы. Защищенным является
член, который открыт для своей иерархии классов, но закрыт вне этой иерархии.
з.iщищенныЙ член создается с помощью модификатора доступа protected.
При объявлении protected-члеН(l он, по сути, является закрытым, но с одним
исключением. Это исключение вступает в силу, когда защищенный член наследу
ется. В этом случае защищенный член базового класса становится защищенным
членом производного класса, а следовательно, и доступным дЛЯ ПРОИЗDОДНОГО
#include <iostream>
using namespace std;
class В {
protected: / / ЗдеClt перекеННlolе i и j - защищенные чnевы.
int i, j; // Хотя эти члены закрыты в рамках класса В,
// тем не менее они доступны для класса D.
public:
void set (int а, int Ь) { i а; j Ь;
class D : public В {
int k;
public:
// Класс D ножет обращатьCR х членах i и j хпасса В,
\
// посхоnъху ОНИ не private-, а рrоteсtеd-чnеиы.
void setk () { k = i *j; }
int main ()
D оЬ;
ob.setk();
оЬ. showk () i
return О;
class имя_класса {
// рrivаtе-члены по умолчанию
С++: PYI<OBOACTBO д,ля начинающих 479
protected:
};
// рrоtесtеd-члены
public:
/ / рubliс-члены
-Ф
:s:
1:
о
ID
~
.Ф
.<
Напомню, что раздел защищенных членов необязателен. :u
:0
Ключевое слово protected можно использовать не только для придания членам :I
класса статуса "защищенности", но и в (ачестве специфИJ<атора доступа при насле
довании базового класса. Если базовый класс наследуется как защищенный, все его
открытые и защшценные члены становятся защищенными членами производнаго
class D : protected В {,
то все незакрытые члены класса В станут защищенными членами класса о.
ного "мира"?·
1. Верно: если базовый класс наследуется закрытым способом, его открытые члены
стаНОВЯТСЯ закрытыми членами производного класса.
з. Чтобы сделать член класса доступым в рамках иерархии классов, но закрbпым для
остального "мира", нужно использовать спецификатор protected.
480 Модуль10.НаследоваНие
Конструкторы и наследование
в иерархии классов как базовые, так и производные классы могут иметь
собственные конструкторы. При этом возникает важный вопрос: какой КОН
структор отвечает за создание объекта производного класса? Конструктор ба
зового, производного класса, или оба одновременно? Ответ таков: конструк
тор базового класса создает часть объекта, соответствующую базовому клас
су, а конструктор производного класса - часть объекта, соответствующую
производному классу. И это вполне логично, потому что базовый класс "не
видит" элементы производного класса или не имеет доступа к ним. Поэтому
их "конструкции" должны быть раздельными. В предыдущих примерах K.lac-
сы опирались на конструкторы по умолчанию, создаваемые автоматически
,
объекта несложен: просто создается объект производного класса. Часть 06ъекта, со
ф
ответствующая базовому классу. создается автоматически с помощью конструктора s
:I:
О
по умолчанию. Например, рассмотрим перера60танную версию класса Triangle, 111
#include <iostream>
#include <cstring>
using паmеэрасе std;
double area ()
return getWidth() * getHeight() / 2;
void showStyle()
cout « "Этот треугольник" « style « "\п";
);
int main ()
Triangle tl ("равнобедренный", 4.0, 4.0);
Triangle t2("прямоугольный', 8.0, 12.0);
cout « "\п";
cout « "Информация о треугольнике t2:\n"i
t2.showStyle();
t2.showDim();
cout « "Площадь равна" « t2.area() « "\п";
return О;
ВАЖНОI
Dэтом случае должны вы
-
:Ф
:s
::1:
:~
:0
:<[
:Ф
:<
111'1 ВЫ308..к.owc;rР~la.QроВJ)азоа.Qr..Q ,()
:0
:I
класса
Если ба.10ВЫЙ класс имеет конструктор, то ПРОИЗDОДИЫЙ класс должен яшю
вызваТI) его для инициализации той части "своего" объекта, которая соответству
ет ба.10ВОМУ классу. ПРОИЗВОдИЫЙ класс может пызывать конструктор, опреде
ленный в его базовом классе, используя расширенную форму объявления кон
структора ПрОИ380ДНОГО класса. Формат такого расширенного объявления имеет
следующий вид.
конструктор производного
- - класса (список аргументов)
-
конструктор_базов ого_класса (список_аргументов)
.
// тело конструктора лроизводного класса
#include <iostream>
#include <cstring>
using патезрасе stdi
double heighti
public:
void showDim ( ) {
cout « "Ширина и высота равны ., «
width « "и " « height « "\п";
// Функции доступа.
doub~e getWidth() { return widthi }
double getHeight() ( return height;
void setWidth(double w) { width = Wi }
void setHeight(double h) { height = hi
}i
double area() (
return getWidth() * getHeight() / 2;
void showStyle() {
cout « "Этот треугольник " « style « "\п";
С++: PYI<OBOACTBO дl\fI начинающих 485
};
ф
:s;
:I:
int main () а
CII
Triangle
Triangle
tl("равнобедренный",
t2("прямоугольный",
4.0, 4.0);
8.0, 12.0); i
u
а
I
cout « "Информация о треугольнике t1:\n";
t1. showStyle () ;
tl.showDim();
cout « "Площадь равна" « tl.area() « "\п";
cout « "\п";
cout « "Информация о треугольнике t2:\n";
t2. showStyle () ;
t2.showDim();
cout « "Площадь равна" « t2.area() « "\п";
return О;
iinclude <iostream>
#include <cstring>
486 МОДУЛЬ 10. Наследование
void showDim() (
cout « "Ширина и высота равны " «
width « " и " « height « "\л";
~
~оиструхтор по укоnчаниа xnасса ТwoDShape. */
Triangle () ( U
о
strcpy(style, "неизвестный"): I
double area ()
return getWidth() * getHeight() / 2;
void showStyle() {
cout « "Этот треугольник" « style « "\п":
}:
int main ()
Triangle t1:
Triangle t2("прямоугольный", 8.0, 12.0):
Triangle tЗ(4.0):
tl = t2:
cout « "\п";
cout « "\п";
cout « "\п";
return О;
Последовательность действий
1. Создайте файл TruckDemo. срр и скопируйте в него rlOслещrюю реализа
цию класса Vehicle из модуля 8.
#include <iostream>
using паmезрасе stdi
fuelcap = f;
mpg = m;
ф
s
:t
~
// Функция
int range ()
вычисления максимального
{ return mpg * fuelcap;
пробега.
}
~<
u
о
::r:
// Функции доступа к членам класса.
int getyassengers () ( return passengers;
int get_fuelcap() ( return fuelcap;
int get_mpg() { return mpg; }
};
int main ()
«
semi.range() « " километров.\п";
cout « "Чтобы проехать " « dist
« " километра, полуторке неОбходимо " «
dist I semi.get_mpg() «
.. литров топлива.\п\п";
return О;
тров.
тров.
тров топлива.
int groundClearancei //
public:
//
} i
//
высота дорожного
в дюймах
просвета
-
.ф
:s
.:х:
:0
:ш
:0
:4:
:Ф
.<
:u
:0
Главное - понимать, что, создап базовый класс, определяющий общие па ;::1:
раметры объекта, можно сформировать множество специализированных
классоп путем Iщследования базового. В каждый производный класс доста
точно добавить уникальные атрибуты. В этом и состоит суть наследования.
ВАЖНi
D СоздаWИ~OJ:OVровweaой
иерархии
До сих пор мы исполЬ..,опали простые иерархии. состоящие только из базо
вого и ПРОНЗВОДного классов. Но можно построить иерархии, которые содержат
любое количество уровней наслсдоuаНI1Я. Как упоминалось выше, один произ
водный класс ВlIолне допустимо использовать в качестве базового для другого.
Например, из трех классов (А, В I1 С) С может быть производным от В, который.
в свою очередь, может быть проIJзводныM от А. В подобной ситуации каждый
производныif класс наследует содержимое всех своих баЗОRЫХ классов. В данном
случае класс С наследует ВСС члены классов В и А.
// Многоуровневая иерархия.
#include <iostream>
#include <cstring>
usiлg лаrnеsрасе stdi
double width;
double height;
public:
// KOHCTPY~TOP по умолчанию.
TwoDShape() (
width = height = 0.0;
void showDirn() (
cout « "Ширина и высота равны " «
width « " и " « height « "\n";
strcpy(style, "неизвестный"):
double area ()
return getWidth() * getHeight() / 2;
void showStyle() {
cout « "Этот треугольник" « style « "\n"i
}i
}i
496 Модуль 10. Наследование
int main ()
ColorTriangle tl("синий", "прямоугольный", 8.0, 12.0);
ColorTriangle t2("красный", "равнобедренный", 2.0, 2.0);
cout « "\n";
return О;
Этот пример иллюстрирует еш.е ОДИII важНblЙ МОМСНТ: ссли конструктору ба
зового класса требуются параметры, все произnодныс КЛаССЫ должны передавать
Q)
эти параметры "по иерархии", нсзависимо оттого, нужны ЛИ эти парамстръr само sх
О
му производному классу.
ВАЖНОI
i
u
о
базовых классов
в С++ произподный класс одновременно может Н:1СJ1СДОШLТЬ два или больше
базовых классов. Например, n этой корuткой программс класс О t!аследует 06а
класса 81 и 82.
// Пример использования нескольких базовых клаССОЕ.
#include <iostream>
using патеsрасе stdi
c1ass 81 (
protected:
int Х;
public:
void showx() { cout « х « "\л"; }
} i
c1ass 82 {
protected:
int У;
public:
void showy () { cout « у « "\п"; }
};
int main ()
D оЬ;
return О;
}
Как ВИДНО IIЗ этого при мера, чтобы обеспечить наследование несколышх
базовых клаССО8, необходимо через запятую l1ере'lНСJlJП'l> их имена в виде СI!И
ска. При этом нужно указать спецификатор ДОСТУllLl для каждого наследуемо
го баЗ080ГО класса.
ВАЖНОI
ПР Кадо ВЫI1QAblsuo:rС8.ф~ЦI4И
конструкторов и деструкторов
Базовый или ПРОИ:'\ВОДIJЫЙ кnасс (или оба ОДНОlJрсмешю) :'ЮГУТ содержать
конструктор и/или деструктор. Важно rlOIIИМi\'IЪ IIОрЯДОК, В котором ВЫlIOЛIIЯЮТ
ел эти ФуН1ЩИИ. При использовании MCXaIll13i1la ШIСJlедования оБЫЧIIО ВОЗНlll<а
ет два важных вопроса, связанных с l(онструкторами и деструкторами. Перrщй:
в каком ПОрЯДI<С вызываются конструкторы базового и IIРОIlЗВUДIЮГО КЛ<1ссе,в?
Второй: в каком IIорядке 13ызьшаются деструкторы базuвого и IlРОИЗВОДllОГО
классов? Чтобы ответить IШ эти вопросы, рассмотрим простую программу.
#include <iostream>
bsing namespace std;
class В
public:
В() ( cout « "Построение базовой части объекта.\п"; )
-В() ( cout « "Разрушение базовой части объекта.\п"; )
);
class D: public В (
public:
D () { cout « "Построение произво.IlНОЙ части объекта. \п"; }
С++: PYI<OBOACTBO для начинающих 499
ф
}; :s;
:I:
О
ID
int main ()
о оЬ;
i
u
о
:I:
return О;
Как отмечено в комментариях функции main (), эта программа лишь создает
и тут же разрушает объект оЬ, который имеет тип о. При выполнении программа
отображает такие результаты.
1. Да, производный Krracc можно ИСllОJl,ьзоватъ в качестве базового для дрyroго произ
водного класс.а.
выведенным из класса В. */
Любой указатель на базовый класс (В данном примере это указатель р) мож
но использовать для доступа только к тем частям объекта производноro класса,
которые были унаследованы от базового класса. Так, в этом примере указатель р
можно использовать для доступа ко вссм элементам объекта 0_ оЬ, выведенным
из объекта в _оЬ. Однако « элементам, которые составляют СПСl1ИФИ4ССКУЮ "над
стройку" (Над базой, Т.е. над базовым классом В) объекта О_оЬ, доступ с помо
щью указателя р получить нельзя.
Кроме того, необходимо понимать, что хотя "базовый" указатель можно ис
пользовать для доступа к объектам любого производного тина, обратное утверж
дение нсверно. Другими словами, ИСПОЛl>зуя указатель на ПРОll3ВОДНЫЙ класс,
нельзя получить доступ к объекту базового типа.
Как вы уже Зllаете, указатель инкрементируется и декремснтируется от
носителыiO своего базового типа. Следовательно, если указат(>ЛI) на базовый
класс используется для доступа I( объекту производtlОГО тина, иш<рементиро
вание или декрементирование I-rС заставит его ссылаться на слсдующий объект
производного класса. Вместо этого он будет указывап> ("по его мнению") на
502 МОДУЛЬ 10. Наследование
Тот факт, что указатель на базовый тип можно ИСПОЛЬ30lJап, для ССЫЛКI1 на
люБОI';'! объект, выведеНI1Ыli из базового. чрезвычайно важен 11 JlРИllЦипиаПСIf для
С++. Как будет lIоказано ниже, эта гибкость Яl3лястся ключевым MOMetlTO:l1 ДЛЯ
способа реализации динамического ПОЛИМОРфllзма lJ С++.
ВАЖНОI
IIIа.ВИрЦ(.Q АЬИЬte..ф.У--NКЦИИ
и полиморфизм
ФуtlJt<lмеllТ, lIа котором u С++ строится поддержка ПОЛII!l.~ОРфИЗМ3, СОСТОIIТ IIЗ
механизма наследования н указателей на базовый JUlacc. Конкретным средством,
котоrюе в действитеЛhНОСТИ 110зволяет реализовап) полиморфизм, является DIlP-
туалЫlая функция. (Оставшаяся часТl) этого модуля как раз и посвящеl·lа расс\ю
трению этого важного средства.)
#include <iostream>
using namespace stdi
class В (
public:
virtual void who() { // +- Об~nеRИе .ир~уanъвой фуихции.
cout « "Базовый клаСС.\n"i
};
class О1 : public В {
public:
void who () { / / Переопредenевие фуJUЩИИ "Ьо () .ЦJU[
// .пасса Dl.
cout « "Первый производный класс.\п";
} i
class О2 : public В {
public :.
void who() { // Ещ$ ОДНО переопредеnение фувхции "Ьо()
// ДnR .пасса D2.
504 МОДУЛЬ 10. Наследование
};
int main ()
В base_obj;
В *р;
О1 D1_obj;
О2 D2_obj;
р = &base_obj;
/ / Вызываем ВИР'Х'УanьнyJD ф)'JI1ЩИJD who () черва ухааа'Х'ет. на
// базовый класс.
p->who(); // доступ к функции who() класса В
р =
&D1_obj;
p->who(); // доступ к функции who() класса О1
р = &D2_Obj;
p->who () ; / / доступ к функции wrlO () класса О2
return О;
Базовый класс.
Первый производный класс.
Второй производный класс.
Теперь рассмотрим код этой программы подробно, чтобы понять, как она ра
ботает.
В классе В функции who () объявлена виртуальной. Это означает, что ее
можно переопределить в производном классе (в классе, выведенном из В). И
она действительно переопределяется в обоих ПРОI1ЗВОДНЫХ классах О1 и О2. В
функции main () объявляются четыре переменные: base _ obj (объект типа В),
р (указатель на объект класса В), а также два объекта D1_obj и D2_obj двух
производных классов О1 If О2 соответственно. Затем указателю р присваивает
ся адрес объекта base _ obj, и вызывается функция who () . Поскольку !рункция
who () объявлена виртуальной, С++ во время выполнения программы опреде-
С++: руководство ДМ! начинающих 505
01 obj. who () ;
Однако при вызове виртуальной функции таким способом игнорируются ее
полиморфные атрибуты. И только при обращении к виртуальной функции через
указатель на базовый класс достигается динамический полиморфизм.
Поначanу может 110казаться, что Ilt:'рсопределение виртуальной функции в
производном классе представляет собоii специальную форму перегрузки фуш(
ций. Но это не так. В действительности мы имеем дело с двумя принципиально
разными процессами. Прежде всего, версии перегружеНIIОЙ ФУНКЦИИ должны
отличаться друг от друга типом и/или количеством параметров, в то время как
тип и количество параметров у версий переОllределенной виртуальной функ
ции должны в точности совпадать. И в самом деле, прототипы виртуальной
функции и ее переопрсделений должны быть абсолютно одинаковыми. Если
прототипы будут различными, то такая ФУНКЦIIЯ будет попросту СЧllтаться пс
регруженноj:j, и ее "ВИРТУ,Ulьная сущность" утратится. Кроме того, виртуальная
функция должна быть членом класса, для которого она определяется, а не его
"другом". Но в то же время виртуальная функция может быть "другом" ино
го класса. И еще: функциям деструкторов разрешается быть виртуальными, а
функциям конструкторов - нст.
506 Модуль 10. Наследование
} ;
class В {
public:
virtual void who()
cout « "Базовый класс.\п";
}
};
class О1 : public В {
public:
void who() {
cout « "Первый производный КЛilСС. \п";
}
};
int main ()
ф
sз:
в base_obj; О
ID
О
В *р;
~
01 01 obj; <
u
02 02 obj; о
::r:
р &base_obj;
=
p->who(); // Обращаемся :к ФУНКЦИИ who() :класса В.
р = &Ol_obj;
p->who(); // Обращаемся к ФУНКЦИИ who() класса 01.
р = &02 obj;
p->who(); /* ОбращаемСR к фуккции _Ьо() класса В, поскольку
в классе D2 она не переопредenена. */
return О:
Базовый класс.
Первый производный класс.
Базовый класс.
#include <iostream>
#include <cstring>
using namespace stdi
void showDim() (
cout « "Ширина и высота равны " «
width « " и " « height « "\n";
};
char style(20): //
public:
/*
действующий по
рrivаtе-член
void showStyle() {
cout « "Этот треугольник" « style « "\п";
}
};
// Конструктор npямоугольника.
Rectangle(double w, double h)
512 МОДУЛЬ 10. Наследование
TwoDShape(w, Ь, "прямоугольник") { }
11 Конструктор квадратов.
Rectangle(double х) :
TwoDShape(x, "прямоугольник") { }
bool isSqua re ()
i f (getWidth () getHeight()) return true;
return false;
};
int main ()
// Объявляем массив указателей на объекты типа TwoDShape.
TwoDShape *shapes[5];
return О;
ф
:s:
1:
О
Результаты выполнения этой программы таковы. 1:11
Площадь равна 40
ПЛощадь равна
ВАЖНОI
DRI.~ИСJ.о...вИРt"УаАЫlЫе...ф.y,и1ЩI4J4
и абстрактные классы
Иногда полезно со;щаТI> базовый класс, опредr JlЯЮЩИЙ ТОЛЫШ своего рода "пустой
бл;urк", J<ОТОРЫЙ унаследуют все ПРОИЗВОДНЫС классы, ПРllчем каждый И3 них запал
и ит этот "БЛaJ 11(" с06ствешюй ишtюрмациеЙ. Такой ~шсс определяет "сугь" функций,
которые производные классы должны ре3JIИЗОВ<1ТЪ, но сам при этом J-Ie обесJlечивает
реaJJИ3iЩЮ1 одной ИЛИ НI'СКОЛЫ<ИХ фуш(циЙ. Подобная ситуация может IЮ.ЗНИКIIУТЬ,
когда базовый класс попросту не n СОСТОЯНИИ рсапизоватъ фУНlщию. Этот случай
был пронлm()стрирооаll веrсией класса TwoDShape (и:) предыдущей программы), в
которой определеиие функции area () представляло собой "заглушку", ПОСI<ОЛЫ,У в
ней ПЛОЩ3Дl, фИI)'РЫ JiC ВЫЧИCJlЯлась Н, естествешlO, не от06раЖ3JIась.
В будущем, создавая собствеииыс библиотеки классов, вы убсдитесь, что от
сутствие у ФУНl(цИИ 'Iеткого определения в контексте своего (базового) класса,
#include <iostream>
#include <cstring>
using namespace stdi
11 Конструктор по умолчанию.
TwoDShape() (
width = height = 0.0;
strcpy(name, "неизвестный");
void showDim ()
cout « "Ширина и высота равны " «
width « " и " « height « "\п";
};
С++: РУI<ОВОДСТВО ДЛЯ начинающих 517
~
public:
U
О
/* Конструктор ,по умолчанию. Он автоматически вызывает :I:
конструктор класса TwoDShape, действующий по умолчанию.
*/
Triangle () {
strcpy(style, "неизвестный");
strcpy(style, "равнобедренный");
void showStyle()
cout « "Этот треугольник" « style « "\п";
};
// Конструхтор прямоугольников.
Rectangle(double w, double h) :
TwoDShape(w, h, "прямоугоnьник") { }
// Конструхтор квадратов.
Rectangle(double х) :
TwoDShape(x, "прямоугольник") { }
bool isSquare ()
i f (getWidth () getHeight()) rеturп true;
return fa1se;
};
int main () { .
// Объявляем массив указателей на объекты типа TwoDShape.
TwoDShape *shapes[4J;
cout « "\п";
С++: PYI<OBOACTBO M~ начинающих 519
rеturл О;
Если класс имеет хотя бы одну чисто виртуальную функцию, el'o называют
абсrпрактuы.At. Абстрактный класс характеризуется одной важной особенностью:
у такого класса не может быть объектов. Чтобы убедиться в этом, попробуйте
-
:Ф
::s::
:]:
:0
.11:1
:0
:<i
:Ф
.<
создать производнъrй класс, который не персопределяет функцию area () (для :u
': О
этого достаточно удалить переопределение этой ФУНКЦИII из класса Triangle). :I
Вы тут же (т.е. во время КОМПИЛЯЦИИ) получите сообщCIIИС об ошибке. АБСТР<lКТ
НЫЙ класс можно использовать только в качестве базового. IIЗ которого будут вы
водиться другие классы. ПРИ\fЮlа того, что абстрактный KJ1acc нельзя использо
вать ДЛЯ создания объектов, лежит, безус.ловно, D отсутствии определения для его
одной или нескольких функций. Поэтому в ФУНКЦИИ main () IIредыдущей про
граммы размер массива shapes сокращен до 4 элементов, 11 больше не создается
"заготовка для фигуры" в Dиде "об06щсннnго" объекта КЛiкса TwoDShape. Но,
как доказывает эта программа, даже ссл" базовый класс ЯВJlяется абстрактным,
мы все равно можем объявлять указатели lIа его тип, ,<оторые затем будем ис
пользовать для указания на объекты производных классов.
ства. В частности, вы узнаете, как псреГРУЗI1ТI> операторы "«" и"»" ДJlЯ пвода
и вывода объектов создаваемых вами классов, а также КaI< отформатировать вво
димые ИЛI1 пьшодимые данные 11 IfСl10льзовать манипуляторы ввода-вывода. За
вершает этот модуль рассмотрение средств файлового ввода-вывода.
в дисковый файл.
в самой общей форме поток можно назвать логическим шперфейсом с фай
лом. С++-определение термина "файл" можно отнссти К дисковому файлу, экра
ну, клавиатуре, порту, файлу на магннтной ленте и пр. Хотя файлы отличаются
по форме и возможностям, J;Jce потоки одинаковы. ДОСТOIШСТ1Ю этого подхода
(с точки зрения программиста) состоит в том, что ОДI-lO устройство компьютера
может "выглядеть" подобно любому другому. Это значит. что поток обеспечиваст
интерфейс, согласующийся со всеми ycтpoikToaMII.
Поток связывастся с файлом при ВЫПОЛllеtllш операUlIII открытия файла,
а отсоеДИliЯСТГЯ от него с 11O~\ОЩЬЮ оперании ~акрытия.
Встроенные С++-потоки
в С++ содержится ряд встроенных потоков (cin, cout, cerr и clog), кого
рые автоматически открываются, как только программа начинает I)ЫПОЛНЯТЬСЯ.
ВАЖНОI
"" KAQCChLOOIOКQB
Как вы узнали в модуле 1, С++-система ВlIода-вывода использует заголовок
<iostream>, в котором для помержки операций ввода-вывода определена до
вольно сложная иерархия классов. Эта иерархия начинается с системы шаблон
ных классов. Как будет отмечено в модуле 12, шаблонный класс определяет фор-
С++: РУКОВОДСТВО ДЛ9i начинающих 525
таты их выполнения. Поэтому имя класса ios будет употребляться в этой КЮ1ге
довольно часто. И помните: если включить в программу зarолонок <iostream>,
она будет иметь доступ К этому важному классу.
в ЯЭЫI<(' С++ оператор "«" называется оператором. вывода ИЛИ вставки. по
СКОЛЬКУ он I3ставляст символы в поток. Ar-rаЛОГIIЧflО оператор "»" называется
операmоро.м ввода или u~шлеч.еIlUЯ, IIOСКОЛЬКУ OIJ извлекает данные из потока.
Операторы ввода-выМда уже прегружеf-IЫ (В заГОЛОВl(е <iostream», '11'0-
бы они МОГЛИ ВЫIЮJlНЯТЬ операции потокового ввода или вьшода данных любых
BcтpOelJHblX C++-тиrЮВ. Здесь вы узнаете: как определить эти операторы для сЬб
CTBCll1lbJX классов.
class ThreeD
ThreeD.
-
public:
int х, у, z; // 3-мерные координаты
о
ThreeD(int а, int Ь, int с) { х = а; у = Ь; z = С; 1
}; ~s
Чтобы создать операторную функцию вывода для 06ъс!{тов типа ThreeD, не ~
обходимо переrpузить оператор "«" для класса ThreeD. ВОТ ОДIШ из возможных ;+
:U
способов.
iinclude <iostream>
using namespace std;
528 МОДУЛЬ 11. С++-система ввода-вывода
...........................................................................................................................- ....
class ThreeD
public:
int х, у, Zi / / з-о coordinates
ThreeD (int а, int Ь, int с) { х а; у Ь; z с; }
};
int main ()
return О;
tinclude <iostream>
using namespace stdi
class ThreeD (
int х, у, z; // 3-мерные координаты (теперь это
/1 рrivаtе-члены)
530 МОДУЛЬ
.................... 11. С++-система ввода-вывода
-............................................................................................-............ .
public:
ThreeD(int а, int Ь, int с) { х = а; у = Ь; z = с; }
// Опера~ор вывода, определ.еКЫЙ Дn. xnасса ThreeD, ~еперь
/ / _nlle~clI ФУНJCцией-" дру:rои" и поэтому имеет ДОСТУП JC e:ro
/ / sахры'пDI д8иRым. •
friend ostream &operator«(ostream &stream, ThreeD obj);
};
int main ()
cout « а « Ь « с;
return О;
#include <iostream>
using namespace stdi
class ThreeD (
int х, у, Zi / / З-мерные координаты
public:
ThreeD(int а, int Ь, int с) ( х = а; У = Ь; z = С;
friend ostream &operator«(ostream &stream, ThreeD obj)i
friend istream &operator»(istream &stream, ThreeD &Obj)i
,
11 Отображение координат Х, У, Z (определение оператора
/1 вывода ДЛЯ класса ThreeD) .
ostream &operator«(ostream &stream, ThreeD obj)
532 Модуль 11. С++-система ввода-вывода
................................................................................................................................
.
// прием трехмерных хоордиват (опредenеиие оператора ввода
/ / данных Дnll x.nасса ТhreeD) •
istrearn &operator»(istrearn &strearn, ThreeD &obj)
(
cout « "Введите значения координат X,Y.,Z: ";
strearn » obj.x » obj.y » obj.z;
return strearn;
int main()
cout « а;
cin » а;
cout « а;
return О;
1, 2, 3
Введите значения координат X,Y,Z: 5 6 7
5, 6, 7
Подобно функциям выпода, функции ввода не могут быть членами класса, для
обработки. данных которого они предназначены. Их можно· объявить "друзьями"
этого класса или просто независимыми фУНКltиями.
За исключением того, что функция ввода ДОЛЖllа возвращать ссьшку на объект
типа i s t rearn, тело этой функции может содержать все, что вы считаете нужным
в нее ВКЛЮЧIПЬ. НО логичнее использовать операторы ввода все же по прямому
назначению, Т.е. ДЛЯ выполнения операций ввода.
С++: руководство для начинающих 533
:0
:~
2. Какие действия выполняет оператор ввода для КЛ<1сса? :0
·СО
:со
ВАЖНОI
!'М' Форматирдвание доыI:lых
с использованием функций
членов класса ios
в системе ввода-вывода С++ каждый поток связан с набором флагов фор
матирования, управляющих процессом форматирования информации. В клас
се ios объявляется перечисление fmtflags, u котором определены следующие
значения. (Точнее, эти значения определены в классе ios_base, который, как
упоминалось выше, является базовым для КЛ<1сса ios.)
УстаНО8ка флю'з
showpoint приводит к отображению десятичной тоtlКИ И
хвостовых нулей ДЛЯ всех чисел с плавающей ТОЧI<ОЙ - IiУЖНЫ они или нет.
После установки флага scientific числовые 3liачения с плавающей точкой
отображаются в ЭКСllонеllЦИaJlЫJOМ представле1lИИ. Если уста1l0влен флаг fi:.:ed.
вещественные значения отображаются в обычиом предстаnлеШIII. ЕСЛII 11(' уста-
С++: PYI(OBOACTBO ДМ! начинающих 535
#include <iostream>
using namespace std;
int main ()
.{
// Установха фnаrов форматирования showpos и scient~c
11 с помо~ю функции setf().
cout.setf(ios: :showpos);
cout.setf(ios: :scientific);
536 Модуль 11. С++-система ввода-вывода
return О;
+123 +1.232300е+ОО2
fmtflags flags();
Эта функция возвращает текущее значение флагов форматирования для вызы
вающего потока.
#iпсludе <iostream>
using namespace std;
iпt mаiп ()
С++: PYI<OBOACTBO ДЛfl начинающих 537
ios::fmtflags [;
i f (f & ios::showpos)
cout « "Флаг showpos установлен для потока cout.\n";
else
cout « "Флаг showpos сброшен для потока cout.\n";
f = cout.flags():
f = cout .flags () :
return О:
}
реэ~ьтаты выполнения этой программы таковы.
~~
•
:
:<1
I
О
: 0
Рассмотрим программу, которая демонстрирует flСПОЛЬЗОВaJil1е этих трех .0)
: ш
ФУНКItиЙ. :0
:~
:.лt П1аiл ()
(
r
cout.setf(ios::showpos);
cout.setf(ios::scientific);
cout « 123 « " " « 123.23 « "\п";
/1 Уставовка точности.
cout.precision(2); 11 Две цифры после десятичной точки.
11 Установка ширииы полн.
cout.width(10); // Все поле состоит из 10 символов.
cout « 123 « n ";
return О:
+123. +1.232300е+ОО2
+123 +1.23е+ОО2
#*####+123 +1.23е+ОО2
char fill () ;
streamsize width();
streamsize precision();
О
ends Вставляет в поток ну левой символ (,\ о ') Вывод ~
fixed Устанавливает флаг fixed ВЫВОД ...
ф
u
flush «Сбрасывает" поток ВЫВОД :s:
у
hex Устанавливает флаг hex Ввод-вывод +
internal Устанавливает флаг internal Вывод
+
U
left Устанавливает флаl' 1 е f t Вывод
noboolalpha Обнуляет флаг boolalpha Ввод-вывод
noshowbase Обнуляет флаг showbase Вывод
noshowpoint Обнуляет флаг showpoint Вывод
noshowpos Обнуляет флаг showpos Вывод
noskipws Обнуляет флаг skipws Ввод
nounitbuf Обнуляет флаг uni tbuf ВЫВОД
nouppercase Обнуляет флаг uppercase Вывод
oct Устанавливает флаг oct Ввод-вывод
resetiosflags( Обнуляет флаги, заданные в параметре f Ввод-вывод
fmtflags f)
right Устанавливает флаг r igh t Вывод
scientific Устанавливает флаг s cie n t ific Вывод
setbase( Устанавливает основание системы счисления Вывод
int bdse) равкой значеюпо base
setfill ( Устанавливает символ-заполнитель равным Вывод
int сЬ) значению параметра сЬ
setiosflags( Устанавливает флаги, заданные R параметре f Ввод-вывод
frntflags f)
setprecision( Устанавливает количество цифр точности Вывод
параметра w
showbase Устанавливает флаг showbase Вывод
showpoint Устанавливает флаг showpoint Вывод
showpos Устанавлиnaет флаг showpos Вывод
skipws УСТaJLавливаетфлаг skipws Ввод
unitbuf Устанавливает флаг uni tbuf Вывод
uppercase Устанавливает флаг и рре rca s е Вывод
ws llgonycкaeт ведущие "ПQQБСJlьные" СИМВОЛЫ Ввоn
542 МОДУЛЬ 11, С++-система ввода-вывода
#include <iostream>
#include <iomanip>
using паmеврасе std;
int main ()
(
cout « setprecision(2) « 1000.243 « endli
cout « setw(20) « "Привет всем!";
return О;
1е+ООЗ
Привет всем!
#include <iostream>
#include <iomanip>
using namespace stdi
int main ()
{
cout « setiosfiaqs(ios::showpos) «
setiosfiaqs(ios: :scient~c) «
123 « " " « 123.23;
return О;
С++: PYI<OBOACTBO ДМl начинающих 543
11
А в этоИ программе демонстрируется использованис ~/lIIlш!улятора W5, I<UTO-
рый пропускает ведущие "пробеJlьные" символы IlрИ ВВО/1:С строки 13 массив
Пропуск·начальных "пробельных"
~include <iostream>
символов.
s:
-
using namespace 5td;
о
~
int main () ~
u
:s:
u
char 5[80J; +
:+
:u
cil1 » ws » S; 11 ИСПОJIЬзование мa.виnУnЯ'rора ws.
cout « 5;
ВАЖНОI
_ СО.здаыме,J:оБСIвеыwbIX
манипуЛSlТОРНЫХ функций
Программист может создават/) соБСТJlt'lIllые маНИПУЛЯТОРlfЫС функции. Су
ществует два Тlша маШШУРЯТОРНblХ ФУНКЦИЙ: ПРЮJИмающие и не l1рИl1имающие
аргументы. Для создания парамеТРИЗ0ваННblХ манипулЯ1'ОРОВ используются ме
тоды. рассмотрение которых ВЫХОДИТ::Iа рамки этой кннПf. Однако создание ма
Нlшуляторов, которые не IfМСЮТ параМСТРОIJ, не вызывает особых трудностей.
Все ма'l-/unулятор"ые фУIl'КЦUU IJыо()да данных без парамt:'ТРОП имеют следую
щую CTPYJ<TYPY.
ostream &mалiр_лаmе(оstrеаm &streaт)
{
11 код манипуляторной ФУНRЦИИ
return stream;
Здесь элемент manip_ пате означает имя маНИПУЛЯТОРii. В(lЖНО понимать, что,
несмотря на то, что 1\·lаНИПУJJЯТОР ПРИlIим.ает в качестве единственного аргумента
#inc1ude <iostream>
#inc1ude <iomanip>
using namespace std;
int main ()
return О;
#include <iostream>
~
о
::!:
#include <iomanip> Ф
t;
using паmеэрасе std; S
U
.+.
istream &prompt(istrearn &stream) u+
cin » hex;
cout « "Введите число в шестнадцатеричном формате: ";
return streami
int main ()
int ii
cin » prornpt » i;
cout « ii
return О;
в С++ файл открывается путем связывания его с потоком. KaI< вы знаете, су
ществуют ПОТОI<И трех ТI1110В: ввода, вывода и ввода-вывода. Чтобы открыть вход
НОЙ 110ТОК, необходимо объявить 110ТОКОnЫЙ объект типа ifstream. Для откры
тия ПЫХОI1НОГО потока IIУЖНО объявить поток класса ofstream. ПОТОК, который
предполагается ИСIlОЛI,:юuать для операций как ввода, так и вывода, должен быть
объявлен как объект класса fstream. Например, при выполнении следуюшего
фрагмента кода будет создан ВХОДIIОЙ поток, выходной и поток, позволяющий
выполнять операции в обоих направлениях.
ifstream in; // входной поток
ofstream out; // ВЫХОДНОЙ поток
fstream both; // лоток ввода-вывода
Создав ПОТО1<, его нужно связать с файлом. Это можно сделать с помощью
ФУNКЦИИ ореп ( ) , приче~ в каждом из трех потокооых классов есть своя фУНК
ция-член ор~п ( ) . Представим их прототипы.
void ifstream::open(const char *fllename,
ios::openmode mode = ios::in);
void ofstream::open(
const char *fllename,
ios::openmode mode = ios::out I ios::trunC)i
void fstream::open(
const char *fllелаmе,
ios::openmode тode = ios::in I ios::out)i g
Здесь элемент Лlепаmе оэrtачает имя файла, которое может включать специфи '":а
катор пути. Элемеrп определяет способ открытия файла. Он должен при '"
~'"
mode
нимать одно ИЛИ несколько ЗI·raчениЙ переч.исления openmode, которое опреде
лено в классе ios. Ниже приuедены значения это(·о l1ерсчисления. '"::;
о
ios: :арр ~
u
ios::ate :s:
u
ios: : binary +
+
ios: : in u
ios: : out
'ios: : trunc
Несколько значений пере'lИсления openmode можно объединять посредством
логического сложения (ИЛИ).
Включение значения ios: : арр в параметр тode обеспечит присоединение
к концу файла псехвыводимых данных. Это значение можно применять только
к фаЙла.\f, открытым для вывода данных. При открытии файла с использовани
ем значения ios: : а te поиск будет начинаться с конца файла. Несмотря на это,
операции ввода-вывода могут по'-прежнему вып.11Iiятьсяя по всему файлу. .
Значение ios: : in говорит о том, что данный файл ОТКРЫ8ается для ввода
данных, а значение ios: : out обеспеЧИDает открытие файла для их вывода.
ЗначеНllе ios: : binary ПОЭlюляет открыть файл в двоичном режиме. По
умолчанию все файлы открываются () TCJ(CTOBOM режиме. Как УIlОМИНалось выше,
n текстовом режиме могут ПрОИСХОДllТh некоторые преобразования символов
(например, послсдовательнос~ь, состоящая из символов возврата каретки и пере
хода на новую строку, может быть преобразоnзва в символ новой строки). Но при
ОТJ<PlхТИИ файла в двоичном режиме Jlикакого преобразования символов не Dbl-
полняется. Следует иметь 8 виду, что любой файл, содержащий форматирован
ный текст или еще нео6работанные данные, можно открыть [(ак в двоичном, Ta~
и в текстовом режиме. Единственное различие между этими режимами состоит в
npеобразовании (или нет) символов.
Использование значения ios: : truпс приводит к разрушению содержимого
файла, имя которого совпадает с параметром filename, а сам этот файл усекается
до Ilулевой длины. При создании 8ЫХОДНОГО потока типа ofstream любой суще
ствующий файл с ИМенем filename автоматически усекается до нулевоii длины.
При выполнении следующего фрагмента кода открывается обычный выход
ной файл.
548 МОДУЛЬ 11. С++-система ввода-вывода
................................................................................................................................
ofstream out;
out.open("TecT") ;
Поскольку параметр mode функции ореп () по умолчанию устанавливается
равным значению, соответствующему типу открываемого потока, впредыдушем
if (!mystream)
cout « "Не удается открыть фаЙл.\п";
// обработка ошибки
Прежде чем делать попытку получить доступ к файлу, следует всегда прове
рять результат вызова функции ореп ().
Можно также проверить факт успешного открытия файла с помощью функ
ции is_open (), которая является членом КЛi\ССОВ fstream, ifstream 11 01's-
tream. Вот ее прототип.
bool is_open () ;
Эта функция возвращает значение ИСТИНА. если поток связан с открытым
файлом, и ЛОЖЬ - в противном случае. Например; используя следующий код,
можно узнать, открыт ли в данный момент потоковый объект mystream.
if(!mystream.is_open(»
cout « "Файл не OTKpblT.\n";
11 .. -
Хотя вполне корректно использовать функцию ореп () для открытия файла,
в большинстве случаев это делается по-другому, поскольку классы ifstream,
ofstream и fstream включают КОНСТРУI<ТОРЫ,которые автоматически откры
mystream.close():
~
]j
ер
#include <iostream>
#include <fstream>
using namespace std;
int main ()
return О:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
char ch:
int i:
float f;
char str[80];
Следует иметь в виду. что при использовании Ol1epaTOpa "»" ДЛЯ считывания
данных И3 текстовых файлов происходит прео(JразоваJiие некоторых символов.
Например. "пробeJIЫlые" символы опускаются. Если необходимо предотвра
тить каЮfе бы то ни было преобразования символов, откройте файл в ДВШIЧНОМ
реЖlIме доступа. Кроме того, помните. что при использовании оператора "»"
ДЛ>I СЧI1ТЬШ~НИЯ строк ... ввод прекращается при обнаружении первого "пробель
ного" символа.
С++: PYI<OBOACTBO ДМ] начинающих 551
Ответ.
выtюдa? Если да. то имеет ЛJl СА'Ь'СlI ее nримеllеlluе в C++-IIРОграм.мох?
.. _
зуя операторы
1
"«" и "»"7'" :w ы___ ~. ...
ВАЖНОI
lIdJ::IеформаJИРQваИ~ВВОД";,ВЫВQД
данных в двоичном режиме
Несмотря на простоту чтения (11 записи) форматироuанных Tel(CTOnWX фаi:j
лов (подобных тем, которые ИСПОJ1l,:Юl3аЛI1СЬ 13 предыдущих примерах), они не
tinclude <iostream>
#include <fstream>
using namespace std;
char Chi
С++: руководство для начинающих 553
if(argc!=2) {
cout « "Применение: имя_про граммы <имя_Файла>\п";
return 1; g
ID
::iI
ID
in . close () ;
return О;
while(in.get(ch»
cout « ch;
Этот вариант также имеет право на существование, поскольку функция get ()
возвращает потоковый объект in, который при достижении КOIща файла примет
значение false.
В следующей программе для записи строки в файл используется функция
put ().
11 Использование функции put() для записи строки в файл.
#iлсludе <iostream>
554 Модуль 11. С++-система ввода-вывода
#inc1ude <fstream>
using namespace std;
int main ()
{
char *р = "Всем привет!\п";
retur.n О;
После выполнения этой программы файл test будет содержать строку Всем
привет! ,за которой будет следовать символ новой строки. Никакого пре06разо
вания СИМВО)ЮIJ здесь не происходит.
самое большое количество байтоп, которое может быТl) передано в. процессе лю
бой операции ввода-вывода.
о
При выполнении следующей программы сначала в файл записывается массив g
целых чисел, а затем он же считывается из файла. са
Ji
са
~
// Использование функций read() и write() .
• са
са
*inc1ude <iostream> О
:::1:
finclude <fstream> ...
Ф
U
using паmезрасе stdi s:
u,
+
int main () +
u
{
int п[5] = (1, 2, 3, 4, 5};
register int i;
out_c1ose ();
in. close () ;
return О;
streamsize gcount();
Функция gcoun t () возвращает количество символов, считанных n IIроцессе IIЫ
полнения последней операции ввода данных.
2. Каково назначение ФУНКЦИИ get ()? Каково назначение функции put ()?
3. С помощью какой функции можно считать блок данных?·
.- -
ввода-вывода
С++-система ввода-вывода содержит множество других полезных ФУНlшиЙ.
Рассмотрим некоторые из них.
1. Чтобы считать или записать двоичные данные, используйте спецификатор реж ItMa
ios: : binary.
2. Функция get () считывает символ, а ФУНКЦIIЯ put () записывает символ.
#include <iostream>
#include <fstream>
·
~~.~.....~9.~Y~~.~.~:..~~~~.~~.:~~.<?~~9.~9~~~~~?~9...................................... .
int main ()
char str[80];
return О;
ФУНКЦИЯ getline ( )
РаеС:\ЮТРIlМ еще одну ФУНКЦИЮ. которая по;~rюляет вводить даЮIЫС. Речь и;сет
о ФУШ(UI1If getline (). которая является членом каждuго lIйТОКОВОГО класса.
пr~JtнаЗllаченного для ввода IIНфuрмзции. Вот 1(;)(( выглядят ПРОТОТIIПЫ версий
этой функции.
bool eof () ;
Эта функция возвращает значение true при достижснии КОllца файла; в против
ном случае она возвращает значение false.
int peek () ;
Функция рее k () возвращает следующий СИМDОЛ потока, или значеIiне
EOF, если
ДОСПlrnу·г конец файла. С"итанный символ возвращается·в МЛадшем байте 311(1-
чения, возвращаемого функцией.
Последний символ, считанный из потока, моЖItО вернут!> n потOI<, IIСГЮЛЬЗУЯ
функцию putback (). Ее прототип выгющит так.
Функция flush ()
При выводе данных немедлеliIiОЙ 11Х записи на фJJзическое устройство, свя
занное с потоком, не происходит. Подлежащая ВЫВОДУ I1IнjЮР:'fflllIlЯ накаплива
ется во внутреннем буфере до тех 110\"1, пока этот буфер не заПОЛIIИТСЯ целиком.
И только тогда CI'O содержимое IIсреl1нсЫВ<t.ется на диск. О;l.Н;)I(О сvществует воз
можность немедлеНIiОЙ lIерсзашюп!> на диск храllИМУЮ в 6уфер(" ~шФО\"1~lаJtIfЮ.
560 МОДУЛЬ 11, С++-система ввода-вывода
не дожидаясь его заполнения. Для этого следует вызвать функцию ilush (). Ее
прототип имеет такой вид.
ostream &ilush();
К вызовам функции flush () следует I1риGсгать в случае, ссли прогрй.\1ма I1Р('Д
назначена для выполнения в неблагоприятных средах (для которых xapaKTep~ы,
например, частые отключения электричсства).
Последовательность действий
1. Создайте файл с ИМСIIСМ CompFiles. Cl=p.
2. Этот файл должен начшшться такими строками кода.
/'*
Проект 11.1
'*/
#include <iostream>
#include <fstream>
using namespace std;
register int i;
int numread;
С++: руководство ДЛ)\ начинающих 561
if(argc!=3)
cout « "Применение: compfi1es <filel> <file2>\n";
-g
111
J5
ер
g
return 1;
111
111
О
::!:
Обратите внимание на то, что имена сравниваемых файлов указываются в
командной строке. ~
s
()
. +
3. Добавьте код, который обеспечивает открытие файлов для выполнения
+
операций ввода данных в двоичном режиме. ;U
ifstream f1(argv[1], ios::in I ios::binary);
if (!П) (
cout « "Не удается открыть первый фаЙл.\п";
return 1;
do (
fl.read((char *) bufl, sizeof bufl);
f2.read((char *) buf2, sizeof buf2);
if(П.gсоuпt() != f2.gcount(»)
cout « "Файлы имеют различный размер.\п";
f1 . close () ;
f2 : close () ;
return О;
562 Модуль 11. С++-система ввода-вывода
f1. close () ;
f2. close () ;
return О;
*/ g
CD
2i
ер
*include <iostream>
*include <fstream>
using патеэрасе std;
~
CD
О
~
~
int main(int argc, char *argv[]) (J
s
~
+
register int i; u
int numread;
if (argc! =3) (
cout « "Применение : compfiles <file1> <file2>\n";
return 1;
do (
f1.read«char *) buf1, sizeof bufl);
f2.read«char *) buf2, sizeof buf2);
if (f1.gcount () != f2 .gcount () {
cout « "Файлы имеют различнЫЙ'размер.\п";
564 Модуль 11. С++-система ввода-вывода
.................................................................................................................................
f1 . close () :
f2 . close () :
return О:
fl . close () ;
f2 . close () ;
return О;
ВАЖНОI
Используемый здесь целочисленный тип off type (он определен 8 классе ios)
позволяет хранить самое большое допустимое значение, которое может иметь па
раметр offset. Тип seekdi r определен как перечисление, которое имеет следу
ющие значения.
Значение Описание
ios: : beg Начало файла
ios: : cur Текущая ПОЗJЩИЯ
_i_о_s_:_:_е_П_d
______К~о_н_е_ц~файл~______________________________________________
в С++-системе ввода-вывода предусмотрена возможность управления двумя
указателями, связанными с файлом. Эти так называемые get- и рut-указаmелu
определяют, в каком месте файла должна выполниться следующая операция вво
да и вывода соответственно. При каждом выполнении операции мода или выво
да соответствующий указатель автоматичеСlСИ изменяется. Используя функции
seekg () и seekp () ,можно "перемещать" нужный указатель и получать тем са
мым доступ к файлу в ПРОИЗВОЛЫlOм порядке.
Функция see~g () персмещает ТСl<ущиi:"1 gеt-указатель соответствующего
файла на offset байт относительно позиции, задашюй параметром оrigiл.
Функция seekp () перемещает текущий рut-указатель соответствующего файла
на offset байт относительно позиции, заданной параметром оz·igiл.
В общем случае произвольный доступ для операций ввода-вывода должен вы
полняться только для файлов, открытых в двоичном режиме. Преобразования
символов, происходящие в текстовых файлах, могут привести к тому, что запра
шиваемая позиция файла не будет соответствовать его реальному содержимому.
В следующей программе демонстрируется использование функции seekp ().
Она позполяет задать имя файла 8 командной строке, а за ним - конкретный
байт, который нужно в нем изменить. Программа затем записьшает в указанную
позицию символ "х". Обратите внимание на то, что обрабатываемый файл дол
жен быть открыт для выполнения операций чтения-записи.
~.....~~~Y~~..~.~:..~~~.~~.~~.~~~.<?~~?~~~~~~~?~? ......................................
#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;
return 1;
out.put ('Х');
out.close();
return О;
#include <iostream>
С++: PYI<OBOACTBO ДМ) начинающих 567
#inc1ude <fstream>
#inc1ude <cstd1ib>
using namespace std;
char ch;
if(аrgс!=З)
in.seekg(atoi(argv[2]), ios::beg);
whi1e(in.get(ch»
cout « ch;
return О;
~FЫ~
зиции в файле?· Emu.,. . . .____. .___..
_ _. . . .М8_ _~_ _. . . . === ____..______..______..___...
~
об операциях ввода-вывода
С++-систсма ввода-въшода поддерживает информацию о результатах выпол
нения каждой операции Iшода-вьшода. Текущее состояние потока ввода-вывода
ОПИСblвается в объекте ПIlJa iostate, который представляет собой перечисле
иие (оно определено n КЛ(1ссе ios), включаюшес.слсдующие члены .
................ _ - -.-, ... ... .. -
~---_ ~ ., ... " .. ,,----,-,-,---_._-,-~-----_ ..................... _- ~- ... _._-_.--
Имя Значение
.~~~-------------
ios: : goodbi t ОШl\БКJI отсутствуют
ios: : eofbi t 1 при обнаружении конца файла; О в пропшном случае
ios: : failbi t 1 при [ю:mикновении испраВIJМОЙ ошибки ввода-вывода; О в IlРО
тиВlЮМ случае
iostate rdstate();
Функция rdstate () возвращает текущий статус флагоп ошибок. Нетрудно
догадаТhСЯ, что, судя по lIриведешlOМУ выше списку флагов, фуикция rdsta-
te () возвратит значение goodbi t при отсутствии каких бы то ШI было ошибок.
В противном случае она возвращает соответствующий флаг ошибки.
~
bool fail () ;
bool good(); m
m
Функция eof () рассматривалась выше. Функция bad () возвращает значение О
:!
Ф
ИСТИНА, если в результате выполнения операции ввода-вывода был установ >-
U
лен флаг badbi t. ФУНКЦИЯ fail () возвращает З~laченне ИСТИНА, если в ре :s:
u
зультате выполнения операции ввода-вывода был устаlJовлен флаг f а i lb i t. +
+
Функция good () возвращает значение ИСТИНА, если при выполнении опера u
ции ввода-вывода ошибок не произошло. В противном случае эти функции воз
вращают значение ЛОЖЬ.
Если при выполнении операции ввода-вывода произошла ошибка, то, возмож
но, прежде чем продолжаТh выполнение программы, имеет смысл сбросить флаги
ошибок. Для этого используйте ФУНКЦИЮ clear () (член класса ios), ПРОТОТИП
которой выглядит так.
11. Предположим, что файл связан с ВХОДIIЫМ потоком strm. Покажите. как
считать все содержимое файла?_
ВАЖНОI
Itl' Обра6Q1КQ ИСКАЮNитеАhNhlX
ситуаций
Исключитель1/.ая ситуация (или исключение) - это ошибка, которая во:'ни
кает во время выполнения программы. Используя С++-подсистему обработки
исключительных ситуаций, с такими ошибками [)полие можно справляться. При
их ВОЗlшюювении во время работы програМ:\1Ы автоматически вызывается так
называемый обработчик UСКЛЮ'lеllиЙ. В этом-то и состоит принципиальное пре
имущества системы обработки ИСl<Лючений, поскольку именно она Uотвечае1'" за
код обработки ошибок, который прежде приходилось "вручную'" вводить в и без
того объемные программы.
try {
//
/I
trу-блок (блок кода,
на налич.ие ошибок)
-
:ф
:3:
.ф
~ о
.....
: ... J
:Ф
// саtсh-блок (обрабОТЧик исключения типа typel) ~~
Ji
catch (type2 arg) :I:
О
.<
// саtсh-блок (обработчик исключения типа type2) :\0
:0
:3
catch (tуреЗ arg)
// саtсh-блок (обработчик исключения типа type3)
}
//
catch (typeN arg)
// саtсh-блок ( обработчик исключ.ения типа typeN)
Блок try должен содержать код, КОТОРЫЙ, по вашему мнению, следует про
верять на предмет возникновения ошибок. Этот блок может включать лишь
несколько ИНСТРУIЩИЙ некоторой функции либо охватывать весь код функции
main () (в этом случае, по сути, "под колпаком" системы обработки исключений
будет находиться вся программа).
После "выброса" ИСКЛIO'Jение перехватывается соответствующей инструкци
ей catch, которая выполняет его обработку. С одним trу-блоком может быть
связана не одна, а неСIЮJJЬКО са tch-инструкциЙ. Какая именно из них будет ВЫ
полнена, определяется типом ·исключения. Другими словами, будет вьшолне
на та саtсh-инструкция, тип исключения котороЙ (т.е. тип данных, заданный
в са tсh-инструкции) совпадает с типом сгенерированного исключения (а все
остальные будут проигнорированы). После перехвата исключения параметр а rg
примет его значение. Таким путем могут перехватываться данные любого типа,
включая объекты классов, созданных программистом.
Общий формат инструкции throw ВЫГЛЯДlIТ так:
throw exception;
Здесь с помощью элемента exception задается исключение, сгенерированное
инструкцией throw. Если это исключение подлежит перехвату. то инструкция
throw должна быть выполнена либо в самом блоке try, либо в любой вызывае
мой из него функции (т.е. прямо или косвенно)_
574 Модуль 12; ИСI<лючениS1, шаблоны и I<ое-что еще
#include <iostream>
using namespace std;
int main ()
{
cout « "НАЧАЛО\п";
cout « "КОНЕЦ";
return О;
НАЧАЛО
В trу-блоке
Рассмотрим внимательно код этой программы. как видите, здесь try-БЛОI{ со
держит три инструкции, а инструкция ca.tch (int i) предназначена для обра
ботки иск.лючения целочисленного типа. В этом t rу-блоке выполняются только
две из трех инструкций: cout и throw. После генерирования иск.лючения управ
ление передается саtсh-выражению, при этом выполнение trу-блока прекра-
С++: PYI<OBOACTBO ДЛfl начинающих 575
"
щается. Необходимо нонимать, что catch-ИНСТрукция не вызывается, а просто
с 11ее продолжается ВЫПОЛllение программы после "выброса" ИСК.lllоченЮL (Стек
программы автоматически настраивается в соответствии с ('оздавшейся ситуаци
ей.) Поэтому соut-инструкция, СJlедующая после thrО~I-1\J-IСТРУКЦИИ, никогда
не вы полнится.
-~
ф
...;r
о
ф
Обычно при выполнении саtсh-блока делает с}! 110flЫТЮl исправить ()Шllбку о
#include <iostream>
using namespace std;
int main ()
{
cout « "НАЧАЛО\п";
cout « "КОНЕЦ";
return О;
576 Модуль 12. ИСl<лючени~, шаблоны и I<ое-что еще
В trу-блоке
*/
#include <iostream>
using namespace stdi
int main ()
cout « "НАЧАЛО\П"i
cout « i « "\п";
~
cout « "КОНЕЦ";
ф
....о
'J
сь
return О;
~
:s:
:о
Эта программа генерирует такие результаты. :с
НАЧАЛО ~
о
В trу-блоке 3
r:i.
В функции Xtest(), значение test равно: О s:
:с
В функции Xtest(), значение test равно: 1 ф
#include <iostream>
using namespace std;
catch(int i)
cout « "Перехват! Исключение ~: " « i « '\п';
int main ()
578 Модуль 12. ИСl<лючения, шаблоны и I<ое-что еще
cout « "НАЧАЛО\п";
Xhandler(l) ;
Xhandler(2);
Xhandler(O) ;
Xhandler(3);
cout « "КОНЕЦ";
return О;
int rnain ()
cout « "НАЧАЛО\п";
Xhandler(l);
Xhandler(2);
Xhandler(O);
Xhandler(3) ;
cout « "КОНЕЦ";
580 Модуль 12. ИСl<лючения, шаблоны и I<ое-что еще
return О;
НАЧАЛО
Перехват! Исключение ~: 3
КОНЕЦ
#include <iostream>
using namespace std;
class В {
};
class D: public В {
С++: руководство ДЛ~ начинающих 581
}i
int main ()
D derivedi
-
try (
throw derivedi
catch(D d)
cout « "Этот перехват никогда не произоЙдет.\п";
return О;
catch( ... )
11 Обработка всех исключений.
#include <iostream>
using паmеsрасе std;
int main ()
cout « "НАЧ!U10\п";
Xhandler(O);
Xhandler(1);
-Xhandler (2);
cout « "КОНЕЦ";
rеturл О:
НАЧАЛО
Перехват!
Перехват!
Перехват!
КОНЕЦ
С++: PYI<OBOACTBO дl\9I начинающих 583
1/ ...
Здесь элемент список_имен_ ТИПОВ должен включать толr,ко те имена типов дан
НЫХ, которые разрешается генеРИРОllать функции (элементы списка разделяются за
пятыми). Генерирование исключения любого друтого типа приведет к аварийному
окончанию програММhl. Если нужно, чтобы ФУНЮЩЯ вообще не МОГ.lа генерировать
исключения, используйте в качестве этого элемента пустой список.
#include <iostream>
using namespace
, stdi
int main ()
cout « "НАЧАЛО\п";
try{
Xhandler(O); /1 Попробуйте также передать
/1 функции Xhandler() аргументы 1 и 2.
catch (int i) {
cout « "Перехват iпt-исключения.\п";
catch (char с) (
cout « "Перехват сhаr-исключения.\п";
catch(double d) {
cout « "Перехват dоublе-исключения.\п";
cout « "КОНЕЦ";
return О;
С++: руководство ДМI начинающих 585
#include <iostream>
using namespace std;
void Xhandler ()
{
try {
throw "Привет"; 11 генерирует исключение типа char *
int main ()
cout « "НАЧАЛО\п";
try{
Xhandler () ;
catch (char *)
cout « "Перехват исключения з функции main () . \п";
cout « "КОНЕЦ";
return О;
НАЧАЛО
Шаблоны
Шаблон - это одно из самых сложных и мощиых средст13 8 С++. Он не пошел
в исходную спецификацию С++, 11 лишь несколько лет нао1М стал неотъемлемоj:,!
частью программирования на С++. Шаблоны позволяют достичь одну из самых
трудных целей 8 програММИРОDаllИИ - создать многократно используеМЫII !<од.
Используя шаблоны, можно создавать обобщеtlllые функции Jf классы.
В обобщенной функции (или классе) обрабатываемый ею (им) тип данных зада
ется как параметр. Таким образом, одну функцию или класс можно использовать
для раэных типов даюlых' не предоставляя явным обраЗОI\'1 конкретные версии
для каждого типа данных.
ВАЖНО\
Здесь элемент Ttype представляет собой "заполнитель" для типа данных, об
рабатываемых функцией. Это имя может быть использовано в теле функции.
Но оно означает всего лишь "заглушку", вместо которой компилятор автоматиче
ски подставит реaJlЫIЫЙ тип данных при создаНlI1I конкретной версии функции.
И хотя для задания обобщеюlОГО типа в tеи.рlаtе-объяплснии по традиции
применяется ключевое слово class, можно также использовать ключевое слово
typename.
В следующем при мере создается обобщенная фУI-IIЩИЯ, которая меняет места
ми значения двух llеремеШIЫХ, используемых при ее ВЫЗОВС. Поскольку общий
процесс обмена значениями переМСIII-lЫХ НС зависит от их типа, 011 является пре
красным кандидатом для создания обобщенной функции.
// Пример шаблонной функции.
#include <iostream>
using namespace std;
Х temp;
С++: PYI<OBOACTBO ДЛt;l начинающих 589
temp = а;
а Ь;
Ь = temp;
int main ()
cout «
,
"После перестановки i, j: " « i «
« j « ' \п' ;
cout « "После « « ,
перестановки х, у:
" х
« у « ' \п';
cout « "После перестановки а, Ь: " « а « ,
« Ь « ' \п' ;
return О;
Исходные значения i, j: 10 20
Исходные значения х, у: 10.1 23.3
590 МОДУЛЬ 12. ИСl<лючени~, шаБЛОНbI
................................................................ и I<ое-что еще
...............................................................
~ .
Исходные значения а, Ь: х z
После перестанов~и i, j: 20 10
После перестанов~и х, у: 23.3 10.1
После перес~ансв~и а, Ь: z х
#include <iostream>
using namespace std;
:Ф
::j
;Ф
int main () :0
:5=
:Ф
myfunc(lO, "Привет"); :0
:~
:s;
Ji
myfunc(0.23, lOL); :r
:0
:<
:\0
:0
return О; ;3
:..;
::s;
::r
:Ф
в этом примере при выполнении функции main () , когда КОМПИЛЯТОР гене
рирует конкретные экземпляры функции myfunc (). заполнители типов typel
и type2 заменяются сначала парой типов данных int и char ... а затем парой
1~
:u
;S
double и long соответственно.
temp = а;
а = Ь;
Ь = temp;
ccut « "Выnолняе;гся шаблонная функция swapargs() .\л";
temp = а;
а = Ь;
Ь = temp;
cout « "Это iпt-специализация ФУНКЦИИ swapargs() .\п";
int main ()
// фуккцик svaparqs().
swapargs(x, у) ; // Вызывается Обобщенная функция
// swapargs() .
swapargs(a, Ь) ; // Вызывается
, обобщенная функция
// swapargs() .
« Ь « '\п' ;
return О;
С++: РУI<ОВОДСТВО AMl начинающих 593
Исходные i, j: 10 20
значения
После перестановки i, j : 20 10
После перестановки х, у: 23.3 10.1
После перестановки а, Ь: z х
temp == а;
а = Ь;
Ь = temp;
cout « "Это iпt-специализация функции swapargs.\n";
• Обоб""еиwbl.& КАассы
Помимо обобщенных функций, можно также определить обобщенный класс.
Для этого создается класс, в котором определяются все используемые им Cl.Лго
ритмы; при этом реальный тип обрабатываемых в нем данных будет задан как
параметр при создании объсктов этого класса.
Обобщенныс классы особенно полезны в случае, когда в них используется
ЛOl'ика, которую можно обобщить. Например, алгоритмы, которые поддержи
вают функционирование очереди целочисленных значений, также подходят и
для очереди символов. Механизм, который обеспечивает поддержку связно
го списка почтовых адресов, также ГОДlпся для поддержки связного списка,
Здесь элемент Ttype представляет собой "заполнитель" для имени типа, кото
рый будет задан при реализации класоа. При необходимости МОЖНО определить
несколько обобщенных типов данных, используя список элементов, разделенных
запятыми.
Здесь элемент тип означает имя типа данных, которые будут обрабатываться :ж
зсмпляром обобщенного класса. Функции-члены обобщенного класса автомати-
С++: РУКОВОДСТВО ДЛЯ начинающих 595
*include <iostrearn>
using narnespace std;
public:
MyClass(T а, Т ь) (
х = а;
у ; Ь;
intrnain()
return О;
*/
ffinclude <iostream>
using namespace std;
int main ()
return О;
10 0.23
Х Это тест.
iinclude <iostream>
using namespace std;
public:
MyClass(T а) {
cout « "В теле оБОБщенного класса MyClass.\n"i
Х = д;
т getx() { return х; }
};
int Х;
public:
MyClass(int а)
cout « "В теле специализации MyClass<int>.\n"i
х = а * а;
int main ()
MyClass<double> d(lO.l);
cout « "double: " « d.getx() « "\п\п";
return О;
...
о
7
1. Какое ключевое слово используется ДЛЯ объявления обобщенной функ Ф
О
ции J1ЛJ1 I(ласса? х
S
2. Можно ли явным образо~ перегружать обобщенную функцию? 2i
:J:
3. Будут ли в обобщенном классе все его функции-члены обобщенными ав ~
ID
томатически?· О
. . . . . .______. . . . . .
mв . . . . . .____. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . 3
с;;
s
:J:
Ф
7
Проект 12.1. r.So.ctдa~_
~
С\, 1: 1, " • It • С - "'~\, 1'11
очереди U
:s:
GenericQ. срр В ПРОСКТС 8.2 мы создали класс Оиеие, предназначенный
ДЛЯ организации очереди символов. В этом проекте мы
преnбразуем его в обобщенный класс. Класс Оиеие - прекрасный кандидат
для преобразования в обобщснный класс, поскольку его логика не зависит от
типа данных, которыми 011 оперирует. другими словами, один и тот же меха
низм в этом случае можно использовать для организации очереди перемен
ных любого Тlша (например, Ilелых чисел, значений с плавающей точкой или
даже объеКТОR классов, создаваемых программистом). Определив обобщен
ный класс Queue, мы сможем затем использовать его везде, где нам нужно
организовать очередь объектов.
Последовательность действий
1. Скопируйте класс Queue из проекта 8.2 в файл с именем GenericQ. срр.
2. Прео6разуйте прсжнее объявление класса Queue в шаблонное.
putlOC++i
q[putloc] = data;
getloc++;
return q[getloc);
/*
Проект 12.1
...:r
о
Ф
I
size len;
putloc = getloc О;
putloc++;
q[putloc] data;
if(getloc == putloc) I
cout « " -- Очередь пуста.\п";
return О;
getloc++;
return q[getloc];
};
iQa.put(l);
iQa .put (2) ;
iQa.put(3);
iQb.put(10);
iQb.put(20)i
iQb.put(30);
dQa.put(l.Ol);
dQa.put(2.02);
С++: PYI(OBOACTBO ДМ! начинающих 603
dQa.put(3.03);
dQb.put(10.01)i
dQb.put (20.02) ;
dQЬ.рut(ЗО.О3);
-
: (1)
:~
;(1)
:0
:~
:Ф
:0
:~
cout « "Содержимое dоublе-очереди dQa: "; :s
:J5
for(int i=Oi i < 3; i++) ::1:
:0
cout « dQa.get() « " "; :<
:10
:0
сои t « end1; :3
~~
cout « "Содержимое dоиblе-очереди dQb: "; ~ffi
for(int i=O; i < 3; i++) ~~
:$
cout « dQb.get () « " "; :(j
cout « endl; :S
return О;
Содержимое iпt-очереди 1 2 3
iQa:
Содержимое iпt-очереди iQb: 10 20 30
содержимое dоublе-очереди dQa: 1.01 2.02 3.03
Содержимое dоиblе-очереди dQb: 10.01 20.02 30.03
~ ..
lИ.LДиwомиwес"ое расаР8Д8А81::1148
памяти
Для С++-программы существует два основных способа хранения информа
шш в ппераТИШlO1i па~I.НТИ компьютера. Первый состоит в использовании пере-
604 Модуль 12. Исключени~, шаблоны и I(ое-что еще
Нижняя
oбnасть
адресов
3"
ф
о
5=
ф
о
ся. Следовательно, несмотря на то, что динамическое распределение памяти (по :.:
сравнению с фиксированным) обеспечивает большую гибкость, оно и в этом слу
:s:
J5
:J:
чае имеет свои пределы. О
.<
Язык С++ содержит два оператора, new и delete, которые выполняют функ :10
:0
ции по выделению и освобождению памяти. Оператор new позволяет динами :3
ti
чески выделить область памяти, а оператор delete освобождает динаМИLJескую :s:
:J:
Ф
память, ранее выделенную оператором new. Приводим их общий формат.
#include <iostream>
#inc1ude <new>
using namespace std;
int main ()
{
int *р;
try
р new int; // Выдел.ем паккть Дn. int-зиаченик.
catch (bad_a110c ха) { // Проверка факта иеудачного
// выnоnнеИИR оператора new.
cout « "Не удалось вьщелить память.\п";
return 1;
*р 100;
return О;
сь
Спросим у опыноrоo проrраммиста О
~
s:
·21
Вопрос. Мне nРlIXD(}шrос • • идет. C++-nроzpIlJlUlbl, • которых с целью д.lНllJIfuче :I:
#include <iostream>
608 Модуль 12. ИСI<лючения, шаблоны и I<ое-что еще
tinclude <new>
using narnespace std;
int rnain ()
{
int *р;
try
р new int (87); // Инициanизируек П~. чиcnои 87.
catch (bad_alloc ха) {
cout « "Не удалось выделить память.\п";
return 1;
delete р;
return О:
#include <iostream>
С++: PYI(OBOACTBO д,лfl начинающих 609
#include <new>
using namespace std;
int main ()
int *р, i;
try
р new int [10]; // Вwдen_eм П~ дn_ 10-эnеме.,.о~о
// t\еJlОЧИc.n.ано~ каССИ8а.
catch (bad_alloc ха)
cout « "Не удалось выделить память.\п";
return 1;
return О;
iincludc <iostream>
4tinclude <new>
using namespace stdi
class Rectangle
int width;
int heighti
public:
Rectangle (int w, int h) (
width = W;
height = h;
cout « "Создание прямоугольника шириной " « width «
" и высотой" « height « ".\п";
~Rectangle()
iлt area () {
return width * height;
} ;
int main ()
Rectangle *р;
try (
р = new Rectangle(10, 8); // ~enRеи Пам8~. ~ об~к~а
// ~ипа Rectanqle. Э~о вызо.е~
// конструктор xnасса Rectanqle.
С++: PYI(OBOACTBO Дl\9. начинающих 611
...:r
о
сь
cout « "ПЛощадь прямоугольника равна" « p->area() О
~
« ".\п"; :s:
:о
:r
о
cout « "\п"; ~
о
3
Ii
delete р; // Освобождение пами~и, занимаемой объек~ом. :s:
// Это вызоае~ дeCТPYK~OP xnасса Rectanqle. ffi
return О; §
(J
s:
Результаты выполнения этой программЪ1 таковы.
#include <iostream>
#include <new>
using namespace std;
612 Модуль 12. Исключения, шаблоны и I<ое-что еще
class Rectangle
int width:
int heighti
public:
Rectangle () // Добавnяем конструктор беs паракетров.
width = height = О:
cout « "Создание прямоугольника ШИРИНОЙ " « width «
" и ВЫСОТОЙ" « height « ".\п";
-Rectangle ()
cout « "Разрушение прямоугольника ШИРИНОЙ " « width «
" и ВЫСОТОЙ" « height « ".\п";
int area () {
return width * heighti
};
int main ()
Rectangle *р;
try
р new Rectangle [3]:
С++: PYI<OBOACтeO для начинающих 613
- ~
(1)
о
!т
сь
cout « "\п"; о
:.о:
:s;
11
р[О).sеt(З,4); :I:
·0
р[l) .set (10, 8); :<
:10
:0
р (2) . set (5, б); :3
:ri
::s;
:х
for(int i=O; i < 3; i++) :(1)
\ ()~
cout « "Площадь равна" « p[i] .area() « endl;
:
cout « "\п";
:S
rеturл О;
ПЛощадь равна 12
Площадь равна 80
Площадь равна ЗА
_ ЛNШI1?·
:ан
ВАЖНIi
Itlt Dростраыаво I4МАИ
Пространства имен были кратко описаны в модуле 1. Здесь мы рассмотрим
,а более детально. ОНИ ПОЗDОЛЯЮТ локализовать имена идентификаторов, чтобы
избежать конфликтных ситуаций с н·ими. В С++-среде программирования ис
пользуется огромное количество имен переменных, функций и имен классов. До
введения Ilространств ИМСJ-IlIсе эти имена конкурировали за память в глобальном
I1ространстве имен, что и было причиной DОЗlfИкновения многих конфликтов.
Например, если бы в вашеi{ программе была определена функция toupper {J,
она могла бы (в зависимости от списка параметров) переопределить стандарт
ную библиотечную функцию toupper () ,поскольку оба имени должны были бы
храниться R глобальном пространстве имен. Конфликты с именами возникали
также при использовании в одной программе нескольких библиотек СТОрОJ::iНИХ
ПРОИЗ80дителеЙ. В этом случае имя, определенное в ОДJЮЙ библиотеке, конф.1RК
товало с таким же именем из другой библиотеки. Подобная ситуация ос06енко
неприятна IlрИ использоваllИИ одноименных классов. Например, если в вашей
программе определен класс Videot-1ode. и в библиотеке, используемой вашей
I1рогrаммоЙ. определен класс с таким же имене~, конфликта не избежать.
Для решения ОШlсанной проблемы было создано ключевое слово namespace.
IIОСКОЛhКУ 0110 локализует видимость объявленных в нем имен, это значит, что
ПРОСJрансТlЮ имен позволяет использовать одно и то же имя в различных IЮН-
текстах, не вызывая при этом КОlJфликта имен. Возможно, больше всего от но
вовведения "повезло" С++-бнблиотеке стандартных функций. До появления
ключевого слова namespace вся С++-библиотека была определена в гл06алыюм
ПРОС1"ранстве имен (которое было, конечно же, единствеIlНЫМ). С наступлением
паmеsрасе-"эры" С++-библиотека определяется D собственном простраlJСТВС
-
:ф
:~
:Ф
: 0
• >-
:7
:Ф
имен, именуемом std, которое значительно понизило вероятность В031111101O :0
:~
:s:
вения конфликтов имен. В своей Ilрограмме програММJlСТ оолен создавать соб J5
1:
ственные пространства имен, чтобы локализовать видимость тех Ifмен, которые,
:~
по его мнению, могут стать причиной конфликта. Это особеНIIО важно, если вы :10
:0
занимаетесь созданием библиотек классов или функций. :3
ri
:s
1:
Ф
Понятие пространства имен
КlIючевое слово namespace позволяет программисту "отделиться" от гло
§
U
бального пространства имен llYTeM создания некоторой декларативной области. :S
По сути, пространство имен определяет область видимости. ОбщиН формат за
дания пространства имен таков.
namespace пате (
// объявления
class counter
int c9unt;
public:
counter(int п)
616 Модуль 12. ИСl<лючени~, шаблоны и I(ое-чтоеще
void reset(int п)
if(n <= upperbound) count п;
int run ()
if(count > lowerbound) return count--i
else return lowerbound;
} i
CounterNameSpace::upperbound = 10;
Чтобы объявить объект типа counter вне пространства имен Counter-
NameSpace, используйте инструкцию, подобную следующей.
CounterNameSpace::counter оЬ;
#include <iostream>
using namespace std;
namespace CounterNameSpace
int upperbound;
int lowerbound:
class counter
int count;
public:
counter (int п) (
if(n <= upperbound) count п;
else count = upperbound;
void reset(int п)
if(n <= upperbound) count п;
int run () (
if(count > lowerboupd) return count--;
else return lowerbound;
};
int main ()
// опера'1'ора
// рaspешеRИR ХОВ'1'еХС'1'&.
int i;
do (
i = оЫ. run () ;
cout « i « " ";
while(i > CounterNameSpace::lowerbound);
cout « endl;
CounterNameSpace::counter оЬ2(20);
do (
i = оЬ2. run () ;
cout « i « " ";
while(i > CounterNameSpace::lowerbound);
cout « endl;
ob2.reset(lOO);
CounterNameSpace::lowerbound 90;
do (
i = ob2.run();
cout « i « " ";
while(i > CounterNarneSpace::low~rbound);
return О;
namespace NS {
int i;
С++: руководство ANi) начинающих 619
ф
// ::f
ф
namespace NS ( 5=
сь
int j; о
~
:s:
:iS
1:
Здесь пространство имен NS разделено на две части. Однако содержимое каждой
2
\о
части относится к ОДllаму и тому же пространству имен NS. а
Любое пространство имен должно быТI> объявлено вне всех OCTMbllblX обла 3
ti
стей видимости. Это означает, что. нельзя объявлять пространства имен, которые :s:
1:
Ф
локализованы, например, в рамках функции. При этом одно пространство. ю.IСН J
Q
может быть вложено в другое. <
:.:
U
S
ИНСТРУКЦИЯ usinq
Если программа включает множества ссылок на члены некатарого пространства
имен, то нетрудно представить, что ие06ХОДИМОСTh указываТ1. имя ,утога простран
ства имен при каждом к нему обращеlШИ, ачень Cl<apa угомит вас. Эту Jlроблему па
зволяет решить инструкция us i ng, которая применяется в двух форматах.
using паmе::член;
#include <iostream>
using паmезрасе std;
паmезрасе CounterNameSpace
int upperbound;
int lowerbound;
class counter
int count;
public:
counter (int п) (
if(n <= upperbound) count п;
void reset(int п) (
if(n <= upperbound) count п;
int run () {
if(count> lowerbound) return count--;
else return lowerbound;
};
int main ()
/ / прос~ааСТJIа икеа.
// Теперь для установки значения переменной upperbound
// не нужно указывать пространство имен. ~
ф
upperbound = 100;
...
о
;г
ф
// Но при обращении к переменной lowerbound и другим о
~
CounterNameSpace::lowerbound = О; ~
О
CounterNameSpace::counter оЫ(10): 3
int i; ti
s
:J:
Ф
do
i = оЫ. run ( ) :
§
U
s:
cout « i « " ":
while(i > CounterNameSpace::lowerbound):
cout « endl:
counter оЬ2(20):
do
i = ob2.run():
cout « i « " ";
while(i> lowerbound)i
cout « endl:
ob2.reset(100);
lowerbound = 90;
do (
i = оЬ2. run () :
cout « i « " ";
while(i > lowerbound):
return О:
622 Модуль 12. ИСl<лючения, шаблоны и I<ое-что еще
namespace {
// объявления
Пространство имен s td
Стандарт С + + определяет всю свою библиотеку в собственном пространстве
имен, именуемом std. Именно поэтому большинство программ в этой книге
включает следующую инструкцию:
:s:
лифицировать каждое имя в отдельности. Ji
1:
О
Q
создается? <
~
u
2. Являются ли пространства имен аддитивными? S
\
при первом создании объекта инициализируются нулевыми значениями, если не
задано других значений инициализации.
При объявлении статического члена данных в классе программист его не
определяет. Вместо этого он должен обеспечить его глобальное оnределеuШ! вне
такого l<ласса. Это реализуется путем повторного объявления этой статической
переменной с помощью оператора разрешения области видимости, который 110-
зволяет идентифицировать, к какому классу она принадлежит. И только ТОj'да
для этой статической переменной будет выделена память.
Рассмотрим пример использования stаtiс-члена класса.
#include <iostream>
using namespace std;
class ShareVar {
static int num; // ~ ОБЪ«ВЛRем stаtiс-чnев д&квwx. Ero
// будут совместно использовать все
// эхземпnRpЫ xnасс& ShareVar.
public:
void setnum (int i) пит = i; };
void shownum() ( cout « num « " ";
};
int main ()
ShareVar а, Ь;
return О;
С++: руководство для начинающих 625
................................................................................................................................
О О 10 10
в этой программе обратите внимание на то, Ч'го статический uеЛО'lисленный
член пит и объявлен в классе ShareVar, и определен в Kat/ecTBe глобальной пе
ременной. Как было заявлено выше, необходимость такого двойного объявления
вызвана тем, что при объявлении члена num в классе ShareVar память для него
не выделяется. С++ инициализирует переменную пит значением О, поскольку
никакой другой инициализации в ПРOl-рамме нет. Поэтому в результате двух пер
вых вызовов функции shownum () для объектов а и Ь отображается значение О.
Затем для объекта а член пит устанавливается равным 10, после чего для обоих
объектов а и Ь с помощью функции shownum () выводится на экран значение
члена пит. Но так как существует только одна копия переменной пит, разделяе
мая объектами а и Ь, при вызове функции shownum () для обоих объектов будет
пьшедено одно н то же значение 10.
Если stаtiс-переменная является открытой (т.е. риbliс-переменной),
к ней можно обращаться напрямую через имя ее класса, без ССЬUlки на какой
либо конкретный объект. (Безусловно, обращаться можно также 11 через имя объ
екта.) Рассмотрим пример.
#include <iostream>
using namespace std;
class Test {
public:
static int пит;
void shownum() { cout « пит « endli }
};
int main ()
Test а, Ь;
return О:
Test::num = 100:
Вполне допустимо устанавливать значение открытой stаtiс-переменной и
через объект:
а.пит = 200;
Любой из этих способов установки псреМСНllо!1 совершенно легален.
#include <iostream>
using namespace std:
С++: PYI<OBOACTBO ДЛЯ начинающих 627
class Test (
static int count;
public': i
~ I
Ф
Test () О
~
count++; :s:
::а
cout « "Создание o~ъeKTa " « :I:
о
count « endli ~
о
3
r:i.
:s:
:I:
-Test () ф
cout « "Разрушение
count «
объекта
endli
" «
~
:s:L'
count--i
int Test::count;
int main ()
Test а, Ь, с;
delete р;
r
628 МОДУЛЬ 12. ИСI<лючеНИ>l, шаблоны и кое-что еще
return О;
Создание объекта 1
Создание объекта 2
Создание объекта 3
В данный момент существует 3 объекта.
Создание объекта 4
После создания объекта класса Test насчитывается 4 объекта.
Разрушение объекта 4
После удаления объекта насчит~вается 3 объекта.
Разрушение объекта 3
Разрушение объекта 2
Разрушение объекта 1
В Э'Рой программе обратите внимание на то, как вызывается статическая фУНК
ция numObj ects () . в первых двух ее вызовах испuльзуется имя класса, членом
которого она является.
Test: :numObjects()
В третьем вызuве используется обычный СИlП<lКСИС. т.е. имя объекта и опера
тор "точка".
а. numObjects ()
ВАЖНОI
Кll •ЛИИQмическая.идеwт.ифико,
• •
~WI
типов (RПI)
с динамической идентификацией типов ВЫ. IЮЗМОЖНО, незнакомы, посколы<у
это средство отсутствует в тшшх tlеПОЛИМОРфНlrIХ языка..х. как С или традишroн-
С++: руководство д!\)! начинающих 629
#include <iostrearn>
#include <typeinfo>
630 Модуль 12. Исключения, шаблоны и кое-что еще
class MyClass
//
}:
int rnain ()
int i, j;
float f;
MyClass оЬ;
if(typeid(i) == typeid(j»
cout « "Типы переменных i и j одинаКОБЫ.\П":
return О;
#include <iostream>
#include <typeinfo>
using паmезрасе std;
class Base
virtual void f() (); 1/ делаем класс Вазе полиморфным
1/ ...
);
\
class Derived2: public Вазе {
11
\
};
int main ()
Derived2 оЬ2;
р = &ЬаsеоЬ;
cout « "Переменная р указывает на объект типа ";
cout « typeid(*p) .пате() « endl;
р &obli
=
cout « "Переменная р указывает на объект типа ";
cout « typeid(*p) .пате(} « endl;
632 Модуль 12. ИСJ<лючениSl, шаблоны и I<ое-что еще
р = &оЬ2;
cout « "Переменная р укаЗbIБает на объект типа ";
cout « typeid(*p) .паmе() « endl;
return О;
typeid (имя_типа)
Например, следующая инструкция совершенно допустима.
dynarnic_cast<type> (expr)
Здесь элемент type означает новый тип, который яuляется целью выполнения
этой операции, а элемент expr - выражение, при водимое к этому новому типу.
Тип type должен быть представлен указателем или ссылкой, а выражение
expr
должно приводиться К указателю или ссылке. Таким образом. оператор dynami с_
cast можно использовать для преобразования указателя одного типа в указа
тель другого или ссылки одного типа в ссылку другого.
Оператор const_cast
Оператор
const_cast используется для явного переопределения модифи
каторовconst и/или volatile. Новый тип должен совпадать с исходным, за
исключением его атрибутов const или volatile. Чаще всего оператор const_
cast используется для удаления признака постоянства (атрибута const). Его
общий формат имеет следующий вид.
const_cast<type> (expr)
Здесь элемент type задает новый тип операции приведения, а элемент expr
означает выражение, которое приводится к новому типу. Необходимо подчер
кнуть, что использование оператора const_cast для удаления сопst-атрибута
является потенциально опасным средством. Поэтому 06ращайтесь с ним очень
осторожно.
Оператор static_cast
Оператор static_cast выполняет операцию неполиморфного приведения
пшов. Его можно использовать для любого стандартного преобразования. При
этом во время раБОТbI программы юшаких проверок на допустимость ие выпол
няется. По сути, static_cast может заменить "традиционный" оператор при
ведения типов.
Оператор
static cast имеет следующий общий формат записи.
static_cast<type> (expr)
636 Модуль 12. ИСl<лючени~, шаблоны и I<ое-что еще
Здесь элемент type задает новый тип операции приведения, а элемент expr
означает выражение, которое приводится к этому новому типу.
Оператор reinterpret_cast
Оператор reinterpret_cast прсобразует ОДИН тип в принципиалыю дру
гой. Напримср, его МОЖIlQ. использовать для преобразования указателя в целое
значение и целого значения - в указатель. Его также можно использовап. для
приведения наследственно несовместимых типов указателей. Этот оператор имс
ет следующий общий формат записи.
reinterpret_cast<type> (expr)
Здесь элемент type задает IЮUЫЙ тип операции привсдения, а элемент expr
означает выражение, которое приводится к этому новому типу.
Что же дальше?
Цель этой книги - преподать начинающему программисту основные элемен
ты языка С++, т.С. те средства и методы программирования, которые используют
ся в обычной "еЖСДIiСВНОЙ" пракТИlсе. Освоив материал этой книги, вы сможете
смело приступать к созданию реалЫiЫХ программ. Однако помните, что С-о; -
очень богатый язык, который содержит множество средств высокого уровня, без
изучения которых невозможно стать профессиональным программистом. К ним
относятся следующие:
о
от
ф
жит подробное описание элементов языка С++ и библиотек. Ваш нынешний ба о
'"
:s:
гаж знаний вполне позволит оам ОСИЛIIТЬ материал этого справочника.
JS
1:
О
.<
:10
:0
;3
ri.
1. Поясните, как использовать ИНСТРУКЦIIИ try, catch и throw для под :s:
1:
Ф
держки обработки исключитеЛЫIЫХ ситуаций. 7
12. С помощью какого оператора можно получить тип объекта при выполне
нии программы?
Директива #define
ДиреКтива # define используется для определения идентификатора и символь
ной последовательности, которая будет подставлена вместо идентификатора везде,
где он встречается в исходном коде программы. Этот идентификатор называется
Ашкp0w.4ене.м.., а процесс замены - .макроnодcmано8КОЙ (реализацией макрорасшире
ния). Общий формат использования этой директивы имеет следующий вид.
#define макроимя последовательность_символов
Обратите внимание на то, что здесь нет точки с запятой. Заданная по следов а -
тельность_символов завершается только символом конца строки. Между Э.'1е
ментами имя_макроса и последовательность_символов может быть любое
количество пробелов.
Например, если вы хотите использовать слово UP в качестве значения 1и сло
во DOWN в качестве значения О, объявите такие две директивы #define.
#define UP 1
#dеfiле DOWN О
1 О 2 Q.
О
Важно пониматъ, что макроподстановка - это просто замена идентификатора (J
(J
соответствующей строкой. Следовзтелыю, есm1 вам нужно определить стандарт
~
о
ное сообщение, используйте КОД, подобный этому. Q.
с
Ф
#define GETFILE "Введите имя файла" Q.
С
/ / ...
Препроцессор заменит строкой "Введите имя файла" каждое вхождсние
идентификатора GETFILE. ДЛЯ компилятора эта cout-ИИСТрукция
cout « GETFILE;
в действительности выглядит так
а не эта:
использовалась директива idefine. Поэтому вам еще часто IIридется с ней стал
киваться в С++-коде.
#include <iostream>
using namespace std;
int main ()
{
int х, У;
х = 10;
У = 20;
cout « "МиНИМУМ равен: " « MIN(x, у);
return О;
#include <iostrearn>
using namespace std;
? 1 О
•~
u
~
о
а.
t:.
ф
.а.
:С
int rnain ()
(
if(EVEN(9+1)) cout <~ "четное число";
else cout « "нечетное число ";
return О;
Напомню. что оператор "%" имеет более высокий приоритет, чем оператор "+".
Это значит, что Сllачала выполнится операция деления по модулю (%) для
числа 1. а затем' ее результат будет сложен с числом 9, что (конечно же) не
равно о. Чтобы исправить ошибку, достаточно заключить в круглые скобки
аргумент а в МaI<рnопределении EVEN, как показано в следующей (исправлен
ной) версии той же программы.
#include <iostream>
using narnespace std;
int rnain ()
(
if(EVEN(9+1» cout « "четное число";
else cout « "нечетное число";
return О;
644 Приложение А. Препроцессор
ке, и нет никаких затрат системных ресурсов на вызов функции, скорость работы
вашей программы будет выше по сравнению с I1рименением обычной функции.
Но повышение скорости является платой за увеличение размера программы (I<:З
за дублирования кода функции).
Несмотря на то что маКрООllредсления все еще встречаются в С++-коде, ~!a
красы, действующие подобно функциям, МОЖIIО заменить спецификатором il1-
line, который справляется с той же ролью лучше и безопаснее. (Вспомните:
спецификатор inl ine обеспечивает вместо вызова функции расширение ее тела
в строке.) Кроме того, inlinе-функции не требуют дополнительных круглых
скобок, без которых не могут обойтись макроопределения. Однако макросы, дей
ствующие подобно функциям, все еще остаются частью С++-программ, посколь
ку многие С/С++-программисты продолжают использовать их по привычке.
Директива #error
Директива #error представляет собой указание компилятору остановить
компиляцию. Она используется в основном для отладки. Общий формат ее за
писи таков.
#error сообщение
Обратите внимание "а то, что элемент сообщение не заключен в двойные ка
ВЫ'IКИ. При встрече с директивой #error отображается заданное сообщение и
другая информация (она зависит от конкретной реализации рабочей среды), по
сле чего компиляция прекращается. Чтобы узнать, какую информацию отобра
жает в этом случае компилятор, достаточно провести эксперимент.
Директива #include
Директива препроцессора # incl ude обязывает компилятор включить в файл,
который ее содержит, либо стандартный заголовок, либо другой исходный файл,
задаНIIЫЙ в директиве #include. Имя стандартного заголовка заключается в
угловые скобки, как показано в примерах, приведенных в этой книге. Например,
эта директива
#include <fstrearn>
С++: PYI<OBOACTBO ДЛfl начинающих 645
Если имя файла заключено в угловые скобки, то поиск файла будет осущест
вляться в одном или нескольких специальных каталогах, определенных конкрет
ной реализацией. Если же имя файла заключено в кавычки, поиск файла выпол
няется, как правило, в текущем каталоге (что также определено конкретной ре
ализацией). Во многих случаях это означает поиск текущего рабочего каталога.
Если заданный файл не найден, поиск повторяется с использованием первого
способа (как если бы имя файла было заключено в угловые скобки). Поскольку
маршрут поиска файлов определяется конкретной реализацией, то за детальным
описанием этого процесса лучше обратиться к руководству пользователя, при
лагаемому к вашему компилятору.
#if константное_выражение
последовательность инструкций
#endif
Если константное_выражение Яl3ляется истинным, код, расположенный непо
средственно за этой директивой, будет СКОМШfЛИРОван. Рассмотрим 1lрlfмер.
646 Приложение д. Препроцессор
int main ()
#if МAX>lO
cout « "Требуется дополнительная память\п";
#endif
/ / '"
return О:
константы МАХ больше 10. Этот пример иллюстрирует важный момент: выра
жение, которое стоит после директивы #if, ВЫ'lИсляется во время КОМПИЛЯЦИИ.
Следовательно, оно должно содержать только идентификаторы, которые были
предварительно определены, или константы. Использование же перемеНIIЫХ
здесь исключено.
#define МАХ 6
int main ()
#if МAX>lO
cout « "Требуется дополнительная память.\п");
#else
cout « "Достаточно имеющейся паМRТИ.\п";
С++: руководство ДМ! начинающих 6А7
........................................................................................................-....................... .
iendif
Q.
// """ О
U
return О;
~
Q.
!Б
Q.
в этой программе для имени МАХ определено значение, которое меньше 10, с::
/ / .""
ielif выражение N
последовательность инструкций
#endif
Например, в этом фрагменте кода используется идеНТИфИl(атор COMPILED_
ВУ, который позволяет определить, кем компилируется программа.
fldE'fine JOHN О
#define ВОВ 1
648 Приложение А. Препроцессор
!tdefine ТОМ 2
#else
char who[] = "Тот";
#endif
Директивы #if и #elif могут быть вложенными. В этом случае диреК1'И
ва !tendif, ielse или !telif связывается с ближайшей директивой !tif или
tte 1 i f. Например, следующий фрагмент кода совершенно допустим.
#ifndef макроимя Q.
О
последовательность инструкций U
U
#endif
~
о
Если макроимя ис определено с помощью [(акой-нибудь диреКТl1ВЫ #define, Q.
с
то последовательность инструкций, расположенная между директивами
~
#ifdef и #endif, будет скомпилирована. Как директива #ifdef, так И дирек С
ТJoтa # ifndef может иметьдирекпшу #else ИЛИ #elif. Кроме того, директивы
#ifdef и #ifndef можно вкладывать точно так же, как и директивы #if.
Директива #undef
Дирекrnва #undef используется для удалеtIИЯ предыдущего определения не
которого макроимени. Ее общий формат таков.
#undef макроимя
Рассмотрим пример.
11 ...
#undef TlМEOUT
#undef WAIT
Здесь имена TlМEOUT и WAIT определены до тех пор, пока не выполнится со
ответствующая директива #undef. Основное назначение ДИРСI<'ПШbl #undef -
разрешить локализацию макроимен для тех частей кода, в которых они нужны.
#ifdef MYFILE
650 Приложение А Препроцессор
Директива #line
Директива #liле используется для изменения содержимого IlсевдопереМСII
иых __ LINE __ и __ FILE __ ' которые являются зарезервированными иденти
Здесь номер - это любое положительное целое число, а имя_Файла - любой до
пустимый идентификатор файла. Значение элемента номер становится номером
текущей строки, а значение элемента имя файла - именем исходного файла.
Имя файла - элемент необязаТСJJЬНЫЙ. ДиреКТlша # 1 ine используется, глав
ным обра.10М, в целях отладки и в специальных приложениях.
Директива #pragma
Работа директивы #pragrr.a зависит от конкретной реализации компилятора.
Она позволяет выдавать компилятору различные инструкции, предусмотренные
создателем компилятора. Общий формат ее использоваиия таков.
#pragma имя
#define rnkstr(s) 11 s
/
int main ()
{
cout « mkstr(Я в восторге от С++);
return О;
D строку
#include <iostream>
using namespace std;
#define concat(a, Ь) а ## Ь
int rnain ()
int ху = 10;
return О;
652 Приложение А. Препроцессор
в строку
cout « ХУ;
Если эти операторы вам кажутся странными, помните, что они не являются
операторами "повседневного спроса" и редко используются в программах. Их
OCllODHoe назначение - позволить препроцессору обрабатывать некоторые спе
циальныс ситуации.
ЗарезервироваННblе макроимена
в языке С++ определено шесть встроенных макроимен.
LINE
FILE
ОАТЕ
ТIME
STDC
__ cplusplus
Макросы __ LINE __ и __ FILE __ описаны при рассмотрении директивы
4t 1 i пе выше в этом приложении. ОНИ содержат llOMep текущей строки и имя фай
ла компилируемой программы.
МаI(РОС __ ОАТЕ __ представляет собой строку в формате месяц/день/год
которая означает дату трансляции исходного файла в объектный код.
Врсмя трансляции исходного файла в объектный код содержится п виде СТРОКИ
в макросе __ Т IME __. Формат этой строки следующий: часьг.минуты. секунды.
Точное lIазначение макроса __ STDC __ заВИСИТ от конкретной реализации
компилятора. Как правило, если макрос __ STDC __определен, то компилятор
примет ТОЛl,ко стандартный С/С++-код, КОТОРЫЙ не содержит никаких нестаrl
дартных расширений.
Компилятор, соответствующий ANSI/ISO-стаНIlарту С++, определяет макрос
__ cplusplus как значение, содержащее по крайней мере шесть цифр. "Нестан
даРТllые" КОМПИЛЯТОрЫ ДОЛЖНЫ использовать эначеliИС, содержащее пять (или
даже меньше) цифр.
Прило)кение Б
Использование
устаревшего С++
I<омпилятора
654 Приложение Б. Использование устаревшего ...
<iostream>
<fstream>
Чтобы преобразовать эти "новые" загOJlовки в "старые" заголовочные фаЙл.ы,
достаточно добавить расширение . h.
ВJСЛЮЧая современнъrй заголовок 1:1nPO"'PCl.'fMY. необходимо помнить, что его со
держимое относится к пространств}' IIМСII s td. Кll, упоминалось выше. простр.ш
СТ80 имен - это просто дскларапlВНая 06.1IacТl •. Ее tlaЗllзчеJlие - ЛОК3JНlзоваТh имена
идентификаторов во избежанис коллl13ИЙ с IIМСllа,\m. В старых версиях СТ+ имена
библиотечных функций помещаются в глобаJlьное I1РОСТРанство имен, ;) не в про-
С++: PYl<OBOACтaO M~ начинающих 655
Предметный указатель
Символы д
#define, дирекпша 640 Декремент 90
#elif, директива 647 Деструктор 368
#elldif, директива 645 Директива
#error, диреКТ1lва 644 #define 640
#if, диреКТlша 645 #eljf 647
#ifdef, директива 648 #епdif 645
#ifndef, директива 648 #епог 644
#include, директива 644,654 #if 645
#pragma, директива 650 #ifdef 648
#undef, директива 649 #ifпdеf 648
_cplusplu~. макрос 652 #include 644,654
_DATE_. макрос 652 #li11c 650
_FILE_. макрос 652 #ргаgmз 650
_LINE_, макрос 652 #tll1def 649
_STDC_, макрос 652 Директивы прелроцессора 640
_TIME_, макрос 652 ДополнителЫIЫЙ код 73
Fll.E 650
LINE __ 650 3
ЗаГОЛ(1DКИ 250
А Зarоловок
Абстрактный класс 518 <cctype> 176
Ap~{eнт G3 <сшаth> 78, 127
ApГY~\1ellТЫ функции <cstdio> 171
по умолчанию 292 <cstdlib> 64, 111
Аргуменш функций 215 <cslril1g> 173
<fsl:ream> 545
Б <iostream> 37,522,524,557
Байт-код 28 <typeinfo> 629
Библиотеки С++ 64 Заголовочный файл
Блок 225 <iostгeam.l1> 522
Блок кода 56
и
в ИдеНТl\фИкатор 66
llиртуальная машина Java 28 Индекс 159
Виртуальные функции 502 Инициализация
continue 145 л
сщ,t 45
Литерал 83
do-wl,ile 136 вешественный 83
fOT 54, 125
восьмеричный 84
goto 152 символьный 83
if 52,110 строковый 84,170
s\\'itch 117 целочислеНIiЫЙ 83
while 134 шестнадцатеричный 84
Искл ючение 572
bad cast 634 м
bad=typeid 632
Макроимена 652
исключителыlя ситуация 572
Макроимя 640
Макроподстановк., 640
к
Макрос
Класс 30,350 _cplusplus 652
basic ios 525 DЛТЕ 652
basic-iostream 525 -FILE -652
basic- istr('.am 525 -LINE- 652
basic-ostream 525 -STDC 652
basic=strearnbuf 525
=ПМЕ= 652
fstream 546 Манипулятор
ifst,"eam 546 boolal рЬа 541
ios 533,547 dec 541
ios_base 525 cndl 541
ofstream 546 ends 541
string 84, 169 fixed 541
type_info 629 flush 541
абстрактный 518
Ьех 541
базовый 466 internal 541
вложенный 376 left 54 t
полиморфный 503, 630 noboolalpba 541
лроизводный 466 noshowbase 541
Классы
nosl1Owpoint 541
обобщенные 594 noshowpos 541
Ключевые CJl0B3 С++ 66 noskipws 541
Комментарий 36 nounitbuf 541
Компаранд 255 nouppeTcase 541
Ком I1ИЛЯТОР
oct 541
С++ Builder 34 resetiosflagsO 541
Visua[ С++ 34 right 541
КОliСтанты 83 scientific 541
KOHcТPYRToP 368,480 setbaseO 541
копии 405,411 setfi110 541
параметрИЗ0ванньп1 370 setiosflagsO 541, 542
Куча 605 setprecisionO 541
Кэш 315 setwO 541
658 Предметный указатель
ш continue 145
cout 3В,524
Ша6ЛОli 587
ШаБЛОIiНая функция 590
D
А dec, флаг 534
delete 605
Лrraу 158
delete, оператор 605
atof() 246
do~whi1e 136
auto 231
double 71
auto, спецификатор 308
dynamic_cast, оператор БЗ3
в
Е
в 23
enum 318
badO 569 eofO 559
bad_cast, ИСКJIЮчение 634
extem ЗОВ
basic_ios, КJIaCC 525
662 Предметный указатель
...............................................................................................................................
v
virtuaI 502
Visua\ С++ 26.34
VisuaI Studio .NEт, среда разработки
программ 35
void 71.212.214
volati1e. специфи.катор 307
volatile. спецификатор типа 307
w
wcerr 524
wchar_t 71
wcin 524
wclog 524
\vcout 524
w!1i]e 134
widthO 538
wnteO 554.
НаучНО-nОnУЛЯР"0е uздаllliе
Герберт Шилдт
8
этой книге описаны все ОСНОО- ISBN 5-8459-0768-3
I1ые средства языка С++: от в продаже
элементарных понятий до
супервозможностеЙ. После рассмо-
трения основ програМl\Iирования
ПОЛНЫИ
СПРАВОЧНИК
по (++,
4-Е ИЗДАНИЕ
Герберт Шилдт
ANSI/ISO. Иllф()Р~!<ЩИЮ.
lt:Jложенную 11 I(IIIIГ('. можно
средах прuграммнроrзаНIIЯ.
по С,
ка С и его бибЛНОТ(:'КII стшщартных
функциil. Главныi'1 акцент сдслаJl
на стандарте ЛNSI/ISО Я;J(,(ка С
4-Е ИЗДАНИЕ Приведево ОПllсаНlfе I(ак стандарта
С89, 1'(IК н С99. В кнше oco(ioe
nНlIмание уделяется Y'ICTY характе
граММИРОIШIIШ1 11 Olll'paLIllUIIIIbIX
Герберт Шилдт систем, IIСllOЛЬЗУЮЩllХСЯ в иастоя
П
v ~~' IЮI'}I<I ('ЛСiIУl'Т Iюста Г"IП, ПР/ ку.
олныи ~.~, Сам /10 се()!' Я:IЫ)( С;; 04l'lIl,
(jОЛЫIIОЙ, а 6IJблгютска классов
справочник С# еще (JО.ilI,ше. LJTo(ih] Чllтате:IIО
()J,IЛО лсгче СОIl.llа,~а,-ь с Tal(llM
ОГIЮМIIЫ~I ()бl,е~\Ом маТСРll<lла,
:па КllllПl ()J,IЛ(l разделена
lIа TPIJ '{асп/, llOl'LlЯШСННЫХ
разобраться 13 ПРlIмерах,
ПРlIlJедснных в каждой главе,
Э
та книга отличается от множестоа IS8N 5-8459-0786-1
других книг по языку ]ava. В то 8 продаже
http://www.williamspublishing.com
Параллельное
и распределенное
программирование
с использованием С ++
В
этоii книге представл('rr аРХlIтектурный подход к распрс.1С.1СН ISBN 5-8459-0686-5
нам!' н параЛЛСЛЬJlОМУ програММllроваНIIЮ с иrПОЛЪЗОlJаНllем
в продаже
языка С++. Здесь OIllIC;]llbl простые методы програММIlРООЗН1:Н
параллеЛЫIЫХ ВIIРТУ:JЛЫIЫХ МilПlIЩ И основы Р;t:JраGотки кластrр·
пых IlРIIЛОЖ('lIиii.
МиоголеТllllii ОПЫТ анторов ЮlllГИ в области разра(ЮТКIIНРnГРi1ММ·
110\"0 О()ССIIС'IСНШ1 убедил IIХ в ТОМ, чтодля успешного ПРnСJ'Т11РUl';J·
ПIIЯ програюIныx средств 11 эффеКТИВIIО!1 IIХ рсализаUИII без УIIН
нерсаЛЬНОСТII ПРlIменж'мых средств уже не оБОЙТIIСЬ. Чтобы :Iффек
Т11НI\О rешать задаЧJl, стоящпс I1ер!'д СОВрСМL'I1ПЫМ программистом,
ЖСIlШI~НI.
ронаНIIЯ;
http / jwww.williamspublishing.com
С++
ДЛЯ ··чаЙников"
5-еиэдание
с. ДЭВИС
К
НИ\'U представляет собой введение ISBN 5-8459-0723-3
в ЯЭblК программирования С++. В продаже
Основное отличие даНlIОЙ книги от
предыдущих изданий С++ для "цаЙl/uков"
R том, что это издание не требует от чита-
теля каких-либо дополнительных зна-
ний, в ТО время как предыдущие издания
опиралис\" на знание читателем языка
http jjwww.dialektika.com