Text
                    С++
А Beginner.s Guide
L
. Osborne/McGrawHill


rерберт Wмпдт С++ дnя начинающих Москва ЭК М 2013 
ББК 32.97 УДК 681.3 Ш58 ШИJIДТ rерберт Ш58 с++ для начинающих. Серия «Шаr за шаrом» / Шилдт r.; пер. с анrл. ..... М.: ЭКОМ Паблишерз, 2013. ..... 640 С.: ил. Книrа известноrо американскоrо специалиста и популяризатора язы.. ков проrраммирования, посвященная основам языкa с++. Начиная с таких базовых понятий языка. как типы данных, массивы, строки, указатели и функции, книrа охва1'ывает 'также важнейшие эJIeменты объектно--ориен" тированноrо проrраммирования  классы и объекты, наследование, вирту.. а.льные функции, потоки вводавывода, исключения и шаБЛОНЬL Каждый раздел сопровождается простыми и наrлядными примерами, позволяющи.. ми ПОЛУЧИТЬ практические навыки cOBpeMeHHoro проrраммирования. Книrа предназначена для приступающих к изучению языка С++ oд" Horo из самых универсальных и распространенных на сеrодня языков про.. rраммирования. ISBN: 978-5..9790..0127..2 ISBN: 0..07..219467..7 (ааrл.) @ ЭКОМ Паблишерз, 2009 @ 2005 Ьу McGrawHill Companies АН rights reserved. Printed in the U nited Sta tes of America. 
Об авторе rерберт Шилдт принадлежит к числу ведущих авторов мировоrо уровня в области проrpаммирования. Он является крупным специали стом по языкам С, С++, Java и С#, а также по проrpаммированию в системе Windows. Ero книrи по проrpаммированию были переведены на все основные иностранные языки и разошлись по миру в количест ве более 3 миллионов экземпляров. r. шилдт..... автор мноrочисленных бестселлеров, к числу которых относятся книm С++: The Coтp/ete Refereпce, С#: Тhe Coтp/ete Refereпce, Java 2: Тhe Coтp/ete Refereпce, С#: А Begiппer's Gиide, Java 2: А Begiппer's Gиide, а также С: Тhe Coтp/ete Refereпce. Иллинойский Университет присвоил [. Шилдту степень Ma rистра по вычислительной технике. Связаться с ним можно в ero KOH сультационном отделе по телефону (217) 5864683. 
От переSQдчика Эта книrа представляет собой превосходно написанное пособие мя ПРИС1УПаюших к изучению языка С++  одноrо из наиболее широко распространенных и универсальных языков проrpаммирования. Книr по С++ издано несметное количество. Зайдите в любой мarазин Tex нической книrи  на полках всеrда стоит несколько десятков книr тa Koro рода, как пере водных, так и написанных отечественными aвTopa ми. Среди них есть книrи по самым разнообразным специальным ac пектам проrpаммирования  использованию СОМтехнолоrий, распределенному проrpаммированию, разработке драйверов устройств и др. Есть и MHoro пособий для начинаюших. Чем среди этоrо изоби лия вьщеляется настоящая книrа? · Книrа написана чрезвычайно простым, доступным языком. От читателя не требуется особых знаний по вычислительной техни ке или навыков проrpаммирования. От caMoro начала книrи и до ее последних страниц новый материал вводится последовательно неболь шими порциями и объясняется так просто и понятно, что книrа леrко читается, а излаrаемые в ней понятия хорошо усваиваются. К наибо лее сложным вопросам автор обрашается несколько раз, терпеливо разъясняя суть дела. · Книrа написана интересно. Очень мноrие книrи по вычислительной технике характерны тем, что, приступая к их изучению, засыпаешь на первой же странице. Воз можно, ЭТО связано с боязнью авторов отойти от скрупулезно точных формулировок и определений, которые всеrда имеют несколько cy конный характер. Книra r.Шилдта не относится к этой катеrории; она написана так, что производит впечатление увлеченной беседы двух за интересованных лиц  автора и читателя. Автор не боится объяснять сложные концепции cOBpeMeHHoro проrpаммирования простым чело веческим языком  не всеrда исчерпывающе точным, но живым, об разным и доходчивым. · Все рассматриваемые темы и даже небольшие по объему разделы сопровождаются простыми, но весьма наrлядными проrраммны ми примерами. Мноrие авторы помешают в свои книrи по проrpаммированию сложные, развернутыIe примеры с массой не относящихся к делу дeTa лей, среди которых иной раз трудно вычленить рассматриваемое cpeд ство языка. В книrе r.Шилдта все примеры исключительно просты и редко занимают больше однойполутора страниц текста. По этой при чине они, естественно, носят несколько формальный характер, однако их простота и наrлядность с лихвой компенсируют этот недостаток. Читатель, выполняя приведенные в книrе при меры на своем компью тере, может леrко внести в них собственные изменения и JlОllолнения, чтобы лучше понять иллюстрируемый в примере материал. Кстати, 
От переводчика 7 автор подробно объясняет методику самостоятельной работы над при мерами в разных операционных средах, а в приложении останавлива ется на особенностях использования относительно старых версий компиляторов, которые MOryт оказаться в распоряжении читателя. · Будучи пособием для начинающих, кииrа, тем не менее, отнюдь не оrpаничивается изложением начальных сведений. Наоборот, около половины книrи посвящено наиболее сложному, но и наиболее ценному средству языка с++  объектно..ориентиро ванному проrpаммированию. Разумеется, объем и направленность книrи заставили автора оrpаничиться рассмотрением лишь наиболее важных, принципиальных аспектов этоrо метода. Однако для начи нающих этоrо вполне достаточно; желающим изучить объектноори ентированное проrpаммирование в больших деталях автор peKOMeндy ет свои же книrи с более rлу60КИМ изложением этоrо материала, но и более сложные в чтении. Книry r.Шилдта можно адресовать самому широкому кpyry читате лей: школьникам, студентам младших курсов, инженерам, OC03HaB шим необходимость научиться проrpаммировать на современном язы ке. Все они, изучив кииry, по достоинству оценят как силу и красоту языка с++, так и умение автора простым и словами излаrатъ сложные вопросы. К.т. Фuноzенов 
Предисловие с++ является исключительно удобным языком для разработки BЫ сокопроизводительноrо проrpаммноrо обеспечения. Новые принципы синтаксиса и стиля, определенные в этом языке, повлияли на все по следующие языки проrpаммирования. Например, и Java, и с# про изошли от с++. Более Toro, с++ является языком, принятыIM между народным проrpаммистским сообществом, и почти все профессио нальные проrpаммистыI по меньшей мере знакомы с этим языком. Овладевая с++, вы приобретаете твердый фундамент для дальнейше ro yrлубленноrо изучения любых аспектов cOBpeMeHHoro проrpамми рования. Цель настоящей книrи  познакомить вас с основами языка с++. Книrа постепенно вводит вас в мир языа,, используя при этом мноrочисленные примеры, вопросы для самопроверки и за конченные проrpаммные проекты; при этом от вас не требуется иметь предварительный опыт проrраммирования. Книrа начинает ся с изложения таких базовых вопросов, как процедуры компиля ции и запуска проrpамм на с++; затем обсуждаются ключевые слова, возможности и конструкции, составляющие язык с++. За кончив изучение книrи, вы получите твердое представление о cy ществе языка с++. С caMoro начала следует подчеркнуть, что эта книrа должна слу жить лишь отправным пунктом в изучении проrpаммирования. с++ является весьма сложным языком с оrpомными возможностя ми, И практическая работа на с++ не оrpаничивается использова нием ключевых слов, операторов и синтаксических привил, co ставляющих язык. Успешное проrраммирование на с++ требует использования боrатоrо набора классов и библиотечных функций, способствующих разработке прикладных проrpамм. Хотя HeKOTO рые элементы библиотек и рассматриваются в этой книrе, ее orpa ниченный объем не позволил уделить этому вопросу должное вни мание. Чтобы стать первоклассным проrpаммистом на с++, вы должны обязательно освоить библиотеки этоrо языка. Завершив чтение этой книrи, вы приобретете знания, на базе которых CMO жете серьезно заняться изучением библиотек и всех остальных ac пектов с++. Как орrаНИЗ0вана эта книrа Книrа представляет собой последовательный учебник, в котором каждый следующий раздел опирается на предьщущий. Книrа состоит из 12 модулей, в каждом из которых обсуждается тот или иной аспект 
Предисловие 9 С++. При этом в книry включены некоторые специальные элементы, способствующие закреплению изучаемоrо вами материала. Цели Каждый модуль начинается с перечисления поставленных в нем целей, rоворящих о том, что именно вы будете изучать. В каждом MO дуле отмечены разделы, посвященные достижению этих целей. Вопросы для самопроверки Каждый модуль завершаетсй перечнем вопросов для самопроверки, которые позволят вам оценить ваши знания. Ответы на эти вопросы приведены в Приложении А. Минутная тренировка в конце каждоro тематическоrо раздела при водится короткий спи сок вопросов, отвечая на которые, вы сможете удостовериться в пони мании ключевых тем данноrо раздела. Ответы на эти вопросы приво дятся на той же странице. Спросим у эксперта По всей книrе разбросаны вставки "Спросим у эксперта". В них в виде вопросов и ответов приводятся дополнительные сведения или интересные комментарии к изучаемой теме. Проекты каждый модуль содержит один или несколько проrpаммных проекroв, показывающих, как применяется на прaкrике изучеННЫЙ вами материал. Эrи проекты предС13ВЛЯЮТ собой реальные примеры, которые вы сможете использовать как ompавные roчки для своих собственных проrpамм. Не требуется предварительноrо опыта проrраммирования Изучение книm не предполаraет наличия у вас предварительноrо опыта проrpаммирования. Таким образом, вы можете приступить к 
10 Предисловие чтению книrи, даже если вы никоrда ранее не проrpаммировали. Ko нечно, реально в наше время большинство читателей будет иметь хотя бы небольшой опыт проrpаммирования на том или ином языке. Для большинства это будет опыт написания проrpамм на Java или С#. Как вы увидите, С++ является прародителем обоих этих языков. Таким образом, если вы уже знакомы с Java или С#, изучение С++ не COCTa вит для вас большоrо труда. Требуемое nporpaMMHoe обеспечение Для Toro, чтобы компилировать и запускать проrpаммы из этой книrи, вам понадобиться современный компилятор для С++, напри мер, одна из последних версий Microsoft Visual С++ или Borland С++ Builder. Не забудьте: все nporpaMMbI имеются во Всемирной паутине Имейте в виду, что все примеры и проrpаммные проекты, включен ные в эту книry, можно бесплатно получить в Интернете по адресу www.osborne.com. Для дальнейшеrо изучения Книrа с++ для начинающих открывает вам пyrь к целой серии книr r. Шилдта по проrpаммированию. Ниже перечислены некоторые кни rи, которые Moryт представить для вас особый интерес. Для более основательноrо знакомства с языком С++, познакомь тесь со следуюшими книrами: С++: Тhe Coтplete Refereпce Имеется русский перевод: r. Шилдт. Полный справочник по С++. ..... М. и др.:Вильямс, 2004. ..... 748 с.: ил. С++ Froт the Grouпd ир Имеется русский перевод: r. Шилдт. С++ базовый курс. ..... М. и др.:Вильямс, 2005. ..... 748 с.: ил. Teach YourselfC++ Имеется русский перевод: r. Шилдт. Самоучитель С++. ..... СПб.: ВНVСанктПетербурr, 1998. ..... 683 с. STL Prograттiпg Froт the Grouпd ир 
Предисловие 11 С++ Prograттer's Reference Для изучения проrpаммирования на языке Java мы рекомендуем тa кие киши: Java 2: А Beginner's Gиide Имеется русский перевод: r. Шилдт. Java 2. ..... СПб. и др.: BНV СанктПетербурr, 1998. Java 2: Тhe Coтp/ete Reference Имеется русский перевод: П. Ноyroн, r. Шилдт. Полный справоч ник по Java 2. ..... СПб. и др.: БЧИПетербурr, 2000. ..... 1050 С.: ил. Java 2 Prograттer's Reference Для изучения проrpаммирования на языке С# r. Шилдт предлаraет вам следующее: С#: А Beginner's Guide Имеется русский перевод: r. Шилдт. С#: учебный курс. ..... СПб. и др.: Питер, 2002. ..... 508 С.:ИЛ. С#: Тhe Coтp/ete Reference Имеется русский перевод: r. Шилдт. Полный справочник по С#. ..... М. и др.:Вильямс, 2004. ..... 748 С.:ИЛ. Для изучения проrpаммирования в системе Windows мы peKOMeндy ем целую rpуппу кииr [. Шилдта: Windows 98 Prograттing Froт the Groиnd ир Windows 2000 Prograттing Froт the Groиnd ир MFC Prograттing Froт the Groиnd ир Имеется русский перевод: r. Шилдт. MFC: Основы проrpаммиро вания. ..... Киев: B 1997. ..... 556 С.:ИЛ. Тhe Windows Prograттing Aпnotated Archives Если вы хотите ознакомиться с языком С, который создал базу Bce ro современноro проrpаммирования, вас MOryr заинтересовать сле дующие кииrи: С: Тhe Coтp/ete Reference Имеется русский перевод: r. Шилдт. Полный справочник по С. ..... М. и др.:Вильямс, 2002. ..... 699 С.:ИЛ. Teach Yoиrse/fC Если вам нужно получить быстрые и точные oтвeThI, 06ращайтесь за помощью к rерберту Шилдту, признанному специалисту по про rpаммированию. 
Модуль 1 ОСНОВЫ С++ 1.1 Ознакомиться с историей создания С ++ 1.2 Рассмотреть соотношение С++ и языков С, С# и Java 1.3 Узнать о трех принципах объектно..ориентированноrо проrрамми" рования 1.4 Научиться подrотавливать, компилировать и запускать nporpaMMbI на С++ 1.5 Освоить использование переменных 1.6 Освоить использование операторов 1 .7 Научиться вводить с кла виатуры 1.8 Научиться работать с предложениями If и 10r 1.9 Получить представление об использовании nporpaMMHbIx блоков 1 . 1 О Начать использовать функции 1. 11 Узнать о ключевых словах С ++ 1 . 1 2 Освоить понятие идентификатора 
14 Модуль 1. ОСНОВЫ С++ Е сли есть язык, определяющий существо COBpeMeHHoro проrpаммирова ния, то ЭТО язык с++. Он превосходно удовлетворяет требованиям разработки высокопроизводительноrо проrpаммноrо обеспечения. Ero синтаксис стал стандартом для профессиональных языков про rpаммирования, а используемые в нем стратеrические принципы про ектирования проходят красной нитью через всю вычислительную Tex нику. с++ послужил также основой для разработки таких языков, как Java и с#. Можно сказать, что с++ открыл путь ко всему COBpeMeHHO му проrpаммированию. Цель этоrо модуля  дать вам представление о языке с++, включая ero историю, присушую ему стратеrию проектирования, а также наибо лее важные средства этою языка. Безусловно, наибольшая сложность в изучении языка проrpаммирования заключается в том, ЧТО ни один эле мент языка не сушествует в отрыве от дрyrиx. Напротив, все компонен ТbI языка работают совместно. эта взаимозависимость затрудняет обсу ждение какоroлибо аспекта с++ без затраrивания дрyrиx аспектов. С целью преодоления указанной трудности в этом модуле дается краткий обзор ряда средств С++, включая общую форму проrpаммы на этом языке, некоторые управляющие предложения и операторы языка. Здесь мы не будем вдаваться в детали, но обратим внимание на общие KOH цепции, характерные для любых С++проrpамм. Цель _ Краткая история С++ История С++ начинается с языка С. Причина этоro ясна: С++ постро ен на фундаменте С. Таким образом, С++ является нздмножеством языка С. С++ был получен путем развития языка С и добавления в Hero аппарата подцержки объектноориентированноrо проrpаммирования (о чем будет подробнее сказано ниже). Кроме тoro, С++ отличается от С некоторыми дополнительными средствами, в частности, расширенным набором библиотечных проrpамм. Однако значительную часть своей мощности и кpacoТbI язык С++ унаследовал непосредственно от С. По этому для roro, чтобы полностью понять и оценить С++, вы должны прежде всею познакомиться с тем, как и почему возник язык С. Язык с: Заря современной эры проrраммирования Изобретение языка С определило начало современной эры проrpамми рования. Влияние этоrо языка невозможно переоценить, поскольку он 
Краткая история С++ 15 фундаментально изменил наш подход и orношение к проrpаммирова нию. Стратеrия проектирования, заложенная в язык С, и ею синтаксис влияют на все появляющиеся языки проrpаммирования. Язык С явился одной из rлавных революционных сил в развитии проrpаммирования. Язык С был изобретен и впервые реализован Деннисом Ритчи на машине PDPll фирмы ОЕС, использующей операционную систе му UNIX. С явился результатом эволюционноrо процесса, начавше rося СО cTaporo языка под названием BCPL. Этот язык был разрабо тан Мартином Ричардсом. Идеи BCPL нашли отражение в языке под названием В, который был изобретен Кеном Томпсоном, и KO торый В дальнейшем привел к разработке в 1970x rr. языка С. Перед изобретением С компьютерные языки в основном разраба тыIалисьъ либо в качестве академических упражнений, либо в админи стративно создаваемых комитетах. С языком С получилось иначе. Эroт язык был спроектирован, реализован и развит работающими проrpаммистамипрактиками, и в нем нашли отражение их подходы к разработке проrpамм. Средства этоrо языка были oтrочены, протести рованы, продуманы и переработаны людьми, которые реально исполь зовали язык в своей работе. В результате С сразу привлек массу cтo ронников и быстро стал любимым языком проrpаммистов вcero мира. Язык С вырос из революции cтpy"mypHOZO проzраммирования, про изошедшей в 1960x rr. Перед появлением концепции С'фуктурноro проrpаммирова.ния написание больших проrpамм вызывало значитель ные сложности, потому что лоrика построения проrpаммы имела тeH денцию вырождаться в так называемый "макаронный код"  запутан ную массу переходов, вызовов подпроrpамм и возвратов, затрудняющих изучение и модификацию проrpаммы. Структурные языки подошли к решению этой проблемы пyreм добавления тщательно разработанных управляющих предложений, подпроrpамм с локальными переменными и ряда дрyrиx усовершенствоваНИЙ. С появлением CтpyктypНbIX языков стало возможно писать относительно большие проrpаммы. Хотя в то время были и дрyrие структурные языки, в частности, Pascal, язык С оказался первым языком, в котором успешно сочета лись мощь, элеrантность и выразительность. Ero лаконичный, но в то же время леrко осваиваемый синтаксис вместе со стратеrией, yтвep ждающей rлавенство проrpаммиста (а не языка), быстро завоевал сим патии проrpаммистов. Сеrодня это уже трудно представить, но С стал, можно сказать, той струей свежею воздуха, которую так долro ждали проrpаммистыI. В результате С быстро превратился в наиболее широко используемый язык CтpyктypHOro проrpаммирования 1980x rr. Потребность в С++ Прочитав написанное выше, вы можете удивиться, зачем же был изо бретен язык С++. Если С оказался успешным языком компьютерною 
16 Модуль 1. ОСНОВЫ С++ проrpаммирования, 10 почему возникла необходимость в чемто дpy юм? Ответ заключается в тенденции J(, усложнению. На протяжении всей ИС1Ории проrpаммирования нарастающая сложность проrpамм служила стимулом для разработки лучших способов справляться с нею. с++ появился как выражение этою процесса. Чтобы лучше понять связь возрастающей сложности проrpамм с развитием компьютерных языков, придется HeMHoro yrлу6иться в историю вычислительной техники. СО времени изобретения вычислительной машины подходыI к про rpаммированию претерпели драматические изменения. Коrда появились первые компьютеры, их проrpаммирование вьmолнялось вводом машин ных двоичныx команд с передней панели компьютера с помощью пере ключателей. Пока размер проrpамм не превышал нескольких сотен KO манд, такой подход был вполне рабorocпособным. Коrда проrpаммы cтa ли длиннее, был изобретен язык ассемблера, в результате чеrо проrpаммистыI получили возможность работать с более объемными и сложными npоrpаммами, используя символическое представление Ma иmнныx команд. Дальнейшее усложнение проrpамм привело к ПОЯR1Iе нню языков ВЫСОкОro уровня, предоставивших npоrpаммистам дополни тельные средства, облеrчаюшие работу со сложными проrpаммами. Первым широко используемым компьютерныIM языком был, конеч но, FORTRAN. Хотя FORTRAN был весьма впечатляющим первым ша юм, вряд ли ею можно считать языком, способствующим написанию ясных, леrко понимаемых проrpамм. В 1960x rr. родилось структурное проrpаммирование, новый метод, использование Koтoporo было eCTeCT венно при работе на языках вроде С. С появлением CтpyктypHOro про rpаммирования впервые стало возможным написание относительно сложных проrpамм без большой затратыI труда и времени. Однако при достижении проrpаммным проектом определенноrо размера даже с ис пользованием методов cтpyктypHoro проrpаммирования сложность про екта моrла превысить возможности проrpаммиста. К концу 1970x rr. MHome проекты стали приближаться к этой rpанице. В качестве пути решения этой проблемы стал разрабатываться HO вый принцип написания проrpамм, получивший название объеJ(,тно ориентированноzо проzра.ммирования (ООП). С помощью ООП про rpаммист получил возможность создавать более длинные и сложные проrpаммы. Однако язык С не подцерживал объектноориентирован ное проrpаммирование. Желание иметь объектноориентированную модификацию С привело к созданию С++. Таким образом, хотя С является одним из наиболее любимых и ши роко используемых профессиональных языков проrpаммирования в мире, наступил момент, коrда сложность разрабатываемых проrpамм превысила ero возможности. При достижении проrраммой определен Horo размера она становится настолько СЛОЖНОЙ, что проrpаммист уже не может воспринять ее, как единое целое. Целью С++ является пре одоление этоrо барьера и предоставление проrpаммисту возможности понимать и обслуживать более сложные проrpаммы. 
Краткая история С++ 17 С++ рОДИЛСЯ С++ был изобретен Бьярном Страуструпом в 1979 r. в ВеО Laboratories в Муррей Хилл, штат Нью..Джерси. Сначала Страуструп назвал новый язык "С с классами". Однако в 1983 r. язык получил название С++. Страуструп построил С++ на базе С, сохранив все возможности С, ero характерные особенности и достоинства. Он перенил таюке и базо.. вую стратеrию С, соrласно которой npоrpаммист, а не язык оnpеделя.. ет структуру проrpаммы. Важно понимать, что Страуструп не создал совершенно новый язык проrpаммирования. Напротив, он расширил уже имеющийся весьма успешный язык. Большая часть средств, добавленных Страуструпом к С, предназна.. чена для поддержки объектно..ориентированноrо проrpаммирования. В сущности, С++ является объектно..ориентированной модификаци.. ей С. Построив новый язык на базе С, Страуструп обеспечил плавный переход к внедрению ООП. Вместо TOro, чтобы изучать совершенно новый язык, проrpаммисту, работающему на С, было достаточно осво.. ить лишь несколько новых средств, после чеrо он Mor пожинать все плоды методолоrии объектно..ориентированноrо проrpаммирования. Создавая С++, Страуструп понимал, что добавляя к языку С под.. держку объектно..ориентированноro проrpаммирования, необходимо было в то же время сохранить дух языка С, включая ero эффектив.. ность, rибкость и стратеrию npоrpаммирования. К счастью, эта цель была достиrнута. С++ предоставляет проrpаммисту все возможности С, добавляя к ним моryщество объектов. Хотя первоначально С++ создавался в качестве средства разработ.. ки очень больших проrpамм, ero использование отнюдь не оrpаничи" вается этой сферой. В действительности 06ъектно..ориентированные средства С++ MOryт эффективно npименятъся для решения практиче.. ски любых задач проrpаммирования. Леrко найти примеры разработки на С++ таких проrpаммных npoeктов, как текстовые или rpафические редакторы, базы данных, персональные файловые системы, сетевые утилиты и коммуникационные проrpаммы. В силу Toro, что С++ вос" принял эффективность С, ero часто используют для разработки высо" копроизводительноrо системноrо npоrpзммноro обеспечения. Он так.. же широко используется и для проrpаммирования в системе Windows. Эволюция С++ С момента cBoero появления С++ претерпел три серьезных модифика.. ции, каждая из которых расширяла и изменяла язык. Первая модифи" кация была предложена в 1985 r., вroрая  в 1990 r. Третья модифика.. ция появилась в процессе стандартизации С++. Несколько лет назад 
18 Модуль 1. ОСНОВЫ С++ началась разработка стандарта ШIЯ С++. С этой целью орrанизациями ANSI (Американский национальный институт стандарroв) и ISO (Меж дународная орrанизация по стандартизации) был создан объединенный комитет по стандартизации. Первый вариант предлаrаемоro стандарта был выпущен 25 января 1994 r. В этот вариант комитет ANSI/ISO по С++ (членом KOToporo я являлся) включил средства, определенные Страуструпом, с добавлением некоroрых новых. В целом первый вари ант стандарта отражал состояние С++ на тот момент времени. Вскоре после завершения работы над первым вариантом стандарта С++ произоито событие, которое заставило серьезно расширить стандарт: создание Александром Степановым стандартной библиотеки шаблонов (Standard Template Library, STL). STL представляет собой Ha бор обобщенных процедур, с помощью которых можно обрабатывать данные. Эта библиотека сочетает в себе моryщество и элеrантность. Но она также весьма велика. Выпустив первый вариант стандарта С++, комитет проrолосовал за включение STL в спецификацию С++. Добавление STL расширило возможности С++ далеко за пределы ero первоначальноrо определения. С друrой стороны, включение STL, хотя и вполне оправданное, замедлило процесс стандартизации С++. Уместно заметить, что стандартизация С++ заняла значительно больше времени, чем это можно было предполаrать. В процессе cтaH дартизации к языку было добавлено MHoro новых средств, а также BHe сено большое количество мелких изменений. В результате вариант С++, определенный комитетом ANSI/ISO, оказался значительно объ емнее и сложнее первоначальноrо замысла Страуструпа. Окончатель ный набросок стандарта был выпущен комитетом 14 ноября 1997 r., и весь стандарт стал действовать в 1998 r. Эту спецификацию С++ обыч но называют стандартным с++. Настоящая книrа описывает как раз стандартный С++. Это вари ант С++, поддерживаемый всеми основными компиляторами языка С++, включая Visual С++ фирмы Мiсrоsоft и С++ Builder фирмы Borland. Таким образом, все проrpаммы и вообще все сведения, изла raeMble в этой книre, являются переносимыми. Цель _ Как С++ соотносится с языамии Java и С# Как скорее Bcero известно большинству читателей, относительно He давно появилось два новых языка: Java и С#. Язык Java был разработан фирмой Sun Microsystems; С# создан фирмой Мiсrоsоft. Поскольку иноrда возникает непонимание тoro, как связаны эти два языка с язы ком С++, мы кратко обсудим этот вопрос. 
Как С++ соотносится с языками Java и С# 19 С++ является родительским языком по отношению к языкам Java и С#. Хотя и в Java, и в С# добавлены, удалены или модифицированы некоторые свойства, в целом синтаксис всех этих трех языков практи чески одинаков. Более Toro, объектная модель, используемая в С++, аналоrична тем, что используются язьп{ами Java и С#. Наконец, обшее ощущение проrpаммиста при работе на этих языках оказывается Becь ма схожим. Это значит, что если вы знаете С++, вы с леrкостъю изу чите Java или С#. Справедливо также и обратное. Если вы знакомы с Java или С#, изучить С++ не составит труда. Использование в Java и С# синтаксиса и объектной модели С++ имело в виду именно эту цель; оно обеспечило быстрое принятие новых языков сонмом опыт ных С++проrpаммистов. Основное отличие Java и С# от С++ заключается в том, что они предназначены для разных вычислительных сред. С++ разрабатыIал ся с целью обеспечения создания высокопроизводительных проrpамм для KOHкpeTHoro типа процессора и определенной операционной сис темы. Например, если вы хотите написать проrpамму, которая будет выполняться на процессоре Pentium фирмы Intel под упрамением операционной системы Windows, то лучше С++ вы ничеrо не найдете. Java и С# создавались для обеспечения специфических требований, возникаюших при написании проrpамм реальноrо времени, работаю ших в среде сети Интернет (С# также имел своей целью задачу упро шения разработки проrpаммных компонентов). Интернет объединяет MHoro разных типов процессоров и операционных систем. С ero ши роким распространением особое значение приобрела возможность создания межплатформенных, переносимых проrpамм. Первым языком, решаюшим эту задача, стал Java Используя этот язык, можно писать проrраммы, работаюшие в самых различных Bы числительных средах. Jаvапроrpамма может свободно перемещаться в сети Интернет. Однако, ценой, которую вы платите за переноси мость, является эффективность; Jаvапроrpаммы выполняются Meд лен нее проrрамм, написанных на С + + . То :же справедливо и по OT ношению к С#. Вывод таков: если вы разрабатываете высокопроиз водительное проrpаммное обеспечение, используйте С++. Если вам нужны высокомобильные, переносимые проrpаммы, используйте Java или С#. СПРОСИМ У ЭКСПЕРТА Вопрос: Каким образом Java и с# создают межnлатформенные, переноси мые проrpаммы, и почему С++ не обладает этой возможностью? Orвeт: Java и с#, в отличие от С++, MOryr создавать межплатформен ные, переносимые проrpаммы блаrодаря формату объектноrо модуля, фор.. мируемоrо компилятором. В случае с++ компилятор формирует машин.. ный код, который затем может непосредственно выполняться центральным 
20 Модуль 1. ОСНОВЫ С++ процессором. Таким образом, объектный модуль привязан к конкретному процессору и определенной операционной системе. Если вы захотите BЫ полнить С++..проrpамму в дрyrой системе, вам придется перекомпилиро вать исходный текст проrpаммы в машинный код, предназначенный для BЫ бранной вами среды. для TOro, чтобы иметь возможность выполнять C++ проrpамму в различных средах, необходимо создать ДЛЯ нее несколько раз личных вариантов выполнимых модулей. Переносимостъ проrpамм, написанных на Java или С#, объясняется тем, что эти проrpаммы компилируются не в машинный, а в псевдокод, пред ставляющий собой некоторый промежyrочный язык. В случае Java этот про межyrочный язык носит название байткода. В случае С# он называется промежуточным ЯЗЫКОМ Мiсrosоft (Microsoft Intermediate Language, MSIL). В обоих случаях псеводкод выполняется специальной исполняющей системой. Jаvапроrpаммы используют виртуальную машину Java (Java Vn1ual Macblne, JVМ). для С#проrpамм соответствующая система носит название Common Language Runtime (CLR). В результате Jаvапроrpамма может выполняться в любой среде, для которой имеется виртуальная машина Java; С#проrpамма может выполняться в любой среде, если для нее имеется реализация CLR. Поскольку исполняющие системы Java и С# располаrаются в лоrиче ском плане между проrpаммой и центральным процессором, при выполне нии Java и С#проrpамм возникают издержки, которые отсутствуют у C++ проrpамм. Поэтому С++проrpаммы выполняются как правило быстрее, чем эквивалентные им проrpаммы, написанные на Java или С#. Минутная тренировка 1. От KaKoro языка произошел с++? 2. Какое rлавное обстоятельство привело к созданию с++? 3. с++ является родительским языком по отношению к Java и с#. Пра вильно ли это? 1. С + + произошел от С. 2. fлавным обстоятельством, приведшим к созданию С++, явилась возрастающая слож ность проrpамм. 3. Правильно. Цель _ Объектно-ориентированное проrраммирование Центральным свойством с++ является поддержка объектноориенти pOBaHHoro проrpаммирования (ООП). Как уже отмечал ось, ООП по служило стимулом к созданию с++. В силу этоro стоит рассмотреть 
Объектно..ориентированное проrраммирование 21 хотя бы самые базовые принципы ООП перед тем, как вы приступите к написанию даже простейшей С++проrpаммы. Объектноориентированное проrpаммирование вобрало в себя луч шие идеи cтpyктypHoro проrpаммирования и скомбинировало их с He кoroрыми новыми концепциями. В результате возник новый и лучший способ орrанизации проrpаммы. Вообще проrpамму можно орrанизо вать двумя способами, положив во rлаву yrла либо коды (описывающие, что происходит), либо данные (над которыми выполняются действия). Если проrpамма использует только MeToдbI cтpyктypHoro проrpаммиро вания, то обычно в основе ее орraнизации лежат коды. При таком под ходе можно сказать, что "коды воздействуют на данные". Объектноориентированные проrpаммы работают как раз наобо рот. В основе их орrанизации лежат данные, и их принцип заключает ся в том, что "данные упрамяют ДОС1УПом к коду". Используя объект ноориентированный язык, вы определяете данные и процедуры, KO торым разрешается обрабатывать эти данные. В результате тип данноro однозначно определяет, Kaкoro рода операции допустимо BЫ полнять над этим данным. С целью поддержки принципов объектноориентированноro про rpаммирования все объектноориентированные языки, включая С++, обеспечивают три характерных принципа: инкапсуляцию, полимор физм и наследование. Рассмотрим эти принципы подробнее. Инкапсуляция Инкапсуляция представляет собой проrpаммный механизм, который связывает данные с обрабатыIающимии их кодами и зашищает и те, и дрyrие от внешних воздействий и ошибочных действий. В объект ноориентированном языке коды и данные MOryr быть связаны так, что вместе они создают автономный черный ящик. Внутри этоrо ящика содержатся все необходимые данные и коды. При связыва нии таким образом данных и кодов создается объект. Дрyrими сло вами, объект представляет собой устройство, поддерживающее ин капсуляцию. Внутри объекта коды или данные или и те, и дрyrие MOryт иметь aT рибут private, что делает их закрытыми для внешнеrо мира, или public, что открывает эти элементы объекта. Закрытые коды и данные из вестны и доступны только из дрyrих частей Toro же объекта. Дрyrими словами. к закрытым кодам и данным нельзя обратиться ни из Kaкoro фраrмента проrpаммы, существующеrо вне объекта. Если же код или данные объявлены с атрибутом pubIic, то доступ к ним открыт из лю бых частей проrpаммы, несмотря на то, что эти элементы определены внутри объекта. Обычно открытые элементы объекта используются для обеспечения контролируемоro интерфейса с закрытыми элемента ми Toro же объекта. 
22 Модуль 1. ОСНОВЫ С++ в с++ базовой единицей инкапсуляции является класс. Класс определяет содержание объекта. Класс описывает как данные, так и коды, предназначенные для операций над этими данными. с++ ис пользует спецификацию класса при конструировании объектов. Объекты являются экземплярами класса. Таким образом, класс в сущности представляет собой набор чертежей, по которым строится объект. Код и данные, составляющие класс, называются членами класса. Конкретно, членыпеременные, называемые также переменными экземп ляра, ..... это данные, определенные в классе. Членыфункции, или про сто фуНкции..... то коды, предназначенные для операций над данными. функция..... это термин с++, обозначающий подпроrрамму. СПРОСИМ У ЭКСПЕРТА Вопрос: Я сталкивался с обозначением подпроrpаммы термином метод. Метод и функция ..... это одно и то же? Ответ: В общем случае, да. Термин метод получил распространение вместе с языком Java То, что проrpаммисты на с++ называют функцией, Jаvа..проrpаммисты обозначают словом метод. Этот термин стал так ши.. роко использоваться, что ero часто при меняют и по отношению к функ.. циям с++. Полиморфизм Полиморфизм (от rpеческоrо слова, означающеrо "MHoro форм") обо значает средство, позволяющее посредством единоro интерфейса по лучить доступ К целому классу действий. Простым примером поли морфизма может служить рулевое колесо автомобиля. Рулевое колесо (интерфейс) остается одним и тем же, независимо от Toro, какой тип рулевоrо механизма используется в данном автомобиле. Дрyrими сло вами, рулевое колесо действует одинаково для любых автомобилей: с непосредственным приводом на колеса, с rидравлическим усилителем или с реечной передачей. Поворот рулевоro колеса влево застамяет автомобиль двиrатъся влево не зависимо от типа рулевоrо механизма. Достоинство единоrо интерфейса, очевидно, заключается в том, что если вы умеете пользоваться рулевым колесом, вы можете ездить на любых автомобилях. Тот же принцип используется в проrpаммировании. Рассмотрим, например, стек (список, действующий по правилу "первым вошел, по следним вышел"). Пусть вашей проrpамме требуется три стека различ ных видов. Один стек используется для целых чисел, дрyrой для чисел с плавающей точкой, а третий для одиночных символов. Алrоритм реализации всех трех стеков будет одинаков, несмотря на то, что 
Объектно..ориентированное проrраммирование 23 данные, заносимые в разные стеки, различаются. Если вы используете не объектноориентированный язык, вам придется создать три набора проrpамм обслуживания стеков, и для каждоrо набора использовать различающиеся имена. Однако в С++, учитывая наличие полимор физма, вы можете создать один обобщенный набор проrpамм обслу живания стеков, который будет одинаково хорошо работать во всех трех ситуациях. В этом случае, если вы знаете, как использовать один стек, вы точно так же сможете использовать все три. В общем случае концепция полиморфизма часто выражается фра зой "один интерфейс, MHoro методов". Эrо означает возможность раз работать обобщенный интерфейс для rpуппы схожих действий. Поли морфизм уменьшает сложность проrpаммы, обеспечивая обращение посредством одноrо интерфейса к обобщенному классу действий. Выбор же KOHKpemHozo действия (дрyrими словами, метода), соответствующе ro каждой ситуации, воз.лаraется на компилятор. Вам, проrpаммисту, не требуется осуществлять этот выбор вручную. Вам только нужно помнить о наличии обобщенноrо интерфейса и использовать ero в He обходимых случаях. Наследование Наследование является процессом, который позволяет одному объекту приобретатъ свойства дрyrоrо объекта. Важность наследования опре деляется тем, что оно поддерживает концепцию иерархической клас сификации. Леrко сообразить, что большая часть наших знаний по строена по иерархическому (т. е. сверху вниз) принципу. Например, антоновка является частью класса яблок, который, в свою очередь, есть часть класса фруктов; фрукты же входят в более широкий класс пище вых продуктов. Класс пищевые продукты обладает определенными кa чествами (съедобность, пищевая ценность и т. д.), которые, лоrично предположить, приложимы и к ero подклассу фрукты. В дополнение к этим качествам класс фрукты обладает специфическими характери  стиками (сочность, сладость и др.), которые выделяют ero среди дpy rих пищевых продуктов. Класс яблоки определяет качества, xapaктep ные для яблок (растут на деревьях, не являются тропическими продук тами и т. д.). Класс антоновка, в свою очередь, наследует все качества всех предшествующих классов и определяет лишь те качества, которые выделяют антоновку среди прочих яблок. Если не пользоваться иерархией, то каждый объект должен был бы явно определять все свои характеристики. При использовании же Ha следования объект определяет лишь те качества, которые делают ero уникальным в рамках ero класса. Более общие качества он может Ha следовать от родительскоro класса. Таким образом, механизм наследо вания позволяет объекту быть специфическим экземпляром более об щеrо класса. 
24 Модуль 1. ОСНОВЫ С++ Минутная тренировка 1. Перечислите принципы ООП. 2. Что является базовой единицей инкапсуляции в С++? 3. Как называются подпроrpаммы в С++? 1. Принципами ООП являются инкапсуляция, полиморфизм и наследование. 2. Базовой единицей инкапсуляции в С++ является класс. 3. Подпроrpаммы в С++ называются функциями. СПРОСИМ У ЭКСПЕРТА Вопрос: Вы утверждаете, что объектноориентированное проrpаммирова ние (00") является эффективным способом работы с большими проrpам мами. Однако представляется, что использование 00" может привести к существенным издержкам при работе снебольшими проrpаммами. Спра ведливо ли это применительно к языку С++? OrвeT: Нет. Ключевым свойством языка С++ является то, что он позво ляет писать объектноориентированные проrpаммы, но не требует, чтобы вы использовали 00". В этом заключается одно из важных отличий С++ от языков JavajC#, которые cTporo базируются на объектной модели, тpe бующей, чтобы любая проrpамма была хотя бы в минимальной степени объектноориентированной. В противоположность этому С++ предостав ляет вам право выбора. Кроме Toro, большая часть объектноориентиро ванных средств С++ прозрачны во время выполнения nporpaMMbl, поэто му издержки оказываются незначительными или даже вовсе отсутствуют. Цель _ Первая простая nporpaMMa Наконец наступило время заняться проrpаммированием. Начнем с компиляции и запуска приведенной ниже короткой с++проrpаммы. /* Это простая С++проrрамма. Назовите этот файл Sarnple.cpp. */ #inclиde <iostream> using namespace std; / / С++проrрамма начинает свое выполнение с функции main () . int rnain ( ) 
Первая простая nporpaMMa 25 { coиt « "с++ является мощным nporpaммиьtМ средством."; retиrn о; } Вы должны выполнить следующие три шаrа: 1. Ввести текст проrpаммы. 2. Откомпилировать проrpамму. З. Запустить проrpамму. Перед тем, как приступить к описанию этих шаrов, уточним зна чение двух терминов: исходный код и объектный код. Исходным KO дом называется текст проrpаммы, написанный на том или ином язы ке; исходный код пишется и читается проrpаммистом. Объектный код представляет собой машинную, выполнимую процессором форму проrpаммы. Объектный код создается компилятором. ВВОД nporpaMMbI Проrpаммы, обсуждаемые в этой книrе, можно получить на web сайте www.osborne.eom. Однако проrpаммы можно вводить и вруч ную. Собственноручный ВВОД проrpамм поможет вам запомнить ключевые правила. Для ввода проrpамм вручную вам понадобится текстовый редактор (не текстовый процессор). Текстовые процессо ры обычно вместе с текстом сохраняют информацию о форматах. Эта информация поставит в тупик компилятор С++. Если вы рабо таете на платформе Windows, вы можете воспользоваться peдaKTO ром \\brdPad (Блокнот) или любой друrой привычной для вас про rpаммой ввода текста. Имя файла, в котором содержится исходный код, в принципе MO жет быть каким yrодно. Однако С++проrpаммы обычно хранятся в файлах с расширением .ерр. Таким образом, вы можете дать файлу с С++проrpаммой любое имя, но он должен иметь расширение .ерр. Подrотавливая первый пример, назовите исходный файл Sample.epp, чтобы вам было леrче читать последующие пояснения. Для большин ства остальных проrpамм этой книrи вы можете выбирать имена про извольно. КОМПИЛЯЦИЯ nporpaMMbI Способ компиляции файла Sample.epp зависит от компилятора и от Toro, какие опции вы используете. Кроме Toro, мноrие компилято ры, в частности, Мiсrоsоft Visual С++ и Borland С++ Builder, пре доставляют два различающихся способа компиляции проrраммы: 
26 Модуль 1. ОСНОВЫ С++ компилятор командной строки и интеrpированную среду разработ ки (Integrated Development Environment, IDE). Поэтому затрудни тельно привести обобщенные инструкции по компиляции c++ проrpаммы. Вам придется познакомиться с инструкциями к вашему компилятору. Все же мы приведем некоторые рекомендации. Если вы используе те Visual С++ или С++ Builder, то простейший способ компиляции и запуска проrpамм этой книrи заключается в использовании компиля торов командной строки, входящих в эти пакетыI. Например, для KOM пиляции Sample.cpp с помощью Visual С++ вам следует ввести на KO мандной строке следующую команду: с:\.. .>cl GX Sample.cpp Опция GX ускоряет компиляцию. Чтобы использовать компиля тор командной строки Visual С++, вы должны прежде Bcero запустить командный файл VCVARS32.BA"L входящий в пакет Visual С++. (Visual Studio .NET также предоставляет rотовую среду командной строки, которую можно активизировать, выбрав пункт Visual Studio .NET Command Prompt из списка инструментальных средств, перечислен ных в меню Мiсrоsоft Visual Studio .NE которое активизируется BЫ бором Start I Programs [Пуск I Проrpаммы] с панели задач). Чтобы OT компилировать Sample.cpp с помощью С++ Builder, введите следую щую команду: С:\...>Ьсс32 Sample.cpp В результате компиляции вы получите выполнимый объектный код. В среде Windows выполнимый файл будет иметь то :же имя, что и исходный файл, но расширение .ехе. Таким образом, выпол нимый вариант проrраммы Sample.cpp будет находиться в файле Sample.exe. Запуск nporpaMMbI После тoro, как проrpамма откомпилирована, ее можно запустить на выполнение. Поскольку компилятор С++ создает выполнимый объ ектный код, Д1Iя запуска проrpаммы достаточно ввести ее имя на KO мандной строке. Так, для запуска проrраммы Sample.exe введите сле дующую команду: C:\...>Sample Выполняясь, проrрамма выведет на экран следующую строку: с++ является МОЩНЫМ проrраммным средством. 
Первая простая nporpaMMa 27 Если вы используете интеrpированную среду разработки (IDE), вы сможете запустить проrpамму, выбрав в меню пункт Run. Познакомь тесь с инструкциями к вашему конкретному компилятору. Впрочем, проrpаммы этой книrи проще компилировать и запускать с KOMaнд ной строки. Последнее замечание. Проrpаммы этой книrи являются консоль ными приложениями, которые выводят свои результаты не в окно Windows, а на экран DOS. Дрyrими словами, они выполняются в ce ансе командной строки. Разумеется, С++ превосходно подходит для проrpаммирования в системе Windows. Более Toro, он является наи более распространенным языком разработки приклздных проrpамм для системы Windows (приложений Windows). Однако, проrpаммы этой книrи не используют rрафический интерфейс пользователя системы Windows (Graphic User Interface, GUI). Причину этоrо леr ко понять: проrpаммы для Windows по своей природе сложны и Be лики по объему. Для создания даже простейшеrо приложения Windows потребуется от 50 до 70 строк кода, которые можно считать издержками rpафическоrо интерфейса. Если для демонстрации свойств С++ составлять приложения Windows, то каждое будет co держать сотни строк кода. С дрyrой стороны, консольные проrрам мы оказываются значительно короче и именно их обычно использу ют при обучении языку. После Toro, как вы освоили С++, вы CMO жете без труда приложить свои знания к разработке приложений Windows. Первый проrраммный при мер строка за строкой Проrpамма Sample.cpp, несмотря на свою краткость, включает несколько ключевых средств, общих для всех С++проrpамм. Рассмотрим внима тельно каждую часть этой проrpаммы. Проrpамма начинается со C1JIOK /* Это простая C++nporpaMMa. Назовите этот файл Sarnple.cpp. */ эти строки представляют собой "омментарий. Как и большинство языков проrpаммирования, С++ допускает включать в исходный код проrpаммы замечания проrpаммиста. Содержимое комментариев иr норируется компилятором. Цель комментариев  описать или объяс нить выполняемые проrpаммой операции любому, кто будет читать ее текст. В нашем примере комментарий идентифицирует проrpамму. В 
28 Модуль 1 ОСНОВЫ С++ более сложных проrpаммах вы будете использовать комментарии для тoro, чтобы объяснить, ШIЯ чеrо введена в проrpамму каждая ее деталь, и каким образом она выполняет свои функции. Дрyrими словами, с помошью комментариев вы даете описание Toro, что делает ваша про фамма, как бы "с места события". В С++ используются комментарии двух видов. Один, который вы только что видели, называется мноzострочным комментарием. Этот вид комментария начинается со знаков /* (знак деления, за которым стоит звездочка). Такой комментарий заканчивается толь ко коrда в проrpамме встретится комбинация */. Весь текст, распо лаrаемый между этими двумя знаками комментария, полностью иr норируется компилятором. Мноrострочные комментарии MOryт co держать одну или несколько строк. Второй вид комментариев (однострочных) также демонстрируется в нашей проrрамме; мы рассмотрим ero чуть позже. Следуюшая строка кода выrлядит таким образом: #include <iostream> Язык С++ определяет несколько заzоловков, которые содержат информацию либо необходимую, либо полезную для вашей про фаммы. Рассматриваемая проrpамма требует подключения зarоловка <iostream>, который поддерживает систему BвoдaBЫBoдa С++. Этот заrоловок поставляется вместе С компилятором. Заrоловки включа ются в проrpамму с помощью директивы #include. Позже мы pac смотрим более подробно назначение заrоловков и их использование проrpаммой. Далее в проrpамме стоит такая строка: иsing namespace std; Эта строка указывает компилятору, что он должен использовать пространство имен std. Пространства имен являются относительно HO вым добамением к С++. Подробное обсуждение этоrо средства будет проведено позже; здесь мы дадим только ero краткое описание. Про странство имен создает декларативный район, в который помещаются различные элементы проrpаммы. Элементы, объявленные в одном пространстве имен, отделены от элементов, объявленных в дрyrом пространстве. Пространства имен оказывают помощь в орrанизации больших проrpамм. Предложение using информирует компилятор о том, что вы хотите использовать пространство имен std. В этом про странстве имен объявлена вся библиотека стандартноrо С++. Исполь зуя пространство имен std, вы упрощаете доступ к стандартной биб лиотеке. (Поскольку пространства имен являются относительно HO вым средством, ваш компилятор может их не подцерживать. В этом случае см. Приложение С, в котором описан обходный путь) 
Первая простая nporpaMMa 29 Следующая строка проrраммы представляет собой KOMMeHTa рий: / / C++nporpaммa начинает свое выI1лнениеe с функции main () . В этой строке демонстрируется второй вид комментариев, допус тимых в С++: однострочный комментарий. Этот вид комментария начинается со знаков / / и заканчивается в конце строки. Обычно проrpаммисты используют мноrострочные комментарии для вклю чения в проrрамму ДЛИННЫХ детальных пояснений, а одностроч ные  коrда требуется лишь краткое замечание. Разумеется, выбор вида комментария и стиля ero использования  это вопрос вкуса проrpаммиста. Следующая строка, как это поясняется в предшествующем KOMMeH тарии, представляет собой начало проrpаммы: int main ( ) Все С++проrpаммы состоят из одной или нескольких функций. Как уже отмечалось ранее, функция ..... это подпроrpамма. Любая функция С++ должна иметь имя, при этом единственная функция, которая должна включаться в каждую С++проrpамму, называется malп( ). Функция main( )  это то место в проrpамме, rде начинается и rде (чаще Bcero) заканчивается ее выполнение. (Cтporo rоворя, выпол нение С++проrpаммы начинается с вызова функции тain( ), и в большинстве случаев заканчивается, коrда осуществляется возврат из этой функции.) Открывающая фиrypная скобка, которая стоит в сле дующей строке, отмечает начало кода функции таin( ). Слово int, предшествующее main(), задает тип данноro, возвращаемоrо функци ей main( ). Как вы узнаете позже, с++ поддерживает несколько BCтpO енных типов данных, и Ant является одним из них. это обозначение происходит от слова integer (целое). Далее в проrpамму включена строка: coиt « "с++ является мощным проrраммным средством. 11; Это предложение консольноrо вывода. Оно приводит к выводу на экран сообщения С++ является МОЩНЫМ nporpaMMHblM средст- ВОМ. Вывод на экран осуществляется с помощью оператора вывода «о Оператор« действует так, что выражение (каким бы оно ни было), стоящее справа от Hero, выводится на устройство, указан ное слева. COBt представляет собой предопределенный идентифи катор, обозначающий консольный вывод, который, как правило, закреплен за экраном. Таким образом, рассматриваемое предложе ние выводит на экран сообщение. Заметьте, что это предложение заканчивается знаком точки с запятой. Так заканчиваются все предложения с++. 
30 Модуль 1. ОСНОВЫ С++ Сообщение "с++ является мощным проrpаммным средством." представляет собой строку. В С++ строкой называется последователь ность символов, заключенная в двойные кавычки. Строки широко ис пользуется в проrpаммах на С++. Следующая строка проrpаммы осушествляет выход из main( ): return о; Эта строка завершает функцию main( ) и заставляет ее вернуть зна чение О в вызывающий процесс (которым обычно является операци онная система). Для большинства операционных систем возвращае мое значение О указывает на то, что проrpамма завершается правиль но. Дрyrие значения свидетельствуют о завершении проrpаммы в результате возникновения какойлибо ошибки. return является одним из ключевых слов С++ и используется для возврата значения из Функ ции. Все ваши проrpаммы должны при нормальном (т. е. без ошибок) завершении возвращать О. Завершающая фиrypная скобка в конце проrpаммы формально за канчивает проrpамму. Обработка синтаксических ошибок Введите, откомпилируйте и выполните предыдущую проrpамму (если вы этоrо еще не сделали). Как вы, возможно, знаете по опыту, при вводе текста проrpаммы в ваш компьютер леrко случайно напечатать ЧТОТО неправильно. К счастью, если в проrpамме встречается непра вильная строка, компилятор, пытаясь откомпилировать проrpамму, сообщает о синтаксической ошибке. Большинство компиляторов пыта ется найти смысл в проrpамме, что бы вы в ней ни написали, и поэто му сообщение об ошибке, выводимое компилятором, не всеrда будет соответствовать действительной ошибке. Если, например, в предыду щей проrpамме случайно опустить открывающую Фиrypную скобку после main( ), то компилятор может указать на предложение cout, как место синтаксической ошибки. Если вы получаете сообщение о син таксической ошибке, то для нахождения ошибочноrо места вам, воз можно, придется проанализировать несколько последних строк кода. Минутная тренировка 1. [де начинается выполнение С++проrpаммы? 2. Что такое cout? 3. К чему при водит использование предложения #include <iostream>? 
Вторая простая nporpaMM8 31 1. с++..проrpамма начинает выполнение с функции таln( ). 2. cout предстаWIяет собой предопределенный иденrификатор, связанный с консольным выводом. 3. Это предложение включает в текст проrpаммы заrоловок <iostream>, который помер.. живает ввод"вывод. Спросим у эксперта Вопрос: Помимо сообщений об ошибках, мой компилятор может выводить предупреждающие сообщения нескольких видов. Чем предупреждения (warnings) orличаются ОТ оumбок (епоrs) и какие видыI предупрежnaIOЩИХ сооб.. щений следует активизировать? 0ПIeт: Бол.ьumнство С++"компиляторов, помимо сообщений о фатальных синтаксических ОlШfбках, MOryr еще ВЫВОДИТЬ предупреждающие сообщения нескольких видов. Сообшения об оumбках возникают, если в проrpамме име.. IOТCЯ безусловно неправильные места, например, orcyrcтвyeт точка с запятой в конце преможения. Предупрежnающие сообщения rоворят О наличии подоз.. рительноro, но формально правЮIЬНОro кода. Решать, насколько эти лодозре.. ния оправданы, придется вам, проrpаммисту. Предупреждения также используются для указания на неэффективные кон.. струкции или устаревшие средства языка. Обычно вы можете сами выбрать, ка.. KOro рода предупреждающие сообщения вы хотите видеть. проrpаммыI этой книrи написаны в соответствии со cтaндaprным С++, и, если их ввести без оumбок, они при компиляции не бyдyr reнерировать какиелибо предупреж.. дающие сообщения. для примеров этой юrnrи вы можете использовать режим вывода сообще ний, устанавливаемый компилятором по умолчанию ("нормальНЫЙ" режим). Однако при этом вам СТОИТ обратиться к документации по вашему компилято" ру и определить, какие возможности вьшода предупреждающих сообщений имеются в вашем распоряжении. Мноrие компиляторы обладают весьма изо щренными средствами, кorорые MOJy1' помочь вам обнаруживать тонкие ошиб ки перед тем, как ОНИ BыpaCIyr в большие проблемы. Изучение системыI преду.. лреждений вашеro компилятора вполне стоит затраченноro труда и времени. Цель _ Вторая простая nporpaMMa Возможно, в проrpаммировании нет более фундаментальной KOHCT рукиии, чем переменная. ПеремеnnQЯ представляет собой именован ную ячейку памяти, которой может быть присвоено определенное зна чение. В дальнейшем, по ходу выполнения проrpаммы, значение пере менной может быть изменено. Дрyrими словами, содержимое переменной не фиксировано, а подцается изменениям. 
32 Модуль 1 Основы С++ Приводимая ниже проrpамма создает переменную с именем length (Д1Iина), присваивает ей значение 7 и затем выводит на экран сообше ние Длина равна 7. // Использование переменной. #inclиde <iostream> иsing namespace std; nt main ( ) / Объявим переменную int length; // здесь объявляется переменная length = 7; // здесь length присваивается значение 7  Присвоим length значение coиt « " Длина равна "; coиt « length; // здесь выводится 7  Выведем значение length. retиrn о; } Как уже отмечалось ранее, имена в с++ можно выбирать произ вольно. Так, вводя текст проrpаммы, вы можете присвоить файлу с проrpаммой любое удобное Д1Iя вас имя. Например, файлу с этой про rpаммой можно дать имя VarDemo.cpp. В этой проrpамме вводятся две новых концепции. Вопервых, пред.. ложение int length; // здесь объявляется переменная объявляет переменную С именем length типа int (целое число). В с++ все переменные, перед тем, как их использовать, должны быть объявлены, при эroм В объявлении переменной должно быть указано, кaкoro рода значения будyr содержаться в этой переменной. эта называется типом переменной. В нашем случае length может содержать только целочислен ные значения, т е. целые числа в диапазоне от 32768 до 32767. для тoro, чтобы в языке с++ объявить целочисленную переменную, перед ее име нем следует поставить ЮIЮчевое слово int. В дальнейшем вы увидите, чro с++ поддерживает большое количество встроенных типов переменных. (Вы можете создавать и собственные типы данных.) Второе важное средство используется в следуюшей строке кода: length = 7; // здесь length присваивается значение 7 Как следует из комментария, в этом преД1Iожении переменной length присваивается значение 7. В с++ в качестве оператора присваи 
Использование операторов 33 вания используется один знак равенства. Этот оператор копирует зна чение, находящееся справа от Hero, в переменную, имя которой указа но слева от оператора. После выполнения этоrо предложения пере менная length будет содержать число 7. Следующее предложение выводит на экран значение переменной length: cout « lengthi // здесь выводится 7 Если вы хотите вывести на экран значение переменной, просто укажите имя этой переменной справа от знака« в предложении cout. В нашем конкретном случае, поскольку length содержит число 7, именно это число будет выведено на экран. Перед тем, как приступить к изучению следующеrо раздела, попробуйте присвоить length дрyrие значения и проконтролируйте результат. Цель _ Использование операторов Как и большинство друrих компьютерных языков, С++ поддерживает весь диапазон арифметических операторов, которые позволяют вам манипулировать числовыми значениями, используемыми в проrpам ме. К арифметическим операторам относятся: + Сложение Вычитание '" Умножение / Деление Эти операторы используются в С++ В точности так же, как и в обычной алrебре. В приведенной ниже проrpамме оператор'" используется для вы.. числения площади прямоyrольника по данным длине и ширине. // Использование оператора. #inclиde <iostrearn> иsing namespace std; int rnain ( ) { int length; // объявление переменной int width; // объявление'друrой переменной int area; // объявление третьей переменной length = 7; // здесь length лрисваивается значение 7 
34 Модуль 1. Основы С++ width = 5; // здесь width присваивается значение 5 area = length * width; // вычисление площади Присвоим произведение length coиt « "Площадь равна "; и width переменной area. coиt « area; // здесь выводится 35 retиrn о; } в проrpамме объявляются три переменные: length, width и area Пе ременной length присваивается значение 7, а переменной width ..... зна чение 5. Далее проrpамма вычисляет произведение и присваивает по лученное значение переменной area Вывод проrpаммы выrлядит сле дуюшим образом: Площадь равна 35 в нашей проrpамме в действительности не было необходимости вводить переменную area Проrpамму можно было составить следую шим образом: // Упрощенный вариант проrраммы для вычисления площади. #inclиde <iostream> иsing namespace std; int main ( ) { int length; // объявление переменной int width; // объявление друrой переменной length = 7; // здесь length присваивается значение 7 width = 5; // здесь width присваивается значение 5 coиt « "Площадь равна "; coиt « length*width; // здесь выводится 35 retиrn О; непосредственный ВЫВОД length * width. } в этом варианте проrpаммы плошадь вычисляется в предложении cout путем умножения length на wldth. Полученный результат выводит ся на экран. Еше одно замечание перед тем, как мы двинемся дальше: в одном предложении объявления можно объявить две или несколько пере 
Ввод с клавиатуры 35 менных. Просто разделите ИХ имена запятыми. Например, перемен ные length, wldth И area можно бьто объявить таким образом: int length, width, area; // все переменные объявляются в / / одном предложении Объявление нескольких переменных в одном предложении являет ся весьма распространенной практикой в профессионально написан ных С++проrpаммах. Минутная тренировка 1. Должна ли переменная быть объямена перед ее использованием? 2. Покажите, как присвоитъ переменной min значение О. 3. Можно ли в одном предложении объявления объявить более одной пе ременной? 1 Да, в с+ + переменные должны быть объявлены перед их использованием 2 mlП = О; 3 Да, в одном предложении объявления можно объявить две или любое число переменных Цель _ Ввод с клавиатуры В предыдущих примерах данные, над которыми выполнялись дейст вия, определялись в проrраммах явным образом. Например, приве денная выше проrpамма определения площади вычисляет площадь прямоуrольника размером 7 на 5, причем эти размеры являются ча стью самой проrраммы. Разумеется, алrоритм вычисления площади прямоуrолъника не зависит от ero размеров, и наша проrpамма была бы значительно полезнее, если бы она запрашивала у пользо вателя размеры прямоyrольника, а пользователь вводил бы ИХ с клавиатуры. Для тoro, чтобы дать пользователю возможность ввода данных в проrpамму с клавиатуры, вы будете использовать оператор ». это оператор с++ ввода. Чтобы прочитатъ данное с клавиатуры, исполь зуйте предложение TaKoro ВИда: cin » переменнаЯi Здесь cin представляет собой еще один предопределенный иденти фикатор, автоматически предоставляемый С++. cin  это сокращение от coпsole iпput (КОНСОЛЬНЫй ввод); по умолчанию cin связан с клавиату рой, хотя ero можно пере направить и на дрyrие устройства. Вводимое данное ПОС1УПает в указанную в предложении переменную. 
36 Модуль 1. Основы С++ Ниже приводится проrpамма вычисления плошади, которая позво ляет пользователю вводить размеры прямоyrольника: /* Интерактивная проrрамма, вычисляющая площадь прямоуrольника. */ #include <iostrearn> using narnespace stdi int main ( ) { int lengthi // объявление переменной int widthi // объявление друrой переменной cout « "Введите длину: "; cin » length; // ввод длины  Ввод с клавиатуры значения переменнойlепgth cout « "Введите ширину: "; cin »widthi // ввод ширины  Ввод с клавиатуры значения переменной width cout « "Площадь равна "; cout « length * widthi //,вывод площади return о; } Ниже приведен пример работы проrpаммы: Введите длину: 8 Введите ширину: 3 Площадь равна 24 Обратите особое внимание на эти строки: сои t « "Введите длину: "; cin » lengthi // ввод длины Предложение cout запрашивает у пользователя ввод данных. Предложение cin считывает ввод пользователя, сохраняя значение в length. Таким образом, значение, введенное пользователем (в дaH ном случае оно должно быть целым числом) помешается в перемен ную (в данном случае length), указанную справа от оператора ». В результате после выполнения предложения cin переменная length будет содержать длину прямоyrольника. (Если пользователь вводит 
Ввод с клавиатуры 37 нечисловое данное, length будет присвоено значение О.) Предложе ния, запрашивающие и читающие ширину, действуют в точности так же. Некоторые дополнительные возможности BblBOAa До сих пор мы использовали лишь простейшие виды предложений COBt. Однако COBt позволяет формировать значительно более сложные предложения вывода. Вот два полезных усовершенствования. Вопер вых, вы можете с помощью одноrо предложения COBt выводить более одной порции информации. Например, в проrpамме вычисления пло щади для вывода на экран используются две строки: coиt « "Площадь равна "; coиt « length * width; // вывод площади эти два предложения можно написать более компактно: coиt « "Площадь равна" « length * width; В такой редакции в одном предложении COBt используются два опе ратора вывода. В данном случае на экран выводится строка "Площадь равна .., а за ней значение площади. Вообще же вы можете в одно предложение вывода включать сколь yrодно длинные цепочки опера.. ций вывода. Просто используйте отдельный знак < < ШIЯ каждоrо BЫ водимоro элемента. Вторая возможность, предоставляемая преД1Iожениями COBt, за ключается в выводе нескольких строк данных. До сих пор нам не нужно было переходить на следующую строку экрана (дрyrими сло вами, выполнять последовательность возврат каретки ..... перевод строки). Однако необходимость в такой операции возникнет очень скоро. В с++ последовательность возврат каретки ..... перевод строки rенерируется с помощью символа новой строки. Для Toro, чтобы включить в строку этот символ, используйте комбинацию знаков \0 (обратная косая черта, за которой стоит строчная буква n). Чтобы посмотреть, как действует комбинация \0, рассмотрите следующую проrpамму: /* Эта проrрамма демонстрирует действие кода \n, который осуществляет переход в начало следующей строКИ. */ include <iostream> 
38 Модуль 1. Основы С++ иsing narnespace std; int rnain ( ) { coиt « n один\n n ; coиt « "два\n" ; coиt « .. тр и" ; coиt « .. четыре" ; retиrn о; } Эта проrpаммы выведет на экран следуюшее: один два тричетыре Символ новой строки может быть помешен в любое место выводи мой строки, не обязательно в ее конце. Поэкспериментируйте с ним, чтобы досконально разобраться, как он влияет на форму вывода. Минутная тренировка 1. Как выrлядит оператор ввода С++? 2. С каким устройством связан cin по умолчанию? 3. Что обозначает комбинация \о? 1 Оператором ввода является > > 2 cio по умолчанию связан с клавиатурой 3. Комбинация \0 обозначает символ новой строки Еще один тип данных в рассмотренных выше проrpаммах использовались переменные типа iot. Однако такие переменные Moryт содержать только целые числа. В результате ИХ нельзя использовать в тех случаяХ, коrда число может быть дробным. Например, переменная типа int может coдep жать значение 18, но не 18 и три десятых, что записывается в про фаммах как 18.3. К счастью, кроме iot, в С++ определен еше целый ряд различных типов данных. Для тех чисел, которые имеют дробную часть, в С++ определено два типа данных с плаваюшей точкой: t10at и doubIe, представляюшие значения с одинарной и двойной точно стью соответственно. Из этих двух типов doubIe используется, пожа луй, чаше. 
Ввод с клавиатуры 39 Чтобы объявить переменНyIO типа doubIe, используйте предложение вроде следующеrо: double result; Здесь result  это имя переменной, которая имеет тип doubIe. Поскольку переменная result принадлежит типу с плавающей точ кой, она может содержать такие значения, как 88.56, 0.034 или  107.03. Чтобы лучше разобраться в том, чем doubIe отличается от int, pac смотрите следующую проrpамму: /* Эта проrрамма иллюстрирует различия типов int and double. */ #include <iostream> using narnespace std; int rnain () { int ivar; // объявление переменной типа int доиЫе dvar; // объявление переменной с плавающей точкой ivar = 100; // npисвоить ivar значение 100 dvar = 100.0; // присвоить dvar значение 100.0 coиt « "Исходное значение ivar: · « ivar « "\n.; coиt « n Исходное значение dvar: · « dVar « "\n.; cout « 11 \n 11; / / вывести пустую строку . Вывод на экран пустой строки // теперь разделим обе переменные на 3 i var = i var / 3; dvar = dvar I 3.0; coиt « 11 i var после деления: .. « i var « .. \n 11 ; coиt « "dvar после деления: .. « dvar « .. \n" ; retиrn о; } Вывод этой проrpаммы показан ниже: Исходное значение ivar: 100 Исходное значение dvar: 100 
40 Модуль 1 Основы С++ ivar после деления: ЗЗ dvar после деления: ЗЗ.ЗЗЗЗ Коrда на 3 делится ivar, выполняется целочисленное деление и pe зультат оказывается равным 33  дробная часть результата теряется. Однако, коrда на 3 делится dvar, дробная часть сохраняется. В проrpамме использован еще один новый прием. Обратите внима ние на эту строку: cout « "\n"; // вывести пустую строку Это предложение выводит на экран пустую строку. Используйте ero, если вы хотите разделить части вывода пустыIии строками. Спросим у эксперта Вопрос: Зачем в с++ предусмотрены различные типы данных для целых чисел и значений с плавающей точкой? Почему не сделать все числа OДHO ro типа? OrBeT: с++ предоставляет различные типы данных, чтобы вы моrли пи сатъ более эффективные проrpаммы. Целочисленная арифметика работает быстрее вычислений с плавающей точкой. И если вам в проrpамме не нуж ны дробные числа, то нет смысла идти на издержки, связанные с использо ванием типов t10at и doubIe. Кроме Toro, объем памяти, требуемый для раз мещения данных одноrо типа, может быть меньше, чем для данных дрyrоrо типа. Предоставляя различные типы данных, с++ дает вам возможность наилучшим образом использовать системные ресурсы. Наконец, HeKOTO рые алrоритмы требуют использования (или по крайней мере выиrpывают от использования) определенных типов данных. Набор встроенных типов r данных с++ обеспечивает максимальную rибкость при разработке про rpaMM. Проект 1..1 Преобразование футов в метры Хотя рассмотренные выше проrpаммы и иллюстрируют некоторые важные свойства языка с++, в практическом плане они вряд ли по лезны. Однако, несмотря на небольшой объем сведений, полученный вами к этому моменту, вы уже можете написать проrрамму, имеющую практический смысл. В данном проекте мы создадим проrpамму, KO торая будет преобразовывать футыI в метры. Проrpамма запрашивает у 
Ввод с клавиатуры 41 пользователя Д1Iину в футах и выводит на экран значение этой длины, преобразованное в метры. Метр приблизительно эквивалентен 3,28 фута. Таким образом, нам следует использовать данные с плавающей точкой. Для выполнения преобразования проrpамма объявляет две переменные типа doubIe. В одной будет содержаться число футов, во второй  результат преобра зования в метры. Шаr за marOM 1. Создайте новый файл С++, назвав ero FtoM.cpp. (Как уже oтмe чалось, имена файлов можно выбирать произвольно, так что при же лании вы можете использовать и дpyroe имя.) 2. Начните писать проrpамму со строк, которые объясняют назна чение проrpаммы, включите заrоловок <iostream> и задайте простран ство имен std: /* Проект 11 Эта проrрамма преобразует футы в метры Назовите эту проrрамму FtoM.cpp. */ #include <iostream> using namespace std; 3. Начните функцию main( ) с объявления переменных f и т: int main () { double f; // содержит длину в футах double m; // содержит результат преобразования в метры 4. Добавьте код, который введет число футов: сои t < < .. Введите длину в футах: "; cin » f; // читает числО футов 5. Добавьте код, который выполнит преобразование и выведет pe зультат: m = f / 3.28; // преобразование в метры cout « f « " футов составляет" «m« " метров."; 6. Завершите проrpамму, как это показано ниже: 
42 Модуль 1 ОСНОВЫ С++ retиrn о; } 7. Законченная проrpамма должна выrлядеть таким образом:. /* Проект 11 Эта проrрамма преобразует футы в метры Назовите эту проrрамму FtoM.cpp. */ #inclиde <iostream> иsing namespace std; int main () { доиЫе f; // содержит длину в футах доиЫе m; // содержит результат преобразования в метры coиt« "Введите длину в футах: "; cin » f; // читает число футов m = f / 3.28; // преобразование в метры coиt « f « " футов составляет" «m« .. метров."; retиrn о; } 8. Откомпилируйте и запустите проrрамму. При мер ее вывода: Введите длину в футах: 5 5 футов составляет 1.52439 метров. 9. Попробуйте ввести друrие значения. После этоrо измените про фамму так, чтобы она преобразовывала метры в футы. Минутная тренировка 1. Каково ключевое слово с++ для целочисленноrо типа данных? 2. Что такое doubIe? 3. Как вывести на экран пус1)'lO строку? 1 Для целочисленных данных предумотрен тип int 2 doubIe  это ключевое слово ШIЯ типа данных с rтаваюшей точкой и с двойной точностью 3 Для вывода на экран пустой строки восполь JуИтесь \п. 
Два управляющих предложения 4з Цель _Два управляющих предложения Каждая функция выполняется предложение за предложением, от начала к концу. Однако с помощью различных управляющих пред ложений, включенных в язык с++, можно изменить этот поток выполнения. Позже мы рассмотрим управляющие предложения детально, здесь же кратко остановимся только на двух таких пред.. ложениях, так как мы будем использовать их в дальнейших приме рах проrрамм. Предложение if с помощью условноrо предложения языка с++ if можно селективно выполнять определенный участок проrpаммы. Предложение If языка с++ действует так же, как и в любом дрyrом языке. Оно, в частности, синтаксически идентично предложениям If языков с, Java и С#. Ero простейшая форма выrлядит следующим образом: if(условие) предложеllие; Здесь условие предстамяет собой выражение, результат Koтoporo воспринимается либо как истина (true), либо как ложь (false). В С++ истина соответствует ненулевому значению, а ложь  нулевому. Если условие истинно, предложение будет выполняться. Если условие лож но, предложение не выполняется. Например, следующий фраrмент выводит на экран фразу 10 меньше 11, потому что 10 действительно меньше 11. if(10 < 11) cout « "10 меньше 11"; Рассмотрим, однако, следующее предложение: if(10 > 11) cout « "эта строка не выводится"; в этом случае, поскольку 10 не больше 11, предложение cout BЫ полнятъся не будет. Разумеется, операнды внутри предложения if не обязательно должны быть константами. В качестве операндов можно использовать и переменные. В с++ определен полный набор операторов отношения, которые используются в условных выражениях. Эти операторы перечислены ниже: 
44 Модуль 1 ОСНОВЫ С++ ,= Значение Меньше чем Меньше чем или равно Больше чем Больше чем или равно Равно Не равно Оператор < <= > >= Обратите внимание на то, что проверка на равенство требует ис пользования двойноrо знака равенства. Ниже приводится проrpамма, иллюстрирующая использование пред ложения if: / / Демонстрация использования i f . #inclиde <iostrearn> иsing narnespace stdi int main () { int а, Ь, с; а = 2; Ь = 3; if(a < Ь) coиt « "а меньше чем b\n";  Предложение if // это предложение ничеrо не выведет на экран if(a  Ь) coиt « "это вы не увидите\n"i coиt « "\n"; с = а  Ь; // с содержит 1 coиt « "с содержит l\n"; if(c >= О) coиt « "с неотрицатеЛЬНО\n"i if(c < О) coиt « "с отрицатеЛЬНО\n"i coиt « "\n"; с = ь  а; // с теперь содержит 1 coиt « "с содержит l\n"i if(c >= О) cout « "с неотрицательно\п"; if(c < О) coиt « "с отрицательно\п"; retиrn О i } 
Два управляющих предложения 45 Ниже приведен вывод этой проrpаммы: а меньше чем Ь с содержит  1 с отрицательно с содержит 1 снеотрицательно Цикл for с помощью конструкции цикла вы можете повторно выполнять HeKO торую последовательность кодов. В с++ предусмотрен боrатый accop тимент конструкций циклов. Здесь мы рассмотрим только цикл for. Если вы знакомы с с# или Java, вам будет приятно узнать, что цикл for действует в С++ точно так же, как и в ЭТИХ двух языках. Ниже приве дена простейшая форма цикла for: fоr(инициализация; условие; приращение) предложение; В этой конструкции инициализация устанавливает начальное значе ние управляющей переменной. условие является выражением, которое анализируется в каждом шаrе цикла. приращение представляет собой выражение, определяющее, как будет изменяться значение управляю щей переменной в каждом шаrе цикла. Приводимая ниже проrpамма демонстрирует простой цикл for. Она выводит на экран числа от 1 до 100. // Проrрамма, иллюстрирующая цикл for. #inclиde <iostrearn> иsing namespace stdi int rnain ( ) { /ЭrО цикл for for(coиnt=l; coиnt <= 100; coиnt=coиnt+l) cout « coиnt « " "; return о; int coиnt; } в этой проrpамме переменная count инициализируется числом 1. В каждом шаrе цикла проверяется условие count <= 100 
46 Модуль 1. ОСНОВЫ С++ Если результат истинен, значение count выводится на экран, после чеrо count увеличивается на 1. Коrда COU"t достиrает значения, боль шеrо 100, условие становится ложным и цикл прекрашает свое выполнение. В профессионально написанной С++проrpамме вы почти никоrда не встретите предложение вроде cOllnt=collnt+l потому что с++ включает в себя специальный инкрементный опера тор, выполняющий эту операцию более эффективно. Инк.рементный оператор записывается как ++ (два последовательных знака плюс). Этот оператор увеличивает операнд на 1. С ero помошью предыдущее предложение for запишется таким образом: for(count=l; count <= 100; count++) cout « count « " "; Именно такая форма будет использоваться в дальнейшем в этой книrе. В С++ имеется и дек.рементный оператор, который записывается как ..... ...... Он уменьшает операнд на 1. Минутная тренировка 1. Зачем служит предложение if? 2. Зачем служит предложение for? 3. Назовите операторы отношения С++. 1. В С++ предложение if является условным. 2. Предложение for образует одну из форм цикла в с+-+ . 3. В с++ имеются следуюшие операторы отношения: ==-, !==, <, >, <-= и >== Цель IIIIИспопьзование npOrpaMMHbIX бпоко,В Еще одним ключевым элементом С++ является проzраммный блок. Проrpаммный блок представляет собой rруппу из двух или более предложений. Блок образуется заключением ero предложений 
Использование nporpaMMHblX блоков 47 в фиrypные скобки. Блок, будучи создан, становится лоrической единицей, которую можно использовать в любом месте, rде может находиться одиночное предложение. В частности, блок может слу жить целевым объектом (мишенью) в предложениях if и for. Pac смотрим пример предложения if: if (w < Ь) { v = w * h; w = о; } в этом фраrменте если w меньше чем Ь, выполняются оба пред ложения внутри блока. Таким образом, эти два предложения BHYT ри блока образуют лоrическую единицу, в которой одно предложе ние не может выполняться без BToporo. Во всех случаях, коrда вам требуется лоrически связать два или несколько предложений, вы заключаете их в блок. С использованием проrpаммных блоков мноrие алrоритмы реализуются с большей ясностью и эффектив ностью. Рассмотрим проrpамму, которая использует проrpаммный блок для предотвращения деления на о: / / Демонстрация проrраммноrо блока. #include <iostream> иsing namespace stdi int rnain () { doиble resиlt, n, d; coиt « "Введите значение: "; cin » n; coиt « "Введите делитель: "; cin » d; Мишенью этоrо предложе.. ния if является весь блок / / целевым объектом предложения i f является блок if(d 1= О) { coиt « "d не равно нулю, поэтому делить можно" « "\n"; resиlt = n / d: coиt « n « " / 11 « d « 11 равно" « result; } retиrn о; } 
48 Модуль 1. ОСНОВЫ С++ Ниже приведен пример проrона проrpаммы: Введите значение: 10 Введите делитель: 2 d не равно нулю, поэтому делить можно 1 О / 2 равно 5 в этом случае мишенью предложения if является проrpаммный блок, а не просто одиночное предложение. Если проверяемое в пред ложении if условие истинно (как это имеет место в примере проrона проrpаммы), то выполняются все три предложения, заключенные в блок. Попробуйте задать в качестве делителя ноль и посмотрите на pe зультат. В этом случае весь код внутри блока не выполняется. Спросим у эксперта Вопрос: Приводит ли использование проrpаммноrо блока к потере эф.. фективности по времени? Дрyrими словами, потребляют ли скобки { и } дополнительное время при выполнении проrpаммы? OrвeT: Нет. Проrpаммный блок не добавляет никаких издержек. Наобо рот, блаrодаря тому, что проrраммные блоки способствуют упрощению не.. которых алrоритмов, их использование обычно увеличивает скорость BЫ полнения и эффективность. Как будет показано далее, проrpаммные блоки имеют дополнитель ные свойства и области использования. Однако основное их назначе ние заключается в создании лоrически неразрывных единиц кода. Знак точки с запятой и позиционирование в с++ знак точки с запятой обозначает конец предложения. Дрyrими словами, каждое предложение должно заканчиваться точкой с запя той. Как вы знаете, блок предстзмяет собой последовательность лоrИ чески связанных предложений, окруженных открывающей и закры вающей фиrypной скобкой. Блок не заканчивается знаком точки с за пятой. Поскольку в блок входит rpуппа предложений, и каждое из них заканчивается точкой с запятой, то вполне лоrично, что весь блок не имеет в конце точки с запятой; на конец блока указывает закрываю шая фиrypная скобка. с++ не рассматривает конец строки текста как конец предложе ния ..... ТОЛЬКО точка с запятой заканчивает предложение. По этой при 
Использование nporpaMMHblX блоков 49 чине не имеет значения, в каком месте строки вы размешаете предло жение. Например, для С++ х = у; у = у + 1; cout « х « " " « у; будет значить то же самое, что х = у; у = у + 1; cout « х « " " « у; Более Toro, отдельные элементы предложения тоже MOryт распола rаться на отдельных строках. Например, приведенный ниже фраrмент вполне правилен: cout « "Это длинное предложение. Сумма равна Ь + с + d + е + f; " « а + Разделение длинной строки на несколько более коротких часто ис пользуется для повышения наrлядности проrpаммы. Кроме TOro, умышленное деление на строки помоrает избежать автоматическоrо перехода на следующую строку в неудачном месте. Практика использования отступов Вы моrли заметить, что в рассмотренных проrpаммах некоторые предложения писались с отступом. С++ относится к языкам свобод НОЙ формы, что означает возможность располаrать предложения в любом месте строки. Однако с течением лет в практике проrpамми стов утвердился определенный стиль использования ОТС1УПОВ, суще ственно способствующий ясности и удобочитаемости текста про rpзммы. В настоящей книrе мы придерживаемся этоrо стиля и peKO мендуем вам делать то же. Соrласно общепринятому стилю вы отступаете вправо на один уровень после каждой открываюшей фи rypной скобки и перемещаетесь назад на один уровень после каждой закрывающей фиrypной скобки. Есть, однако, определенные предло жения, в которые удобно вводить дополнительные отступы; мы pac смотрим их позже. Минутная тренировка 1. Как создается проrpаммный блок? Для чеrо он предназначен? 2. В С++ предложения заканчиваются 3. Все предложения С++ должны начинаться и заканчиваться на одной строке. Правильно ли это? 
50 Модуль 1. Основы С++ 1. Блок начинается открываюшей фиryрной скобкой ( { ) и заканчивается закрываюшей фиrypной скобкой ( } ). Блок служит ШIЯ создания лоrической единицы кода. 2. Точкой с запятой 3. Неправильно. Проект 1..2 Создание таблицы преобразования футов в метры Этот проект демонстрирует использование цикла for, предложения if, а также проrpаммных блоков при разработке проrpаммы, выводящей на экран таблицу преобразования футов в метры. Таблица начинается с 1 фута и заканчивается на 100 футах. После каждЫХ 10 футов выводится пустая строка. С этой целью в проrpамме используется переменная с именем couDter, которая отсчитывает выводимые строки. Обратите особое внимание на эту переменную. Шаr за marOM 1. Создайте новый файл с именем FtoMTabIe.cpp. 2. Введите следующую проrрамму: /* Проект 1  2 Эта проrраммы выводит на экран таблицу npеобразования футов в метры. Назовите эту nporpaммy FtoMTable.cpp. */ #inclиde <iostream> using namespace stdi int main () { doиble f; / / содержит длину в футах doиble m; / / содержит результат преобразования в метры int counteri coиnter = о i  Счетчик строк первоначально устанавливается в О. 
Использование nporpaMMHblX блоков 51 for(f = 1.0; f <= 100.0; f++) { rn = f / 3.28; / / преобразуем в метры coиt « f « " футов составляет 11 « m « 11 метров. \n" ; Инкремент счетчика строк в coиnter++;  каждом шаrе цикла / / после каждых 10 строк вывести пустую строку if (counter == 10) { 111 coиt « "\n"; // вывод пустой строки coиnter = о; // сброс счетчика строк Если counter рав.. но 10, вывести пустую строку. } } retиrn о; } 3. Обратите внимание на использование переменной CouDter для вывода пустой строки после каждЫХ десяти строк. Первоначалъно эта переменная устанавливается в О вне цикла for. Внутри цикла она ин крементируется после каждоrо преобразования. Коrда значение CouDter достиrает 10, выводится пустая строка, CouDter сбрасывается в О и процесс продолжается. 4. Orкомrmлиpyй're и запустите проrpамму. Ниже приведена часть ее вывода. 06pamтe внимание на форму вывода чисел. В теХ случаях, KOrдa число оказывается дроБным, оно вывoи'Icяя С десяrnчной дробной частью. 1 футов составляет 0.304878 метров. 2 футов составляет 0.609756 метров. 3 футов составляет 0.914634 метров. 4 футов составляет 1.21951 метров. 5 футов составляет 1.52439 метров. 6 футов составляет 1.82927 метров. 7 футов составляет 2.13415 метров. 8 футов составляет 2.43902 метров. 9 футов составляет 2. 7439 метров. 10 футов составляет 3.04878 метров. 11 футов составляет 3.35366 метров. 12 футов составляет 3.65854 метров. 13 футов составляет 3.96341 метров. 14 футов составляет 4.26829 метров. 15 футов составляет 4.57317 метров. 16 футов составляет 4.87805 метров. 17 футов составляет 5.18293 метров. 18 футов составляет 5.4878 метров. 19 футов составляет 5.79268 метров. 20 футов составляет 6.09756 метров. 
52 Модуль 1. ОСНОВЫ С++ 21 футов составляет 6.40244 метров. 22 футов составляет 6.70732 метров. 23 футов составляет 7. 0122 метров. 24 футов составляет 7.31707 метров. 25 футов составляет 7.62195 метров. 26 футов составляет 7.92683 метров. 27 футов составляет 8.23171 метров. 28 футов составляет 8.53659 метров. 29 футов составляет 8.84146 метров. 30 футов составляет 9.14634 метров. 31 футов составляет 9.45122 метров. 32 футов составляет 9.7561 метров. 33 футов составляет 10.061 метров. 34 футов составляет 10.3659 метров. 35 футов составляет 1 О . 6707 метров. 36 футов составляет 10.9756 метров. 37 футов составляет 11.2805 метров. 38 футов составляет 11.5854 метров. 39 футов составляет 11.8902 метров. 40 футов составляет 12.1951 метров. 5. Попробуйте самостоятельно изменить проrpамму так, чтобы она выводила пустую строку после каждЫЙ 25 строк вывода. Цель _Знакомимся с функциями С++проrpамма конструируется из строительных блоков, называемых функциями. В Модуле 5 мы обсудим функции более детально, здесь же оrpаничимся кратким обзором. Начнем с определения термина ФУНК цuя: функцией называется подпроrpамма, содержашая одно или He сколько предложений С++. у каждой функции есть имя, и это имя используется для вызова функции. Для Toro, чтобы вызвать функцию, просто укажите в исход ном тексте вашей проrраммы ее имя, сопровождаемой парой крyrлых скобок. Предположим, что некоторая функция названа MyFunc. Для Toro, чтобы вызвать MyFunc, вы должны написать: MyFunc(); Вызов функции при водит К тому, что управление передается этой функции, в результате чеrо выполняется код, содержашийся внутри функции. После Toro, как выполнятся все предложения, составляю 
Знакомимся с функциями 53 щие функцию, управление возвращается назад в вызывающую про фамму. Таким образом, функция выполняет некоторую задачу, в KO торой возникла необходимость по ходу проrpаммы. Некоторые функции требуют указания одноro или нескольких ap zуменmов, которые передаются функции при ее вызове. ApryмeHT представляет собой значение, передаваемое функции. Перечень apry ментов указывается внyrpи крyrлых скобок В предложении вызова функции. Если, например, функция MyFunc( ) требует в качестве apry мента целое число, то приведенная ниже строка вызовет MyFunc( ) с передачей ей в качестве apryмeHTa числа 2: MyFиnc(2)i Если apryмeHТOB два или больше, они разделяются запятыми. В этой книrе термин список арzументов будет относиться к перечню ap ryмeHToB, разделяемых запятыIи.. Учтите, что не всем функциям тpe буются apryмeHты. Если apryмeHThI не нужны, при вызове функции скобки остаются пустыми. Функция может возвращать значение в вызывающий ее код. Не все функции возвращают значение, хотя мноrие это делают. Значение, возвращаемое функцией, может быть присвоено переменной в вызы вающем коде путем помещения вызова функции с правой стороны предложения присваивания. Если, например, функция MyFunc( ) ВОЗ вращает значение, ее можно вызвать таким образом: х = MyFunc ( 2) ; это предложение выполняется следующим образом. Прежде вcero вызывается MyFuDC( ). Коrда осуществляется возврат из функции, воз вращаемое ею значение присваивается переменной х. Допустимо тaK же использовать вызов функции в выражении. Например, х = MyFиnc(2) +10; в этом случае возвращаемое функцией MyFunc( ) значение при бавляется к 10, и результат присваивается переменной х. Вообще всеrда, коrда имя функции встречается в предложении, она вызыва ется автоматически, что дает возможность получить возвращаемое ею значение. Повторим: apZYMeHтOM называется значение, передаваемое в Функ цию. Возвращаемое значение  это данное, которое передается назад в вызывающий код. Ниже приведена короткая проrpамма, демонстрирующая вызов функции. Она использует одну из встроенных в с++ функций, KOTO рая называется abs( ), чтобы вывести на экран абсолютное значение 
54 Модуль 1 ОСНОВЫ С++ числа. Функция abs() принимает один apryмeHT, преобразует ero в аб солютное значение и возвращает результат. // Использование функции abs(). #inclиde <iostream> #inclиde <cstdlib> иsing narnespace std; int rnain ( ) { int resиlt; result = abs (10) ;  Здесь вызывается функция аЬа( ) и вы.. полняется присваивание возвращаемоrо ею значения переменной result coиt « result; retиrn о; } В приведенной проrpамме значение .....10 передается функции abs( ) в качестве apryмeHTa. Функция abs( ) получает apryмeHT, с которым она вызывается, и возвращает ero абсолютное значение, которое в данном случае составляет 10. Это значение присваивается переменной result. В результате проrpамма выводит на экран" 10". Обратите внимание на включение в проrpамму заrоловка <cstdlib>. Этот заrоловок требуется для функции abs( ). Во всех случаях, если вы используете библиотечную функцию, вы должны включать заrоловок, в котором она описана. Вообще rоворя, вы будете использовать в своих проrpаммах Функ ции двух видов. Один вид функций ..... это те функции, текст КОТОРЫХ вы пишете сами; примером такой функции является main( ). Позже вы научитесь писать и дрyrие прикладные функции. Реальные С++про фаммы всеrда содержат большое число функций, написанных пользо вателем. Функции BToporo вида предоставляются компилятором. Примером такой функции является abs( ), использованная в предыдущей про фамме. Проrpаммы, которые вам придется писать, всеrда будут coдep жать как функции, созданные вами, так и функции, предоставленные компилятором. Обозначая функции в тексте этой книrи, мы пользовались и будем пользоваться впредь соrлашением, общепринятыIM в среде проrpамми стов на С++. После имени функции ставятся крyrлые скобки. Если, например, функция имеет имя getval, то при использовании этою име ни в тексте книrи мы будем писать getval( ). Такой прием поможет вам отличать имена переменных от имен функций. 
Ключевые слова С++ 55 Библиотеки С++ Как только что было сказано, функция abs( ) поставляется вместе с Ba шим компилятором С++. Эта функция, как и MHoro дрyrих, хранится в стандартной библиотеке. Мы на протяжении всей книrи будем ис пользовать в примерах проrpамм различные библиотечные функции. В С++ определен большой набор функций, содержащихся в CTaH дартной библиотеке функций. Эти функции выполняют различные за дачи общеro назначения, включая операции BBoдaBЫBoдa, математи ческие вычисления и обработку строк. Если вы используете библио течную функцию, компилятор с++ автоматически связывает объектный код этой функции с объектным кодом вашей проrpаммы. Стандартная библиотека с++ очень велика; она содержит большое количество функций, которые наверняка потребуются вам при напи сании проrpамм. Библиотечные функции действуют как строительные блоки, из которых вы собираете нужную вам конструкцию. Вам следу ет познакомиться с документацией к библиотеке вашеrо компилятора. Вас может удивить, насколько разнообразны библиотечные функции. Если вы напишите функцию, которую будете использовать снова и снова, ее также можно сохранить в библиотеке. В дополнение к стандартной библиотеке функций, каждый компи лятор с++ содержит библиотеку 1CJIаССО8, являющуюся объектноори ентированной библиотекой. Однако, чтобы начать использовать эту библиотеку, вам придется обождать, пока вы познакомитесь с класса ми и объектами. Минутная тренировка 1. Что такое функция? 2. Функция вызывается указанием ее имени. Правильно ли это? 3. Что представляет собой стандартная библиотека функций с++? 1 Функция  это подпроrpамма, содержащая одно или несколько предложений с+ + 2 Правильно 3 Стандартная библиотека с++ предстааляет собой собрание фУНЮlИЙ, предосrавляемых всеми компиляторами с++. Цель _ Ключевые слова ++ к настоящему времени в стандартном с++ определены 63 ключевых слова; все они приведены в табл. 1  1. Вместе с формальным синтакси сом с++ они образуют язык проrpаммирования С++. Ранние версии 
56 Модуль 1. ОСНОВЫ С++ с++ определяли еше ключевое слово overload, но теперь оно устарело. Имейте в виду, что язык с++ различает прописные и строчные буквы, при этом он требует, чтобы все ключевые слова вводились строчными буквами (на нижнем реrистре клавиатуры). Таблица 1-1. Ключевые слова С++ asm auto bool break case catch char class const const cast continue default delete do doubIe dynamic  cast else епит explicit export extern false float for friend goto if inline int long mutabIe namespace new operator private protected pubIic register reinterpret  cast return short signed sizeof static static  cast struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile wchar  t while Цель _ Идентификаторы в с++ к идентификаторам относятся имена, присвоенные функциям, переменным или любым дрyrим определенным пользователем объек там. Идентификатор может состоять из одноrо или нескольких СИМВО лов. Имена переменных допустимо начинать с любой буквы латинско ro алфавита или со знака подчеркивания. Далее может идти буква, цифра или знак подчеркивания. Знаки подчеркивания обычно ис пользуются для повышения наrлядности имен переменных, например, linecount (счетчикстрок). Прописные и строчные буквы рассматри ваются компилятором, как различные символы; таким образом, для с++ myvar и My\2r являются различающимися именами. При назна чении имен переменным или функциям следует иметь в виду важное оrpаничение: ключевые слова с++ нельзя использовать в качестве идентификаторов. Кроме Toro, нельзя использовать в произвольных целях предопределенные идентификаторы, такие, как cout. 
Идентификаторы 57 Вот несколько примеров правильных идентификаторов: Test ир х у2 top ту  var Maxlncr Slmplelnterest23 Не забывайте, что недопустимо начинать идентификатор с цифры. Так, 9800К не может служить идентификатором. Хороший стиль про rpаммирования требует, чтобы ваши идентификаторы отражали суще ство обозначаемых ими объектов. Минутная тренировка 1. Что является ключевым словом, for, For или FOR? 2. Какие символы может содержать идентификатор с++? 3. Являются имена index21 и Index21 одним и тем же идентификатором? 1 Ключевым словом является for В С t t все ключевые слова СОС10ЯТ из строчных букв 2 Иден lификаl0р с++ может содержать буквы латинскоrо алфавита, цифры и знак под черкивания 3 Нет, с++ различает символы ни:жнеrо и Bepxнero реrистров ""Вопросы для самопроверки 1. rоворят, что с++ находится в центре cOBpeMeHHoro мира проrpам мирования. Поясните это утверждение. 2. Компилятор с++ создает объектный код, который может быть He посредственно выполнен компьютером. Справедливо ли это YТBep ждение? 3. Каковы три основных принципа объектноориентированноrо про rpаммирования? 4. В какой точке С++проrpаммы начинается ее выполнение? 5. Что такое заrоловок? 6. Что такое <iostream>? Каково назначение следующей проrpамм" ной строки? #include <lostream> 7. Что такое пространство имен? 8. Что такое переменная? 9. Какое (или какие) из приведенных ниже имен переменных непра вильны? а. count Ь.  count 
58 Модуль 1. ОСНОВЫ С++ с. count27 d. 67 count е. if 10. Каким образом в проrpамму вводится однострочный комментарий? А мноrocтрочный? 11. Приведите обобщенную форму предложения if. Выполните то же для цикла for. 12. Каким образом в проrpамме задается проrpаммный блок? 13. Сила тяжести на Луне составляет приблизительно 17% от земной. Напишите проrpамму, которая выведет на экран таблицу перевода земных фунтов в их лунные эквиваленты (1 фунт равен 453 rpaм мам, хотя в данном случае это не имеет значения). Вычислите 100 значений таблицы от 1 до 100 фунтов. После каждых 25 фунтов BЫ ведите пустую строку. 14. [од на Юпитере (т. е. время, требуемое Юпитеру для полноrо обо рота BOкpyr Солнца) составляет около 12 земных лет. Напишите проrpамму, которая преобразует юпитерианские rоды в земные. Предоставьте возможность пользователю вводить с клавиатуры число юпитерианских лет. Обеспечьте возможность ввода дробно ro числа лет. 15. Как изменяется ход выполнения проrpаммы, коrда вызывается функция? 16. Напишите проrpамму, которая усредняет абсолютные значения пяти произвольных чисел, вводимых пользователем. Выведите на экран результат. 
Модуль 2 Знакомимся с данными, типами и операторами Цели, достиrаемые в ЭТОМ модуле 2.1 Изучить типы данных С ++ 2.2 Научиться использовать литералы 2.3 Узнать о создании инициализированных переменных 2.4 Рассмотреть подробнее арифметические операторы 2.5 Познакомиться с лоrическими операторами и операторами отноше ний 2.6 Изучить возможности оператора присваивания 2.7 Узнать об операциях составных присваиваний 2.8 Разобраться в преобразовании типов в операциях присваивания 2.9 Разобраться в преобразовании типов в выражениях 2.10 Научиться использовать приведение типа 2.11 Освоить правила применения пробелов и скобок 
60 Модуль 2. Знакомимся с данными, типами и операторами Я дром языка проrpзммирования являются ero типы данных и операто ры. Эти элементы определяют rраницы языка и области ero примене ния. Как и следует ожидать, с++ подаерживает боrатый набор типов данных и операторов, что делает ero удобным для решения caMoro ши pOKoro Kpyra задач проrpаммирования. Типы данных и операторы представляют собой весьма обширный предмет. Мы начнем здесь с изучения фундаментальных типов данных с++ и ero наиболее широко используемых операторов. Мы также pac смотрим более подробно переменные и выражения. Почему так важны типы данных Тип, которому принадлежит конкретная переменная, важен пото му, ЧТО он определяет допустимые операции и диапазон значений, KO торые может принимать эта переменная. В с++ определено несколько типов данных, и каждый тип имеет вполне определенные характери стики. Поскольку типы данных отличаются дрyr от дрyrа, все пере менные должны быть объявлены перед их использованием, и объяв ление переменной всеrда содержит спецификатор типа. Компилятор требует предоставление ему этой информации для Toro, чтобы он Mor сrенерировать правильный код. В с++ отсутствует концепция "безти повых" переменных. Вторая причина важности типов данных заключается в том, что He которые базовые типы данных тесно привязаны к строительным бло кам, которыми оперирует компьютер: байтам и словам. Таким обра зом, с++ позволяет вам работать с теми же типами данных, что и сам процессор. Этим, в частности, объясняется возможность писать на с++ чрезвычайно эффективные проrpаммы системноrо уровня. Цель _Типы данных С++ с++ предоставляет встроенные типы данных, соответствующие цe лым, символам, значениям с плавающей точкой и булевым перемен ным. Обычно именно TaKoro рода данные хранятся и обрабатываются проrpаммой. Как вы узнаете из дальнейших разделов этой книrи, С++ позволяет вам конструировать более сложные типы, к которым OTHO сятся классы, структуры и перечислимые типы, однако и эти типы в конечном счете составляются из встроенных типов данных. Ядром системы типов с++ являются семь базовых типов данных, перечисленных ниже: 
Типы данных С++ 61 Тип char wchar t I"t float doubIe bool vOld Значение Символ "Широкий" символ Целое Число с плавающей точкой Число с плавающей точкой удвоенной точности Булева переменная Пустой тип с++ допускает указание перед некоторыми базовыми типами MO дификаторов. Модификатор изменяет значение базовоrо типа, более точно приспосабливая ero к потребностям определенных ситуаций. Модификаторы типов данных перечислены ниже: signed (со знаком) unsigпed (без знака) long (длинный) short (короткий) Модификаторы signed, unsigned, long и short приложимы к типу int. Модификаторы signed и unsigned приложимы к типу char. Тип doubIe может быть модифицирован описателем long. В табл. 2 1 перечисле ны все допустимые комбинации базовых типов и модификаторов типа. В таблице показаны также rарантированные минимальные диа пазоны значений для каждоrо типа, как это специфицировано CTaH дартом ANSI/ISO С++. Таблица 2-1. Все числовые типы данных, определенные в С++, и их минимальные rарантированные диапазоны значений соrласно спецификации стандарта ANSI/ISO С++ Тип Минимапьный диапазон char От 127 до 127 u"slg"ed char ОтО до 255 slg"ed char От 127 до 127 I"t От З2767 до 32767 u"slg"ed I"t От О до 65535 slg"ed I"t То же, что int short I"t От 32767 до 32767 u"slg"ed short I"t От О до 65535 slg"ed short I"t Т о же, что short int lo"g I"t От 2147483647 до 2147483647 
32 Модуль 2. Знакомимся с данными, типами и операторами signed long int unsigned long int 110at doubIe long doubIe т о же, что long int От О до 4294967295 От 1 E37 до 1 Е+37, с шестью значащими цифрами От 1 E37 до 1 Е+37, с десятью значащими цифрами От 1 E37 до 1 Е+37, с десятью значащими цифрами Обратите внимание на то, что минимальные диапазоны, указанные в табл. 2..1, значат именно это: минимальные диапазоны. Компилятор с++ вполне может увеличить один или несколько диапазонов допус.. тимых значений, и большинство компиляторов так и поступает. Та.. ким образом, диапазоны допустимых значений данных в с++ зависят от реализации. Например, на компьютерах, выполняюших арифмети" ческие операции в дополнительном коде (что характерно для боль.. шинства современных компьютеров) диапазон чисел int составит по меньшей мере  32768 ... 32 767. Однако во всех случаях диапазон типа short int будет поддиапазоном типа int, который, в свою очередь, будет поддиапазоном типа long int. То же справеД1IИВО для типов float, doubIe и long doubIe. В данном случае термин поддuапазон обозначает диапа.. зон, меньший или равный исходному. Так, int и long int MOryт иметь одинаковые диапазоны, но диапазон int не может быть больше, чем у long int. Поскольку с++ определяет лишь минимальные диапазоны, кото.. рые должны поддерживаться типами данных, то для Toro, чтобы опре.. делить фактические допустимые диапазоны чисел, вы должны обра.. титься к документации вашеrо компилятора. В качестве примера в табл. 2..2 приведены данные о типичной ширине в битах и диапазонах типов данных с++ в 32..разрядной среде, используемой, например, системой Windows ХР. Рассмотрим теперь каждый тип данных более детально. Таблица 2-2. Типичные ширины в битах и диапазоны типов данных С++ в 32"разрядной среде Тип Ширина, бит Типичный диапазон char 8 От  128 до 127 unsigned char 8 ОтО до 255 signed char 8 От 128 до 127 int 32 От 2147483648 до 2147483647 unsigned int 32 От О до 4294967295 signed int 32 От 2147483648 до 2147483647 short int 16 От 32768 до 32767 unsigned short int 16 От О до 65535 signed short int 16 От 32768 до 32767 
Типы данных С++ 63 lo"g i"t 32 То же, что int sig"ed lo"g i"t 32 Т о же, что signed int unsig"ed lo"g int 32 Т о же, что unsigned int float 32 От 1.8E38 до 3.4Е+38 doubIe 64 От 2.2E308 до 1.8Е+308 lo"g doubIe 80 От 1.2E4392 до 1.2Е+4392 bool Неприменимо truе(истина) или false (ложь) wchart 16 От О до 65535 Целые числа Как вы уже знаете из Модуля 1, переменные типа int содержат цe лые величины, у которых нет дробных частей. Переменные этоrо типа часто используются для управления циклами и условными предложе ниями, а также для счета. Операции с величинами типа int, поскольку у НИХ отсутствуют дробные части, выполняются значительно быстрее, чем для типов с плавающей точкой. В силу важности целых чисел для проrpаммирования, в С++ для них определен ряд вари  тов. Как было показано в табл. 2 1, целые MOryт быть короткими, о ычными И длинными. Далее, Д1IЯ каждоrо типа имеются варианты со наком и без знака. Целое со знаком может содержать как положительные, так и отрицательные значения. По умолчанию типы int рассматриваются как числа со знаком. Таким об разом, использование описателя signed является избыточным (хотя и разрешенным), потому что объявление по умолчанию предполаrает значение со знаком. Целые без знака MOryт содержать только положи тельные значения. Чтобы создать целое без знака, используйте моди фикатор unsigned. Различие между целыми со знаком и без знака заключается в том, каким образом интерпретируется старший бит числа. Если объявлено целое со знаком, то компилятор С++ при rенерации кода предполarает, что старший бит числа используется как флаz знака. Если флar знака pa вен О, число положительное; если он равен 1, число отрицательное. Oт рицательные числа почти всеrда предстзвляются в дополнительном коде. Для получения отрицательноrо числа этим методом все биты числа (за исключением флаrа знака) инвертируются, и к полученному числу при бавляется 1. После этою флаr знака устанавливается в 1. Целые со знаком используются во множестве различных алrорит мов, однако для них характерен в два раза меньший диапазон возмож ных абсолютных значений, чем для их аналоrов без знака. PaCCMOT рим, например, 16..битовое целое, конкретно, число 32767: 01111111 11111111 
64 Модуль 2 Знакомимся с данными, типами и операторами Для значения со знаком, если установить в 1 ero старший бит, чис ло будет интерпретироваться как  1 (в предположении, что использу ется метод дополнительноrо кода). Если, однако, вы объявили это число, как unsigned int, то при установке в 1 старшеrо бита число cтa нет равным 65535. Для Toro, чтобы разобраться в различиях интерпретации С++ целых чисел со знаком и без знака, рассмотрим эту простую про rpaMMY: #inclиde <iostrearn> /* Эта проrрамма демонстрирует различие между целыми числами со знаком и без знака. */ иsing narnespace stdi int main ( ) { short int ii // короткое целое со знаком short unsigned int j; // короткое целое без знака j = 60000 i  . . -- 1 = J; cout « i « h h « j; 60000 нахОДИТСЯ в ДОПУСТИМОМ диапазоне значе ний ДЛЯ un8igned 800rt int, но, как правило, будет вне доnycтимоrо диапазон ДЛЯ чисел signed short int. В результате при присваива ния ero переменной i оно будет интерпретиро ваться как отрицательное значение retиrn О i } Вывод этой проrpаммы приведен ниже: 5З6 60000 Вывод именно этих значений определяется тем, что комбинация битов, представляющая число 60000 как короткое целое без знака, бу дет интерпретироваться как  5536, если рассматривать это значение как короткое целое со знаком (в предположении, что числа 16 разрядные). В С++ предусмотрены сокращенные обозначения для объявления целых чисел unsigned, short и long. Вы можете просто использовать unsigned, short и long без int. Тип int в этом случае предполаrается по умолчанию. Таким образом, оба следующие предложения объявляют целые переменные без знака: иnsigned Х; unsigned int у i 
Типы данных С++ 65 СИМВОЛЫ переменныIe типа char содержат 8битовые коды ASCII символов, например, А, z или G, или любое 8битовое число. Для Toro, чтобы за дать в проrpамме символ, ero следует заключить в одиночные кавыч КИе В следующем при мере переменной сЬ присваивается код симво ла Х: char сЬ; сЬ = ' Х' i Значение переменной типа char можно вывести на экран с помо шью предложения cout. Так, следующая строка выводит значение пе ременной сЬ: coиt « "Это сЬ: " « сЬ; При выполнении этоrо предложения на экран будет выведено: Это сЬ: Х Тип char можно модифицирова1Ъ с помощью модификаторов signed или unsigned. Вообще roворя, будет ли тип char по умолчанию со зна ком или без знака, зависит от реализации. Однако для большинства компиляторов char считается типом со знаком. В таких средах исполь зование с char модификатора sigDed является избыточным. В этой кни re будет предполarаться, что переменные типа char  это переменные со знаком. Тип char может содержать значения, отличные от набора символов ASCII. Этот тип можно также использовать как "маленькое" целое с диапазоном значений обычно от..... 128 до 127; такую переменную мож но использовать вместо int в тех случаях, коrда в данной ситуации не нужны большие числа. Например, в приведенной ниже проrpамме пе ременная типа char используется для управления циклом, который выводит на экран символы латинскоrо алфавита: // Эта проrрамма выводит латинский алфавит. #inclиde <iostream> иsing namespace stdi Использование переменной типа char для управления ЦИКnОМ for. int main () { char letter; for(letter = 'Д'; letter <= 'Z'; letter++) 
66 Модуль 2 Знакомимся с данными, типами и операторами coиt « letter; retиrn о; } Цикл for позволяет решить поставленную нами задачу, так как сим вол А представляется в компьютере значением 65, а значения кодов букв от А до Z следуют подряд в возрастающем порядке. Поэтому пе ременной letter первоначально присваивается значение 'А'. В каждом шarе цикла letter получает приращение 1. В результате после первой итерации переменная letter содержит код буквы 'В'. Тип wchart содержит символы, входящие в расширенный набор. Как вы, возможно, знаете, мноrие человеческие языки, например, ки тайский, используют большое число символов, больше, чем помеща ются в 8 бит, предоставляемых типом char. Тип wchart и был добавлен в С++ дЛЯ поддержки этих языков. Хотя в этой книrе мы не будем ис пользовать тип wchart, вам придется обратиться к нему, если вы буде те адаптировать проrpаммы для международноrо рынка. Спросим у эксперта Вопрос: Почему в с++ определены лишь минимальные диапазоны для встроенных типов данных, а не их точные rраницы? OrвeT: Не задавая точных rраниц, с++ позволяет каждому компилято ;Jy оптимизировать типы данных применительно к используемой операци онной среде. Отчасти поэтому с++ позволяет создавать высокопроизводи тельное проrpаммное обеспечение. Стандарт ANSI/ISO с++ только YCTa навливает, что встроенные типы должны удовлетворять определенным требованиям. Например, стандарт требует, чтобы тип int "имел eCTeCTBeH ный размер, задаваемый архитектурой исполнительной среды". Таким об разом, в 32разрядной среде int будет иметь размер 32 бит. В 16разрядной среде длина int составит 16 бит. Было бы неэффективно и избыточно за ставлять 16разрядный компилятор реализовывать, например, int с 32би товым диапазоном значений. Идеолоrия с++ позволяет избежать этоrо. Разумеется, стандарт с++ задает такой минимальный диапазон для BCтpO енных типов, который будет доступен во всех средах. В результате если вы в своих проrpаммах не выходите за рамки ЭТИХ минимальных диапазонов, то ваши проrpаммы будут переносимы в друrие среды. Последнее замеча нне: каждый компилятор с++ специфицирует диапазоны базовых типов в заrоловке <cUmits>. Минутная тренировка 1. Каковы семь базовых типов данных? 2. В чем заключается различие между целыми со знаком и без знака? 
Типы данных С++ 67 3. Может ли переменная типа char использоваться в качестве "маленько ro" целоrо? 1 char, wchart, int, float, doubIe, 0001, void представляют собой семь бaJОВЫХ 1ИIIОВ данных 2 Целое со знаком может содержать как положительные, так и отрицательные lначения Целое без знака может содержать лишь положительные значения. 3 Да Типы данных с плавающей точкой Переменные nmов 80st и double используются либо при необходимо сти иметь дробные числа, либо коrда ваше приложение имеет дело с очень большими или, наоборот, очень маленькими числами. Типы 80at и double различаются значением наибольшеro (наименьшеro) числа, KOТO рое может содержаться в переменных этих 1ИПов. Обычно тип double MO жет хранить число, приблизительно на 10 порядков большее, чем 808t. Из этих двух типов чаще используется doubIe. Одна из причин этоrо заключается в том, что мноrие математические функuии из библиоте ки функций с++ используют значения типа double. Например, ФУНК ция sqrt( ) возвращает значение ти  оuые, являющееся квадратным корнем из арryментз Toro же типа. приводимой ниже проrpамме функция sqrt() используется для вычис ения длины rипотенузы пря моyrольноrо треyrольника при заданных длинах двух ero катетов. /* Использование теоремы пифаrора для нахождения длины rипотенузы прямоуrольноrо треуrольника при заданных длинах двух ero катетов. */ #inclиde <iostrearn> #include <crnath> 4 иsing namespace std; 3аrоловок <cmath> нужен для функции sqrt( ). i n t та i n () { doиbl е х, у, z; х = 5.0; У = 4.0 z = sqrt (х*х + у*у);  Функция sqrt( ) является частью математиче екой библиотеки С++ coиt « "rипотенуза равна 11 « z; retиrn о; } 
68 Модуль 2 Знакомимся с данными, типами и операторами Ниже приведен вывод проrpаммы: rипотенуза равна 6.40312 Еще одно замечание относительно приведенной выше проrрам мы. Поскольку sqrt( ) является частью стандартной библиотеки функций С++, в проrрамму следует включить стандартный заrоло вок <cmath>. Тип long doubIe позволяет работать с очень большими или очень Ma ленькими числами. Наиболее полезен этот тип в проrpаммах для науч  ных исследований. Например, типом long doubIe можно пользоваться для анализа астрономических данных. Булев тип данных Тип Ьооl является относительно новым добавлением в С++. Он хранит булевы (т. е. истина или ложь) значения. В С++ определены две булевы констзнтыI, true (истина) и false (ложь), которые только и можно использовать с переменными этоrо типа. Перед тем, как перейти к дальнейшему, важно понять, как опреде ляются в С++ значения true и false. Одной из фундаментальных KOH цепций С++ является интерпретация любоrо ненулевоrо значения как true, а нулевоrо ..... как false. Эта концепция полностью совместима с типом 0001, потому что при использовании в булевом выражении С++ автоматически преобразует любое ненулевое значение в true, а нулевое ..... в false. Справедливо также и обратное: при использовании в небуле вом выражении true преобразуется в 1, а false ..... в О. Автоматическое преобразование нулевых и ненулевых значений в их булевы эквива ленты оказывается особенно важным в управляющих предложениях, как это будет показано в Модуле 3. Ниже приводится проrpамма, демонстрирующая использование типа bool: // Демонстрация булевых значений. #inclиde <iostream> иsing namespace std; int rnain () { bool Ь; Ь = false; cout « "Ь равно" « Ь « "\n"; ь = trиe; 
Типы данных С++ 69 coиt « "Ь равно" « Ь « "\n"; // булево значение может управлять п редложением if if(b) coиt « "Это выполняется.\п";  I ь = false; " " Одиночное булево значение может if (Ь) coиt« Это не выполняется. \n ; ....... ynpaвnятb предложением If // результатом действия оператора отношения является // значениеt rиe/false coиt « "10 > 9 есть" « (10 > 9) « "\п"; retиrn о; } Ниже приведен вывод этой проrpаммы: Ь равно О Ь равно 1 Это выполняется. 10 > 9 есть 1 В приведенной проrpамме есть три интересных момента. Вопер вых, как видно из вывода проrpаммы, если значение типа ЬОО' BЫBO дится с помошью сопt, на экране отображается О или 1. как будет по казано в дальнейшем, существуют опции вывода, которые позволяют вывести на экран слова "false" и "tme". BOBTOPЫX, значение булевой переменной может само по себе ис пользоваться для управления предложением if. Нет необходимости пи сать предложение if таким образом: i f (Ь = = t rиe) ... В...третьих, результатом действия оператора отношения, например, <, ЯR1Iяется булево значение. Именно поэтому выражение 10 > 9 BЫBO дит значение 1. Далее, BOкpyr выражения 10> 9 необходимы дополни тельные скобки, потому что оператор « имеет более высокий при оритет , чем <. Тип void Тип void описывает выражение, не имеюшее значения. Это может показаться странным, но далее в книrе будет показано, как использу ется void. 
70 Модуль 2 Знакомимся с данными, типами и операторами Минутная тренировка 1. Каково основное различие между Boat и doubIe? 2. Какие значения может иметь переменная типа bool? В какое булево значение преобразуется ноль? 3. Что такое void? 1 Основное различие между tloat и doubIe состоит в допустимой величине значений, co держащихся в переменных этих типов 2 Переменные типа 0001 MOryт иметь значения либо true, либо false Ноль преобразуется в false 3 void представляет собой rип величины, не имеющей значения Проект 2..1 Разrовор с Марсом В точке, rде Марс находится ближе Bcero к Земле, он все же OT стоит от Земли приблизительно на 34000000 миль. Считая, что на Марсе есть КТOTO, с кем вам хочется поrоворить, какова будет за держка между моментом отправления радиосиrнала с Земли и MO ментом достижения им Марса? В данном проекте создается проrpам ма, отвечающая на этот вопрос. Вспомним, что радиосиrнал распро страняется со скоростью света, т. е. приблизительно 186000 миль в секунду. Для Toro, чтобы определить задержку, следует разделить расстояние на скорость света. Выведем на экран значение задержки в секундах, а также в минутах. Шаr за marOM 1. Создайте файл с именем Mars.cpp. 2. Для вычисления задержки вам понадобятся значения с плаваю шей точкой. Почему? Потому что временной интервал будет иметь дробную часть. Вот переменные, используемые проrpаммой: doиble distance; // расстояние оиЫе lightspeed; // скорость света doиble delay; // задержка doиble delaYlnmin; // задержка в минутах 3. Присвойте переменным distance и lightspeed начальные значения, как показано ниже: distance = 34000000.0; // 34000000 миль lightspeed = 186000.0; // 186000 миль в секунду 
Типы данных С++ 71 4. Для вычисления задержки разделите distance на lightspeed. Вы по лучите задержку в секундах. Присвойте это значение переменной delay и выведите результат. Эти шаrи показаны ниже: delay = distance / lightspeed; coиt « "Временная задержка при разrоворе с Марсом: n < delay« n секунд.\п"; 5. Разделите число секунд в delay на 60, чтобы получить задержку в минутах; выведите этот результат с помощью таких строк: delayinmin = delay / 60.0; coиt « "Это составляет" « delayinmin« " минут."; 6. Ниже приведена полная проrpамма Mars.cpp: /* Проект 2  1 Разrовор с Марсом */ #inclиde <iostream> using namespace std; int та in () { doиble distance; doиble lightspeed; doиble delay; doиble delayinmin; distance = 34000000.0; // 34000000 миль lightspeed = 186000.0; // 186000 миль в секунду delay = distance / lightspeed; cout « "Временная задержка при разrОБоре с Марсом: " « delay « 11 секунд. \n" ; delayinmin = delay / 60.0; cout « "Это составляет n « delayinmin« n минут."; retиrn о; } 
72 Модуль 2 Знакомимся с данными, типами и операторами 7. Откомпилируйте и запустите проrpамму. Вы получите следую ший результат: Временная задержка при разrоворе с Марсом: 182.796 секунд. Это составляет 3.04659 минут. 8. Попробуйте самостоятельно получить время задержки, возни кающей при двусторонней связи с Марсом. Цель _Литералы Литералами называются фиксированные значения, которые может прочитатъ человек, и которые не MOryт быть изменены проrpаммой. Например, значение 101 представляет собой целочисленный литерал. Литералы часто называют константами. По большей части литералы и их использование интуитивно настолько понятны, что мы использова ли ИХ в той ИЛИ иной форме во всех предыдущих проrpаммах. Теперь наступил момент, коrда понятие литерала следует обсудить с формаль ной точки зрения. В С++ литералы MOryт быть любоrо из базовых типов данных. Спо соб представления литерала зависит от ero типа. Как упоминалось pa нее, символьные литералы заключаются в одиночные кавычки. Напри мер, 'а' и '%' суть символьные литералы. Целочисленные литералы задаются в виде чисел без дробной части. Например, 10 и  100 суть целочисленные KOHcTaHThI. Литералы с пла вающей точкой требуют указания десятичной точки, за которой может следовать дробная часть числа. Например, 11.123 есть константа с nла вающей точкой. С++ допускает также использование для чисел с nла вающей точкой обозначений, принятых в научном мире (с указанием порядка в виде степени десяти). Все литеральные значения имеют тот или иной тип данноrо, однако этот факт вызывает вопрос. Как вы знаете, существуют несколько типов целых чисел, например, iDt, short iDt или uDsigned long int. Вопрос заклю чается в следующем: как компилятор определяет тип литерала? Напри мер, какой тип имеет число 123.23, 808t или doubIe? Ответ на этот BO прос сосroит из двух частей. Вопервых, компилятор С++ автоматиче ски принимает определенные предположения относительно типа литерала и, BOBТOpЫX, вы можете ШIЯ литерала явно указать ero тип. По умолчанию компилятор С++ помещает целочисленный литерал в минимальный по размеру и совместимый с конкретным значением литерала тип данноrо, начиная с int. Таким образом, для 16битовых целых чисел литерал 10 будет иметь тип iDt по умолчанию, но литерал 
Литералы 73 103000 получит тип long. Хотя число 10 моrло бы поместиться в тип char, компилятор не поступит таким образом, так как типы целочис ленных литералов начинаются с int. По умолчанию литералы с плавающей точкой получают тип doubIe. Так, значение 123.23 будет типа doubIe. Практически для всех проrpамм, которые вы будете составлять, учась языку, умолчания компилятора вполне будут вас устраивать. В тех случаях, коrда предположения по умолчанию о типах числовых ли тералов, которые принимает С++, вас не устраивают, вы можете за дать конкретный тип числовоrо литерала с помощью суффикса. Для типов с плавающей точкой чтобы задать для литерала тип t1oat, следует вслед за числом указать спецификатор F. Если сопроводить число спе цификатором L, число приобретет тип long doubIe. Для целочисленных типов суффикс U указывает на тип unsigned, а L на long. (Для задания типа unsigned long следует использовать оба спецификатора, и и, и L.) Ниже приведено несколько примеров: Тип AaHHoro int long int unsigned int unsigned long float doubIe long doubIe Примеры констант 1 123 21000 2З4 35000L 34L 10000U 987U 40000U 12323UL 900000UL 123.23F 4.34e3F 23.23 123123.33 0.9876324 1001.2L Шестнадцатеричные и восьмеричные литералы Как вы, возможно, знаете, в проrpаммировании иноrда оказывается проwе использовать не десятичную, а восьмеричную или шеCПfадцате ричную систему счисления. ВосьмеричlЮJl система имеет в качестве OCHO вания число 8 и использует цифры от О до 7. Шестпадцатеричная система имеет в качестве основания число 16 и использует цифры от О до 9 плюс буквы от А до F, которые обозначают 10, 11, 12, 13, 14 и 15. Например, шестнадцатеричное число 10  это то же самое, что 16 в десятичной сис теме. Поскольку эти две системы счисления использyюrcя очень часто, в с++ предусмотрена возможность задавать целочисленные литералы не юлько в десятичной системе, но и в двух дрyrих. Шестнадцатеричный литерал должен начинаться с Ох (ноль, за которым идет буква х). BOCЬMe ричный лиreрал начинается с нуля. Вот два примера: Ьех = OxFF; / / это составляет 255 в десятичной системе oct = 011; // это составляет 9 в десятичной системе 
74 Модуль 2. Знакомимся с данными, типами и операторами Спросим у эксперта Вопрос: Вы показали, как задается символьный литерал. Можно ли тем же способом задать литерал типа wchart? OrвeT: Нет. Константе, состоящая из "широких" символов (т. е. из дaH ных типа wchart) должен предшествовать спецификатор L. Например: wchart wc; wc == L'A'; Здесь переменной wc присваивается значение константы из "широких" символов, эквивалентной букве А. В обычном каждодневном проrpамми ровании вы, скорее Bcero, не будете использовать "широкие" символы, oд нако они MOryт вам понадобитъся, если вы занимаетесь переводом про.. rpaMM на дрyrие языки. СТРОКО8ые литералы Помимо литералов предопределенных типов, С++ поддерживает еше один тип литералов: строку. Строка представляет собой последо вательность символов, заключенную в двойные кавычки. Пример строки: "Это проверка". В предыдуших проrpаммных примерах вы уже сталкивались со строками в некоторых предложениях cout. Обра тите внимание на важное обстоятельство: хотя С++ позволяет опреде лять строковые константы, в нем нет BcтpoeHHoro типа строковых дaH ных. Строки, как вы вскоре увидите, описываются в С++, как масси вы символов. (С++, однако, имеет строковый тип в своей библиотеке классов. ) Символьные Еsс..последовательности Из большинства отображаемых на экране символов можно образо вывать символьные литералы, заключая их в одиночные кавычки, oд нако несколько символов, например, символ возврата каретки, созда ют проблемы при работе в текстовом редакторе. Кроме тoro, HeKOТO рые символы, например, одиночные или двойные кавычки, имеют в С++ определенное значение, и их нельзя использовать непосредст венно в качестве символьных констант. Для таких случаев в С++ пре дусмотрено специальное средство..... управляющие, или Еsспоследова тельности, которые иноrда называют символьными константами с об ратным слешем; эти KOHcTaHТbI перечислены в табл. 23. Теперь вы видите, что комбинация \n, которая встречалась в наших проrpаммах, является одной из таких Еsспоследовательностей. 
Литералы 75 Таблица 2-3. Символьные Еsс-последовательности КОД Значение \Ь Шаr назад \f Перевод страницы \п Новая строка \r Возврат каретки \t rоризонтальнаятабуляция \" Двойные кавычки \' Символ одиночной кавычки \\ Обратная косая черта, обратный слеш \v Вертикальная табуляция \а .Звуковой сиrнал \7 ? \N Восьмеричная константа (rде N есть сама восьмеричная константа) \xN Шестнадцатеричная константа (rде N есть сама шестнадцатеричная константа) Спросим у эксперта Вопрос: Является ли строка, состоящая из одноrо символа, символьным литералом? Например, "k" и 'k' ..... это одно и то же? OrвeT: Нет. Не следует путать строки с символами. Символьный лите.. рая представляет одиночный символ (букву, цифру и проч.) типа char. Строка же, даже содержащая лишь одну букву, все еще является строкой. Хотя строки состоят из символов, строки и символы являются объектами разных типов. в приведенной ниже проrpамме иллюстрируется использование некоторых Еsc..последователъностей: // Демонстрация некоторых Еsспоследовательностей. #inclиde <iostream> using namespace std; int main () { coиt« "один\tдва\tтри\п"; cout «"12З\Ь\Ь4S";  Последоватепьность \Ь\Ь выполнит два шarа на. зад и затирание 2 и 3 retиrn о; } 
76 Модуль 2 Знакомимся с данными, типами и операторами Вывод этой проrpаммы: один 145 два три в этой проrpамме в предложении cout используются символы табу ляции для позиционирования слов "два" и "три". Следующее предло жение cout выводит символы 123. Далее выводятся два символа воз врата на шаr, которые затирают выведенные уже 2 и 3. Наконец, OTO бражаются символы 4 и 5. Минутная тренировка 1. Каков тип литерала 10 по умолчанию? А каков тип литерала 10.0? 2. Каким образом задать 100 в виде long int? Каким образом задать 100 в виде unsigned int? 3. Что такое \Ь? 1 1 О имее1 тип int, а 1 О О  doubIe 2 100 в виде long int записывается как 100L 100 в виде unsigned int записывается как 100U. 3 \Ь представляет собой Еsспоследовательность, выполняюшую ВОlврат на шаr Цель _ Подробнее о переменных Мы уже познакомились с переменными в Модуле 1. Здесь мы noro ворим о них подробнее. Как вы уже знаете, переменные объявляются с помощью предложения TaKoro вида: тип имяпеременuой; Здесь тип  Э1О тип дзнноro, соответствующий объявляемой перемен ной, а uмяпеременной ..... ее имя. ВЫ может объявить переменную любоrо допус1ИМОro ТШlа. СоЗдавая переменную, вы создаете тем самым экземп ляр ее типа. Таким образом, свойства переменной определяюrcя ее типом. Например, переменная mпа 0001 содержит булевы значения; ее нельзя ис пользовать для хранения значений с rтавающей точкой. При 310М Heдo пустимо изменять тип переменной в течение времени ее сущеcmoвзния. Так, из переменной типа int нельзя сделать переменную типа doubIe. Инициализация переменной Переменной можно присвоить значение одновременно с ее объяв лением. Для этоrо укажите после имени переменной знак равенства, а 
Подробнее о переменных 77 за ним  присваиваемое переменной значение. Такое предложение носит название инициализации переменной. Общая форма предложения инициализации выrлядит так: тип nеременная == значение; Здесь значение ..... это значение, присваиваемое переменной OДHO временно с ее созданием. Приведем несколько примеров: int coиnt = 10; // присвоим coиnt начальное значение 10 char сЬ = ' Х'; / / инициализируем сЬ кодом буквы Х float f = 1.2F; // f инициализируется значением 1.2 При объявлении двух или нескольких переменных одноrо типа в форме списка с разделяющими запятыIи,, вы можете присвоить одной или нескольким переменным начальные значения. Например, int а, Ь = 8, с = 19, а; // ь и с получают начальные значения в этом примере инициализируются только переменные Ь и С. Динамическая инициализация Хотя в предыдущих примерах для инициализации переменных ис пользовались только константы, с++ позволяет инициализировать пере менные динамически, с помощью любых выражений, образующих опре деленные значения в то время, коrда объявляется инициализируемая пе.. ременная. Ниже приведена короткая проrpамма, вычисляющая объем цилиндра при заданных значениях радиуса ero основания и высоты: // Демонстрация динамической инициализации. #inclиde <iostream> иsing narnespace std; int rnain () { аоиЫе radiиs = 4.0, height = 5.0; // динамически инициализируем переменную volиme аоиЫе volиme = 3.1416 * radius * radiиs * height; coиt « .Объем равен · « volиme; i r е t и rn о; voIume инициanизируется динамически 80 вре- } мя выполнения 
78 Модуль 2. Знакомимся с данными, типами и операторами в приведенной проrpамме объявляются три локальные перемен ные  radius, height и volume. Первые две, radius и height, инициализи руются константами. Однако переменная volume инициализируется динамически; ей присваивается значение объема цилиндра. Здесь важно отметить, что инициализирующее выражение может использо вать любые элементы, принимающие определенные значения к MO менту инициализации, включая вызовы функций, дрyrие перемен ные и литералы. Операторы с++ предоставляет боrатыIй набор операторов. Оператором называ ется символ, обозначающий для компилятора указание на выполнение определенноro математическоrо или лоrическоrо действия. В с++ предусмотрены четыIеe основных класса операторов: арифметических, побитовых, отношения и лоzическux. Кроме этоrо, в с++ имеются He сколько дополнительных операторов, которые обслуживают опреде ленные специфические ситуации. В этой rлаве мы рассмотрим опера торы арифметические, отношения и лоrические. Также будет paCCMOT рен оператор присваивания. Операторы побитовые и специальные будут описаны позже. Цель .Арифметические операторы в с++ определены следующие арифметические операторы: Оператор * Значение Сложение Вычитание (также унарный минус) Умножение Деление Деление по модулю Инкремент Декремент + / % ++ Операторы +, , * и / выполняют те же действия, что и в обычной алreбре. Их можно применять к любым встроенным типам данных. Их также можно использовать с переменными типа char. 
Арифметические операторы 79 Оператор % (деление по модулю) возврашает остаток от цело численноrо деления. Вспомним, что коrда знак / используется с целым числом, получаемый остаток отбрасывается; например, 10/3 при целочисленном делении будет равно 3. Для получения остатка в операции деления следует воспользоваться оператором %. Так, 10 % 3 равно 1. В С++ оператор % может использоваться только с целочисленными операндами; к типам с плаваюшей точкой он He применим. Следующая проrpамма демонстрирует использование операции дe ления по модулю: // Демонстрация использования оператора %. #include <iostream> using namespace std; int main () .{ int х, у; х = 10; у = 3; coиt « х « " / " « у « " равно " « х / у « " с остатком " « х % У « "\n"; х = 1; у = 2; coиt « х « " / " « у « " равно" « х / у « "\n" « х « " % " « у « " равно" « х % у; retиrn о; } Вывод проrpаммы приведен ниже: 10 / 3 равно 3 с остатком 1 1 / 2 равно О 1 % 2 равно 1 Инкремент и декремент в Модуле 1 нам уже встречались операторы инкремента и дeкpe мента ++ и  ...... Эти операторы обладают некоторыми очень интерес ными свойствами. Начнем с Toro, что точно определим действие опе раторов инкремента и декремента. 
80 Модуль 2 Знакомимся с данными, типами и операторами Оператор инкремента добавляет к операнду 1, а оператор дeкpe мента вычитает 1. Таким образом, х = х + 1; эквивалентно х++; и х = х  1; эквивалентно  X; Оба оператора, и инкремента и декремента, MOryт как предшество вать операнду (префикс), так и следовать за операндом (постфикс). Например, х = х + 1; может быть записано и как ++Х; // префиксная форма и как х++; // постфиксная форма в приведенном примере безразлично, используется ли инкремент как префикс или как постфикс. Однако если инкремент или декремент используется как часть большеrо выражения, возникает важное разли чие. Если оператор инкремента или декремента предшествует операнду, с++ выполняет операцию до получения значения операнда с целью ис пользования ero в оставшейся части выражения. Если же оператор сле дует за операндом, с++ сначала получит значение операнда, и лишь за тем выполнит ero инкремент или декремент. Рассмотрим такой пример: х = 10; у = ++Х; в этом случае у получит значение 11. Однако, если написать эти строки иначе: х = 10; у = х++; 
Операторы отношения (сравнения) и лоrические 81 значение у окажется равным 10. В обоих случаях конечное значение х будет 11; разница в том, коrда это случится. Возможность управлять моментом выполнения операций инкремента и декремента оказывает ся весьма полезной. Относительные приоритеты арифметических операторов покзззны ниже: Высший ++   (унарный минус) * / % Низший +  Операторы, имеющие одинаковый приоритет, выполняются KOM пилятором слева направо. Разумеется, для изменения порядка выпол нения арифметических операций MOryт быть использованы скобки (крyrлые). Скобки рассматриваются с++ точно так же, как и любыми дрyrими компьютерными языками: они пере водят операцию или rpуппу операций на более высокий уровень приоритета. Спросим у эксперта Вопрос: Имеет ли оператор инкремента ++ какое..ли60 отношение к име ни языка с++? Ответ: Да! Как вы знаете, с++ построен на базе языка С. с++ добавил в С несколько усовершенствований, большинство которых ПОдI1ерживает объ ектноориентированное проrpаммирование. ТаЮlМ образом, с++ представ ляет собой расширение (инкремент) языка С, и добавление к имени С сим волов ++ (которые, разумеется, образуют оператор инкремента) вполне оп равдано. Страуструп первоначально назвал с++ "С с классами", однако по предложению Рика МаСЮlllИ он позже изменил имя на С++. Хотя новый язык в любом случае бьт обречен на успех, присвоение ему имени с++ дей ствительно raрантировало ему место в истории, потому что любой проrpам мист на С сразу же ero узнавал! Цель _Операторы отношения (сравнения) и лоrические в выражениях оператор отношения и лоzическuй оператор слово oт ношение обозначает взаимоотношение двух величин, т. е. результат их 
82 Модуль 2. Знакомимся с данными, типами и операторами сравнения, а слово лоzический обозначает способ, которым объединя ются истинное и ложное значения. Поскольку операторы отношения образуют истинный или ложный результат, они часто используются совместно с лоrическими операторами. Именно по этой причине они обсуждаются здесь вместе. Операторы отношения и лоrические перечислены в табл. 24. Обратите внимание на отношения равенства и неравенства: в с++ не равно обозначается знаками !==, а равно  двойным зна ком равенства, ==. В с++ результат операции отношения или лоrической образует результат типа bool. Друrими словами, pe зультат операции отношения или лоrической всеrда равен либо true, либо false. Таблица 2-4. Операторы отношения и лоrические в С++ Операторы отношения Оператор Значение  ( > Больше чем >= Больше чем или равно < Меньше чем <= Меньше чем или равно  Равно != Не равно Операторы поrические Оператор Значение && И 11 или ! НЕ  Замечание Для старых компиляторов результат операции отношения и лоrиче.. ской может быть целым числом, принимающим значение О или 1. Это различие носит в основном академический характер, потому что С++, как это уже отмечалось, автоматически преобразует true в 1, а false в О и наоборот. Операнды, участвующие в операции отношения, MorYT при надлежать почти любому типу; необходимо только, чтобы их сравнение имело смысл. Операнды лоrической операции должны 
Операторы отношения (сравнения) и лоrические 83 образовывать истинный или ложный результат. Поскольку HeHY левое значение истинно, а нулевое ложно, это означает, что ло rические операторы допустимо использовать с любым выражени ем, дающим нулевой или ненулевой результат. Фактически мож но использовать любое выражение, кроме тех, результат которых есть void. Лоrические операторы используются для поддержки базовых лоrи ческих операций И (AND), ИЛИ (OR) и НЕ (NOT), соrласно следую щей таблице истинности: р q рИq рИЛИ q НЕр Ложь Ложь Ложь Ложь Истина Ложь Истина Ложь Истина Истина Истина Истина Истина Истина Ложь Истина Ложь Ложь Истина Ложь Ниже приведена проrpамма, демонстрирующая несколько операто ров отношения и лоrических: // Демонстрация операторов отношения и лоrических. #include <iostream> using narnespace std; int rnain () { int i, j; Ьооl Ы, Ь2; i = 10; j = 11; if (i < j) cout < "i < j \n" ; if(i <= j) cout « "i <= j\n"; if(i 1= j) cout « i 1= j\n"; if (i  j) coиt « I.это не будет выполняться\п tI ; if(i >= j) coиt « " это не будет выполняться\п"; i f (i > j) cout « n это не будет выполняться\n" ; ы = trиe; Ь2 = false; if(bl && Ь2) coиt « " это не будет выполняться\n"; if(! (Ы && Ь2» coиt « "1 (Ы && Ь2) есть истина\п"; if(bl 11 Ь2) coиt« "Ы 11 Ь2 есть истина\n"i retиrn о; } 
84 Модуль 2. Знакомимся с данными, типами и операторами Ниже приведен вывод этой проrpаммы: i < j i <= j i ! = j ! (Ы && Ь2) есть истина Ы I I Ь2 есть истина Оба типа операторов, и лоrические, и отношения, имеют более низкий приоритет, чем арифметические операторы. Это означает, что выражение вроде 10 > I + 12 дает тот же результат, что и вариант 10 > (1 + 12). Результат, разумеется, ложен. С помощью лоrических операторов можно объединять любое коли чество операторов отношения. Например, в этом выражении объеди нены три оператора отношения: var > 15 II ! (10 < coиnt) && 3 <= itern в приведенной ниже таблице показаны относительные приоритеты операторов отношения и лоrических: ВItIСWИЙ > >= < <= == ,= Низwий && 11 Проект 2..2: Конструирование поrической операции исключающее ИЛИ в С++ не определен лоrический оператор, выполняющий операцию исключающеrо ИЛИ (XOR). Исключающее ИЛИ ..... это двухместная опе рация, результат кoroрой истинен лишь в том случае, коrда один и толь ко один операнд истинен. Она имеет следующую таблицу истинности: р q р ХОА q Ложь Ложь Ложь Ложь Истина Истина Истина Ложь Истина Истина Истина Ложь 
Операторы отношения (сравнения) и лоrические 85 Некоторые проrpаммистыI считают, что отсутствие операции XOR в С++ ..... серьезная ошибка. Дрyrие придерживаются точки зрения, что отсутствие лоrическоrо оператора XOR отражает рациональность язы ка С++ ..... стремление к отсутствию избыточности. Действительно, ло rическую операцию XOR нетрудно создать с помощью предоставляе мых С++ трех базовых лоrических операций. В этом проекте мы сконструируем операцию XOR с помощью опе раторов &&, 11 и !. После этоrо вы сможете сами решить, является ли отсутствие лоrическоrо оператора XOR ошибкой проектирования или элеrантной чертой языка! Шаr за warOM 1. Создайте файл с именем XOR.cpp. 2. Если р и q являются булевыми переменными, лоrический опера тор XOR конструируется таким образом: (р 11 q) && !(р && q) Рассмотрим это выражение детально. Прежде Bcero над р и q BЫ полняется операция ИЛИ. Если результат истинен, это значит, что по меньшей мере один из операндов истинен. Далее, над р и q BЫ полняется операция И. Результат будет истинен, если оба операнда истинны. Затем этот результат инвертируется с помощью оператора НЕ. Выражение !(р && q) будет истинно, коrда либо р, либо q, либо оба вместе ложны. Наконец, этот результат лоrически умножается (операция И) на результат операции (р 11 q). В конце концов все BЫ ражение будет истинно, если один из операндов (но не оба вместе) истинен. 3. Ниже приводится полный текст проrpаммы XOR.cpp. Она дeMOH стрирует результатыI операции XOR дЛЯ всех четыехx возможных KOM бинаций значений (т. е. сочетаний tme/false) операндов р и q. /* Проект 2  2 Реализация операции XOR с помощью лоrических операторов С++. */ #inclиde <iostrearn> #include <crnath> иsing namespace std; 
86 Модуль 2 Знакомимся с данными, типами и операторами int main ( ) { Ьоо 1 р, q; р = true; q = trиei cout « р « " XOR " « q « " равно " « ( (р I 1 q) && ! (р && q) ) « "\n" i р = falsei q = true; cout « р « " XOR " « q « " равно " « ( (р 1 1 q) && ! (р && q) ) « ., \n" i р = true; q = false; cout « р « " XOR " « q « " равно " « ( (р 1 I q) && ! (р && q) ) « "\n" i р = falsei q = falsei cout « р « " XOR " « q « " равно " « ( (р 11 q) && ! (р && q) ) « "\n"; return о; } 4. Orкомпилируйте И запустите проrpамму. Ниже приведен ее вывод: 1 XOR 1 равно О О XOR 1 равно 1 1 XOR О равно 1 О XOR О равно О 5. Обратите внимание на внешние скобки, окружающие операцию XOR в предложениях COBt. Эти скобки необходимы в силу действия приоритетов операторов С++. Оператор« имеет более высокий при оритет, чем лоrические операторы. Чтобы убедиться в этом, удалите внешние скобки и перекомпилируйте проrpамму. Вы увидите, что компилятор даст сообщение об ошибке. 
Оператор присваивания 87 Минутная тренировка 1. Для чеrо предназначен оператор %? К данным каких типов он приме ним? 2. Каким образом вы объявите переменную типа int с именем index и с Ha чальным значением 1 О? 3. Каков тип результата операций отношения или лоrических? Оператор деления по модулю % возвраUlает остаток от целочисленноrо деления Ero можно использовать с целыми числами. 2 lnt lndx = 10; 3. Результат операций оrношения и лоrических имеет тип 0001 Цель _Оператор присваивания Уже в Модуле 1 вы использовали оператор присваивания. Теперь наступило время рассмотреть ero с формальной точки зрения. Onepa тор прuсваиванuя обозначается одиночным знаком равенства, ==. Дей ствие этоro оператора в С++ практически такое же, как и в любом дpyrOM компьютерном языке. Ero общая форма: nеременная == выражение; Здесь переменная принимает значение используемоrо выражения. Операroр присваивания имеет любопыпiе свойcmo: он позволяет соз давать цепочку присваиваний. Рассмотрим, например, такой фрarмент: int х, у, z; х = у = z = 100; // присвоить х, у, и z значение 100 в этом фрarменте переменные х, у и z получают значение 100 в oд ном предложении. Такое присваивание допустимо, так как оператор == выдает значение выражения, стояшеro справа от Hero. Значение Bыpa жения z == 100 есть 100, и оно присваивается переменной у, а затем, в свою очередь, переменной х. С помощью "цепочки присваиваний" леrко придать rpуппе переменных одно и то же значение. Цель 8Составные присваивания в С++ предусмотрены специальные операторы составных присваиваний, которые упрощают написание некоторых предло 
88 Модуль 2 Знакомимся с данными, типами и операторами жений присваивания. Приведенное ниже предложение присваи вания х = х + 10; может быть записано с использованием cocтaBHoro присваивания тa ким образом: х += 10; Операторная пара +== указывают компилятору, что переменной х следует присвоить значение х плюс 10. Вот дрyrой пример. Предложение х = х  100; равносильно следующему: х = 100; Оба предложения присваивают переменной х значение х минус 100. Для большинства двухместных операторов (т. е. операторов, тpe буюших двух операндов) в с++ предусмотрена возможность COCTaBHO ro присваивания. Таким образом, выражение вида nеременная == nеременная оператор выражение; может быть преобразовано в такую сжатую форму: nеременная оператор == выражение; Изза тoro, что предложения cocTaBHoro присваивания короче CBO их развернутыIx эквивалентов, операторы cocTaBHoro присваивания иноrда называют операторами кратКО20 nрисваиванuя. Операторы cocтaвHoro присваивания обладaюr двумя достоинствами. вопервы,, они более компакrны по сравненlOO со своими "поJпIым" эк вивалентами. Boвтopых они MOryr порожцать более эффективный BЫ полнимый код (пoroму что при их использовании операнд оценивается лишь один раз). По ЭIИМ причинам операroры cocтaвHOro ПРИСВаивания широко ИСПОЛЪЗyIOfCя в профессиональном проrpаммировании на с++. Цель _ Преобразование типов в операциях присваиваня _ в тех случаях, коrда переменные одноrо типа сопоставляются с пе ременными дpyroro типа, выполняется операция nреобразования типа. 
Преобразованиетиповвоперацияхприсваивания 89 в предложениях присваивания правило преобразования типа весьма простое: значение правой стороны операции присваивания (т. е. сто-- роны выражения) преобразуется в тип левой стороны (переменной мишени), как это проиллюстрировано ниже: int Х; char ch; float f; сЬ = х; х = f; f = ch; f = Х; /* строка 1 * / /* строка 2 */ /* строка 3 */ /* строка 4 */ в строке 1 левые (старшие) 8 бит целочисленной переменной х О1брасываются, и в сЬ помешаются младшие 8 бит. Если значение х было между  128 и 127, сЬ и х будyr иметь совпадающие значения. В противном случае значение сЬ будет отражать только младшие биты х. В строке 2 переменная х примет целую часть f. В строке 3 f преобразует 8битовое целое значение, хранящееся в сЬ, в то же зна чение, но в формате с плавающей точкой. Это же произойдет в cтpo ке 4, только Здесь f преобразует в формат с плаваюшей точкой значе ние типа lot. В процессе преобразования целых (int) значений в символы или длинных целых в обычные целые теряется соответствующее количе ство старших битов числа. Во мноrих 32..разрядных средах это приве дет при преобразовании int в cbar к потере 24 бит, а при преобразова нии int в sbort  16 бит. В случае преобразования типа с плавающей точкой в int будет теряться дробная часть. Если размер типамишени недостаточен для восприятия результата, получится бессмысленное значение. Не забывайте: хотя С++ автоматически преобразует любые BCтpO енные типы дрyr в дрyrа, результат не обязательно будет cooтвeTCTBO вать вашим ожиданиям. Будьте осторожны при смешивании типов в выражения. Выражения Операторы, переменные и литералы используются в качестве компонентов выражений. Возможно, вы уже представляете себе общую форму выражения из опыта проrраммирования или из ал rебры. Однако некоторые особенности выражений мы все же об судим. 
90 Модуль 2. Знакомимся с данными, типами и операторами Цель _ Преобразование типа в выражениях в тех случаях, коrда переменные различающихся типов смеши ваются в выражении, они преобразуются к одному типу. Прежде Bcero все значения char и short int автоматически повышаются до int. Далее, все операнды повышаются до caMoro высокоrо типа, участвующеrо в выражении. Повышение типа осуществляется по мере выполнения последовательных операций. Например, если один операнд имеет тип int, а друrой  long int, тип int повышается до long int. Или если любой из операндов имеет тип doubIe, друrой операнд повышается до doubIe. Таким образом, преобразование, например, из char в doubIe вполне допустимо. После выполнения преобразования каждая пара операндов будет иметь один и тот же тип, и тип результата каждой операции будет совпадать с типом обоих операндов. Преобразование в bool и из bool Как уже ynоминалось, значения типа bool, в случае их использо вания в целочисленных выражениях, автоматически преобразуют ся в целые О или 1. Коrда целочисленный результат преобразуется в тип bool, О превращается в false, а любое ненулевое значение ..... в true. Хотя тип Ьоо' является относительно недавним добавлением в С++, ero автоматические преобразования в целые или из целых означают, что наличие Ьооl не отражается на старых кодах. Кроме Toro, автоматические преобразования позволяют С++ сохранять исходные определения истины и лжи как нулевоrо и ненулевоrо значения. Цель _ Приведение типа в С++ предусмотрена возможность принудительноrо присваива ния выражению конкретноrо типа с помощью конструкции, называе мой приведением типа. В С++ определены пять видов приведений. Че тыре из них дают пользователю возможность полноrо контроля над приведением; они будут рассмотрены в книrе после введения понятия объектов. Однако имеется еще один вид приведения, который можно рассмотреть и использовать уже сейчас. Этот вид является наиболее общим, так как он позволяет преобразовать любой тип в любой дpy rой. Он, кстати, является единственным видом приведения, который 
Приведение типа 91 поддерживался ранними версиями С++. Обшая форма этой KOHCтpYК ции приведения такова: (тип) выражение Здесь тип  это типмишень, в который требуется преобразовать выражение. Если, например, вы хотите удостовериться, что выраже.. нне х/2 при вычислении дает тип t1oat, вы должны написать: (float) х / 2; Приведения считаются операторами. В качестве оператора приве дение является одноместным (унарным) и имеет тот же относитель ный приоритет, что И любой дрyrой одноместный оператор. В некоторых случаях приведение может окззаThCЯ чрезвычайно по лезным. Например, в качестве счетчика цикла естественно исполъзо вать целочисленную переменную, однако вам может понадобитъся BЫ полнять с ней какиелибо вычисления, приводяшие к дробному числу, как это делается в приводимой ниже проrpамме: // Демонстрация приведения типа. #include <iostream> иsing namespace std; i n t та i n ( ) { int i; for(i=l; i <= 10; ++i ) coиt « i « "/ 2 равно: retиrn о; Приведение к типу float позволяет наблюдать дробную часть результата  " « (float) i /2« '\n'; } Ниже приведен вывод этой проrpаммы: 1/ 2 равно: О. 5 2/ 2 равно: 1 3/ 2 равно: 1.5 4/ 2 равно: 2 5/ 2 равно: 2.5 б/ 2 равно: 3 7/ 2 равно: 3.5 8/ 2 равно: 4 9/ 2 равно: 4.5 10/ 2 равно: 5 Если бы в этом примере не осушествлялось приведение (t1oat), то выполнялось бы только целочисленное деление. Приведение 
92 Модуль 2. Знакомимся с данными, типами и операторами обеспечивает получение (и вывод на экран) дробной части pe зультата. Цель _Пробелы и скобки В с++ допустимо включать в выражение символы табуляции и пробелы, чтобы повысить наrлядность и удобочитаемость проrpаммы. Например, приводимые ниже выражения эквивалентны, но второе за метно леrче читать: Х=10/у*(127/х); х = 10 / у * (127/х); Скобки (крyrлые) повышают относительный приоритет содержа шихся в них операций, как это имеет место в алrебре. Использование избыточных или дополнительных скобок не приведет к ошибкам или замедлению выполнения выражения. Весьма полезно использовать скобки, чтобы подчеркнуть и сделать наrлядным порядок выполнения вычислений, как для себя, так и для тех, кто, возможно, в дальнейшем захочет разобраться в вашей проrpамме. Например, какое из приве денных ниже выражений леrче читать? х = y/334*temp+127; х = (у/3)  (34*temp) + 127; Проект 2..3 выислениеe реryлярных платежей по ссуде В этом проекте вы должны написать проrрамму, которая вычисляет сумму реryлярных платежей по ссуде, например, на покупку aBTOMa шины. Исходя из данных значений суммы ссуды, срока займа, числа выплат в rод и ставки процента, проrpамма определит величину pery лярных платежей. Поскольку в проrpамме выполняются финансовые вычисления, вам понадобятся для них переменные с плавающей точ кой. Наиболее распространенным типом TaKoro рода переменных яв ляется doubIe; мы им и воспользуемся. В этом проекте также дeMOHCT рируется одна из новых для нас библиотечных функций С + +: pow( ). 
Пробелы и скобки 93 Для вычисления реryлярноrо платежа вы должны использовать сле дующую формулу: Процент * (CYМMaCcYДbI / Платежейвrод) Платеж == ...................................  1  «Процент / Платежейвrод) + 1) ПлаrежейВfод.ЧислоЛет Здесь Процент определяет ставку процента по ссуде, СуммаСсуды содержит начальную сумму ссуды, Платежейвrод задает число плате жей в rод, а ЧислоЛет указывает срок в rодзх, на который выдана ссуда. Обратите внимание на то, что в знаменателе этой формулы вы дoтк ны возвести одно значение в степень дpyroro. Для выполнения этой операции следует использовать функцию pow( ). Вызов этой функции осуществляется следующим образом: результат == pow (основание, покозотель степени); Функция pow( ) возвращает значение основания, возведенноrо в указанный показатель степени. ApryмeHTЫ pow( ) являются значения ми doubIe; возвращает pow( ) также значение типа doubIe. Шаr за marOM 1. Создайте файл с именем RegPay.cpp 2. Ниже перечислены переменные, используемые в проrpамме: doиble Principali doиble IntRatei doиble PayPerYear; double NиrnYears; double payrnent; double nurner, denorn; double Ь, е; / / начальная сумма ссуды / / ставка процента по ссуде, //например, 0.075 / / число платежей в rод / / число лет, на которые выдана / / ссуда / / реrулярный платеж //временные рабочие переменные / / основание и порядок для вызова / / функции pow ( ) Обратите внимание на то, что объявление каждой переменной co провождается комментарием, поясняющим смысл этой переменной. Такой комментарий облеrчит любому, заинтересовавшемуся вашей проrpаммой, понять, зачем нужна та или иная переменная. Хотя мы не будем включать такие детальные комментарии в большинство при водимых в книrе коротких проrpамм, однако в принципе это весьма полезная практика, особенно коrда вы начнете писать более сложные и длинные проrpаммы. 
94 Модуль 2 Знакомимся с данными, типами и операторами 3. Добавьте в проrpамму следуюшие строки кода, которые будyr BBO дить информацию о ссуде: coиt « "Введите сумму ссуды: "; cin » principal; coиt « "Введите процентную ставку (например, 0.075): "; cin » IntRate; cout « "Введите число платежей в rод: "; cin » PayPerYear; coиt « "Введите число лет: "; cin » NиmYears; 4. Добавьте строки, выполняюшие финансовые вычисления: numer = IntRate * Principal / PayPerYear; е = (PayPerYear * NиmYears); Ь = (IntRate / PayPerYear) + 1; denom = 1  pow(b, е); Payment = nиmer / denom; 5. Завершите проrpамму, выведя на экран сумму реryлярноrо плате ЖЗ, как это показано ниже: coиt « "Платеж составит" « payment; 6. Далее следует полный текст проrpаммы RegPay.cpp: /* Проект 2  3 Вычисление реrулярноrо платежа по ссуде. Назовите этот файл RegPay.cpp */ #include <iostream> #inclиde <cmath> иsing namespace std; int main() { doиble Principal; / / начальная сумма ссуды 
Пробелы и скобки 95 double IntRate; / / cTaBI\a процента по ссуде, //например, 0.075 / / число платежей в rод / / число лет, на которые выдана / / ссуда / / реrулярный платеж / / временные рабочие переменные / / основание и порядок для / / вызова функции pow ( ) doиble payperYear; double NumYears; doиble payment; doиble numer, denom; doиble Ь, е; coиt « "Введите сумму ссуды: "; cin » Principal; coиt « "Введите процентную ставку (например, 0.075): "; cin » IntRate; coиt « "Введите число платежей в rод: "; cin » PayPerYear; coиt « "Введите число лет: "; cin » NumYears; nиmer = IntRate * Principal / PayPerYear; е = (PayPerYear * NиmYears); Ь = (IntRate / PayPerYear) + 1; denom = 1  pow(b, е); Payment = nиmer / denom; coиt « "Платеж составит" « payment; retиrn о; } Ниже приведен ход пробноrо запуска проrpаммы: Введите сумму ссуды: 10000 Введите процентную ставку (например, 0.075): 0.075 Введите число платежей в rод: 12 Введите число лет: 5 Платеж составит 2 О О . 3 7 9 7. Попробуйте самостоятельно модифицировать профамму, чтобы она выводила полную сумму процентов, выплаченных за все время cy шествования ссуды. 
96 Модуль 2 Знакомимся с данными, типами и операторами ""Вопросы для самопроверки 1. Какие типы целых чисел подцерживает С++? 2. Какой тип будет присвоен по умолчанию числу 12.2? 3. Какие значения может иметь переменная типа bool? 4. Какой тип данных соответствует длинному целому? 5. Какая Еsспоследовательность создает символ табуляции? А какая Еsспоследовательность дает звуковой сиrнал? 6. Строка окружается двойными кавычками. Справедливо ли это yт верждение? 7. Перечислите шестнадцатеричные цифры. 8. Приведите общую форму объявления переменной с ее OДHOBpeMeH ной инициализацией. 9. Каково назначение оператора %? Можно ли ero использовать с пе ременными с плавающей точкой? 10. Опишите, чем различаются префиксная и постфиксная формы опе ратора инкремента. 11. Какие из приведенных ниже операторов относятся к лоrическим операторам С++? А. && В. ## С. 11 D. $$ Е. ! 12. Каким дрyrим способом можно записать предложение х = х + 12; 13. Что такое приведение типа? 14. Напишите проrpамму, которая находит все простые числа в диапа зоне от 1 до 100. 
Модуль 3 Предложения управления проrраммой Цепи, достиrаемые 8 ЭТОМ модуле 3.1 Узнать больше о предложении if 3.2 Изучить предложение switch 3.3 Рассмотреть еще раз предложение for 3.4 Разобраться в использовании цикла while 3.5 Научиться использовать цикл do..while 3.6 Понять, rде надо использовать предложение break 3.7 Научиться использовать предложение continue 3.8 Познакомиться со вложенными циклами 3.9 Рассмотреть предложение goto 
98 Модуль 3 Предложения управления проrраммой В этом модуле обсуждаются предложения, управляющие ХОДОМ выпол нения проrpаммы. В с++ имеются три катеrории предложений управ ления проrpаммой: предложения выбора, которые включают в себя if и switch; преД1Iожения циклов, которые включают в себя циклы for, while и do..while; и предложения переходов, включающие в себя break, continue, retum и goto. К предложению return мы вернемся позже; здесь будут рассмотрены все оставшиеся предложения управления, включая и предложения if и for, с которыми вы уже кратко познакомились. Цель _Предложение if Предложение ifуже использовалось нами в Модуле 1. Теперь Hacтy пило время изучить ero более детально. Полная форма предложения if выrлядит так: if(выражение) предложение; else предложение; Здесь мишенями if и else являются одиночные предложения. Пред ложение else не является обязательным. Мишенями и для if, и для else MOryт быть блоки предложений. Общая форма if с использованием блоков выrлядит так: if(выражение) { последовательность предложений } else { последовательность предложений } Если условное выражение истинно, мишень для if выполняется; в противном случае выполняется мишень ШIЯ else, если она существует. Ни при каКИХ обстоятельствах эти две мишени не будут выполняться вместе. Условное выражение, управляющее конструкцией if..else, MO жет быть любым допустимым в с++ выражением, дающим в результа те истину или ложь. Приводимая ниже проrpамма демонстрирует использование предложения if в упрощенном варианте иrpы "Уrадайте маrическое число". Проrpамма rенерирует случайное число, запрашивает вашу доrадку и выводит сообщение ** Правильно **, если вы уrадали Ma 
Предложение if 99 rическое число. В проrpамме используется еще одна библиотечная функция с++, называемая rand( ), которая возвращает случайно выбранное целое число. Эта функция требует подсоединения заrо ловка <cstdlib>. // Проrрамма "Маrическое число". #inclиde <iostrearn> #include <cstdlib> иsing namespace std; int main () { int magic; // маrическое число int gиess; // доrадка пользователя rnagic = rand ( ); / / получим случайное число coиt « "Вводите вашу доrадку: "; cin » gиess; Если дoraдкa соответствует "маrиче. r скому числу", выводится сообщение if(gиess == magic) coиt « "** Правильно **"; return о; } Проrpамма с помощью предложения if определяет, совпадает ли дo raдKa пользователя с маrическим числом. Если совпадает, на экран выводится поощряющее сообщение. Приводимая ниже проrpзмма является усовершенствованием пре дьшущей. В этом варианте используется предложение else для вывода соответствующеrо сообщения в случае ввода пользователем непра вилъноrо числа: / / Проrрамма n Маrическое число 11: 1  е усовершенствование. #include <iostrearn> #include <cstdlib> using namespace std; int rnain ( ) { int rnagic; // маrическое число int gиessi // доrадка пользователя magic = rand(); // получим случайное число 
100 Модуль З. Предложения управления проrраммой coиt « "Вводите вашу доrадку: ": cin » gиess: if(gиess == magic) coиt « "** Правильно **": else coиt« ". ..Жаль, но вы ошиблись.": t При неправильной дoraдKe тоже выводится сообщение retиrn о; } Условные выражения Начинающих проrраммистов иноrда сбивает с толку ТО обстоя тельство, что любое допустимое в с++ выражение может быть ис пользовано для управления предложением if. Отсюда видно, что условное выражение не обязательно должно содержать только опе раторы отношения или лоrические и операнды типа bool. Требует ся только, чтобы результат вычисления управляющеrо предложе ния Mor рассматриваться как ИСТИННЫЙ или ЛОЖНЫЙ. Вспомним (см. предыдущий модуль), что значение О автоматически преобра зуется в false, а все ненулевые значения в true. Таким образом, лю бое выражение, результатом KOToporo является О или ненулевое значение, может быть использовано для управления предложением if. Приведем в качестве примера проrрамму, которая считывает с клавиатуры два целых числа и выводит их частное. Предложение if, управляемое вторым числом, используется для предотвращения деления на ноль. / / Использование значения valиe для управления // предложением if. #include <iostream> иsing namespace std: int main () { 1 N t а Ь · Об ра тите внимание на то , что дл я . " coиt « "Введите делимое: ". управления этим предложением if , достаточно просто переменной Ь. с i n > > а: Нет необходимости использовать coиt « "Введите делитель: "; оператор отношения cin » Ь: . I if (Ь) coиt « "Результат: " « а / Ь« ' \n' ;  else coиt « "Деление на ноль недопустимо.\n": retиrn о: } 
Предложение If 101 Ниже приведены два пробных проroна проrpаммы: Введите делимое: 12 Введите делитель: 2 Результат: 6 Введите делимое: 12 Введите делитель: О Деление на ноль недопустимо. Обратите внимание на то, что значение Ь (делитель) проверяется на ноль с помощью конструкции if(b). Такой метод вполне допустим, так как если Ь равно О, условие, упрамяюшее предложением if, ложно, и выполняется предложение else. В противном случае условие истинно (не О), и выполняется деление. Нет никакой необходимости писать это предложение if таким образом: if (Ь == О) cout « а/Ь« ' \n' ; Такая форма предложения избыточна, неэффективна и мноrими проrpаммистами на с++ считается дурным стилем. Вложенные предложения if Вложенным называется такое предложение if, которое служит ми шенью для дрyroro if или else. Вложенные if широко используются в проrpаммировании. rлавное, что надо помнить о вложенных if в с++, это то, что предложение else всеrда относится к ближайшему к нему предложению if, находяшемуся в том же блоке, что и else, но еше не имеюшеro cBoero else. Вот пример: if(i) { if(j) resиlt = 1; if(k) resиlt = 2; else resиlt = з; // это else относится к if(k) } else result = 4; // это else относится к if(i) Как показыают комментарии, последнее else не относится к ifO), (хотя оно является ближайшим if без cBoero else), потому что оно не находится в том же блоке. Внуфеннее else относится к if(k), которое является ближайшим к этому else. Вы можете выполнить дальнейшее усовершенствование про rpаммы "Маrическое число", добавив в нее вложенное предложе 
102 Модуль 3. Предложения управления проrраммой ние if. Это дополнение сообщит пользователю, в какую сторону он ошибся. / / Проrрамма "Маrическое число": 2e усовершенствование. #include <iostream> #include <cstdlib> using namespace std; int main () { int magic; // маrическое число int guess; // доrадка пользователя magic = rand ( ); / / получим случайное число cout « .. Вводите вашу доrадку: ..; cin » guess; if (guess == magic) { cout « 1.** Правильно **\n"; cout « magic « " и есть маrическое число. \n"; } else { cout« ". ..Жаль, но вы ошиблись."; if(guess »magic) cout «Н Ваше число слишком else cout « .. Ваше число слишком мало.\п"; } велико. \n" ; t Здесь вложенное предложение If предоставля- ет пользователю дополнительную информацию return о; } Цепочка if..else..if в проrpаммировании широко используется конструкция, OCHOBaH нзя на вложенных предложения ifи называемая if-еlsе-ifцепочкой или if-еlsе-ifлестницей. Она выrлядит следующим образом: if(условие) предложение; else if(условие) предложение; else if(условие) предложение; 
Предложение .f 103 else предложение; Условные выражения оцениваются сверху вниз. как только обна руживается истинный результат, выполняется предложение, относя щееся к этому условию, и оставшаяся часть цепочки пропускается. Если ни одно ИЗ условий не дало истинною результата, тоrда выпол няется последнее предложение else. Эro последнее предложение часто выступает в качестве действия по умолчанию; дрyrими словами, если все предшествующие условия не удовлетворяются, выполняется это последнее предложение else. Если последнеrо предложения else в дaH ной конструкции нет, тоrда не выполняется никаких действий. Приводимая ниже проrрамма демонстрирует цепочку if-else-If: // Демонстрация цепочки ifelseif. #include <iostream> usng namespace std; int main () { int х; or(x=Oj х<6; х++) { if(x==l) cout « "х равно единице\п"; else if(x==2) cout « "х равно двум\п"; else if(x==3) cout « "х равно TpeM\n"; else if(x==4) cout « "х равно четыреМ\П"j else cout« "х не находится между 1 и 4\n ll ; } return о; } эта npoфамма выводит следующее: х не находится между 1 и 4 х равно единице х равно двум х равно трем х равно четырем х не находится между 1 и 4 Леrко видеть, что else по умолчанию выполняется только если не удовлетворяется ни одно из предшествующих предложений if. 
104 Модуль 3. Предложения управления проrраммой Минутная тренировка 1. Условие, управляющее предложением if, должно использовать опера тор отношения. Правильно или нет? 2. К которому if всеrда относится else? 3. Что представляет собой цепочка if..else..if? Неправильно Условие, управляющее предложением if, просто должно давать в резуль тате истину или ложь 2 else всеrда оrnосится к ближайшему if в том же блоке, которое еще не имеет cBoero else 3. Цепочка if-else-if представляет собой последовательность вложенных предложений if-else Цель _ Предложение SYiitch Дрyroй алroритм выбора в с++ предоставляет преД7Iожение switch, которое позволяет осуществить выбор требуемоrо варианта из мноrих возможных. Дрyrими словами, проrpамма осуществляет выбор нужной альтернативы. То же самое достиrается последовательностью вложен ных преД7Iожений if, однако использование switch во мноrих случаях оказывается более эффективным. Работает switch таким образом: значе нне выражения последовательно сравнивается с константами выбора в ветвях сне. Коrда обнаруживается константа, равная значению Bыpa жения, выполняется преД7Iожение, закрепленное за этой константой. Общая форма преД7Iожения switch выrлядит следующим образом: switсh(выражение) { case константа 1: последовательность предложений break; case константа2: последовательность предложений break; case константа3: последовательность предложений Ь re а k; default: последовательность предложений } Анализируемое выражение должно давать в результате либо сим вол, либо целочисленное значение (выражения с плавающей точкой 
Предложение sWltch 105 недопустимы). Часто в качестве выражения, управляющеrо выбором, используется просто переменная. Константы в ветвях case должны быть либо целыми числами, либо символьными литералами. Предложение по умолчанию default выполняется, если не было найдено константы, соответствующей значению анализируемоrо BЫ ражения. Предложение default не является обязательным; при ero OT сутствии (и если не обнаружено соответствия) никакие действия не выполняются. Если же соответствие обнаружено, выполняются все предложения соответствующей ветви case, пока не встретится break или, для последней ветви case, а также для ветви default, пока не закон чится вся конструкция switch. Есть четыре существенных момента, которые следует иметь в виду при использовании предложения switcb: · switch отличается от if в том отношении, что switch может анали зировать выражение только на равенство константам выбора в ветвях case, в то время как для if условие выполнения может быть любым. · Никакие две константы в предложении switch не MOryт иметь одно и то же значение. Правда, предложение switch, входящее во внешнее предложение switch, может иметь в ветвях case те же KOH CTaHТbI. · Предложение switcb обычно более эффективно, чем цепочка вло женных if. · Последовательности предложений в ветвях case не есть блоки. Однако, все предложение switch образует блок. Важность этоrо правила станет очевидной по мере изучения вами языка С++. Приводимая ниже проrpамма демонстрирует использование switch. Проrpамма запрашивает у пользователя число между 1 и 3 включи тельно. После этоrо проrpамма выводит поrоворку, которой присвоен этот номер. В ответ на любое дрyrое число проrpамма выводит сооб щение об ошибке. /* Простой reHepaTOp noroBopOK, который демонстрирует использование предложения switch. */ #include <iostream> using namespace stdi int main ( ) { int nит; cout « "Введите число от 1 до з: "; cin » nит; 
106 Модуль 3. Предложения управления проrраммой swi tch (nит) {  Значение num определяет выполняемую ветвь case case 1: cout « "Под лежачий камень вода не течет.\п"; break; case 2: cout« "Лучше синица в руки, чем журавль в небе.\п"; break; case 3: cout « "У дурака деньrи долrо не держатся. \п.. ; break; default: cout « ..Вы можете вводить только 1, 2 или 3. \п.. ; } return о; } Ниже приведены результаты двух проrонов проrpаммы: Введите число от 1 До 3: 1 Под лежачий камень вода не течет. Введите число от 1 до 3: 5 Вы можете вводить только 1, 2 или 3. Формально предложение break не обязательно, хотя в большинстве случаев в конструкциях switch ero приходится использовать. Коrда в последовательности предложений в ветви case встречается преД7Iоже ние break, оно заставляет поток выполнения проrpаммы выйти из Bce ro предложения switch и перейти на следующее предложение вне switch. Если, однако, последовательность ветви case не заканчивается предложением break, тоrда все предложения и выбранной ветви case, U всех нuжележащux будут выполняться до тех пор, пока не встретится break или не закончится все предложение switch. Пример такой ситуа ции демонстрируется в приводимой ниже проrpамме. Можете ли вы сообразить, что она выведет на экран? // switch без предложений break. #include <iostream> using namespace std; int main ( ) { int i; for(i=O; i<5; i++) { 
Предложение swltch 107 switch (i) { case о: cout case 1: cout саэе 2: cout case з: cout case 4: cout « "меньше 1 \n '1 ; « "меньше 2 \n n i « "меньше з\n"i Здесь всюду отcyrcrвуют предложения br88k « "меньше 4 \n" ; « "меньше 5 \n" ; } cout « I \n ' ; } return о; } эта проrpамма выведет на экран следующее: меньше 1 меньше 2 меньше 3 меньше 4 меньше 5 меньше 2 меньше 3 меньше 4 меньше 5 меньше 3 меньше 4 меньше 5 меньше 4 меньше 5 меньше 5 Мы видим, что если в ветви case отсутствует break, то продолжают выполнятся все последующие ветви case. Допустимо иметь пустые ветви case, как это показано ниже: swi tch ( i ) { case 1: с а s е 2:  Пустые nocпeдовательности с_ case з: cout « "i меньше 4"; break; саэе 4: cout « "i равно 4"; break; 
108 Модуль 3. Предложения управления проrраммой в этом фраrменте если i имеет значение 1, 2 или 3, то выводится сообщение i меньше 4 Если же i равно 4, то выводится i равно 4 Выстраивание условий case "штабелем", как это показано в послед нем примере, является весьма распространенным приемом в тех слу чаях, коrда Д7Iя нескольких условий выбора должен выполняться один и тот же фраrмент кода. Вложенные предложения switch ПреД1Iожение switch может входить в последовательность преД1Iоже ний внешнеro преД7Iожения switch. При эroм конфликтов не возникает даже если в списках констант case BHyтpeHHero и внешнеro switch име ются общие значения. Например, приведенный ниже фрarмент вполне допустим: swi tch ( ch1) { case 'Д': cout « "Эта Д является частью внешнеrо switch"; switch(ch2) {  case ' А ' : cout « tt Эта А является частью BНYTpeHHero swi tch tt ; break; case I В': / / ... Вложенное предложение SWltch } break; case 'В': // ... Минутная тренировка 1. KaKoro типа должно быть выражение, управляющее предложением switch? 2. Что происходит, коrда результат выражения в преД7Iожении switch COB падает с константой case? 3. Что происходит, если предложение case не завершается преД7Iожением break? 
Предложение sWltch 109 1 Выражение sWltch должно быть целоrо или символьноrо типов 2. Коrда обнаруживается совпадаЮlцая с выражением константа case, выполняется после.. довательность преJUIожений. ВХОДЯIЦИХ в ветвь соответствующеl о case 3 Если предложение case не завершается предложением break, продолжается выполнение следующей ветви case Спросим у эксперта Вопрос: При каких условиях в алrоритмах выбора требуемоrо варианта следует использовать цепочку if-else-if вместо предложения switch? OrвeT: Как правило, цепочку if-else-if удобнее использовать в тех случа ях, коrда условия, управляющие процессом выбора варианта, не определя ются простым значением. Рассмотрим, например, такую последователь ность if-else-if: if(x < 10) // else if(y > О) // else if(!done) // Такую последовательность нельзя преобразовать в предложение switch, потому что во всех трех условия анализируют различные переменные и, к тому же, разных типов. Какую переменную выбрать в качестве управляю щей для предложения switch? Далее, вам придется использовать цепочку if- else-if, если условие выбора зависит от значения переменной с плавающей точкой или дрyrоrо объекта, тип KOToporo недоnyстим в выражении switch. Проект 3..1 Начинаем строить справочную систему С++ в этом проекте строится простая справочная система, выводящая ин формацию о синтаксисе управляющих предложений С++. Проrpамма выводит меню, содержащее список управляющих преД7Iожений, и затем ожидает, чтобы вы выбрали одно из них. После 31Оro на экран вводится синтаксис соотве1Ствующеro преД7Iожения. В эroт первый вариант про фаммы включены только справки по преД7Iожениям if и swПсЬ. Дрyrие УПРЗR1lЯЮщие преД7Iожения будут добавлены в последующих проектах. Шаr за maroM 1. Создайте новый файл с именем Help.cpp. 
110 Модуль 3. Предложения управления проrраммой 2. Проrpамма начинает свою работу с вывода следующеro меню: Справка по: 1. if 2. switch Выберите один из пунктов: Для реализации пункта 2 вы можете использовать такую последова тельность предложений: cout « "Справка По: \п " i cout « " 1. if\n " ; cout « " 2 . swi tch \п " ; cout « "Выберите один из пунктов: " . , 3. Далее проrрамма вводит выбранный пользователем номер, как это показано ниже: cin » choice; 4. Получив от пользователя номер, проrpамма выводит на экран co ответствующую справку с помощью следующеrо предложения switch: switch(choice) { case 11': cout « "Предложение if:\n\n"i cout « "if(условие) предложениеi\П"i cout « "else предложениеi\П"i breaki case I 2 ' : cout « "Предложение switch:\n\n"i cout « "switсh(выражение) {\п"; cout «" case константа: \п " i cout «" последовательность предложений\п"; cout «" breaki\n"i cout « " // .. .\п"; cout « tt} \n " ; breaki default: cout « t'Этот пункт отсутствует. \п" i } Обратите внимание на то, как предложение default отбирает Heдo пустимые номера. Если, например, пользователь ввел 3, этому числу нет соответствия в ветвях case, что и приведет к выполнению последо вательности default. 5. Ниже npиведен полный текст проrpаммы Help.cpp: 
Предложение switch 111 /* Проект 3  1 Простая справочная система. */ #include <iostream> using namespace stdi in t та in () { char choice; cout « "Справка nO:\n"i cout« " 1. if\n"; cout «tt 2. swi tch \n" ; cout « " Выберите один из пунктов: tt; cin » choice; cout « " \n " ; switch(choice) { case '1': cout « "Предложение if:\n\n"; cout « "if(условие) предложениеi\П"; cout « "else предложение;\п"; breaki case '2': cout « "Предложение switch:\n\n"; cout « "switсh(выражение) {\п" i cout «tt case константа: \n " ; cout «" последовательность предложений\п"; cout «" break;\n"i cout« " // . ..\n"; cout « tt} \n"; break; default: cout « "Этот пункт отсутствует. \п"; } return о; } Ниже приведен пример работы проrpаммы.: Справка по: 1. if 2. switch 
112 Модуль 3 Предложения управления проrраммой Выберите один из пунктов: 1 Предложение i f : if(условие) предложение; else предложение; Цель _Цикл for Начиная с Модуля 1 вы уже использовали прос'JyЮ форму цикла for. Этот цикл обладает удивительными возможностями и rибкостью. Рассмотрим основные черты этой конструкции, начав с ее наиболее традиционной формы. Общая форма цикла for для повторения единственноrо предложе ния выrлядит так: fоr(инициализация; выражение; приращение) предложение; Для повторения блока предложений общая форма будет такой: fоr(иницuализация; выражение; приращение) { последовательность предложений } Инициализация обычно представляет собой предложение присваи вания, которое устанавливает начальное значение переменной уnравле нuя циклом, действующей в качестве счетчика шатв цикла. Выражение является условным выражением, определяющим, будyr ли повторять ся далее шаrи цикла. Приращение определяет величину, на которую бу дет изменяться переменная управления циклом в каждом шаrе цикла. Обратите внимание на то, что эти три основные секции цикла должны разделяться символами точки с запятой. Цикл for будет продолжать свое выполнение, пока условное выражение истинно. Как только это выражение станет ложным, произойдет выход из цикла, и выполнение проrpаммы продолжится с предложения, следующеro за блоком for. Приводимая ниже проrpамма использует цикл for для вывода зна чений квадратных корней из чисел от 1 до 99. В этом примере пере менная управления циклом названа пот. // Показывает квадратные корни из чисел от 1 до 99. #include <iostream> #include <cmath> 
Цикл for 113 using namespace std; int main () { int пит; doubl е sq,...,root; for(num=1; nит < 100; nиm++) { sroot = sqrt((double) num); cout « пит « tt tt « sq.....root « I \n' ; } return о; } Проrpамма использует стандартную функцию sqrt( ). Как уже rOBo" рилось в Модуле 2, функция sqrt( ) возвращает квадратный корень из передаваемоro ей apryмeHтa. ApryмeHT должен быть типа doubIe, и функция возвращает значение также типа doubIe. Для вызова функции требуется заroловок <cmath>. Переменная управления циклом может изменяться как в положи.. тельном, так и в отрицательном направлении, причем на любую вели.. чину. В качестве примера следующая проrpамма выводит числа от 50 до 50 с интервалом 10: // ЦИКЛ, выполняемый в отрицательном направлении. #include <iostream> using namespace std; int main () { int i; for(i=50; i >= 50; i = i10) cout« i « I ';  Цикл, выполняемый в отрицатепыюм направлении return о; } Вывод проrpаммы выrлядит следующим образом: 50 40 зо 20 10 О 10 20 зо 40 50 При использовании циклов важно иметь в виду, что условное выражение цикла всеrда анализируется в начале шаrа. Таким обра.. зом, код внутри цикла может не выполниться ни одноrо раза, если 
114 Модуль 3. Предложения управления проrраммой условие с caMoro начала оказывается ложным. Вот пример такой ситуации: for(count=10; count < 5; count++) cout « count; // это предложение не выполнится Этот цикл выполняться не будет, так как управляющая переменная count при входе в цикл сразу оказывается больше 5. Поэтому условное выражение count<5 ложно с caмoro начала и в результате не будет BЫ полнено ни одноrо шаrа цикла. Некоторые варианты цикла for Цикл for является одним из наиболее rибких предложений в языке с++, потому что он допускает большое количество разнообразных Ba риантов. Например, допустимо использовать несколько переменных управления. Рассмотрим следующий фраrмент кода: for(x=O, у=10; х <= У; ++х, Y)  cout « х « ' I « у « I \n' ; Использование нескольких пе ременных управления циклом Здесь запятыми разделены два инициализирующих предложения и два выражения наращивания. Запятые необходимы, так как они пока зывают компилятору, что в этом цикле по два преД7Iожения инициали зации и наращивания. В с++ запятая представляет собой оператор, который означает "сделай это и это". Запятая широко используется, в частности, в циклах for. Допустимо иметь любое число предложений инициализации и наращивания, хотя, если их больше ДByxтpex, цикл становится слишком неуклюжим. Спросим у эксперта Вопрос: Подцерживает ли с++, кроме sqrt( ), дрyrие математические функции? OrвeT: Да! В дополнение к sqrt( ), с++ поддерживает широкий набор библиотечныx математических функций, например, sin(), cos(), tan(), log(), pow( ), ceil( ), t1oor( ) и MHoro дрyrих. Если вас интересуют математические проrpаммы, вам следует познакомиться с набором математических функ ций С++. Все компиляторы с++ поддерживают эти функции, и их описа ние можно найти в документации к вашему компилятору. Математические функции требуют включения в проrpамму заrоловка <cmath>. Условное выражение, управляющее циклом, может представлять собой любое допустимое выражение с++. Оно совсем не обязательно 
Цикл for 115 должно использовать переменную управления циклом. В следующем примере цикл продолжает выполняться до тех пор, пока функция rand( ) не вернет число, большее, чем 20000. /* ЦИКЛ выполняется, пока случайное число не окажется больше, чем 20000. */ #include <iostream> #include <cstdlib> using namespace std; int main () { int i; int ri r = r and ( ) ; for(i=O; r <= 20000; i++)  r = rand ( ) ; Эrо условное выражение не испопь зует nepeмeнную управления ЦИКnOМ cout « "Число равно " « r « ". Оно было получено на шаrе n « i « " ". . , return О i } Вот пример вывода профаммы: Число равно 26500. Оно было получено на шаrе 3. в каждом шаrе цикла вызовом rand( ) reнерируется новое случайное число. Коrда будет получено число, большее 20000, условие цикла cтa новится ложным И цикл прекращает свою работу. Опущенные секции Еще одно свойство цикла for, отличающее с++ от мноrих дpy rих компьютерных языков, заключается в возможности опускать отдельные секции определения цикла. Если, например, вы хотите описать цикл, который должен выполняться до тех пор, пока с клавиатуры не будет введено число 123, это можно сделать таким образом: 
116 Модуль 3. Предложения управления проrраммой // ЦИКЛ без приращения. #include <iostream> using nаmеэрасе std; int main () { int Х; for(x=O; х 1= 123; ) {  cout « "Введите число: "; cin » Х; Выражение приращения отсутствует. } return о; } в этом определении цикла for отсутствует секция, характери зующая приращение. Это значит, что в каждом шаrе цикла BЫ полняется проверка, не равна ли переменная х числу 123, но больше никаких действий не предпринимается. Если, однако, вы вводите с клавиатуры 123, условие выполнения цикла становится ложным и цикл прекращает свое выполнение. Если в определе нии цикла отсутствует секция, описывающая приращение, то при выполнении цикла управляющая им переменная изменяться не будет. Друrой вариант цикла for можно получить, если переместить ceK цию инициализации за пределы цикла, как это показано в приводи мом ниже фраrменте: Х = о;  х инициализируется вне цикла for ( ; Х< 1 О ; { cout« Х« 1 1; ++Х; } Здесь опущена секция инициализации, а х инициализируется пе ред входом в цикл. Размещение инициализирующих действий вне цикла обычно выполняется лишь в тех случаях, коrда алrоритм BЫ числения начальноrо значения настолько сложен, что ero нельзя разместить внутри предложения for. Обратите внимание на то, что в приведенном примере секция приращения размещена внутри тела цикла. 
Цикл for 117 Бесконечный цикл for Вы можете создать бесконечный цикл (т. е. цикл, который никоrда не прекращает cBoero выполнения), использовав такую конструкцию: for ( ; ; ) { //... } Такой цикл будет выполняться бесконечно. Хотя в принципе суще ствуют проrpаммистские задачи (например, командный процессор операционной системы), которые требуют орrанизации бесконечноrо цикла, большинство "бесконечных циклов" являются всею лишь цик лами со специальными условиями завершения. В конце этоro модуля вы узнаете, как можно остановить цикл TaKoro рода. (Подсказка: это делается с помощью предложения break.) Цикл с отсутствующим телом в С++ допустимы циклы for, в которых отсутствует само тело цикла. Такая конструкция возможна потому, что синтаксически допустимо пустое предложение. "Бестелесные" циклы оказыва ются часто весьма полезными. Например, приведенная ниже проrрамма использует такой цикл для получения суммы чисел от 1 до 1 о: / / Тело цикла может быть пустым. #include <iostream> #include <cstdlib> using namespace stdi int main () { int i; int sum = о; // суммируем числа от 1 до 10 for(i=l; i <= 10; sum += i++)  ЭrОТ ЦИКЛ не имеет тела. cout « "Сумма равна .. « sum; return о; } 
118 Модуль З. Предложения управления проrраммой ВЫВОД проrpаммы выrлядит так: Сумма равна 55 Заметьте, что процесс суммирования выполняется целиком внyrpи предложения for, и в теле цикла нет необходимости. Обратите особое внимание на выражение приращения: sum += i++ Не бойтесь предложений TaKoro рода. В профессиональных про фаммах на с++ они вполне обычны; чтобы разобраться в смысле Ta Koro предложения ero следует разбить на составные части. Это предло жение, выраженное словами, обозначает "прибавьте к sum значение sum плюс i, затем выполните инкремент i". Таким образом, оно делает то же, что и следующая последовательность предложений: sum = sum + i; i++; Объявление переменных управления циклом внутри цикла for Часто переменная, управляющая циклом for, требуется только внyrpи этоro цикла и не используется ниrде в дрyrом месте. В этом случае можно объявить управляющую переменную в инициализирую щей секции предложения for. Такой прием демонстрируется в приво димой ниже проrpамме, которая вычисляет сумму чисел от 1 до 5 и факториал числа 5: // Объявление переменной управления циклом внутри // предложения for. #include <iostream> using namespace std; i n t та i n () { int sum = о; int fact =. 1; // вычислим факториал числа 5 for (int i = 1; i <= 5; i ++) {  здесь i объявлена внутри цикла for sum += i; // i известна всюду внутри цикла fact *= i; } 
Цикл for 119 // однако здесь i неизвестна. cout « .. Сумма равна '1 « вит « .. \п .. ; cout « "Факториал равен" « fact; return о; } Вывод этой проrpаммы: Сумма равна 15 Факториал равен 120 Если вы объявляете переменную внутри цикла for, то вы должны иметь в виду одно важное обстоятельство: эта переменная будет из вестна только внутри предложения for. Дрyrими словами, используя терминолоrию языков проrpаммирования, область видимости пере менной оrpаничена циклом for. Вне цикла for эта переменная просто не существует. Поэтому в предь:щущем примере к i нельзя обратиться за пределами цикла for. Если вам нужно использовать переменную управления циклом rделибо еще в проrpамме, вы не должны объяв лять ее внутри цикла for.  Замечание Область видимости переменной, объявленной в секции инициализа ции цикла fOf, изменялась с течением времени. Первоначально эта пере менная была доступна и после цикла for, но в процессе стандартизации С++ положение изменилось. Современный С++ по стандарту дNSI/ISO оrраничивает область видимости управляющей переменной только цик лом for. Некоторые компиляторы, однако, не накладывают на нее этоrо оrраничения. Вам следует выяснить, каковы правила в отношении этой переменной в той среде, в которой вы работаете. Перед тем, как приступить к изучению дальнейшеrо материала, вам стоит поэкспериментировать с различными вариантами цикла for. Вы увидите, что эта конструкция полна скрытых возможностей. Минутная тренировка 1. Moryт ли секции преД1Iожения for быть пустыми? 2. Покажите, как создать бесконечный цикл for. 3. Какова область видимости переменной, объявленной в предложении for? 
120 Модуль 3 Предложения управления проrраммой 1 Да Все три части предложения for  инициализация, условие и нарашивание  MOryr быть пустыми 2. for ( ; ; ) 3. Область видимости переменной, объявленной внутри предложения for, оrpаничена цик" лом. Вне цикла эта переменная неизвестна Цель _ Цикл while Дрyraя возможность орrанизовать повторные действия предостав ляется циклом while. Общая форма этоrо цикла выrлядит следующим образом: whilе(выражение) предложение; Здесь предложение может быть одиночным предложением или бло ком предложений. Выражение определяет условия, управляющие цик лом, и может быть любым допустимым выражением. Предложение выполняется, пока условие остается истинным. Коrда условие CTaнo вится ложным, проrраммное управление передается на строку, непо средственно следующую за циклом. Приводимая ниже проrpамма иллюстрирует использование цикла while Д7Iя реализации весьма любопытноrо алrоритма. Практически все компьютеры ПОдzlерживают набор символов, расширенный по cpaBHe нию с обычным набором ASCII. Расширенные символы, если они cy ществуют, часто включают такие специальные знаки, как символы иностранных языков и научные обозначения. Символы ASCII имеют значения меньшие, чем 128. Расширенный набор включает символы начиная с кода 128 и до 255. Наша проrpамма выводит на экран все символы от 32 (пробел) до 255. Запустив проrpамму, вы увидите HeKO торые очень интересные символы. /* Эта проrрамма выводит все отображаемые на экране символы, включая расширенный символьный набор, если он существует. */ #include <iostream> using namespace std; int main ( ) { unsigned char сЬ; сЬ = з 2 ; 
Цикл whlle 121 while(ch) {  coиt « сЬ; сЬ++; Использование цикла whi18. } return о; } Рассмотрим выражение условия выполнения цикла приведен ной выше проrpаммы. Может вызвать недоумение, почему для управления циклом while используется просто переменная сЬ. По скольку сЬ объявлена как символ без знака, она может содержать значения от О до 255. Коrда значение этой переменной становится равным 255 и затем еще увеличивается на 1, происходит явление "оборачивания" и сЬ становится равным О. В результате проверка сЬ на равенство нулю может служить удобным условием окончания цикла. Как и в случае цикла for, цикл while анализирует условное BЫ ражение в начале каждоrо шаrа, из чеrо следует, что тело цикла может не выполниться ни разу. Приводимая ниже проrрамма ил люстрирует эту характеристику цикла while. Проrрамма выводит линию точек. Число точек определяется значением, введенным пользователем. Проrрамма не допускает вывод строк длиннее 80 символов. #include <iostream> using namespace std; int main () { int len; cout « ..Введите длину (от 1 до 79): "; cin » len; while(len>O && len<80) { cout« '.'; len; } return о; } Если len выходит за установленные rpаницы, цикл while не выпол няется ни разу. В противном случае цикл выполняется, пока len не достиrнет О. 
122 Модуль 3 Предложения управления проrраммой Тело цикла while может не содержать ни одноrо предложения. Вот пример TaKoro цикла: while (rand () ! = 100) ; Этот цикл повторяется, пока случайное число, rенерируемое Функ цией rand( ), не окажется равным 100. Цель _ Цикл do..while Последняя конструкция цикла в с++  цикл do-while. В отличие от циклов for и while, в которых условие выполнения анализируется в Ha чале каждоro шаrа, в цикле do-while проверка условия выполняется в конце шаrа цикла. Отсюда следует, что цикл do-while всеrда выполня ется по меньшей мере 1 раз. Общая форма цикла do-while выrлядит следующим образом: do{ предложения; } whilе(условие); Если тело цикла состоит из единственноrо предложения, в фиryp ных скобках нет необходимости. Однако они часто используются для повышения наrлядности конструкции do-while. Цикл do-while выпол няется до тех пор, пока условное выражение истинно. В следующей проrpамме цикл выполняется, пока пользователь не введет число 100: #include <iostream> using namespace stdi int main ( ) { int пит; do {  cout « "Введите число cin » пит; } while(num != 100); return о; Цикл do.while вcerдa выполняется по меньшей мере один раз. I (100 для завершения): "; } с помощью цикла do-while мы можем выполнить дальнейшее усовершенствование проrраммы "Маrическое ЧИСЛО". На этот раз 
Цикл do..whlle 123 nporpaMMa "КРУТИТСЯ" в цикле, пока вы не уrадаете правильное число. / / Проrрамма ..Маrическое число..: 3e усовершенствование. #include <iostream> #include <cstdlib> using nаmеэрасе std; int main () { int magic; // маrическое число int guess; // доrадка пользователя magic = rand ( ); / / получим случайное число do { cout « .. Вводите вашу доrадку: ..; cin » guess; if(guess == magic) { cout « "** Правильно ** "; cout « magic « " и есть маrическое число.\n"; } else { cout« "...Жаль, но вы ошиблись.";, if(guess > magic) cout '« " Ваше число слишком велико. \n.. ; else cout « " Ваше число слишком мало.\n"; } } while(guess 1= magic); return о; } Вот результат пробноrо проrона: Вводите вашу доrадку: 10 . ..Жаль, но вы ошиблись. Ваше число слишком мало. Вводите вашу доrадку: 100 . ..Жаль, но вы ошиблись. Ваше число слишком велико. Вводите вашу доrадку: 50 ...Жаль, но вы ошиблись. Ваше число слишком велико. Вводите вашу доrадку: 41 ** Правильно ** 41 и есть маrическое число. Последнее замечание: как и ДЛЯ циклов for и while, в цикле do-while тело цикла может быть ПУСfЫМ, ХОТЯ практически этой возможностью ПОЛЬЗУЮТСЯ редко. 
124 Модуль 3. Предложения управления проrраммой Минутная тренировка 1. В чем основное различие циклов while и do..while? 2. Может ли быть пустыIM тело цикла while? 1 В цикле whПе условие проверяется в начале ка:ждоrо шаrа ЦИЮ1а В цикле do-while это де.. лается в конце шаrа Таким образом, цикл do-while всеrда выполняется по меньшей мере один раз 2 Да, тело цикла while (или любоrо дpyroro цикла С++) может быrь пустым. Спросим у эксперта Вопрос: Учитывая большую rибкость всех циклов С++, по какому кри терию я должен выбирать форму цикла? Дрyrими словами, как мне BЫ брать для решения конкретной задачи правильную форму цикла? Ответ: Если вам надо выполнить известное число шаrов, используйте цикл for. Если вам требуется, чтобы во всех случаях выполнился хотя бы один шаr цикла, используйте цикл do-while. Цикл while наиболее eCTeCT венно использовать в тех случаях, коrда число шаrов цикла заранее неиз вестно. Проект 3..2 Усовершенствование справочной системы с++ в этом проекте выполняется усовершенствование справочной сис темы, созданной в Проекте 31. В данном варианте в справочную систему заносится информация о циклах for, while и do..while. Кроме этоro, в проrpамме проверяется номер пункта, выбранноrо пользова телем, и в случае HeBepHoro номера запрос на ввод повторяется до тех пор, пока не будет введено допустимое число. Для этоro исполь зуется цикл do..while. Стоит отметить, что использование цикла do.. while для выбора пункта меню ЯRЛЯется распространенной практи кой, потому что такое действие всеrда надо выполнять по меньшей мере один раз. Шаr за marOM 1. Скопируйте файл Help.cpp в новый файл с именем Help2.cpp. 2. Измените часть проrpаммы, выводящую возможные варианты, так, чтобы она использовала показанный ниже цикл do..while: 
Цикл dowhlle 125 do { cout « "Справка по: \n" : cout « .. 1. if\n" ; cout « " 2 . switch\n"; cout « .. 3 . for\n" ; cout « .. 4 . while\n" : cout « " · 5. do...whi le \n" : cout « " Выберите один из nyнктов: " . , cin » choice; } while( choice < 11' II choice> '51); После внесения этоro изменения проrpамма будет "крутиться" в цикле, выводя меню, до тех пор, пока пользователь не введет число между 1 и 5. Как видите, цикл do..while в данном случае оказывается весьма полезен. 3. Расширьте предложение switch, включив в Hero справки по цик лам for, while и do..while, как это показано ниже: switch(choice) { саэе 11': cout « "Предложение if:\n\n"; cout « "if(условие) предложение;\n"; cout « "еlве предложение;\п"; break; case I 2 ' : cout « "Предложение switch:\n\n"; cout « "switсh(выражение) {\n"; cout «" саэе константа: \п" ; cout «" последовательность предложений\n"; cout «" break;\n"; cout « " // .. .\n"; сои t < < "} \ n" i break: case I 3 ' : cout « "Цикл for:\n\n"; cout« "fоr(инициализация; условие: приращение)"; cout « " предложение:\n"i break; саэе I 4 ' : cout « .. Цикл whi le: \n \n" ; cout « "while (условие) предложение; \n" ; break; саэе 15': cout « "Цикл dowhile:\n\n": 
126 Модуль 3. Предложения управления проrраммой cout « "do {\n"; cout « " предложение;\n"; cout « "} while (условие); \n" ; break; } Заметьте, что в этом варианте в предложении switch отсутствует ветвь default. Поскольку цикл вывода меню обеспечивает ввод поль 30вателем только допустимоrо номера пункта, отпадает необходи мость включать предложение default для обработки неправильноro ввода. 4. Ниже при водится полный текст проrраммы Help2.cpp. /* Проект З2 Усовершенствованная справочная система, использующая цикл dowhile для выбора пункта меню. */ #include <iostream> tlS i ng namespace stdi i n С md i n () { (tltl r ch() i се; {to { ('(JtJt « "Справка по: \n" ; CtJlJ t « " 1 . if\n" ; ('otl t « " 2 . swi tch \n" ; cout « " 3 . for\n"; coиt « " 4 . while\n" ; cout « " 5 . dowhile\n" i coиt « ,. Выберите один из пунктов: " . , cin » choice; } while( choice < '1' II choice> '5'); cout « "\n \n" ; switch(choice) ( case ' 1 ' : cout « "Предложение if:\n\n"; cout « "if(условие) предложениеi\n"; cout « "else предложение;\n"i break; саэе '2': cout « "Предложение switch: \n\n"; 
Цикл dowhlle 127 cout « "switсh(выражение) {\n ll ; сои t «11 case константа: \n" i cout «11 последовательность предложений\n" ; cout «" break;\n"i cout« " // . ..\n"; cout« "}\п"; breaki case I 3 ' : cout « "ЦИКЛ for: \n \n" i cout« "fоr(инициализаЦИЯi условие; приращение)"i cout « " предложениеi\n"; breaki case 14/: cout « "Цикл while: \п\n" i cout « "whilе(условие) предложениеi\П"i break; case I 5 I : cout « "Цикл dowhile: \n\n"; cout « "do {\n"; '. cout « · предложение;\п"; cout « "} while (условие); \n" i break; } return о; } (просим у эксперта Вопрос: Ранее вы объяснили, как можно объявить переменную в секции инициализации цикла for. Можно ли объявлять переменные внyrpи дрyrиx управляющих предложений с++? OrвeT: Да. В с++ возможно объявить переменную внyrpи условноrо выражения в предложениях if или switch, внyrpи условноrо выражения в цикле wbile, а также внyrpи секции инициализации цикла for. Переменная, объявленная в одном из этих мест, имеет область видимости, оrpаничен ную блоком кода, которым управляет данное предложение. Вы уже видели при мер объявления переменной внyrpи цикла for. Вот пример объявления переменной внyrpи предложения if: if (int х = 20) { х = х  У; if(x > 10) У = о; } в предложении if объявляется переменная х, и ей присваивается значе 
128 Модуль 3 Предложения управления проrраммой ние 20. Поскольку это значение истинно, мишень if выполняется. По скольку переменные, объявленные в условном предложении, имеют об ласть видимости, оrpаниченную блоком кода, управляемоrо этим предло жением, х становится неизвестным за пределами if. Как уже отмечалось при обсуждении цикла for, будет ли переменная, объявленная внутри управляющеrо предложения, известна только в этом предложении, или она будет доступна и после этоrо предложения, зависит от компилятора. Перед тем, как использовать такие переменныI,, вам следу ет свериться с документацией к вашему компилятору. Разумеется, стандарт с++ ANSI/ISO оrоваривает, что переменная известна только в внутри Toro предложения, в котором она объявлена. Большинство проrpаммистов не объявляют переменные внутри каких либо управляющих предложений, за исключением цикла for. Вообще объ явление переменных внутри дрyrих предложений является спорной прак тикой, и некоторые проrpаммистыI считают это дурным тоном. Цель _ Использование break для BblxoAa из цикла с ПОМОЩЬЮ предложения break имеется возможность вьmолнить HeMeд ленныи выход из цикла, обойдя проверку условия. Если внутри ЦИЮIа RCтречается предложение break, цикл немедленно завершается, и управле tlие передается на предложение, следующее за ЦИЮIом. Вот пpocroй пример: #lnclude <iostream> нн i пg паmеsрасе std; int main () { int t; // ЦИКЛ от О до 9, Не до 100! for(t=O; t<100; t++) { if(t==10) break;  cout « t « I 1; ВЫХОД из цикла for, Korдa t становится равным 10 } return о; } Вывод этой nporpaMMbl: 012 3 4 5 6 7 8 9 
Использование break для выхода из цикла 129 Как видно из вывода проrpаммы, она отображает на экране числа от О до 9 и после этоrо завершается. Проrpамма не доходит до 100, по тому что предложение break заставляет цикл завершиться раньше. Если циклы вложены друr в дрyrа (т. е. коrда один цикл включает в себя дрyrой), break осуществит выход только из самою BнyrpeHHero цикла. Вот пример этоro: #include <iostream> using nатеврасе std; int main () { int t, count; for(t=O; t<10; t++) { count = 1; for(;;) { cout « count « I 1; count++; if(count==10) break; } cout « '\n'; } return о; } Вот вывод этой проrpаммы: 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 6 7 8 9 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 Как вы видите, проrpамма выводит на экран числа от 1 до 9 десять раз. Каждый раз, коrда во внутреннем цикле for встречается break, управление передается внешнему циклу for. Обратите внимание на то, что внутренний цикл for является бесконечным циклом, который за вершается с помощью преДТIожения break. Предложение break может быть использовано с любым предложе нием цикла. Оно особенно удобно, коrда некоторое определенное yc 
130 Модуль 3 Предложения управления проrраммой ловие должно привести к немедленному завершению цикла. Одним из примеров такой методики является завершение бесконечных циклов, что бьшо проиллюстрировано в предыдущем примере. Еще одно замечание: break в предложении switch будет влиять на выполнение только этоro предложения switch, но не цикла TOro или иноrо рода, в который это предложение может входить. Цель _ Использование continue Сущеcrвует возможность принудительно завершить некoroрый шar цикла, обоЙДЯ нормальную управляющую СТРУК1УРу цикла. Эro дейcrвие выполняется с помощью предложения сопtinое. Эro предложеюtе заставля ет цикл перейти к следующему шary, обойдя тобой код меЖдУ сontinое и yc ловным выражением, управляющим циклом. Пример 31Оro можно увидеть в следующей проrpамме, кoroрая выводит на экран четные числа от О до 100: #include <iostream> using namespace std; int main () { int Х; for(x=O; х<=100; х++) { if(x%2) continue;  cout « х « I 1; Досрочно завершить шar, если х нечетно } rturn о; } Выводятся только четные числа, потому что нечетное число вызы вает выполнение предложения continue, которое приводит к выходу из этоrо шаrа цикла с обходом предложения вывода cout. Вспомните, что оператор % возвращает остаток от целочисленноrо деления. Коrда х нечетно, остаток равен 1, т. е. результат деления истинен. Если х чет но, остаток равен О, т. е. результат ложен. В циклах while и do-while предложение сontinое вызывает передачу управления непосредственно на условное предложение, после чеro цикл продолжается обычным образом. В случае for вьmолняется секция прира щения цикла, затем условное выражение, после чеro цикл продолжается. Минутная тренировка 1. Что происходит внутри цикла, коrда выполняется предложение break? 
Использование cont.nue 131 2. К чему при водит выполнение преД7Iожения continue? 3. В случае, коrда в цикл входит предложение switch" приводит ли предло жение break в этом switch к завершению цикла? 1 ПреШ10жение break внyrpи цикла приводит к немедЛенному )авеРUlению цикла Выпол.. нение продолжается с первой строки кода после цикла 2 Преможение continue приводит к завеРUlению данноrо шаrа никла с обходом oCTaBUle.. rося в нем кода l Нет Проект 3..3 Завершаем разра- ботку справочной системы С++ Проект накладывает завершающие штрихи на справочную систему С++, разработанную в предыдущем проекте. В этом варианте в систе му добавляется справка по предложениям break, continue и goto. Про фамма таюке позволяет пользователю запросить справку по несколь ким предложениям языка. С этой целью в проrpамму добавлен внеш ний цикл" который выполняется до тех пор, пока пользователь в ответ на предложение проrpаммы выбрать пункт меню не введет q. Шаr за шаrом 1. Скопируйте Help2.cpp в новый файл с именем Help3.cpp. 2. Окружите весь проrpаммный код бесконечным циклом for. Вый дите из этоro цикла посредством предложения break в случае ввода q. Поскольку этот цикл окружает весь остальной код, выход из Hero за ставляет завершиться всю проrpамму. 3. Измените цикл меню, как это показано ниже: do { cout « "Справка по:\п"; cout « " 1. if\n"; cout « " 2. switch\n"; cout « " з: for\n"; cout « " 4. while\n"; cout« .. 5. dowhile\n"; cout « " 6. break\n"; cout « " 7. continue\n"; cout « " 8. goto\n"; cout « "Выберите один из пунктов (q для завершения): "; cin » choice; } whi 1 е ( с ho i с е < ' l' I I (. tl О j С е > ' 8' & & с Ьо i с е ! = ' q' ) ; 
132 Модуль 3 Предложения управления проrраммой Заметьте, что этот цикл включает преД7Iожения break, continue и goto. Он таюке позволяет ввести q, как один из допустимых символов. 4. Расширьте предложение switch, включив в Hero предложения break, сопtinое и gоtц как это показано ниже: case ' 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; 5. Ниже при веде н полный текст профаммы Help3.cpp: /* Проект 3  3 Законченная справочная система, обслуживающая повторные запросы. */ #include <iostream> using namespace std; in t та in () { char choice; for(;;) { do { cout « "Справка по:\п"; cout « " 1. if\n ll ; cout « " 2. switch\n"; cout « 1I 3. for\n"; cout « " 4. while\n"; cout « " 5. dowhile\n"; cout « " 6. break\n"; cout « " 7. continue\n"; cout « " 8. goto\n"; cout « "Выберите один из пунктов (q для завершения): ; cin » choice; } while( choice < '1' II choice> '8' && choice 1= 'q'); 
Использование сопtlпuе 133 if(choice == 'q') break; cout« "\п\п"; switch(choice) { case ' 1 ' : cout « "Предложение if:\n\n"; cout « "if(условие) предложение;\п"; cout « "else предложение;\п"; break; case I 2 ' : cout « "Предложение switch:\n\n"; cout « "switсh(выражение) {\п"; cout « .. case константа: \п 11; cout «" последовательность предложений\п n ; cout «" break; \п" ; cout« " // ...\п"; cout « "} \п tt ; break; case I 3 ' : cout « "Цикл for:\n\n"; cout« "fоr(инициализация; условие; приращение)"; cout « " предложение;\п"; break; case I 4 ' : cout « "Цикл while:\n\n"; cout « "whilе(условие) предложение;\п"; break; case I 5 ' : cout « "Цикл dowhile:\n\n"; cout « "do {\п"; cout « .. предложение;\п"; cout « "} while (условие); \п" ; break; case I 6 ' : cout « "Предложение break:\n\n"; cout « "break; \п"; break; case I 7 ' : cout « "Предложение continue:\n\n"; cout « "continue;\n"; break; case I 8 ' : cout « "Предложение goto:\n\n"; cout « "goto метка;\п"; break; 
134 Модуль 3 Предложения управления проrраммой } cout « tt \n tt ; } return о; } 6. Ниже приведен пример работы nporpaмMbl: Справка по: 1. if 2. switch з. for 4. while 5. dowhile 6. break 7. continue 8. goto Выберите один из пунктов (q для завершения): 1 Предложение i f : if(условие) предложение; else предложение; Справка по: 1. if 2. switch з. for 4. while 5. dowhile 6. break 7. continue 8. goto Выберите один из пунктов (q для завершения): 6 Предложение break: break; Справка по: 1. i f 2. switch з. for 4. while 5. dowhile 6. break 
Вложенные циклы 135 7. continue 8. goto Выберите один из пунктов (q для завершения): q Цель _Вложенные циклы Как вы уже видели в некоторых из приведенных выше примеров, один цикл может быть вложен внутрь дpyroro. Вложенные циклы pe шают широкий спектр проrpаммистских задач и являются существен ным элементом проrpаммирования. Поэтому перед тем, как завер шить обсуждение циклов С++, давайте рассмотрим еще один пример вложенноrо цикла. В приводимой ниже проrpамме вложенный цикл for используется для нахождения множителей чисел от 2 до 100: /* Использование вложенных циклов для наХОЖДения множителей чисел от 2 до 100. */ #include <iostream> using namespace std; i n t та i n () { for(int i=2i i <= 100; i++) { cout « "Множители числа" « i « ": "; for(int j = 2; j < ii j++) if((i%j) == О) cout« j « " "; cout « "\n"; } return о; } Ниже приведена часть вывода проrpаммы: Множители числа 2: Множители числа 3 : Множители числа 4 : 2 Множители числа 5 : Множители числа 6 : 2 3 Множители числа 7 : 
136 Модуль 3 Предложения управления проrраммой М} lожители числа Мllожители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа Множители числа 8: 2 4 9 : 3 10: 2 5 11: 12: 2 3 4 6 13: 14: 2 7 15: 3 5 16: 248 17: 18: 2 3 6 9 19: 20: 2 4 5 10 В этой проrpамме внешний цикл проходит значения i от 2 до 100. Внутренний цикл последовательно анализирует все числа от 2 до i, BЫ водя те, на которые i делится без остатка. Цель _ Использование предложения goto Предложение С++ goto реализует безусловный переход. Коrда это предложение встречается в проrpамме, управление передается в за данную в нем точку. Предложение goto уже MHoro лет как потеряло блаrосклонность проrpаммистов, так как ero использование способ ствует образованию "макаронных" проrpамм. Однако goto все еще время от времени используется и иноrда весьма эффективно. Мы здесь не будем выносить приrовор относительно ценности этоrо предложения в качестве средства управления проrpаммой. Следует, однако, заметить, что не существует проrpаммных ситуаций, которые требовали бы обязательноrо использования предложения goto; оно не является необходимым в законченном языке. Скорее ero можно pac сматривать, как некоторое дополнение, которое, если им пользовать ся со смыслом, принесет пользу в определенных проrpаммных ситуа циях. По этим причинам goto ниrде, кроме настоящеrо раздела, не используется в примерах этой книrи. ПроrраммисТbI недолюбливают goto rлавным образом потому, что оно имеет тенденцию запутывать проrpамму, делая ее почти нечитаемой. Однако в некоторых случаях использование goto не усложняет проrpамму, а, наоборот, проясняет ее структуру. В предложении goto требуется указание метки. Метка  это допус тимый идентификатор С++, сопровождаемый двоеточием. Метка 
Использование предложения goto 137 должна находиться в той же функции, что и использующее ее goto. Ha пример, цикл от 1 до 100 можно орrанизовать с помощью goto и метки: х = 1; loop1:  Х++; if(x < 100) goto loop1; . Выполнение передается в точку loop 1. Один из разумных примеров использования goto  выход из rлубоко вложенноrо проrpаммноro блока. Рассмотрим, например, такой фрar мент кода: for ( . . .) { for ( . . .) { whi le ( . . .) { if(...) goto stoPi } } } stop: cout « "ошибка в проrрамме. \n" ; Устранение goto потребовало бы выполнения ряда допoлниreльнъlX проверок. Простое предложение break здесь не будет работать, потому чro оно обеспечивает лишь выход проrpаммы из caмoro ВнyIpeннеro цикла.  Вопросы для самопроверки 1. Напишите проrpамму, которая вводит символы с клавиатуры до нажатия $. Пусть проrpамма считает число введенных точек. Перед завершением проrpаммы выведите на экран это число. 2. Может ли в предложении switch возникнуть ситуация, коrда после выполнения одной ветви case продолжится выполнение следую щей? Объясните. 3. Напишите общую форму цепочки if-else-if. 4. Рассмотрите следующий проrpаммный фраrмент: if(x < 10) if (у > 100) { i f ( ! done ) х = z; else у = z; } else cout « "ошибка"; // какое if? 
138 Модуль 3 Предложения управления проrраммой к какому if относится последнее else? 5. Напишите предложение for для цикла, который проходит значения от 1000 до О с приращением 2. 6. Допустим ли следующий фраrмент? for(int i = о; i < num; i++) s ит + = i; count = i; 7. Объясните, как выполняется предложение break. 8. Рассмотрите приведенный ниже проrpаммный фраrмент. Что бу дет выведено на экран после выполнения предложения break? for(i = о; i < 10; i++) { while(running) { if(x<y) break; / / ... } cout « ..после while \n.. ; } cout « "После for\n"; 9. Что выведет на экран следующий проrpаммный фраrмент? ror(int i = о; i<10; i++) { CO\Jt_« i«" "; if(!(i%2)) continue; c:out « .. \n.. ; 10. Выражение прирашения в цикле for не обязательно должно изменять значение переменной управления циклом на фиксированную вели чину. Напрornв, переменная управления циклом может изменяться произвольным образом. Используя Э'IO обстоятельство, напишите проrpамму, кoroрая с помощью цикла for вычисляет и выводит на ЭК ран reометрическую проrpессию 1, 2, 4, 8, 16, 32 и т. д. 11. Коды строчных латинских букв в таблице ASCII отличаются от KO дов прописных букв на величину 32. Таким образом, для преобра зования строчных букв в прописные из код буквы следует вычесть 32. Используя это обстоятельство, напишите проrрамму, которая вводит символы с клавиатуры и преобразует строчные буквы в прописные, а прописные  в строчные, выводя результат на экран. Не изменяйте никакие дрyrие символы. Проrpамма должна завер шиться при вводе символа точки, а перед завершением вывести число выполненных изменений. 12. Как выrлядит предложение безусловноrо перехода в С++? 
Модуль 4 Массивы, строки и указатели Цели, достиrаемые в ЭТОМ модуле 4.1 Научиться использовать одномерные массивы 4.2 Познакомиться с двумерными массивами 4.3 Познакомиться с мноrомерными массивами 4.4 Разобраться с понятием строки 4.5 Изучить стандартные функции для обработки строк 4.6 Рассмотреть инициализацию массивов 4.7 Познакомиться с массивами строк 4.8 Освоить основы техники указателей 4.9 Научиться работать с операторами указателей 4.10 Начать использовать указатели в выражениях 4. 11 Изучить взаимосвязь указателей и массивов 4. 1 2 Познакомиться с понятием вложенной косвенности 
140 Модуль 4 Массивы, строки и указатели В этом модуле обсуждаются массивы, строки и указатели. Хотя эти темы MOryт показаться несвязанными, в действительности это не так. В с++ эти понятия тесно переплетены, и чтобы разобраться в одном, необходимо изучить и остальные. Массивом называется коллекция переменных одноrо типа, обраще ние к которым осуществляется по общему имени. Массивы MOryr быть одномерными или мноrомерными, хотя одномерные используются значительно чаще. Массивы предоставляют удобные средства созда ния списков взаимосвязанных переменных. Возможно, чаще дрyrих вы будете сталкиваться с символьными массивами, потому что они используются для хранения символьных строк. Язык с++ не имеет BcтpoeHHoro типа строковых данных. cтpo ки реализуются как массивы символов. Такой подход к строкам обес печивает большие возможности и rибкость, чем это доступно в язы ках, использующих строковый тип данных. Указатель  это объект, содержащий адрес памяти. Указатель, как правило, используется для Toro, чтобы получить доступ к значе нию дрyrоrо объекта. Часто этим дрyrим объектом является массив. Фактически указатели и массивы связаны rораздо теснее, чем это можно предположить. Цель _Одномерные массивы Одномерный массив представляет собой список взаимосвязан ных переменных. Такие списки широко используются в проrрам мировании. Например, одномерный массив может использоваться для хранения учетных номеров активных пользователей сети. В друrом массиве Moryт храниться спортивные результаты бейсболь ной команды. Вычисляя среднее значение набора данных, вы обычно для хранения этих данных используете массив. Для COBpe MeHHoro проrраммирования массивы являются фундаментальными объектами. Общая форма объявления одномерноrо массива выrлядит так: тип имя[размер); в этом объявлении тип задает базовый тип массива. Базовый тип определяет тип данных каждоrо элемента, из которых образуется Mac сив. Число элементов, содержащихся в массиве, задается величиной размер. Приведенная ниже строка объявляет массив целых чисел типа int, имеющий имя sample и состоящий из 10 элементов: int sample [10] ; 
Одномерные массивы 141 Обращение к индивидуальным элементам массива осуществляется с помощью индексов. Индекс определяет позицию элемента внутри Mac сива. В с++ все массивы используют ноль в качестве индекса cвoero первоrо элемента. Объявленный выше массив sample содержит 10 эле ментов с индексными значениями от О до 9. ДОСТУП к элементу массива достиrается пyreм индексации массива с помощью номер элемента. Для индексации массива укажите номер требуемоrо элемента, заключив ero в квадратные скобки. Так, первый элемент массива sample называется sample[O], последний..... sample[9]. В качестве примера рассмотрим про... фамму, которая заполняет массив sample числами от О до 9: #include <iostream> using namespace stdi int main () { int sаmрlе[10];//резервирование места под 10 элементов / / типа int int t; // заполним массив for(t=O; t<10; ++t) sample[t] = t; t / / выведем массив Обратите внимание на индексацию массива 8ample for(t=O; t<10; ++t) t cout « II ЭТО sample [11 « t « "J: tt « sample [t] « tt \n tt ; return о; } Вот вывод этой проrpаммы: ЭТО sample[O]: О Это sample[l]: 1 Это samp 1 е [ 2]: 2 Это sаmрlе[З]: 3 Это sample[4]: 4 Это sample[S]: 5 Это sample [6]: 6 Это sample [7]: 7 Это samp 1 е [ 8]: 8 Это sample[9]: 9 в с++ все массивы располаrаются в последовательных ячейках па... МЯТИ. (Дрyrими словами, все элементы массива располаrаются в па... мяти вплотную дpyr к друry ) Н аименьший адрес соответствует перво... 
142 Модуль 4 Массивы, строки и указатели му элементу, а наибольший адрес  последнему. Например, после BЫ полнения этоrо проrpаммноrо фраrмента: int nums[5]; int i; for(i=O; i<5; i++) nums[i] = i; массив nums будет выrлядеть таким образом: num[O] num[ 1] num[2] num[З] num[ 4] о 1 2 з 4 Массивы так широко используются в проrpаммировании изза TOrO, что они позволяют без труда обрабатывать наборы взаимосвя занных переменных. Вот только один пример. Приводимая ниже nporpaMMa создает массив из десяти элементов и присваивает каж дому элементу значение. Затем проrpамма вычисляет среднее из всех этих значений, а также находит минимальное и максимальное '}начения. /* Вычисление среднеrо и нахождение минимальноrо и максимальноrо значений для набора значений. */ #include <iostream> using namespace std; int main ( ) { int i, avg, minval, maxval; int nums[10]; nums[O] = 10; nums[l] = 18; nums[2] = 75; nums[З] = о; nums[4] = 1; nums[5] = 56; nums[6] = 100; nums[7] = 12; nums[8] = 19; nums[9] = 88; 
Одномерные массивы 143 // ВЫЧИСЛИМ среДНее avg = о; for(i=li i<10i i++) avg += nums [i] ;  Суммирование значений в nuт8. avg /= 10;  Вычислим среднее. cout « "Среднее равно" « avg « '\n'; // наЙДем минимальное и максимальное значения minval = maxval = nums[O]; for(i=l; i<10; i++) { if(nums[i] < minval) minval = nums[i]i if(nums[i] > maxval) maxval = nums[i]; } cout « "Минимальное значение: " « min val « I \n' i cout « .Максимальное значение: · « ma x.....v al « I \n I  return о; найдем минимальное и мaK } СИМ8l1Ьное значения В nUR18 Ниже показан вывод этой проrpаммы: Среднее равно 33 Минимальное значение: 19 Максимальное значение: 100 Обратите внимание на то, как профамма работает с элементами массива nums. Хранение анализируемых значений в массиве заметно облеrчает этот процесс. Переменная i управления циклом for исполь зуется одновременно в качестве индекса. Циклы такою рода широко используются при обработке массивов. Работая с массивами, следует иметь в виду серьезное оrpаниче ние. В с++ недопустимо копировать один массив в дрyrой с помо щью операции присваивания. Например, следующий фрarмент He правилен: int а[10], Ь[10]; // ... а = Ь; // ошибка  недопустимая операция Для передачи содержимоrо одною массива в дрyrой вы должны скопировать каждое значение индивидуально, например, таким образом: for(i=Oi i < 10; i++) а[] 1  b[i] i 
144 Модуль 4. Массивы, строки и указатели rраницы не проверяются! С++ не выполняет проверку rpаниц массива. Эro означает, что ничто не может вам помешать выйти за пределы массива. Дрyrим:и словами, вы можете индексировать массив размера N за пределами N и не получите при этом никаких сообщений об ошибках, ни на этапе компиляции, ни при выполнении. В то же время, такое действие часто приводит к кaтacт рофическим последствиям для вашей проrpаммы. Например, приведен ный ниже пример будет откомпилирован и выполнен без какихлибо co общений об ошибках, хотя мы явно вышли за пределы массива crash: int crash[10], i; for(i=O; i<100; i++) crash[i]=i; в этом случае цикл будет повторяться 100 раз, хотя в массиве crash только десять элементов! В результате будет затерта память, не при надлежащая массиву crash. Если выход за пределы массива происходит в процессе операции присваивания, то скорее Bcero будет затерта память, вьшеленная для дрyrих целей, например, для хранения дрyrих переменных. Если BЫ ход за пределы массива происходит в процесс е чтения, то неправиль ные данные нарушат нормальное выполнение проrpаммы. В любом случае ваша обязанность, как проrpаммиста, следить за тем, чтобы массивы имели достаточный размер для хранения тою, что проrрамма будет в них записывать, и в случае необходимости осуществлять про верку на выход за rpаницы массива. Минутная тренировка 1. Что такое одномерный массив? 2. Индекс первоrо элемента массива всеrда равен нулю. Справедливо ли это утверждение? 3. Осуществляет ли С++ проверку на выход за rpаницы массива? 1 Одномерный массив представляет собой список взаимосвязанных переменных 2 Справедливо Первым индексом всеrда является ноль 3 Нет, С+  не выполняет проверку на выход за rpаницы массива Спросим у эксперта Вопрос: Если выход за пределы массива может привести к катастрофиче ским последствиям, почему с++ не выполняет соответствующую проверку при операциях с массивами? Ответ: с++ разрабатывался с целью дать профессиональным проrpам мистам средство для создания максимально быстрых и эффективных про 
Двумерные массивы 145 rpaMM. Для достижения этой цели в с++ почти не предусмотрено прове рок на ошибки во время выполнения, так как такая проверка замедляет (иноrда драматически) выполнение проrpаммы. Наоборот, с++ ожидает от вас, проrpаммиста, достаточно ответственности, чтобы не допускать BЫ хода за пределы массивов; если же по лоrике проrpаммы такое нарушение все же может иметь место, то вы сами должны добавить в nporpaMMY тpe буемые средства проверки. К тому же, как вы узнаете позже, если ваша проrpамма действительно нуждается в про верке на ошибки времени BЫ полнения, то вы имеете возможность определять массивы собственных ти пов, которые будут выполнять TaKoro рода проверку. Цель _Двумерные массивы С++ допускает возможность создания MHoroMepHblx массивов. Про стейшей формой тaкoro массива является двумерный массив. ДвYMep ный массив есть, в сущности, список, составленный из одномерных Mac сивов. Для тoro, чтобы объявить двумерный массив twoD целых чисел размера 10х20, вы должны написать: int twoD[10] [20]; Обратите особое внимание на это объявление. В отличие от HeKOTO рых дрyrих компьютерных языков, которые для разделения размерно стей массива используют запятые, в С++ каждый размер заключается в собственные квадратные скобки. Например, для обращения к эле менту с номером 3,5 в массиве МоО вы должны использовать обозна чение twоD[З][5]. В приводимом ниже примере двумерный массив заполняется чис лами от 1 до 12: #include <iostream> using namespace stdi int main ( ) { int t, i, nums [З] [4] ; for(t=Oi t < з; ++t) { for(i=Oi i < 4; ++i) { nums [t] [i] = (t * 4) + i + 1;  cout« nums[t] [i] « ' '; } cout « '\n'; Индексация nuт. требует двух индексов } 
146 Модуль 4 Массивы, строки и указатели return о; } в этом примере элемент nums[O][O] будет иметь значение 1, nums[O][l]  значение 2, nums[O][2]  значение 3 и т. д. Значение по следнеrо элемента пums[2][З] будет равно 12. Весь массив можно пред ставить себе так, как это изображено на рисунке: о 1 2 3 .. Правый индекс 1 1 2 3 4 5 6 (2) 8 " 9 10 11 12 о 2 I Левый индекс num [1][2] Двумерные массивы сохраняются в памяти в виде матрицы, состоя щей из строк и столбцов, причем первый индекс определяет строку, а второй  столбец. Отсюда следует, что если элементы массива обраба тываются в том порядке, в каком они фактически располаrаются в па мяти, то правый индекс изменяется быстрее левоrо. Следует иметь в виду, что память под все элементы массива Bыдe ляется во время компиляции. Далее, память, используемая для xpaHe ния массива, должна оставаться выделенной на все время существова ния массива. В случае двумерноrо массива мы можете определить, сколько байтов памяти он требует, по следующей формуле: число байтов == число строк х число столбцов х число байтов в типе массива Таким образом, для четырехбайтовых целых чисел массив с размер ностью 10x5 займет в памяти 10х5х4 == 200 байтов. Цель _Миоrомериые массивы С++ допускает создание массивов с числом измерений больше двух. Вот общая форма объявления MHoroMepHoro массива: тип имя [размер 1] [размер2]... [размерN]; 
MHorOMepHble массивы 147 Например, следующее объявление создает массив целых чисел с размерностью 4хl0х3: int multidim[4] [10] [3]; Массивы с числом измерений три и больше используются не часто, отчасти изза Toro, что они требуют для cBoero хранения зна чительных объемов памяти. Вспомним, что память для хранения всех элементов массива выделяется на все время жизни массива. MHoroMepHbIe массивы MOryr потреблять значительные объемы па мяти. Например, четырехмерный символьный массив с размерно стью 10x6x9x4 потребует 10х6х9х4, или 2160 байтов. Если же каж дый размер массива увеличить в 10 раз (100x60x90x40), то память, необходимая для размещения TaKoro массива, возрастет до 21600000 байтов! Таким образом, большие MHoroMepHbIe массивы MOryт быть причиной нехватки памяти для остальных частей вашей проrpаммы. Проrpамма, создающая массивы с числом измерений больше, чем два или три, может быстро исчерпать всю наличную память! Минутная тренировка 1. Каждое измерение MHofOMepHoro массива задается с использованием соб ственной пары квадратных скобок. Справедливо ли это yrвержnение? 2. Покажите, как объявить двумерный массив целых чисел с именем list и размером 4х9. З. Покажите, как получить доступ к элементу с индексами 2,3 для масси ва из предыдущеrо вопроса. 1. Справедливо. 2. int lз.st[4] [9] 1 311st[2][З] Проект 4..1 Упорядочение массива Поскольку в одномерном массиве данные орrанизованы в виде ин дексированноrо линейноrо списка, такая структура оказывается чрез вычайно удобной для упорядочения данных. В этом проекте вы освои те простой способ упорядочения массива. Как вы, возможно, знаете, имеется целый ряд различных алrоритмов упорядочения. В частности, можно упомянуть быстрое упорядочение, упорядочение встряхивани ем и метод Шелла. Однако наиболее известным, удобным и простым для понимания являеТСSI алrоритм, носящий название метода всплы вающею пузырька или просто пузырьковоrо упорядочения. Хотя этот 
148 Модуль 4 Массивы, строки и указатели метод не очень эффективен  фактически ею нельзя использовать при работе с большими массивами  однако для упорядочения относи тельно коротких массивов он вполне rодится. Шаr за marOM 1. Создайте файл с именем ВоЬЫе.срр. 2. Метод всплывающеro пузырька получил свое название от способа, которым осуществляется операция упорядочения. Она использует по вторные сравнения и, если необходимо, обмен местами соседних эле ментов массива. В ходе эroй операции меньшие значения перемещаются к одному концу массива, а БЫIьшие  к дрyroму. Этот процесс напоми нает всплывание пузырьков в баке с водой. Пузырьковое упорядочение требует выполнения нескольких проходов Bcero массива, причем в каж дом проходе выполняется обмен местами расположенных не в том по рядке элементов. Число проходов, требуемое для полноrо упорядочения вcero массива, на единицу меньше числа элементов в массиве. Ниже приведен код, формирующий ядро пузырьковоrо упорядоче ния. Упорядочиваемый массив назван nums. // Пузырьковое упорядочение. for(a=l; a<size; а++) for(b=sizel; Ь>=а; b) { if(nums[bl] > nums[b]) { // если не в том порядке // элементы обмениваются местами t = n ums [Ь  1] ; nums[bl] = nums[b]; n ums [Ь] = t; } } Обратите внимание на то, что упорядочение требует двух циклов for. Во внутреннем цикле попарно сравниваются соседние элементы массива в поисках тех, которые расположены не в том порядке. Если такая пара найдена, элементы в ней меняются местами. После каждо ro выполнения всех шаrов BHyтpeHHero цикла минимальный из ocтaB шихся элементов перемещается в упорядоченную позицию. Во внеш нем цикле этот процесс повторяется до тех пор, пока все элементы массива не будут упорядочены. 3. Ниже приведен полный текст проrpаммы ВоЬЫе.срр: /* Проек'r 4  1 Демонстрация пузырьковоrо упорядочения. */ 
MHorOMepHble массивы 149 #include <iostrearn> #include <cstdlib> using namespace std; int main () { int nитэ [10] i int а, Ь, ti int sizei size = 10; // число упорядочиваемых элементов // дадим элементам массива случайные начальные значения for(t=O; t<size; t++) nums[t] = rand(); // выведем на экран исходный массив cout « "Исходный массив: \n " ; for(t=O; t<size; t++) cout« nums[t] « ' '; cout « '\n I ; // Это пузырьковое упорядочение. for(a=1i a<size; а++) for(b=sizel; Ь>=а; b) { if(nums[bl] > nиmв[Ь]) { // если не в том порядке // элементы обмениваются местами t = nums[b1]; nums[b1] = nums[b]; nиmэ[Ь] = t; } } // выведем упорядоченный массив cout « .. \nУпорядоченный массив: \п " ; for(t=O; t<size; t++) cout «nums[t] « I '; return о; } Ниже приведен вывод одноrо из проrонов проrpаммы: Исходный массив: 41 18467 6334 26500 19169 15724 11478 29358 26962 24464 Упорядоченный массив: 41 6334 11478 15724 18467 19169 24464 26500 26962 29358 4. Алrоритм ПУЗЫРЬКО80rо упорядочения, будучи вполне удовлетво рительным для небольших массивов, оказывается неэффективным для 
150 Модуль 4. Массивы, строки и указатели массивов большой длины. Наилучшим алrоритмом общеrо назначения считается алrоритм быстроrо упорядочения. Однако этот алrоритм опи рается на средства языка С++, которые вы еще не изучали. Кроме тoro, хотя вариант быстроro упорядочения и реализован в функции с именем qsort( ) из стандартной библиотеки С++, однако чтобы пользоваться этой функцией, вы должны изучить С++ в больших деталях. Цель _Строки Безусловно, наиболее важным применением одномерных массивов является создание символьных строк. С++ поддерживает строки двух видов. Первый, наиболее часто используемый,  это строка с завер шающuм нулем или, дрyrими словами, массив символов, заканчиваю щийся нулем. Строка с завершающим нулем содержит символы, обра зующие эту строку, за которыми помещается ноль. Такие строки ис пользуются чрезвычайно широко, так как они обеспечивают высокий уровень эффективности и предоставляют проrpаммисту возможность выполнять разнообразные строковые операции. Коrда проrpаммист на С++ использует термин "строка", он (или она) обычно имеет в виду именно строку с завершающим нулем. Второй вид строк, определен ный в С++  это класс string, который входит в библиотеку классов С++. Таким образом, string не является встроенным типом. Класс string позволяет использовать объектноориентированный подход при обработке строк, однако он используется не так широко, как строки с завершающим нулем. Здесь мы рассмотрим первый вид строк. Основы техники строк Объявляя символьный массив, который будет содержать строку с завершающим нулем, вы должны задать ему длину на один символ больше, чем у самой длинной помещаемой в Hero строки. Если, Ha пример, вы хотите объявить массив str, в котором будет находиться 1 o символьная строка, то вы должны написать следующее: char str[ll]; Задание величины 11 в качестве длины массива обеспечит в строке место для завершающеrо нуля. Как вы уже знаете, С++ позволяет определять символьные KOH станты. Символьная константа представляет собой список символов, заключенный в двойные кавычки. Вот несколько примеров: " Привет! II 111 like с++ tt "Mars" tt " 
Строки 151 Добавлять ноль в конец строки вручную нет необходимости; KOM пилятор С++ сделает это сам. В результате строка .'Mars" будет выrля деть в память таким образом: м а r s о Последняя строка в нашем примере выrлядит как "". Такая строка называется нулевой строкой. Она содержит только завершающий ноль и больше ничеro. Нулевые строки используются в проrpаммах в каче стве пустыIx строк. Ввод строки с клавиатуры Самый простой способ прочитать В проrpамму строку, вводимую с клавиатуры, заключается в использовании массива char и предложе ння cin. Приводимая ниже проrpамма вводит строку, набираемую пользователем на клавиатуре: // Использование cin для ввода строки с клавиатуры. #include <iostream> using nаmеэрасе stdi int main ( ) { char str[80]; cout « ..Вводите строку: ..; cin » stri // ввод строки с клавиатуры  cout « "Вот ваша строка: "; cout « stri Чтение строки с ПОМОЩЬЮ сёп I return о; } Пример nporOHa проrpаммы: Вводите строку: Проверка Вот ваша строка: Проверка Хотя приведенная выше nporpaмMa технически правильна, она не всеrда будет работать так, как вы этоrо ожидаете. Для тoro, чтобы убе диться в этом, запустите nporpaMMY и попробуйте ввести строку "Эrо новая проверка". Вот что получится: 
152 Модуль 4 Массивы, строки и указатели Вводите строку: Это новая проверка Вот ваша строка: Это Коrда профамма повторяет на экране введенную вами строку, то вы видите только слово "Это", а не всю строку. Так происходит noтo му, что система BBoдaBЫBoдa с++ прекращает ввод с клавиатуры при получении пробельноrо символа. Пробельные символы включают в себя пробелы, символы табуляции и символы новой строки. Для решения проблемы пробельных символов вы должны исполь зоватьдрyrую библиотечную функцию С++, gets(). Общая форма BЫ зова функции gets( ) выrлядит так: gets( имя  массива); Если вам нужно ввести в проrpамму строку с клавиатуры, вызовите функцию gets( ) с именем массива (без какоroлибо индекса) в качест ве apryмeHTa. После возврата из gets( ) массив будет содержать строку, набранную на клавиатуре. Функция gets() вводит символы до тех пор, пока не встретится с символом возврата каретки. Для использования этой функции необходим заrоловок <cstdio>. Приводимый ниже вариант предыдущей проrpаммы использует функцию gets( ), позволяющую вводить строки, содержащие пробелы: / / ИСПОЛhзование gets () для ввода строки с клавиатуры. lfiпсllJdс <jostream> Пjпсludе <cstdio> uяjпg namespace stdi int main () { char str[80] i Чтение строки с помощью g_ts() cout « "Вводите строку: 1'; I gets(str) i // ввод строки с помощью gets() cout « "Вот ваша строка: "; cout « stri return О i } Пример nporOHa профаммы: Вводите строку: Это строка проверки Вот ваша строка: Это строка проверки Теперь пробельные символы читаются и включаются в строку. Еще одно замечание: обратите внимание на то, что в предложении cout 
Некоторые библиотечные функции обработки строк 153 можно непосредственно использовать имя str. Вообше имя символь Horo массива, содержащеrо строку, можно использовать в любом Mec те, rде допустимо применение символьной констзнтыI. Не забывайте, что ни cin, ни gets() не выполняет проверку на выход за rpаницы массива. Поэтому если пользователь введет строку, длина которой больше размера массива, то память за пределами массива бу дет затерта. Позже вы познакомитесь с альтернативой, которая помо raeT избежать этой неприятности. Минутная тренировка 1. Что представляет собой строка с завершаюшим нулем? 2. Какой размер должен иметь символьный массив, в котором будет xpa нится строка длиной 8 байт? 3. Какую функцию можно использовать для ввода с клавиатуры строки, содержащей пробелы? 1 С'трока с завершающим нулем представляет собой массив символов, который заканчи.. вается нулем 2 Если массив предназначен ШIЯ хранения 8"символъной строки, он должен иметь размер по меНЪUlей мере 9 символов. 3 для ввода с клавиатуры строки, содержащей пробелы, можно ИСПОЛЪJOвать функцmo gets( ). Цель _Некоторые библиотечные функции обработки строк С++ поддерживает широкий диапазон функций для манипуляций со строками. Самые известные из них: strcpy( ) strcat( ) strcmp( ) strlen( ) Все строковые функции используют один и тот же заrоловок <cstring>. Рассмотрим эти функции подробнее. strcpy( ) Вызов этой функции выrлядит таким образом: strсру(куда ,откуда) 
154 Модуль 4. Массивы, строки и указатели Функция strcpy( ) копирует содержимое строки откуда в строку куда. Не забывайте, что массив, образующий куда, должен иметь дoc таточный размер, чтобы в Hero поместилась строка откуда. Если места в нем не хватит, то произойдет выход за пределы массива куда, что, скорее Bcero, приведет к аварийному завершению проrpаммы. strcat( ) Вызов этой функции выrлядит таким образом: strcat(s 1 ,s2); Функция strcat( ) присоединяет строку s2 к концу sl; строка s2 oc тается без изменений. Вы должны обеспечить достаточно большой размер строки s 1, чтобы она моrла вместить как свое исходное coдep жимое, так и содержимое s2. strcmp( ) Вызов этой функции выrлядит таким образом: t rc Inp(s I ,s2); Функция strcmp() сравнивает две строки и возвращает О, если они равны. Если sl больше s2 лексикоrpафически (т. е. по порядку следо вания символов алфавита), возвращается положительное число; если она меньше s2, возвращается отрицательное число. При использовании strcmp( ) важно иметь в виду, что при равенстве строк она возвращает false. Таким образом, если вы хотите, чтобы что то произошло при равенстве строк, вам придется использовать опера тор !. В следующем примере условие, управляющее предложением if, истинно, если str равна строке "С++": if(!strcmp(str, "с++") cout« "str = с++"; strlen( ) Общая форма вызова функции strlen( ) такова: strlen(s); Здесь s  это строка. Функция strlen( ) возвращает длину строки, на которую указывает s. 
Некоторые библиотечные функции обработки строк 155 Пример обработки строк в при водимой ниже профамме используются все четыре paCCMOT ренные выше функuии обработки строк: // Демонстрация строковых функций. #include <io8tream> #include <c8tdio> #include <c8tring> u8ing nаmе8расе 8td; int main ( ) { сЬа r 81 [ 8 О], 82 [ 8 О] ; 8trcpY(81, "С++"); 8trcpy (82, tt мощный язык. ") ; cout « "длины: n « 8trlen(81) ; cout « ' , « 8trlen(s2) « '\n'; if(!8trcmp(81,82) cout « "Строки равны\n"; e18e cout « "не равны\n"; 8trcat(81,82); cout« 81 « '\n'; 8trcpY(82,81); cout « 81 « " и " « 82 « "\n"; if(!8trcmp(81,82)) cout « "81 и 82 теперь одинаковы.\п"; return о; } Ниже приведен вывод этой профаммы: длины: 3 22 не равны с++ мощный язык. с++ мощный язык. и с++ МОЩНЫЙ язык. 81 и 82 теперь одинаковы. 
156 Модуль 4. Массивы, строки и указатели Использование завершающеrо нуля Наличие в конце строк завершающеrо нуля часто nOMoraeT упро стить различные операции. Так, приводимая ниже проrpамма преоб разует строчные буквы строки в прописные: // Преобразование строчных символов строки в прописные. #include <iostrearn> #include <cstring> #include <cctype> using namespace std; int rnain ( ) { char str[80]; int i; strcpy(str, "this is а test"); for(i=O; str[i]; i++) str[i] = toupper(str[i]); + Цикл завершается, Korдa индек с о u l < < s t r ; сируется завершающий ноль rcturn о; Вывод этой проrpаммы: TH1S 18 А ТЕ8Т Приведенная профамма использует библиотечную функцию toupper( ), которая возвращает прописной эквивалент cBoero символь Horo арryментз, чем и осуществляется преобразование всех символов строки. Функция toupper( ) использует заrоловок <cctype>. Обратите внимание на то, что в качестве условия для цикла for выступает просто массив, индексируемый управляющей перемен ной. Здесь используется то обстоятельство, что любое ненулевое значение является истинным. Вспомним, что все коды символов не равны О, но любая строка завершается нулем. Поэтому наш цикл выполняется до тех пор, пока он не встретится с нулевым симво лом, коrда str[i] оказывается равным О. Поскольку завершающий ноль отмечает конец строки, цикл останавливается в точности там, rде и требуется. В профессионально написанных С++профаммах вы увидите массу примеров использования завершающеro строку нуля аналоrичным образом. 
Инициализация массивов 157 Минутная тренировка 1. Для чеrо предназначена функция strcat( )? 2. Что возвращает функuия strcmp( ), коrда она сравнивает две эквива лентные строки? 3. Покажите, как получить длину строки с именем mystr. 1 Функция strcat() выполняет объединение (конкатенацию) двух строк. 2 Функция strcmp( ) при сравнении двух эквивалеmных строк возвращает о. 3. st rlen (mystr) (просим у эксперта Вопрос: Имеются ли в С++, помимо toupper( ), дрyrие функции манипу ляции символами? Ответ: Да. Стандартная библиотека С++содерж.ит несколько дрyrих функций манипуляции символами. Например, дополнением к toupper( ) служит функция tolower( ), преобразующая прописные символы в их строч  ные эквиваленты. С помощью функции isupper( ) вы можете определить вид буквы (строчная или прописная); эта функция возвращает true, если буква прописная. Есть и функция islower( ), которая возвращает true в слу чае строчной буквы. К друrим символьноориентированным функциям OT носятся isalpha( ), isdigit( ), isspace( ) и ispunct(). Все эти функции принима ют в качестве apryмeHTa символ и определяют ero катеrорию. Например, isalpha( ) возвращает true, если apryмeHT является буквой алфавита. Цель _Инициализация массивов в с++ предусмотрены средства инициализации массивов. Общая форма инициализации массива сходна с аналоrичным действием для дрyrих переменных, как это показано ниже: тип имямассиваl.Pазмер] == {спuсокзначений}; Здесь списокзначений представляет собой список значений, разде ленных запятыми и совместимых по типу с базовым типом массива. Первое значение помещается в первый элемент массива, второе  во второй элемент и т. д. Обратите внимание на то, что точка с запятой ставится после закрывающей скобки }. В следующем примере массив из десяти целочисленных элементов инициализируется числами от 1 до 10: int i[lO] == {l, 2, 3,4,5,6,7,8,9, lO}; 
158 Модуль 4 Массивы, строки и указатели В результате КОМПИЛЯЦИИ этоrо предложения i[O] будет иметь зна чение 1, а i[9]  10. Символьные массивы, предназначенные для хранения строк, дo пускают упрощенную инициализацию в такой форме: char uмямассuва[размер] == "строка"; Например, следующий проrpаммный фраrмент инициализирует str строкой "С++": char str[4] = "С++"; Тот же результат можно бьто получить и иначе: char s t r [ 4] = {' С', I +', ' +', '\ О ' } ; Поскольку строки в с++ заканчиваются нулем, вы должны объя вить массив достаточно большой длины, предусмотрев место для этою нуля. Именно поэтому массив str имеет в этих примерах длину 4 сим вола, хотя в строке "с++" их только три. Если для инициализации массива используется строковая константа, компилятор автоматиче ски записывает в массив завершающий ноль. MHoroMepHbIe массивы инициализируются так же, как и OДHOMep ные. Например, следующий фраrмент инициализирует массив sqrs числами от 1 до 10 и их квадратами: int sqrs[10] [2] = { 1, 1, 2, 4, 3, 9, 4, 16, !5, 25, 6, 36, 7,49, 8, 64, 9, 81, 10, 100 } ; На рис. 4 1 показано, как массив sqrs располаrается в памяти. Инициализируя мноrомерный массив, вы можете добавить фиryp ные скобки Boкpyr инициализаторов каждоrо измерения. Это действие носит название субаzреzатной zpyппup08KU. Воспользовавшись этим приемом, объявление массива из предьщущеrо примера можно запи сать таким образом: int sqrs [ 1 О] [2] = { {1,1}, 
Инициализация массивов 159 {2, 4}, {З, 9}, {4, lб}, {5,25}, {6, 3б}, {7,49}, {8, 64}, {9, 81}, {10, 100} } ; Если, используя субаrpеrатную rpуппировку, вы не укажите ДOCTa точное количество инициализаторов для данной rpynпы, оставшимся членам будут автоматически присвоены нулевые значения. о ПравЬIЙ индекс 1 4 о 1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 10 100 1 2 з 4 5 6 7 8 9 I Левый индекс Рис. 4-1. Инициализированныи М,I<.:<.:ИВ sqrs 
160 Модуль 4. Массивы, строки и указатели Приведенная ниже проrpамма использует массив sqrs для нахождения квадрата числа, введенноro пользователем. Проrpамма сначала ищет это число в массиве, а затем выводит сoorвeтствующее значение квадрата. #include <iostream> using namespace std; int main () { int i, j; int sqrs [10] [2] = { {1,1}, {2, 4}, {З, 9}, {4, 1б}, {5, 25}, {б, Зб}, {7, 49}, {8, 64}, {9, 81}, {10, 100} } ; CO\lt « "Введите число от 1 до 10: 1I i cin » i; // ищем i for(j=O; j<10i j++) i f (sqrs [j ] [О] ==i) break; cout « "Квадрат" « i « " равен "; cout« sqrs[j] [1]; return о; } Вот пример проrона проrpаммы: Введите число от 1 до 10: 4 Квадрат 4 равен 16 Инициализация массивов неопределенной длины Объявляя инициализируемый массив, можно заставить с++ aBTO матически определять ero размер. Для этоrо достаточно не указывать 
Инициализация массивов 161 размер массива в объявлении. В этом случае компилятор определит отсутствующий размер, пересчитав поставляемые инициализаторы, и создаст массив требуемой длины. Например, int nums[] = { 1, 2, 3, 4 }; создает массив с именем nums из четырех элементов, которые coдep жат значения 1,2, 3 и 4. Массив, при объявлении KOToporo явным об разом не указывается размер (как, например, массив nums), называют массивом неопределенной длины. Такие массивы весьма полезны. Представьте себе, например, что вы, используя массивы с инициализацией, создаете таблицу Интернет адресов: char е 1 [ 16] -:... l' www . ()s})orne. сот" ; char' е2 r 16] = "www.weather.com"; ct1ar е3 [ 1 5] = l' v.тww. amazon. сот l' ; Очевидно, что считать вручную количество символов в каждом aд ресе, чтобы определить длину соответствующеrо массива, слишком хлопотно. Кроме тoro, такой способ чреват ошибками, если вы слу чайно ошибетесь в счете и зададите неправильную длину массива. ro раздо лучше переложить на компилятор вычисление длины этих Mac сивов, как ЭТО показано ниже: cr1ar е1 r 1 = I'www.osborne.com l '; char е2 r 1 = "www.weather.com"; char е3 [] -= "www.amazon.com l '; Помимо TOrO, что такой метод избавляет вас от утомительной рабо ты, он хорош еще и тем, что вы можете изменить любой адрес и не дy мать о соответствующем изменении размера массива. Инициализация массивов неопределенной длины не оrраничи вается одномерными массивами. Для MHoroMepHoro массива вы можете указать все размеры, кроме caMoro левоrо, что даст воз можность правильно индексировать массив. С помощью TaKoro метода вы можете создавать таблицы переменной длины, и компи лятор будет автоматически выделять под них требуемую память. В следующем примере sqrs объявляется как массив неопределенной длины: int sqrs[l [2] = { 1, 1, 2, 4, 3, 9, 4, 16, 5, 2S, 
162 Модуль 4. Массивы, строки и указатели 6, 36, 7,49, 8, 64, 9, 81, 10, 100 } ; Преимуществом тaKoro объявления перед объявлением с явным указанием всех размеров массива является возможность удлинить или укоротить эту таблицу, не внося при этом изменений в размеры массива. Цель _ Массивы строк Особой формой двумерноrо массива является массив строк. Такие массивы широко используются в проrpаммировании. Например, BHYТ ренний обработчик базы данных может сравнивать команды пользова теля с массивом строк допустимых команд. Для создания массива строк используется двумерный символьный массив, в котором левый индекс характеризует число строк, а правый  их максимальную дли ну. Например, приведенное ниже объявление создает массив из 30 строк, каждая из которых может иметь длину до 80 символов: char strarray[30] [80]; Обратиться к индивидуальной строке этоrо массива не составляет труда: вы просто указываете один левый индекс. Например, приведен ное ниже предложение вызывает функцию gets( ) для заполнения третьей строки в strarray: gets(strarray[2]); Чтобы получить доступ к индивидуальному символу в третьей cтpo ке, вы используете предложение вроде следующеrо: cout« strarray[2] [3]; в результате на экран выводится четвертый символ третьей строки. Приводимая ниже проrpамма демонстрирует работу с массивом строк на примере очень простоrо компьютеризованноrо телефонноrо справочника. Двумерный массив numbers содержит пары имятеле фонный номер. Для определения телефонноrо номера вы вводите имя; на экран выводится соответствующий телефонный номер. 
Массивы строк 163 // Простой компьютеризованный телефонный справочник. #include <iostrearn> #include <cstdio> using namespace std; int rnain ( ) { int i; char str [80] ; char numbers[10] [80] = {  II ТОМ 11, II 555  3 3 2 2" , "Мэри", "5558976", , II Джон 11, 1155 5  1 037 n , "Рейчел", "5551400", "Шерри", "555887З" } ; Это массив из 1 О строк, каждая ИЗ кото- рых может содержать до 79 символов. cout « I'Введите имя: 11; cin » str; for(i=O; i < 10; i += 2) if(!strcmp(str, numbers[i]» { cout « "Телефон" « nurnbers[i+l] « "\n"; break; } if(i == 10) cout « "Не найдено.\n"; return о; Вот пример проrона проrpаммы: Введите имя: Джон Телефон 5551037 Обратите внимание на то, как выполняется наращивание управляю щей переменной цикла for: в каждом шаrе к ней прибавляется 2. Так дe лается потому, ЧТО В массиве чередуются имена и телефонные номера. Минутная тренировка 1. Покажите, как инициализировать четырехэлементный массив чисел int значениями 1, 2, 3 и 4. 2. Как подрyrому можно записать это инициализирующее предложение: char s t r [ 7 ] = {" П", 1I Р ", n И 11, n Б", 11 е 11, · т", n \ О.} ; 
164 Модуль 4. Массивы, строки и указатели 3. Напишите следующее предложение в виде массива неопределенной дли ны: int nurns [4] = {44, 55, 66, 77}; 1. int list[4] = { 1, 2, 3, 4 }; 2. char str [7] = "Привет"; 3. in t Ilums [] = { 44, 55, 66, 77}; Цель _Указатели Указатели ЯВЛЯЮТСЯ одним из наиболее мощных средств с++. Они же оказываются и самыми трудными в освоении. Будучи вероятнъlМ источни ком всяческих неприяmостей, указатели тем не менее иrpают ключевую роль в проrpаммировании на С++. Именно посредством указателей с++ поддерживает такие средства, как связанные списки и динамическое BЫ деление памяти. Указатели позволяюr функциям изменять содержимое своих apryмeНТOB. Однако эти и дрyrие возможные применения указателей будyr обсуждаться нами в последуюJ.ЦИX модулях. Здесь вы только получи те базовые сведения об указателях и научитесь их использовать. В последующих 06СYЖLIениях нам понадобится ссьтаться на размеры некоторых базовых типов данных С++. для определенности этих обсуж дений будем считать, что символы имеют длину 1 байт, целые (int)  че тыре байта, переменные с плавающей точкой (t1oat)  тоже четыре байта, а переменные doubIe  восемь байтов. Дрyrими словами, мы будем вести рассуждения применительно к типичной 32разрядной среде. Что такое указатели? Указатель  это объект, содержащий адрес памяти. Очень часто этот адрес указывает на расположение в памяти дрyrоro объекта, например, переменной. Так, если х содержит адрес у, тоrда roворят, что х "указы вает" на у. Переменныеуказатели объявляются особым образом. Общая фор ма объявления переменнойуказателя такова: тип *имяпеременной; Здесь тип  это базовый тип указателя. Базовый тип определяет, на данные KaKoro типа будет указывать этот указатель. имяпеременной  это имя переменнойуказателя. Так, чтобы объявить ip, как указатель на int, вы должны написать: int *ip; 
Операторы указателей 165 Поскольку базовым типом ip указан int, этот указатель можно ис пользовать только для указания на переменные типа int. Теперь объявим указатель на float: float *fPi в этом случае базовым типом fp является float, и указатель ер может указывать только на переменные типа float. Итак, в предложениях объявления указание перед именем перемен ной знака * делает эту переменную указателем. Цель _Операторы указателей Для использования с указателями предусмотрены два специальных оператора: * и &. Оператор & является унарным; он возвращает адрес памяти, в которой расположен ero операнд. (Вспомним, что унарный оператор требует только одноrо операнда). Например, предложение ptr -= &total; помещает в ptr адрес памяти, rде находится переменная total. Этот aд рес является адресом ячейки памяти, в которой хранится переменная total. Этот адрес не имеет ничеzо общеzо со значением total. Про опера цию & можно сказать, что она возвращает адрес переменной, перед KO торой она указана. Таким образом, приведенное выше предложение присваивания можно передать словами, как "ptr получает адрес total". Чтобы лучше разобраться в этом присваивании, предположим, что пе ременная total расположена по адресу 100. Тоrда после выполнения присваивания ptr будет иметь значение 100. Второй оператор, *, в не котором роде обратный по отношению к &. Этот унарный оператор возвращает значение переменной, располо женной по адресу, который указан в качестве операнда этоrо операто ра. Продолжая тот же пример, если ptr содержит адрес переменной total, то предложение val =- *ptr'; поместит значение переменной total в \'81. Если, например, total первона чально имела значение 3200, то \'81 будет теперь иметь 1'0 же значение 3200, потому что это значение, которое хранится в памяти по адресу 100, т. е. по адресу, который присвоен переменной ptr. Операцию * можно Ha звать "по адресу". Таким образом, последнее предложение можно пере дать словами, как "val получает значение, расположенное по адресу ptr" . 
166 Модуль 4. Массивы, строки и указатели Приведенная ниже проrpамма выполняет последовательность толь ко что описанных операций: #include <iostream> using namespace std; int main () { int total; int *ptr; int val; total = 3200; // присвоим total значение 3200 ptr = &total; // получим адрес total val = *ptr; // получим значение по этому адресу cout « tt total равно: tt « val « I \n I ; return о; к сожалению, получилось так, что знак умножения и символ "по адресу" совпадают, что часто сбивает с толку начинающих изучать Ct+. Эти операторы не имеют никакоrо отношения дрyr к дрyry. И мейте ввиду, что оба оператора указателей, & и *, имеют более BЫCO кий относительный приоритет, чем любой арифметический оператор за исключением yнapHoro минуса, с которым они находятся на одном уровне приоритета. Использование указателя часто называют косвенной операцией, по тому что вы обращаетесь к одной переменной не непосредственно, а косвенно, через дрyryю переменную. Минутная тренировка 1. Что такое указатель? 2. Покажите, как объявить указатель valPtr на переменную типа loog iot. 3. Что выполняют операторы * и & применительно к указателям? 1. Указатель  это объект, содержащий адрес памяти, в которой расположен друrой объ.. еКТ. 2. long int *valPtr; 3. Оператор * позволяет получить значение, сохраненное по адресу, перед которым ука.. зан этот оператор. Оператор & позволяет получить адрес объекта, перед которым он указан. 
Операторы указателей 167 Базовый тип указателя имеет большое значение В предыдущем подразделе было показано, что переменной val можно присвоить значение переменной total косвенным образом, посредством указателя. Возможно, при чтении этоro материала вы задались вопро сом: каким образом с++ узнает, сколько байтов надо скопировать в пе ременную Уа' из памяти с адресом, содержащимся в ptr? Или, в более общем виде, каким образом компилятор переносит правильное число байтов в любой операции присваивания, использующей указатель? Oт вет заключается в том, что тип данных, над которыми осуществляется операция посредством указателя, определяется базовым 1Ипом указате ля. В нашем случае ptr является указателем на int, и поэтому в \'81 копи руются четыре байта (в предположении, что используется 32разрядная среда) из памяти с адресом, содержащимся в ptr. Если бы, однако, ptr был указателем на doubIe, то скопировались бы 8 байтов. Весьма важно следить за тем, чтобы переменныеуказатели всеrда указывали на переменные правильных типов. Если, например, вы объ являете указатель на тип int, то компилятор предполаrает, что любой объект, на который указывает этот указатель, является целочисленной переменной. Если в действительности это переменная дрyrоrо типа, вас наверняка ожидают неприятности. Например, приведенный ниже фраrмент неверен: int *р; double f; // ... р = &f; // ОШИБКА Этот фраrмент неверен, потому что вы не можете присвоить указа телю на целочисленную переменную значение указателя на перемен ную типа doubIe. Действительно, операция &С создает указатель на doubIe, однако р является указателем на int. Эти два типа несовмести мы. (Фактически компилятор в этой точке сообщит об ошибке и ваша проrpамма далее компилироваться не будет.) Хотя два указателя должны иметь совместимые типы, чтобы значе ние oдaoro можно было присвоить дрyrому, вы можете преодолеть это оrpаничение (на свой страх и риск) с помощью операции приведения типа. Так, приведенный ниже фраrмент формально верен: int *р ; double f; // ... р = (int *) &f; // Теперь формально правильно Приведение к типу int * преобразует указатель на doubIe в указатель на int. Однако, ИСПОЛЬ'Jование в данном случае приведения типов 
168 Модуль 4. Массивы, строки и указатели является сомнительной операцией. Причина заключается в том, что базовый тип указателя определяет, как именно компилятор будет трактовать данные, на которые этот указатель указывает. В рассматри ваемом примере, хотя р фактически указывает на переменную с пла вающей точкой, компилятор будет "думать", что р указывает на int (потому что р является указателем на int). Чтобы лучше разобраться в том, почему приведение типа в опера циях присваивания указателей различающихся типов обычно не при водит к добру, рассмотрим такую короткую проrpамму: // Эта npоrрамма будет работать неправильно. #include"<iostream> using namespace std; int main ( ) { double Х, у; in t *р; Х = 123.23; р = (int *) &Х; / / используем npиведение типа для присваивания // значения double * переменной типа int * у = *р; / / к чему это приведет?  cout « у; / / Что здесь будет выведено на экран? Эти предложения не приве дут к желаемому результату. return о; } Вот реальный вывод этой проrpаммы: 1.37439е+ОО9 Выведенной значение с очевидностью не равно 123.23! И вот почему. В проrpамме переменной р (которая является указателем на int) бьто присвоено значение адреса переменной х (которая имеет тип doubIe). Дa лее, коrда переменной у присваивается значение, на которое указывает р, у принимает только четыре байта данных (а не восемь, как это требуется для значения типа doubIe), потому что р является указателем на int. В pe зультате преД7Iожение cout выводит не значение 123.23, а "мусор". Операции присваивания посредством указателя Вы можете использовать указатель с левой стороны предложения присваивания для Toro, чтобы присвоить значение ячейке памяти, на 
Иыражения с указателями 169 которую указывает указатель. Приняв, что р является указателем на int, следующее предложение присвоит значение 101 ячейке памяти, на которую указывает р: *р = 101; Это присваивание можно описать словами таким образом: "ячейке, на которую указывает р, присвоить значение 101". Для Toro, чтобы увеличить или уменьшить значение по адресу, содержащемуся в р, можно использовать предложения вроде следующеrо: ( *р) + + ; Здесь необходимы крyrлые скобки, потому что оператор * имеет бо лее низкий приоритет, чем оператор ++. В приведенной ниже проrpамме демонстрируется присваивание значения посредством указателя: #include <iostream> using nаmеэрасе stdi int main ( ) { int *р, пит; р = &nит; *р = 100; 4 сои t < < nит < < I 1; (*р)++;  сои t < < num < < I 1; (*p);  сои t < < nит < < I \ n' ; Присвоим num значение 100 посредством р Инкремент num посредством р. Декремент num посредством р. return о; } Вывод проrpаммы выrлядит следующим образом: 100 101 100 Цель .. Выражения с указателями Указатели допустимо использовать в большинстве выражений с++. Однако для таких операций существуют особые правила. Кроме 
170 Модуль 4. Массивы, строки и указатели тoro, в ряде случаев, чтобы получить правильный результат, вам при дется окружать определенные части выражений с указателями крyrлы ми скобками. Арифметика указателей с указателями можно использовать четыре арифметических опера тора: ++,  , + и . Чтобы разобраться, как работает арифметика указателей, предположим, что рl является указателем на int с текущим значением 2000 (т. е. р1 содержит адрес, равный 2000). В 32разрядной среде после выполнения выражение рl++; содержимое рl станет равным 2004, а не 2001! Так происходит потому, что каждый раз, коrда выполняется инкремент указателя рl, он начи нает указывать на следующую переменную типа int. То же справедливо в отношении операции декремента. Приняв опять, что р 1 содержит значение 2000, после выполнения выражения pl; рl примет значение 1996. Обобщая предьшущие примеры, сформулируем правила арифмети ки указателей. Каждая операция инкремента указателя приводит к тому, что указатель теперь указывает на ячейку памяти со следующим элементом ero базовою типа. Каждый раз, коrда выполняется дeкpe мент указателя, он указывает на ячейку с предыдущим элементом ero базовоrо типа. Для указателей на символы инкремент и декремент действуют на указатель по правилам "обычной" арифметики, потому что символы имеют размер один байт. Однако указатель на любой дpy rой тип данноrо будет увеличиваться или уменьшаться на длину CBoero базовоrо типа. Арифметика не оrpаничивается операциями инкремента и дeкpe мента. Вы можете прибавлять к указателям значения или вычитать из них. Выражение рl = рl+9; приведет к тому, что рl будет указывать на девятый элемент базовоrо типа рl за тем, на который он указывает в настоящий момент. Хотя указатели нельзя складывать, допустимо вычитать один указа тель из дрyrоrо (при условии, что они одноro базовоrо типа). Разность будет представлять число элементов базовоrо типа, разделяющих эти два указателя. 
Выражения с указателями 171 Кроме сложения и вычитания указателя и целоrо числа или вычи тания одноrо указателя из дрyrоrо, никакие дрyrие арифметические операции над указателями недопустимы. Нельзя, например, прибав лять к указателям или вычитать из них значения fl08t или doubIe. Чтобы нarлядно представить себе результаты арифметики указате лей, выполните приведенную ниже короткую проrpамму. Она создает указатели на int (1) и doubIe (1). Затем к этим указателям прибавляются значения от О до 9 и результаты выводятся на экран. Обратите внима ние на то, как по ходу выполнения цикла изменяется каждый адрес в соответствии с базовым типом указателя. (Для большинства 32раз рядных компиляторов i будет увеличиваться на 4, а f ..... на 8). Заметьте, что при использовании указателя в предложении cout ero значение aB томатически выводится в формате, соответствующем архитектуре цeH тральноrо процессора и операционной среде. #inclиde <iostream> using namespace std; int main () { int *i, j[10]; doиble *f, g[10]i int Х; i = j i f = gi for(x=Oi х<10; х++) coиt « i+x « I , « f+x « '\п' i  Вывод адресов, получаемых при при- бaвnении х к каждому указателю. I return о; } Вот пример пробноrо проrона (фактические значения MOryт отли чатъся от приведенных здесь). 0012FE5C 0012FE84 0012FE60 0012FE8C 0012FE64 0012FE94 0012FE68 0012FE9C 0012FE6C 0012FEA4 0012FE70 0012FEAC 0012FE74 0012FEB4 0012FE78 0012FEBC 0012FE7C 0012FEC4 0012FE80 0012FECC 
172 Модуль 4. Массивы, строки и указатели Сравнение указателей Указатели можно сравнивать дрyr с дpyrOM, используя такие опера торы отношения, как = =, < и >. В общем случае результат операции сравнения указателей будет иметь смысл, только если оба указателя будут взаимосвязаны. Например, они MOryт указывать на разные эле менты внутри одноrо и тoro же массива. (В Проекте 42 вы увидите пример такой ситуации.) Существует, однако, и друrой вид сравнения указателей: любой указатель может сравниваться с нулевым указате лем, который равен нулю. Минутная тренировка 1. Вся арифметика указателей выполняется в соответствии с   yкa зателя. 2. Считая, что тип doubIe занимает 8 байт, на сколько увеличится значение указателя на doubIe, если выполнить над ним операцию инкремента? 3. При каких обстоятельствах (в общем случае) сравнение двух указателей имеет смысл? 1. базовым типом 2. Значение указателя будет увеличено на 8. 3. В общем случае сравнение двух указателей имеет смысл, если они оба указывают на один и тот же объект, например, на один массив. Цель _Указатели и массивы В С++ имеется тесная взаимосвязь между указателями и массива ми. Во мноrих случаях указатель и массив являются взаимозаменяе мыми понятиями. Рассмотрим следующий фраrмент: char str[80]; char *рl; рl = str; Здесь str представляет собой массив из 80 символов, а рl  указа тель на символы. Однако для нас более интересна третья строка. В ней рl присваивается адрес первоrо элемента массива str. (После присваи вания рl будет указывать на str[O].) И вот почему: в С++ использова ние имени массива без индекса образует указатель на первый элемент массива. Таким образом, операция рl = str; 
Указатели и массивы 173 присваивает указателю рl адрес элемента str[O). Это очень важный MO мент: при использовании в выражении неиндексированноrо имени массива образуется указатель на первый элемент массива. Поскольку после присваивания рl указывает на начало str, вы MO жете посредством рl получить доступ к элементам массива. Если, Ha пример, вы хотите обратиться к пятому элементу str, вы можете ис пользовать обозначения str[4] или *(рl+4) Оба выражения дадут вам значение пятоrо элемента. Не забывайте, что индексы массива начинаются с нуля, поэтому при индексации str для доступа к пятому элементу используется индекс 4. Для обращения к пятому элементу посредством указателя рl к значению рl также дo бавляется 4, так как сам рl указывает на первый элемент str. Крyrлые скобки, окружающие рl+4 в последнем выражении, необ ходимы, потому что операция · имеет более высокий относительный приоритет, чем операция +. Если скобки опустить, выражение будет вычислять иначе: сначала будет найдено значение, на которое указы вает рl (т. е. первый элемент массива), а затем к этому значению при бавится 4. Фактически с++ предоставляет два метода обращения к элементам массива: с помощью арифметики указателей и с помощью индексации массива. Необходимо владеть обоими методами, так как иноrда ариф метка указателей работает быстрее индексации  особенно, если вы обращаетесь к элементам массива в cтporo последовательном порядке. Поскольку скорость часто является важнейшим критерием в проrpам мировании, обращение к элементам массивов посредством указателей весьма распространено в проrpаммах на с++. Кроме Toro, используя указатели вместо индексации массива, вы иноrда можете написать бо лее компактную проrрамму. Ниже приведен пример, демонстрирующий различия между ин дексацией массива и арифметикой указателей при обращении к элементам массива. Мы рассмотрим два варианта проrpаммы, KOTO рая будет выполнять преобразование букв в строке: прописных в строчные, а строчных  в прописные (обращение реrистра). В пер вом варианте используется индексация. Второй вариант делает то же, но с помощью арифметики указателей. Первый вариант про фаммы выrлядит так: // Обращение реrистра посредством индексации массива. #include <iostream> 
174 Модуль 4. Массивы, строки и указатели #include <cctype> using паmе8расе 8tdi int rnain ( ) { int i; char 8tr[80] = "Thi8 18 А Test"; cout « "Исходная строка: " « 8tr « "\п"; for(i = о; str[i]; i++) { if(i8upper(8tr[i]» 8tr[i] = tolower(8tr[i]); e18e if(islower(8tr[i]» 8tr[i] = toupper(8tr[i]); } cout « "Преобразованная строка: " « 8tr; return о; } ВЫВОД проrpаммы выrлядит так: Исходная строка: Thi8 18 А Te8t Преобразованная строка: tH1S iS а tEST Обратите внимание на то, что nporpaMMa с целью определения pe rистра использует библиотечные функции isupper( ) и islower( ). Функ ция isupper( ) возвращает true, если ее apryмeHT является прописной буквой; функция islower( ) возвращает true, если ее apryмeHT  строч ная буква. Внутри цикла for выполняется индексация массива str, и Ka ждая буква анализируется и преобразуется. Цикл повторяется до тех пор, пока не встретится завершающий строку ноль. Поскольку ноль равнозначен false, цикл завершается. Теперь рассмотрим вариант проrpаммы с арифметикой указателей: // Обращение реrистра посредством арифметики указателей. #include <io8tream> #include <ctype> using паmе8расе 8td; int main () { char *р: 
Указатели и массивы 175 char 8tr[80] = "Thi8 18 А Te8t"; cout « "Исходная строка: " « str « "\n"; р = 8tr; // присвоим р адрес начала массива while (*р) { i f (isupper (*р) ) *р = tolower (*р) ; e18e if(i81ower(*p»  *р = toupper ( *р) ; р++; } Обращение к str посредством указателя. cout « " Преобразованная строка: " « stri return о; } в этом варианте проrpаммы р устанавливается на начало str. Затем внутри цикла while буква, на которую указывает р, анализируется и преобразуется, а затем выполняется инкремент р. Цикл завершается, коrда р будет указывать на завершающий строку str ноль. Правила, по которым компиляторы образуют объектный код, при водят к тому, что эти две проrpаммы MOryr различаться по произво дительности. В общем случае индексация массива требует большеrо числа машинных команд, чем выполнение арифметических преобра зований с указателем. Соответственно, в профессионально написан ной С++проrpамме вы чаще увидите вариант с указателями. Однако в начале обучения языку вы вполне можете использовать индекса цию массивов, пока не приобретете необходимые навыки для работы с указателями. Индексация указателя Как вы только что видели, для обращения к массиву можно исполь зовать арифметику указателей. Как это ни странно, но обратное тоже справедливо. В С++ возможно индексировать указатель, как если бы он был массивом. Вот пример индексации указателя. Это третий вари ант проrpаммы обращения реrистра: // Индексация указателя, как если бы он был массивом. #include <iostream> #include <cctype> 
176 Модуль 4. Массивы, строки и указател using namespace std; int main () { char *р; int i; char str[80] = " This 1s А Test"; cout « "Исходная строка: n « str « "\n"; р = str; // присвоим р адрес начала массива // теперь индексируем р for(i = о; p[i]; i++) { if(isupper(p[i])) p[i] = tolower(p[i]); else if(islower(p[i]))  p[i] = toupper(p[i]); Использование указателя Р. как если бы он был массивом. } cout « "Преобразованная строка: " « str; return о; } Проrpамма создает указатель на char с именем р, и присваивает затем этому указателю адрес первоro элемента str. Внутри цикла for указатель р индексируется с использованием обычноrо синтаксиса индексации Mac сива. Такая операция вполне допустима, так как в с++ выражение p[i] функционально идентично выражению * (p+i). это обстоятельство еще раз иллюстрирует тесную взаимосвязь меЖlIY указателями и массивами. Минутная тренировка 1. Можно ли к массиву обратиться посредством указателя? 2. Можно ли индексировать указатель, как если бы он бьт массивом? 3. Что обозначает имя массива, указанное без индекса? 1. Да, к массиву можно обратиться посредством указателя. 2. Да, указатель можно индексировать так же, как и массив. 3. Имя массива, указанное без индекса, обозначает адрес первоrо злемеmа этоrо массива. Спросим у эксперта Вопрос: Можно ли сказать, что указатели и имена массивов взаимозаме няемы? 
Указатели и массивы 177 OrBeт: Как было показано на предьшуших страницах, указатели и Mac сивы тесно взаимосвязаны и их имена во мноrих случаях взаимозаменяе мы. Например, имея указатель, указывающий на начало массива, можно обращаться к элементам зтоrо массива как посредством арифметики yкa зателя, так и с помощью индексации. Однако нельзя сказать, чтобы имена указателей и массивов были полностью взаимозаменяемы. Рассмотрим, например, следующий фраrмент: int nums [1 О] ; int i; for(i=O; i<10; i++) { *nиmэ = i; / / это правильно nums++; / / ОШИБКА  nums нельзя модифицировать } в этом примере nums представляет собой массив целых чисел. Как yкa зано в комментариях, хотя вполне допустимо использовать с nums оператор * (операция с указателем), было бы rpубой ошибкой модифицировать зна чение nODls. Дело в том, что nums является константой, которая указывает на начало массива. Применить к ней операцию инкремента нельзя. He смотря на то, что имя массива, указанное без индекса, действительно обра зует указатель на начало массива, изменение этоrо указателя недопустимо. Хотя имя массива образует указательконстанту, ero все же можно ис пользовать в качестве части указательноrо выражения, если только не пы таться ero модифицировать. Например, следующая строка представляет собой вполне допустимое предложение, которое присваивает элементу nums[3] значение 100: * (nums+З) = 100; // Это правильно, поскольку nums не // изменяется СТРОКО8ые константы Посмотрим, как с++ работает со строковыми константами, вроде той, что используется в приведенном ниже предложении: cout « strlen ("Ксантиф") ; Коrда компилятор сталкивается со строковой константой, он co храняет ее в таблице строк данной проrpаммы и создает указатель на эту строку. В результате "Ксантиф" образует указатель на начало этой фразы в таблице строк проrpаммы. Поэтому приведенная ниже про фамма составлена вполне разумно; она выводит фразу "Указатели увеличивают возможности С++": 
178 Модуль 4. Массивы, строки и указатели #include <iostream> using namespace std; int main ( ) { char *ptr; Указателю ptr присваивается ад- рес этой строковой константы.  ptr = " Указатели увеличивают возможности C++.\n"; cout « ptr; return о; } в этой профамме символы, образующие строковую константу, xpa нятся в таблице строк, и указателю ptr присваивается адрес нашей строки в этой таблице. Поскольку указатель на таблицу строк nporpaMMbI создается aBTO матически при использовании строковой константы, у вас может поя виться искушение воспользоваться этим указателем для модификации содержимоrо таблицы строк. Однако это рискованное занятие, так как мноrие компиляторы с++ создают оптимизированные таблицы строк, в которых одна строковая константа может использоваться в нескольких местах вашей проrpаммы. В этом случае изменение строки может привести к нежелательным побочным эффектам. Проект 4..2 Переворачивание строки Выше уже отмечалось, что сравнение одноrо указателя с дрyrим имеет смысл, только если они оба указывают на один и тот же объект, например, массив. Теперь, коrда мы рассмотрели взаимоотношение указателей и массивов, мы можем использовать сравнение указателей Д7Iя рационализации некоторых типов алrоритмов. В этом проекте вы увидите пример TaKoro рода. Рассматриваемая здесь проrpамма осуществляет переворачивание строки, оставляя ее на том же месте. Вместо копирования строки за дом наперед в дрyrой массив, проrpамма обращает содержимое строки прямо внутри содержащеrо эту строку массива. Для выполнения этой операции в проrpамме используются две переменныхуказателя. Один указатель первоначально указывает на начало строки, второй  на ее последний символ. В проrpамме также орrанизован цикл, который выполняется до тех пор, пока начальный указатель меньше конечноrо. 
Указатели и массивы 179 в каждом шаrе цикла символы, на которые указывают указатели, об мениваются местами, после чеrо указатели продвиrаются (один впе ред, а дрyrой назад). Коrда начальный указатель становится равным или большим, чем конечный, цикл завершается, а строка оказывается перевернутой . Шаr за шarом 1. Создайте файл с именем StrRev.cpp. 2. Начните с включения в файл следующих строк: /* Проект 42 Переворачивание строки. */ #include <iostream> #include <cstring> using nаmеэрасе std; int main () { char str [] = "это проверка работы проrраммы" i char *start, *endi int leni char t i Обращаемая строка содержится в str. Указатели start и end исполь зуются для обращения к символам строки. 3. Добавьте приведенные ниже строки, которые выводят исходную строку, определяют ее длину и устанавливают начальные значения указателей start и end: cout « "Исходная: " « str « "\n"; len = strlen(str)i start = str i end = &str[lenl] ; Обратите внимание на то, что end указывает на последний символ строки, а не на завершающий ноль. 4. Добавьте фраrмент, обращающий строку: while(start < end) { // обменяем символы 
180 Модуль 4 Массивы, строки и указатели t = *start; *start = *end; * end = t; // продвинем указатели start++; end; } Обмен осуществляется следующим образом. Шаrи цикла повторя ются, пока адрес памяти, содержащийся в указателе start, меньше aд реса, содержащеrося в указателе end. Внутри цикла символы, на KOTO рые указывают start и end, обмениваются местами. Затем выполняется инкремент указателя start и декремент указателя end. Коrда start cтa новится больше или равным end, процесс прекращается, так как все символы строки поменялись местами. Поскольку оба указателя, и start, и end, указывают на один и тот же массив, их можно сравнивать дрyr с дpyrOM. 5. Ниже приведен полный текст профаммы StrRev.cpp: /* Проект 4  2 Переворачивание строки. */ #include <iostream> #include <cstring> using namespace std; int main () { char str[] = "это проверка работы проrраммы ll ; char *start, *end; int len; char t; cout « tt Исходная: tt « s tr « tt \п tt ; len = strlen(str); start = str; end = &str[lenl]; while (start < end) { // обменяем символы t = *start; *start = *end; 
Указатели и массивы 181 * end = t; / / продвинем указатели start++; end   ; } cout « II Перевернутая : '1 « str « аl \n tt ; return о; } Ниже приведен вывод проrpаммы: Исходная: это проверка работы проrраммы Перевернутая: ыммарrорп ытобар акреворп отэ Массивы указателей Указатели, как и любые дрyrие типы данных, MOryт образовывать массивы. Например, объявление массива из 10 указателей типа int BЫ rлядит так: int *pi [1 о] ; Здесь pi является именем массива из десяти указателей на целые числа. Для присваивания адреса переменной типа int с именем var тpeть ему элементу массива указателей, вы должны написать: int var; pi [2] = &var; Не забывайте, что pi является массивом указателей на int. Единст венный вид объектов, которые MOryт содержаться в элементах этою массива..... это адреса целочисленных переменных (не самих перемен ных!) . Чтобы найти значение var, вы должны написать: *pi[2] Как и в случае любых дрyrих массивов, массив указателей можно инициализировать. Обычное использование инициализированных массивов указателей..... хранение указателей на строки. Вот пример, в котором используется двумерный массив указателей на символы Д7Iя реализации небольшоrо толковою словаря: 
182 Модуль 4. Массивы, строки и указатели // Использование двумерноrо массива указателей для создания // словаря. #include <iostream> #include <cstring> using namespace std; char *dictionary [ ] [2] = {  "карандаш", "Инструмент для рисования.", "клавиатура", "Устройство ввода. " , "ружье", "Плечевое оrнестрельное оружие. tI , "Самолет", "Воздушное судно с неподвижными крыльями. 11 , "сеть", "rруппа соединенных между собой компьютеров. " , Это двумерный массив указателей на char, KO торый используется для адресации пар строк I int main () { " " " " } ; char word[80]; int i; cout « "Введите слово: "; cin » word; Для поиска определения слова в dictionary ищется word Если соответствие найдено, определение выводится на экран for(i = о; *dictionary[i] [о]; i++) { I if(lstrcmp(dictionary[i] [О], word» { cout « dictionary[i] [1] « "\n tl ; break; } } i f ( ! * d i с t i о па ry [ i] [О] ) cout « word « " не найдено. \n" ; return о; } Вот пример проrона проrpаммы: Введите слово: сеть rруппа соединенных между собой компьютеров. При создании массива dictionary он инициализируется набором слов и их определений. Вспомним, что с++ записывает все строковые константы в таблицу строк, связанную с вашей проrpаммой, поэтому массив будет содержать лишь указатели на строки. Проrpамма сравни вает введенное пользователем слово со строками, хранящимися в 
Указатели и массивы 183 словаре. Если обнаруживаеIСЯ совпадение, на экран выводится опре деление соответствующеrо слова. Если совпадения не обнаруживает ся, выводится сообщение об ошибке. Обратите внимание на то, что dlctlonary заканчивается двумя пус тыми строками. Они отмечают конец массива. Вспомним, что пустая строка содержит только завершающий ноль. Цикл for выполняется до тех пор, пока первый символ строки не окажется нулем. Это условие обнаруживается с помощью выражения *dictionary[i] [о] Индексы массива задают указатель на строку. Знак * позволяет по лучить символ, расположенный по этому адресу. Если этот символ pa вен нулю, то выражение ложно и цикл завершается. В противном слу чае выражение истинно и цикл продолжает свое выполнение. Соrлашение о нулевом указателе После тoro, как указатель объявлен, но до тoro, как ему будет при своен какойто разумный адрес, он будет содержать произвольное зна чение. Попытка использовать указатель перед ero инициализацией с большой вероятностью приведет к аварийному завершению проrpам  мы. Поскольку нет наде.жноrо способа избежать использования неини циализированноrо указателя, проrpаммистыI на с++ приняли процеду ру, которая помоrает предотвратить некоторые ошибки. По принятому соrлашению, если указатель содержит нулевое значение, то считается, что он никуда не указывает. Таким образом, если всем неопределенным пока указателям присвоить нулевое значение, а профамму написать так, чтобы она не использовала нулевые указатели, вы сможете избе жать случайноrо обращения к неинициализированному указателю. это весьма полезный прием, кoroрым стоит пользоваться. Указатель любою типа может быть инициализирован нулем при ero объявлении. Например, следующее предложение инициализирует р нулем: float *р = о; //р теперь нулевой указатель Для отбраковки нулевых указателей пользуйтесь предложениями if вроде следующих: if(p) // далее выполнение, если р не ноль if(lp) // далее выполнение, если р ноль 
184 Модуль 4. Массивы, строки и указатели Минутная тренировка 1. Строковы е константы, используемые в проrpамме, хранятся в 2. Что создает это объявление: f 1 оа t * fpa [ 18] ; 3. По соrлашению указатель, содержащий ноль, считается неиспользуе мым. Справедливо ли это утверждение? 1. таблице строк 2. Объявление создает массив из 18 указателей на t1oat. 3. Справедливо. Цель _Указатель на указатель Указатель на указатель представляет собой форму вложенной KOC венности, или цепочки указателей. Рассмотрим рис. 42. Леrко видеть, что в случае обычноrо указателя значение указателя представляет co бой адрес переменной (или, можно сказать, адрес значения). В случае указателя на указатель первый указатель содержит адрес BToporo yкa зателя, а тот уже указывает на ячейку памяти, содержащую требуемое значение. Вложенная косвенность может иметь любую rлубину вложенности, однако случаи, коrда требуется иметь более rлубокую вложенность, чем указатель на указатель, редки, да и вообще вряд ли стоит такие KOHCT рукции создавать. Излишне rлубокая вложенность затрудняет понима ние проrpаммы и леrко может привести к принципиальным ошибкам. ПеременнyIO, которая должна служить указателем на указатель, следует таковой и объявить. Это делается помещением дополнитель ной звездочки перед ее именем. Например, приведенное ниже объяв Указатель адрес ПеремеНН8JI I значение Пpocтuкосаеввость адрес .1 адрес ПеремеНН8JI I значение Указатель Указатель Вло_нии косаеввость Рие. 4- 2. Простая и вложенная косвенность 
Указатель на указатель 185 ление создает переменную balance, которая является указателем на указатель типа int: int **balance; Важно понимать, что balance является не указателем на целое, а указателем на указатель на целое. Коrда значениемишень адресуется посредством указателя на yкa затель, обращение к этому значению требует применения оператора * дважды, как это показано в следующем коротком примере: #include <iostream> using namespace std; int main ( ) { · t * ** ln Х, р, qi х = 10; р = &х i  Присваивает р адрес Х. q = &р;  Присваивает q адрес р. cout « **q; // выводит значение х  I Обращение к значению х посредством q Заметьте, что требуются два знака *. return о; } Здесь р объявляется, как указатель на целое, а q  как указатель на указатель на целое. Предложение cout выведет на экран число 10. (просим у эксперта Вопрос: Учитывая большие возможности указателей, я MOry предвидеть, что случайные ошибки в их использовании MOryт привести к серьезным нарушениям в работе проrpаммы. Можете ли вы порекомендовать, как из бежать ошибок при работе с указателями? OrвeT: Вопервых, перед тем, как использовать переменныеуказатели, убедитесь, что они инициализированы. Указатель должен на чтонибудь I указывать, если вы хотите использовать ero по назначению! Bo вторых, следите за тем, чтобы тип объекта, на который указывает указатель, совпа , дал с базовым типом указателя. Втретьих, не выполняйте никаких опера.. ций посредством нулевоrо указателя. Не забывайте: нулевой указатель оз начает, что указатель ни на что не указывает. Наконец, никоrда не приво 
186 Модуль 4 Массивы, строки и указатели дите указатели к друrому типу "просто чтобы проrрамма нормально компилировалась". Как правило, сообщения о несоответствии указателей rоворят о ТОМ, что вы неправильно представляете себе какието элементы проrpаммы. Приведение одноrо типа указателя к друrому обычно требует ся только в необычных ситуациях.  Вопросы для самопроверки 1. Покажите, как объявить массив hightemps типа short int размером 31 элемент. 2. В с++ индексы любоrо массива начинаются с 3. Напишите проrpамму, которая просматривает массив из 10 целых чисел в поисках одинаковых значений. Все найденные пары про фамма должна выводить на экран. 4. Что такое строка, завершающаяся нулем? 5. Напишите проrpамму, которая запрашивает у пользователя две строки, а затем сравнивает эти строки, не обращая внимания на реrистр букв. В этом случае "ok" и "ОК" будут считаться равными. 6. Насколько велик должен быть массивприемник при использова нии функции strcat( )? 7. Как задается каждый индекс в MHoroMepHoM массиве? 8. Покажите, как инициализировать массив nums типа int значениями 5, 66 и 88. 9. В чем заключается принципиальное преимущество объявления Mac сивов неопределенной длины? 10. Что такое указатель? Каковы два оператора указателей? ] 1. Допустимо ли индексировать указатель так, как это делается с Mac сивом? Можно ли обратиться к массиву посредством указателя? 12. Напишите проrpамму, которая подсчитывает число прописных букв в строке и выводит результат на экран. 13. Как называется ситуация, коrда один указатель указывает на дpy rой указатель? 14. Каково значение нулевоrо указателя в с++? 
Модуль 5 Введение в функции Цепи, досrиrаемые 8 ЭТОМ модуле 5.1 Познакомиться с общей формой определения функции 5.2 Научиться создавать собственные функции 5.3 Освоить использование aprYMeHToB функции 5.4 Понять, что такое возвращаемое функцией значение 5.5 Научиться использовать функции в выражениях 5.6 Узнать о локальной области видимости 5.7 Узнать о rлобальной области видимости 5.8 Освоить передачу в функцию указателей и массивов 5.9 Освоить возврат из функции указателей 5.1 О Рассмотреть передачу в функцию main( ) apryMeHToB командной строки 5.11 Узнать о прототипах функций 5.12 Научиться создавать рекурсивные функции 
188 Модуль 5. Введение в функции В этом модуле мы начинаем серьезное изучение функций. Функции можно назвать строительными блоками с++, и отчетливое понимание механизма функций абсолютно необходимо для тoro, чтобы стать yc пешным проrpаммистом на с++. Здесь вы узнаете, как создать Функ цию. Вы также познакомитесь с передачей apryмeHToB, возвратом из функции значения, локальными и rлобальными переменными, прото типами функций и рекурсией. ОСНОВЫ фУНКЦИЙ Функцией называется подпроrpамма, содержащая одно или больше предложений с++ и выполняющая конкретную задачу. До сих пор все проrраммы, которые вы писали, использовали единственную Функ цию main( ). Функции называются строительными блоками с++, по тому что любая проrpамма представляет собой набор функций. Все "предложения действий" вашей проrpаммы находятся внутри функ ций. Таким образом, функция содержит преДТIожения, которые вы обычно рассматриваете, как выполнимую часть проrpаммы. Хотя очень простые проrpаммы, вроде тех, что приводятся В Ha стоящей книrе в качестве примеров, MOryт содержать единственную функцию main( ), большинство проrpамм включает в себя несколько функций. Большие коммерческие проrpаммы MOryт определять сотни функций. Цель .Общая форма определения функции Все функции с++ имеют общую форму, приведенную ниже: типвозврата имя(списокпараметров) { / / тело функции } Здесь типвозврата определяет тип данноrо, возвращаемоrо Функ цией. Функция может возвращать любой тип за исключением массива. Если функция ничеrо не возвращает, то тип возврата должен быть void. Имя функции определяется элементом имя. В качестве имени может использоваться любой допустимый идентификатор, если он еще не за нят. cпиCOKпapaмeтpoв представляет собой последовательность пар типов и идентификаторов, разделяемых запятыми. Параметры  это переменные, которые получают значения apryмeHТOB, передаваемых 
Создание функции 189 функции при ее вызове. Если функция не требует параметров, список параметров будет пуст. Фиrypные скобки окружают тело функции. Тело функции состоит из предложений С++, определяющих, чro именно эта функция делает. Ko rда по ходу этих предложений встречается закрывающая фиrypная скоб ка, функция завершается, и управление передается вызывающему коду. Цель _ Создание функции Создать функцию очень просто. Поскольку все функции имеют об щую форму, они все структурно схожи с функцией main( ), которую вы уже использовали. Начнем с простоro примера, содержащеro две Функ ции: main( ) и myfunc( ). Перед тем, как запустить эту проrpамму (или оз накомитъся с приведенным далее анализом ее работы), попробуйте сами сообразить, что эта функция выведет на экран. // Эта проrрамма содержит две ФУНКЦИИ: main()  myfunc(). #inclиde <iostream> иsing namespace std: void myfиnc (): / / прототип ФУНКЦИИ myfunc  int main ( ) { I Это прототип функции myfunc( ). cout « ttB main () \n" ; myfunc()i // вызов myfunc() , cout « "Снова в main()\n"; return О: } // Это опре еление void myfиnc ( ) ( cout « tt Внутри myfunc () \n tt i } Это функция с именем туШпс( ). Проrpамма работает следующим образом. Начинается все с Функ ции main( ), в которой выполняется первое предложение cout. Затем тайп( ) вызывает функцию myfunc( ). Посмотрите, как это делается: 
190 Модуль 5. Введение в функции указывается имя функции, за котором стоит пара крyrлых скобок. В данном случае вызов функции представляет собой предложение, и как и всякое предложение, оно должно заканчиваться точкой с запятой. Далее myfunc( ) выполняет предложение cout и, встретив закрывающую фиrypную скобку }, возвращается в main( ). В main( ) выполнение BO зобновляется на той строке кода, которая непосредственно следует за вызовом myfunc( ). Наконец, main( ) выполняет второе предложение cout и завершается. Вот вывод этой проrpаммы: в rnain ( ) Внутри myfunc ( ) Снова в main ( ) Способ, которым вызывается myfunc( ), и способ, которым осуществ ляется возврат в таin( ), являются типичными представителями проце дуры, приложимой ко всем функциям. Для вызова функции следует указать ее имя с парой крyrлых скобок. Коrда вызывается функция, управление передается в эту функцию. Далее выполнение продолжается внyrpи функции, пока не встреmтcя закрывающая фиrypная скобка. Завершение работы функции приводит к передаче управления назад в вызывающий код на то преД7Iожение, которое стоит непосредственно за вызовом функции. Обраrnтe внимание на предложение в приведенной выше проrpамме: void myfunc (); / / прототип функции myfunc () как указано в комментарии, это прототип функции тyfunс( ). Прoro типы будyr детально рассмотрены ниже, а здесь только несколько слов. Прототип функции объявляет функцию перед ее определением. Прото 1ИП позволяет компилятору узнать 1Ип возвращаемоro функцией значе НИЯ, а также число и 1ИПы парамe1JIOВ этой функции. Компилятор дол жен иметь всю эту информацию к тому момеюу, коrда он столкнется с первым вызовом функции. Именно поэтому прототип располаraется пе ред main( ). Единственная функция, кoroрой не требуется прототип  эro rлавная функция тain( ), пporoтип которой предопределен в С++. Ключевое слово void, которое указывается как перед прототипом myfunc( ), так и перед ее определением, формально устанавливает, что myfunc( ) не возвращает никакоro значения. В С++ функции, не воз вращающие значения, объявляются void. Цель _ Использование aprYMeHTOB в созданную вами функцию можно передать одно или несколько значений. Значение, передаваемое в вызываемую функцию, называет 
Использование apryMeHToB 191 ся apZYMeпmOM. ApryмeHTЫ позволяют передать в функцию некоторую информацию. Коrда вы создаете функцию, которая принимает один или больше apryмeHТOB, переменные, которые будут принимать эти apryмeHТbI, так же должны быть объявлены. Эти переменные называются пapa метрами функции. Ниже при веде н пример определения функции с именем Ьох( ), которая вычисляет объем коробки и выводит результат на экран. Она имеет три параметра, характеризующие длину (Iength), ширину (width) и высоту (height) коробки: void Ьох (int length, int width, int height) { cout « "объем коробки равен " « length * width * height « "\n"; } Каждый раз, коrда вызывается функция Ьох( ), она вычисляет объ ем коробки, умножая значения, передаваемые ей в переменные length, width и height. Вызывая Ьох( ), вы должны задать три apryмeHTa, например: Ьох (7, 2 О, 4); Ьох ( 5 О, З, 2); Ьох ( 8, 6, 9); Значения, указанные между крyrлыми скобками, представляют собой apryмeHTЫ, передаваемые в Ьох ( ), причем значение каждоrо apryмeHTa копируется в соответствующий ему параметр. Таким об разом, при первом вызове Ьох ( ) 7 копируется в length, 20 копирует ся в width, а 4 копируется в helght. При втором вызове Ьох ( ) 50 KO пируется в length, 3 копируется в width, а 2 копируется в height. При третьем вызове 8 копируется в length, 6 копируется в width, а 9 копи руется в height. Приведенная ниже проrpамма демонстрирует использование Функ ции Ьох( ): // Простая проrрамма, демонстрирующая использование Ьох(). #include <iostream> using namespace std; void Ьох (int length, int width, int height); / / Прототип Ьох ( ) int main () { Ьох(7, 20, 4);  Арсументы. передаваемые в Ьох( ). 
192 Модуль 5. Введение в функции Ьох ( 5 о, 3, 2); Ьох ( 8, 6, 9); return о; Эти napaметры принимают значения apryмeнтoB, передаваемых в Ьох( ). height)  } // Вычисление объема коробки. void box(int length, int width, int { cout « "объем коробки равен " « length * width * height « tt \n" ; } Вывод профаммы: объем коробки равен 560 объем коробки равен 300 объем коробки равен 432  Совет Не забывайте, что термин aprYMeHT относится к значению, которое используется в вызове функции. Переменная, принимающая значение aprYMeHTa, называется параметром. Между прочим, функции, прини мающие при вызове aprYMeHTbI, носят название параметризоsанных ФУНКЦИЙ. Минутная тренировка 1. Что происходит с выполнением проrpаммы при вызове функции? 2. Как различаются apryмeHTЫ и параметры? 3. Если функция требует параметр, rде он объявляется? 1. Коrда вызывается функция, выполнение проrpаммы переходит в функцию. Коrда функ.. ция завершается, выполнение проrpаммы возврашается в вызывающий код на предло.. жение, непосредственно следуюшее за вызовом функции. 2. ApryмeHToM называется значение, передаваемое функции. Параметр  это переменная, КОТорая принимает это значение. 3. Параметры объявляются после имени функции, внyrpи крyrлых скобок. Цель _ Использование предложения return В предыдущих примерах возврат из функции в вызывающий код осуществлялся, коrда в тексте функции встречалась закрывающая фи 
Использование предложения return 193 rypная скобка. Хотя для мноrих функций это вполне допустимо, в ряде случаев такой метод возврата работать не будет. Часто требуется яв ным образом указать, как и коrда функция должна завершить свою pa боту. Для этоrо используется предложение return. Предложение return имеет две формы: одна позволяет вернуть зна чение, дрyrая не делает этоrо. Мы начнем с варианта предложения retum, который не возвращает значения. Если функция имеет ТИП воз врата void (т. е. функuия не возвращает никакоrо значения), использу ется следующая форма предложения retum: return; Если в тексте функции встречается преД7Iожение retum, выполне ние немедленно возвращается в вызывающий код. Любые строки, oc тающиеся в функции, иrнорируются. Рассмотрим, например, следую шую проrpамму: // Использование предложения return. #include <iostream> using namespace std; void f(); int main () { cout « "Перед вызовом\n"; f ( ) ; cout « "После вызова\n"; return о; } // функция void, использующая return. void f ( ) { cout « I.Внутри f () \n" ; return; // возврат в вызывающий код  Это вызывает нeмeдneнный возврат С обходом ocтaв шerося предложения cout. cout «"Это не будет ВЫВОДИТЬСЯ.\n"j } Ниже приведен вывод проrpаммы: 
194 Модуль 5. Введение в функции Перед вызовом Внутри f ( ) После вызова Как ВИДНО из вывода профаммы, f( ) возвращается в main( ), как только в f( ) встречается предложение retum. Второе предложение cout выполняться никоrда не будет. Вот более полезный пример использования предложения return. Функция power( ), включенная в приведенную ниже профамму, BЫBO дит результат возведения целоrо числа в целую положительную CTe пень. Если показатель степени отрицателен, предложение retum за ставляет функцию завершиться, не пытаясь вычислить результат. #include <iostream> using namespace std; void power (int base, int ехр) ; int main () { power ( 1 о, 2); power ( 1 о,  2) ; return о; } // Возведение целоrо числа в целую положительную степень. void power(int base, int ехр) { int i; if(exp < О) retиrn; /* Не умею работать с отрицательными показателями. */ + Возврат, если ехр отрицательно. i = 1; for( ехр; exp) i = base * i; cout « "Ответ: " « i; } Вот вывод проrpаммы: Ответ: 100 Если ехр отрицательно (как это имеет место во втором вызове), power( ) возвращается, обходя весь остаток функции. Функция может содержать несколько предложений retum. как толь ко по ходу выполнения функции встречается одно из них, функция за вершается. Например, приведенный ниже фрarмент вполне правилен: 
Использование предложения return 195 void f() { / / ... switch (с) { case ' а ': return; case I Ь': / / ... case 'с': return; } if(count < 100) return; // } Учтите, однако, что слишком MHoro предложений return ухудшают структуру функции и затуманивают ее смысл. Использовать несколько предложений return имеет смысл лишь тоrда, коrда при этом текст функции становится более понятным. Возвращаемые значения Функция может Bepнyrь значение в вызывающий ее код. Возвра щаемое значение представляет собой способ получить информацию из функции. для возврата значения следует использовать второй вариант предложения return: return зuачеllие; Здесь значение  это то значение, которое возвращается функцией в вызывающий ее код. эта форма предложения retum может использо ваться только с функциями, не возвращающими void. Функция, возвращающая значение, должна задать тип этоrо значе ния. Тип возврата должен быть совместим с типом данною, исполь зуемоrо в предложении retum. Если это не так, во время компиляции будет зафиксирована ошибка. В объявлении функuии может быть yкa зан любой допустимый В с++ тип данноrо, за исключением Toro, что функция не может вернуть массив. Для иллюстрации раБотыI с функциями, возвращающими значения, перепишем функцию Ьох( ) так, как это показано ниже. В этом вари анте Ьох( ) возвращает вычисленный объем. Обратите внимание на то, что помещение функции с правой стороны знака равенства присваи вает возвращаемое значение переменной. // Возврат значения. #include <iostream> using паmеэрасе std; 
196 Модуль 5. Введение в функции int box(int length, int width, int height); // вернем объем int main () { int answer; + Значение, возвращаемое из функции Ьох( ), присваиваеrся переменной answer. answer = Ьох(10, 11, З); // присвоим возвращаемое значение cout « "Объем равен " « answer; return о; } // Эта функция возвращает значение. int box(int length, int width, int height) { return length * width * height ;  Вернем объем. } Вот вывод профаммы: Объем равен 330 В этом примере Ьох( ) возвращает значение length * width * height с по мощью преДJIожения retum. Эro значение присваивается переменной answer. Дрyrими словами, значение, возвращаемое предложением return, становится значением функции Ьох( ) в вызывающей проrpамме. Поскольку функция Ьох ( ) возвращает значение, ее объявление не предваряется ключевым словом void. (Вспомним, что void используется только если функция не возвращает значения.) Вместо этоrо Ьох ( ) объявлена, как возвращающая значение типа int. Обратите внимание на то, что тип возвращаемоrо функцией значения указывается перед ее именем как в прототипе, так и в определении. Разумеется, int не является единственным типом, который MO жет возвращаться функцией. Как уже отмечалось, функция может вернуть любой тип за исключением массива. Например, в приве денной ниже проrрамме функция Ьох( ) модифицирована так, что она принимает параметры типа doubIe и возвращает значение типа doubIe: // Возврат значения типа double. #include <iostream> using namespace std; // используем данные типа double 
Использование функций в выражениях 197 double box(double length, double width, double height); int main () { double answer; answer = Ьох(10.1, 11.2,3.3); //присвоим возвращаемое / / значение cout « "Объем равен n « answer; return о; } // Этот вариант Ьох использует данные типа double. double box(double length, double width, double height) { return length * width * height ; } . Вот вывод этой проrpаммы: Объем равен 373.296 Еще одно замечание: Если функция типа Hevoid осуществляет возврат, встретив закрывающую фиrypную скобку, возвращается He определенное (т. е. неизвестное) значение. В силу своеобразной oco бенности формальноrо синтаксиса с++, в Hevoid функциях нет He обходи м ости обязательно выполнять предложение retum. Такое MO жет случиться, если конец функции достиrнyr прежде, чем в ней встретилось предложение return. Поскольку, однако, функция объяв лена как возвращающая значение, значение все же будет возвращено  несмотря на то, что оно является "мусором". Разумеется, хороший стиль проrpаммирования требует, чтобы создаваемые вами Hevoid функции возвращали значение посредством явноrо выполнения предложения return. Цель _ Использование функций в выражениях В предыдущем примере значение, возвращенное функций Ьох( ), было присвоено переменной, а затем значение этой переменной выводилось на экран посредством предложения cout. Хотя такое по строение проrраммы нель'3Я назвать неправильным, однако более эффективно было бы ИСПОЛЬ'30вать возвращаемое значение непо 
198 Модуль 5. Введение в функции средственно в предложении cout. Так, функцию main( ) из предьшу щий nporpaMMbI можно более эффективно переписать следующим образом: int main () { // используем возвращаемое из Ьох( ) значение непосредственно cout« "Объем равен "« Ьох(10.1, 11.2, з.з); Используем возвращаемое из Ьох( ) значение непосредственно в предложении сош. return о; } Коrда выполняется предложение cout, автоматически вызывается функция Ьох( ), что дает возможность получить ее возвращаемое зна чение. Это значение и выводится на экран. Нет никакоrо смысла CHa чала присваивать это значение какойто переменной. Hevoid функции допустимо использовать в любых выражениях. При вычислении выражения функция вызывается автоматически, и ее возвращаемое значение используется в выражении. Например, приве денная ниже профамма суммирует объемы трех коробок и выводит на экран среднее значение объема: // Использование Ьох() в выражении. #include <iostream> using namespace std; // используем данные типа double double box(double length, double width, double height); int main () { double sшn; эит = Ьох(10.1, 11.2, 3.3) + Ьох(5.5, 6.6,7.7) + Ьох(4.0, 5.0, 8.0); cout « .. Сумма объемов равна .. « эит « .. \n.. ; cout « ..Среднее значение объема равно .. « вит / 3. О « .. \n" ; return о; } // Этот вариант Ьох использует данные типа double. double box(double length, double width, double height) { return length * width * height ; } 
Локальная область видимости 199 Вывод этой проrpаммы: Сумма объемов равна 812.806 Среднее значение объема равно 270.935 Минутная тренировка 1. Приведите две формы предложения return. 2. Может ли vоid-функция возвращать значение? 3. Может ли функция быть частью выражения? 1. ВОТ две формы retum: ret".urn; return значение; 2. Нет, vоid-функция не может возвращать значений. 3. Вызов He..void ФУНКllИИ может быть использован в выражении. KOlna ЭТО происходит, функция выполняется, и ее возвращаемое значение используется в выражении. Правила ВИДИМОСТИ До сих пор мы использовали переменные без cтpororo обсуждения, rде они MOryr быть объявлены, как долrо они существуют, и из каких частей проrpаммы они доступны. Эrи атрибуты устанавливаются пpa вилами видимости, определенными в с++. В целом правила видимости языка управляют видимостью и BpeMe нем жизни объекта. с++ определяет целую систему областей видимо сти (областей действия); важнейшими из них являются две: локальная и ZIlобальная. Переменные можно объявлять и в той, и в дрyrой облас ти. В настоящем подразделе вы увидите, чем различаются перемен ные, объявленные в локальной области видимости, от переменных, объявленных в rлобальной области, и как эти области связаны с Функ циями. Цель 8Локальная область видимости Локальная область видимости создается посредством блока. (Вспомним, что блок начинается открывающей фиrурной скобкой и заканчивается закрывающей Фиrурной скобкой.) Таким образом, каждый раз, коrда вы начинаете новый блок, вы создаете новую область видимости. Переменную можно объявить в любом блоке. Переменная, объявленная внутри блока, называется локальной пe ременной. 
200 Модуль 5. Введение в функции Локальную переменную допустимо использовать только в npедло жениях, расположенных в том блоке, rде она бьта объявлена. То же самое можно выразить иначе: локальные переменные неизвестны вне их собственноrо блока кода. Таким образом, предложения, располо женные вне блока, не MOryт обращаться к объектам, определенным внутри Hero. Суть дела заключается в том, что коrда вы объявляете ло кальную переменную, вы локализуете эту переменную и защищаете ее от неавторизованноrо доступа или модификации. Заметим, что прави ла видимости создают предпосьтки для инкапсуляции. Одно из важнейших правил, касающихся локальных переменных, заключается в том, что они существуют только пока выполняется блок кода, в котором они бьти объявлены. Локальная переменная создает ся в тот момент, коrда выполнение доходит до предложения объявле ния этой переменной, и уничтожается, коrда блок завершает свое BЫ полнение. Поскольку локальная переменная уничтожается при выходе из блока, ее значение теряется. Наиболее распространенным проrpаммным блоком, в котором объявляются локальные переменные, является функция. Каждая функция определяет блок кода, который начинается с открывающей функцию фиrypной скобки и заканчивается на фиrypной скобке, за крывающей функцию. И код, и данные, входящие в функцию, при надлежат этой функции, и к ним не может обратиться никакое пред ложение в любой дрyroй функции, кроме как путем вызова этой функ ции. (Например, невозможно, используя предложение goto, перейти в середину дрyrой функции.) Тело функции скрыто от остальной npo фаммы, и на Hero нельзя воздействовать из дрyrих частей проrpаммы, так же, как и оно не может воздействовать ни на какую дрyryю часть проrpаммы. Таким образом, содержимое одной функции полностью отделено от содержимоrо дрyroй. Можно сказать и подрyrому: код и данные, определенные в одной функции, не MOryт взаимодействовать с кодом и данными, определенными в дрyrой функции, потому что эти две функции имеют различные области видимости. Изза тoro, что каждая функция определяет свою собственную об ласть видимости, переменные, объявленные внутри одной функции, никак не влияют на переменные, объявленные в дрyrой функции, даже если у этих переменных одинаковые имена. Рассмотрим, напри мер, такую профамму: #include <iostream> using namespace std; void fl(); int main ( ) { in t v а 1 = 10;  Эта val лоlCaЛЬН8 по отношению к main( ). 
Локальная область видимости cout « "va! в main(): 1I « va! « I \n I ; f1 ( ) ; cout « "va! в main(): " « va! « I \n I ; 201 return о; } void fl() { int va! = 88;  Эта val локальна по отношению к f1 ( ). cout « "va1 в fl (): " « va1 « "\n"; } ВОТ вывод профаммы: val в main(): 10 val в fl(): 88 val в main(): 10 Переменная, названная val, объявлена дважды, один раз в тюп( ) и дрyrой раз в п(). \'81 в maln() никак не влияет на уаl в fl(), и вообще не имеет к ней никакоrо отношения. Причина этоrо в том, что каждая переменная уаl известна только в той функции, rде она объявлена. как видно из вывода профаммы, несмотря на то, что при вызове fl() объ явленной в ней уа' присваивается значение 88, содержимое уа' в main( ) остается равным 10. Изза тoro, что локальная переменная создается и уничтожается при каждом входе в блок, rде она объявлена, и выходе из Hero, локаль ная переменная не сохраняет свое значение между последовательными активациями ее блока. Это особенно важно применительно к вызовам функций. Коrда вызывается функция, создаются ее локальные пере менные. После возврата из функции все они уничтожаются. Это зна чит, что локальные переменные не сохраняют свои значения от одноro вызова функции до дрyrоro. Если объявление локальной переменной включает инициализатор, то эта переменная инициализируется заново при каждом входе в блок. Например: /* Локальная переменная инициализируется каждый раз при входе в ее блок. */ #inc1ude <iostream> using namespace std; 
202 Модуль 5. Введение в функции void f ( ) ; int main () { for(int i=O; i < з; i++) f(); return о; } // пит инициализируется при каждом вызове f(). void f() { int пит = 99;  num присваивается значение 99 при каждом вызове '( ). cout « пит « .. \n" ; num++; // результат этоrо действия не сохраняется } Вывод проrpаммы подтверждает, что пот инициализируется при Ka ждом вызове С( ): 99 99 99 Локальная переменная, которая не инициализируется при ее объ явлении, будет иметь неопределенное значение до тех пор, пока ей не будет чтото присвоено. Локальные переменные MorYT быть объявлены в любом блоке Чаще Bcero все переменные, которые будут нужны в не которой функции, объявляются в начале ее Функциональноrо блока. Так no ступают rлавным образом для TOro, чтобы тот, кто будет читать этот код, Mor леrко определить, какие переменные будут в нем использо ваться. Однако начало Функциональноrо блока не является единствен ным местом, rде можно 06ъяRЛЯТЬ локальные переменные. Локальную переменную можно объявить rде yroдно, в любом блоке кода. Пере менная, объявленная внyrpи блока, локальна по отношению к этому блоку. Это означает, что переменная не существует, пока не начал BЫ полняться этот блок, и уничтожается вместе с завершением блока. 
Локальная область видимости 203 Никакие предложения вне этоro блока  включая остальные строки той же функции  не MOryr обратиться к этой переменной. Для Toro, чтобы усвоить это оrpаничение, рассмотрим следующую проrpамму: // Переменные MorYT быть локальными по отношению к блоку. #include <iostrearn> using narnespace std; int main () { int х = 19; // х известна всюду. if(x == 19) { in t у = 2 О ;  у локаяьна по отнoweнию К блоку if. cout « "х + у равно" « Х + у « "\n"; } // у = 100; // Ошибка! у здесь неизвестна. return о; } Переменная х объявлена в начале области видимости main( ) и дoc тупна всему последующему коду, входящему в main(). Внутри блока ас объявляется переменная у. Поскольку блок определяет область види мости, у видима только для остальноro кода внутри cвoero блока. По этому вне этоrо блока предложение у = 100; закомментировано. Если вы удалите ведущие символы комментария, при компиляции будет зафиксирована ошибка, поскольку у не видна вне ее блока. Внутри блока if можно использовать х, поroму что код внутри блока имеет доступ к переменным, объявленным во внешнем блоке, содержащем в себе данный. Хотя локальные переменные обычно объявляются в начале их бло ка, это не обязательно. Локальная переменная может быть объявлена в любом месте внутри блока, но, разумеется, перед тем, как она будет использоваться. Например, следующая проrpамма вполне правильна: #include <iostream> using namespace std; int main () { cout « "Введите число: "; 
204 Модуль 5. Введение в функции int а;. // объявим одну переменную cin » а; Эдесь а и Ь объявляются там, rде они нужны. cout « "Введите второе число: "; int Ь; / / объявим друrую переменную cin » Ь; cout « "Произведение: " « а*Ь « '\n'; return о; } в этом примере а и Ь не объявляются до тех пор, пока в них не воз никает необходимость. Честно rоворя, большинство проrpаммистов объявляют локальные переменные в начале использующей их Функ ции, но это вопрос вкуса. Сокрытие имен Если локальная переменная, объявленная во внутреннем блоке, имеет то же имя, что и переменная, объявленная во внешнем блоке, то переменная, объявленная во внутреннем блоке, скрывает переменную из внешнеrо блока. Например: #include <iostream> using namespace std; int main () { int i; int j; i = 10; j = 100; if (j > О) { int i; // эта i отличается от внешней i . i = j / 2; cout « .. внутренняя i: .. « i « ' \n' ; Эта i скрывает внешнюю i. } cout « "внешняя i: " « i « '\n'; return о; } 
rлобальнаяобластьвимости 205 Вывод этой профаммы: внутренняя i: 50 внешняя i: 10 Переменная i, объявленная внутри блока if, скрывает внешнюю ne ременную i. Изменение значения внутренней i не оказывает влияния на внешнюю i. Более Toro, вне блока if внутренняя i неизвестна, и CHO ва становится видимой внешняя i. Параметры функции Параметры функции находятся в области видимости функции. Таким образом, они локальны по отношению к функции. За ис ключением Toro, что они приобретают значения aprYMeHToB, na раметры ведут себя точно так же, как и друrие локальные nepe менные. Цель _ rлобальная область видимости Поскольку локальные переменные известны только внутри той функции, rде они объявлены, у вас может возникнуть вопрос: ка.. ким образом создать переменную, которая будет известна He скольким функциям? Ответом является объявление переменной в rлобальной области видимости. Fлобальной областью видимости является декларативный район, находящийся вне всех функций. Объявление переменной в rлобальной области видимости создает zлобальную nеремеНllУЮ. Спросим у эксперта Вопрос: Что обозначает ключевое слово auto? Я слышал, что оно исполь.. зуется для объявления локальных переменных. Так ли это? Ответ: Язык с++ содержит ключевое слово auto, которое можно ис.. пользовать для объявления локальных переменных. Однако, поскольку все локальные переменные по умолчанию предполаrаются принадлежащими классу auto, это слово фактически никоrда не используется. Поэтому и в примерах этой книrе вы ero ниrде не увидите. Если, однако, вы решите ero использовать, располаrайте ero непосредственно перед типом переменной, как это показано здесь: 
206 Модуль 5. Введение в функции auto char ch; Еще раз подчеркиваем, что слово auto не обязательно, и в этой книrе оно не используется. rлобальныIe переменныIe извес1ныI на прorяжении всей проrpаммы. их может использовать любой участок кода, и они сохраняют свои зна чения в течение вcero времени вьmолнения проrpаммы. Таким образом, их область видимости простирается на всю проrpамму. Вы можете соз дать rлобальныIe переменные, объявив их вне всех функций. Поскольку эти переменные rлобальны, к ним можно обращаться в любых выраже ниях, независимо от тoro, какие функции содержат эrи выражения. Приведенная ниже проrpамма демонстрирует использование rло бальной переменной. Переменная count объявлена вне всех функций. Ее объявление расположено перед функцией main( ). Однако, ero мож но поместить в любом месте, но только не внутри функции. Посколь ку, однако, вы должны объявлять переменную перед тем, как будете ее использовать, лучше Bcero располаrать объявление rлобальных пере менных в самом начале проrpаммы. // Использование rлобальной переменной. #include <iostream> using namespace std; void funcl(); void func2(); int count; // Это rлобальная переменная. int main () { int i; // Это локальная переменная for(i=O; i<10; i++) { count = i * 2;  funcl(); Это обращение к rлобальной count. } return о; } Это таюке обращение к rлобальной count. void funcl () { ! cout « "count: " « count; //Обращение к rлобальной / / переменной 
rлобальная область видимости 207 соцt« '\n'; // новая строка func2(); } void func2 ( ) { t int count; // это локальная переменная эта count локзnЬН8 в func2( ). } for(count=O; count<3; count++) cout « t I '. . , Это обращение к локальной count. Вывод проrpзммы выrлядит так: count: О . . . соиn t: 2 . . . coun t: 4 . . . count: 6 . . . соиn t: 8 . . . coun t: 1 О . . . count: 12 . . . соиn t: 14 . . . count : '16 . . . count: 18 Внимательно посмотрев на проrpамму, леrко заметить, что и main( ), и funcl( ) используют одну и ту же rлобальную переменную count. В func2( ), однако, объявлена локальная переменная count. Функция fппс2( ), используя count, обращается к своей локальной ne ременной, а не к rлобальной. Важно усвоить, что если rлобальная и локальная переменные имеют одно и то же имя, все обращения к име ни переменной внутри функции, в которой объявлена локальная nepe менная, будут относиться к локальной переменной, и никак не влиять на rлобальную. Таким образом, локальная переменная скрывает rло бальную с тем же именем. rлобальные переменные инициализируются при запуске nporpaм мы. Если объявление rлобальной переменной имеет инициализатор, тоrда переменная принимает значение этоrо инициализатора. Если rло6альная переменная не имеет инициализаroра, ее значение делает ся равным нулю. Место под rлобальные переменные вьшеляется в фиксированном районе памяти, выделенном вашей проrpаммой специально для этой цели. rлобальные переменные удобны в тех случаях, коrда одно и то же данное используется несколькими функциями вашей профаммы, или коrда переменная должна сохранять свое значение в течение вce ro времени выполнения проrpаммы. Однако следует избеrать чрез 
208 Модуль 5. Введение в функции MepHoro увлечения rлобальными переменными. К этому имеются три причины: · rлобальные переменные занимают память в течение Bcero BpeMe ни выполнения проrpаммы, а не только коrда они действительно нужны. · Использование rлобальной переменной в том месте, rде rодится локальная, снижает общность функции, так как она теперь зави сит от объекта, который должен быть определен вне нее. · Использование большоrо числа rлобальных переменных может привести к проrpаммным ошибкам изза неизвестных или нежела тельных побочных эффектов. Основная проблема при разработке больших проrpамм заключается в возможности случайной моди фикации значения переменной в силу ее использования в кaKOM 10 дpyrOM месте проrpаммы. Такое может случиться в С++, если в вашей проrpамме слишком MHoro rлобальных переменных. Минутная тренировка 1. В чем основные различия между локальными и rлобальными перемен ными? 2. Допустимо ли объявлять локальную переменную rде уrодно внутри блока? З. Сохраняет ли локальная переменная свое значение при повторных BЫ зовах функции, в которой она объявлена? 1. Локальная переменная известна только внутри блока, в котором она объявлена. Она создается при входе в этот блок и уничтожается, коrда блок завершает свое выполнение. rлобальная переменная объявляется вне всех функций. Она может использоваться Bce ми функциями и существует в течение Bcero времени выполнения проrpаммы. 2. Да, локальная переменная может быть объявлена rде yrодно внутри блока, однако до Toro места, rде она будет использоваться. 3. Нет, локальные переменные уничтожаются при возврате из функции, в которой они объявлены. Цель _ Передача в функции указателей и массивов в предыдущих примерах в качестве apryмeHToB использовались простые значения типов int или doubIe. Однако часто в качестве apry менlОВ приходится использовать указатели и массивы. Хотя передача в функцию таких apryмeHToB осуществляется так же, как и любых дpy rих, однако при их использовании возникают проблемы, которые сле дует рассмотреть особо. 
Передача в ФУНКЦИИ указателей И массивов 209 Передача указателя Для TOrO, чтобы передать указатель в качестве apryмeHтa, вы долж ны объявить параметр типа указателя. Вот пример: // Передача ФУНКЦИИ указателя. #include <iostream> using nam espace std; + void f(int *j); // f() объявляет параметруказатель int rnain () { '( ) принимает указатеяь int * в качестве параметра. int i; int *р; р = &i; / / р теперь указывает на i f (р); / / передача указателя  '( ) вызывается с указателем на целое. cout « i; // i теперь равно 100 return о; } / / f () получает указатель на int. void f(int *j) { * j = 1 О о; / / переменной , на которую ука зывает j, / / теперь присвоено значение 100 } Рассмотрим внимательно эту проrpамму. Мы видим, что функция ( ) принимает один параметр: указатель на int. Внутри таin( ) указа телю р присваивается значение адреса переменной i. Далее вызывает ся ( ) с р в качестве apryмeHтa. Коrда параметруказатель j получает значение р, он так же указывает на i внутри main( ). Таким образом, предложение *j = 100; присваивает значение 100 переменной i. В общем случае С( ) запишет значение 100 по любому адресу, который будет ей передан при вызове. В предыдущем примере фактически не было необходимости ис пользовать переменнуюуказатель р. Достаточно при вызове С( ) yкa зать в качестве apryмeнтa i, предварив это имя значком &. В результате 
210 Модуль 5. Введение в функции в функцию С( ) будет передан адрес i. Модифицированная проrpамма имеет такой вид: // Передача функции указателя. #include <iostream> using namespace std; void f (int *j); int main () { int i; f(&i);  Нет необходимости в р. В '( ) непосредственно передается адрес i. cout « i; return о; } void f(int *j) { *j = 100; / / переменной, на которую указывает j, // теперь присвоено значение 100 } Существенно, чтобы вы усвоили важное обстоятельство, связанное с передачей в функции указателей: коrда вы выполняете внутри Функ ции операцию, использующую указатель, вы фактически работаете с переменной, на которую этот указатель указывает. Это дает возмож ность функции изменять значение объекта, на который указывает арryментуказатель. Передача массива Коrда в качестве apryмeHтa функции выступает массив, в функцию передается не копия вeero массива, а адрес ero nepвoro элемента. (Вспомним, что имя массива без индекса является указателем на первый элемент массива.) Соответственно, объявление параметра в функции должно быть совместимым с этим адресом по типу. Имеются три способа объявить параметр, который должен будет принять указатель на массив. Первый способ заключается в том, что этот параметр объявляется как массив тoro же типа и размера, что и массив, передаваемый в функцию при ее вызове. Эroт способ использован в приведенном ниже примере: 
Передача в ФУНКЦИИ указателей И массивов 211 #include <iostream> using namespace std; void display (int nиrn [10] ) ; int rnain ( ) { int t[10], i; for(i=O; i < 10; ++i) t[i]=i; display (t); / / передача в функцию массива t return о; } // Выведем несколько чисел. void display (int пит [10] )  { Параметр объявлен как массив опредеяенной длины. int i; for(i=O; i < 10; i++) cout «num[i] « 1 1; } Несмотря на то, что параметр num объявлен как целочисленный массив из 10 элементов, компилятор с++ автоматически преобразует ero в указатель на int. Это преобразование необходимо, потому что ни какой параметр не может принять целый массив. Поскольку в Функ цию будет передан указатель на массив, в функции должен быть такой же параметруказатель для ero получения. Второй способ объявить параметруказатель состоит в задании параметра в виде массива неопределенной длины, как это показано ниже: void display (int пит [] )  { Параметр объявлен как массив нeonредепвнной длины. int i; for(i=O; i < 10; i++) cout« num[i] « 1 1; } Здесь параметр num объявлен как массив неопределенной длины. Поскольку с++ не выполняет проверку на выход за rpаницы массива, фактический размер массива не существенен для параметра (но, разу меется, не для проrpаммы). При таком способе объявления компиля тор так же автоматически пре06разует параметр в указатель на int. 
212 Модуль 5. Введение в функции Наконец, пот можно объявить как указатель. Этот метод чаще дpy rих используется в профессионально написанных профаммах. Вот при мер тaKoro объявления: void display(int *nиm)  { Параметр объявлен как указатель. int i; for(i=O; i < 10; i++) cout« num[i] « ' '; } Возможность объявления параметра как указателя проистекает из тoro, что любой указатель можно индексировать как массив, используя квадратные скобки [ ] . Заметьте себе, что все три метода объявления массива в качестве параметра дают один и тот же результат: указатель. Важно понимать, что коrда массив используется в качестве apry мента функции, в функцию передается ero адрес. это означает, что код внутри функции будет воздействовать и, возможно, изменять фак тическое содержимое этоrо массива. В качестве примера рассмотрим входящую в приведенную ниже профамму функцию соЬе( ), которая преобразует значение каждоro элемента массива в ero куб. При вызове функции соЬе( ) ей в качестве nepBOro apryмeHTa предается адрес Mac сива, а в качестве Bтoporo  ero размер: // Измерение содержимоrо массива с помощью функции. #include <iostream> using namespace std; void cube(1nt *п, int num); int main () { int i, nums[10); for(i=O; i < 10; i++) nums[i] = i+1; cout « "Исходное содерЖИМое: "; for(i=O; i < 10; i++) cout « nums[i] cout« '\п'; « ' '; cube(nums, 10); // вычисление кубов  Передадим адрес nums функции ооЬе{ ). cout « "Измененное содержимое: "; for(i=O; 1<10; i++) cout « nums[i] « ' '; return о; } 
Передача в ФУНКЦИИ указателей И массивов 213 // Возведение в куб элементов массива. void cube(int *n, int пит) { while (пит) { *n = *n * *n * *n;  nит  ; n++; Здесь изменяется значение элемента массива, на который указывает n. } } Ниже приведен вывод этой проrpаммы: Исходное содержимое: 1 2 3 4 5 6 7 8 9 10 Измененное содержимое: 1 8 27 64 125 216 343 512 729 1000 Мы видим, что после вызова функции соЬе( ) массив ПППIS в main( ) содержит кубы первоначальных значений ero элементов. Таким обра зом, значения элементов nums были модифицированы проrpаммными предложениями внутри функции соЬе( ). Это стало возможным пото МУ, что параметр функции n указывает на nums. Передача строк Строка представляет собой обычный массив символов с нулем в конце, и коrда вы передаете строку в функцию, фактически передает ся только указатель на начало строки. Этот указатель имеет тип cbar *. Рассмотрим в качестве примера следующую проrpамму. В ней опреде лена функция strInvertCase( ), которая преобразует строчные буквы строки в прописные и наоборот: // Передача в функцию строки. #include <iostream> #include <cstring> #include <cctype> using nатеерасе stdi void strlnvertCase(char *str) ; int main () { char str [80] ; strcpy (str, "This 1е А test") ; str1nvertCase(str); 
214 Модуль 5. Введение в функции cout « str; // вывод модифицированной строки return О i } / / Преобразование реrистра букв внутри строки. void strlnvertCase(char *str) { whi 1 е ( * s t r ) { / / преобразуем реrистр if(isupper(*str» *str = tolower(*str); else if(islower(*str» *str = toupper(*str)i str++; // сместимся к следующему символу } } Вот вывод этой проrpаммы: tHI8 i8 а tE8T Минутная тренировка 1. Покажите, как объявляется vоidфункция count, имеющая единствен ный параметруказатель на long int, названный ptr. 2. Если в функцию передается указатель, может ли функция изменить co держимое объекта, на который указывает этот указатель? З. Можно ли передать функции массив? Поясните ваш ответ. 1. void count (long int *ptr); 2. Да, объект, на который указывает арryментуказатель, может быть изменен кодом Bнyr ри функuии. 3. Массивы нельзя передавать в функцию. Однако указатель на массив передать можно. При этом изменения, вносимые в массив внутри функuии, отображаются в массиве, ис пользуемом как арryмент функции. Цель _ Возврат указателей Функции MOryт возвращать указатели. Указатели возвращаются, как и данные любых дрyrиx типов, и не вызывают никаких особых проблем. Если, однако, учесть, что указатели являются одним из наиболее слож Hых средств С++, краткое обсуждение этой темы вполне уместно. 
Возврат указателей 215 в ПРИВОДИМОЙ ниже проrpамме демонстрируется использование указателя в качестве возвращаемоro значения. Функция get.....substr( ) просматривает строку в поисках заданной подстроки. Функция воз вращает указатель на первую найденную подстроку. Если заданной подстроки найти не удалось, возвращается нулевой указатель. Напри мер, если анализируется строка "Я люблю С++", а в качестве искомой задана подстрока "люблю", то функция вернет указатель на первую букву л в слове "люблю". // Возврат указателя. #include <iostream> using namespace std; char *getsubstr (char *sub, char *str); int main () { char *substr; substr = gеtsuЬstr("три", "один два три четыре"); cout « "подстрока найдена: " « substr; return о; } // Возвращает указатель на подстроку или нулевой указатель, // если подстрока не найдена. char *getsubstr(char *sub, char *str)  { I get.ubstr( ) возвращает указатель на char. int t i char *р, *р2, *starti for(t=Oi str[t]; t++) { Р = &str[t]; // начальная установка указателей start = р; р2 = SUbi while(*p2 && *р2==*р) { // проверка на наличие подстроки р++; р2++; } /* Если р2 указывает на завершающий О (т. е. на конец подстроки) , значит, вся подстрока найдена. */ i f ( ! *р2) 
216 Модуль 5. Введение в функции return start; // вернуть указатель на начало подстроки } return о; // подстрока не найдена } Вот вывод этой проrpаммы: подстрока найдена: три четыре Функция main( ) Как вы уже знаете, функция main( ) выделяется среди всех осталь ных, поскольку это первая функция, которая вызывается, коrда вы за пускаете свою проrpамму. Она обозначает начало проrpаммы. В отли чие от некоторых дрyrих языков проrpаммирования, которые всеrда начинают выполнение с caмoro "верха" nporpaмMbI, с++ начинает любую проrpамму с вызова функции main( ), независимо от тoro, в Ka ком месте исходноrо текста проrpаммы она расположена. (Хотя, как правило, функцию main( ) помещают в самое начало проrраммы, что бы бьто леrче ее найти.) В проrpамме может быть только одна функция main( ). Если вы по пытаетесь включить вторую, проrpамма не будет знать, rде начать BЫ полнение. Большинство компиляторов обнаружат эту ошибку и сооб щат о ней. Как уже отмечалось, функция main( ) предопределена в с++ и не нуждается в прототипе. Цель 8argc и argv: apryMeHTbI функции main( ) Иноrда при запуске проrpаммы требуется передать в нее некоторую информацию. Обычно это делается посредством передачи в main( ) ap zyментов командной строки. Арryментами командной строки называ ются данные, которые вы вводите вслед за именем проrраммы на KO мандной строке операционной системы. (В системе Windows команда Пуск также использует командную строку.) Например, коrда вы KOM пилируете С++проrрамму, вы вводите на командной строке чтото вроде этоrо: cl uмяпРО2раммы 
argc и argv: apryMeHTbl ФУНКЦИИ main( ) 217 Здесь uмяпроzраммы обозначает проrpамму, которую вы хотите OT компилировать. Имя проrpаммы передается компилятору с++ как ap ryмeHT командной строки. В с++ определены два встроенных (но необязательных) параметра функции main( ). Они называются argc и argv и служат для приема ap ryмeНТOB командной строки. Функция main( ) допускает только два па раметра. Однако ваша конкретная операционная среда может поддер живать и друrие параметры, так что вам стоит свериться с дoкyмeHтa цией к используемому вами компилятору. Теперь рассмотрим argc и argv более детально.  Замечание Формально имена параметров командной строки MOryт быть какими уrодно .... вы можете назвать их как вам заблаrорассудится. Однако в Te чение ряда лет для их обозначения использовались именно эти имена .... argc и argv, и мы рекомендуем вам не отходить от этой практики, чтобы любой, читающий вашу nporpaMMY, Mor идентифицировать их как пара метры командой строки. Параметр argc является целым числом, обозначающим число apry ментов, введенных на командной строке. это число будет по меньшей мере равно 1, так как имя проrpаммы рассматривается как первый apryмeнт. Параметр argv является указателем на массив символьных указате лей. Каждый указатель в массиве argv указывает на строку, содержа щую apryмeHT командной строки. На имя проrpаммы указывает argv[O]; argv[t] будет указывать на первый apryмeHT, argv[2] на второй и т. д. Все apryмeHTЫ командной строки передаются в проrpамму как символьные строки, поэтому числовые apryмeHTЫ должны быть пре образованы вашей проrpаммой в соответствующий числовой формат. Важно правильно объявить параметр argv. Чаше Bcero используют такое обозначение: char *arg [] ; Для обращения к отдельным apryмeHтaм вы можете индексировать  В приведенной ниже проrpамме демонстрируется обращение к ap ryмeHтaM командной строки. Проrpамма выводит на экран все apryмeH ты командной строки, которые вы вводите с клавиатуры при ее запуске. / / ВЫВОД на экран aprYMeHTOB командной строки. #include <iostrearn> using narnespace std; 
218 Модуль 5. Введение в функции int main(int argc, char *argv[]) { for(int i = о; i < argc; i++) { cout « argv[i] « "\n"; } return о; } Предположим, что проrpамма имеет имя ComLine. Тоrда, если за пустить ее таким образом: C>ComLine один два три проrpамма выведет на экран следующее: ComLine один два три С++ не оroваривает точный формат apryмeHТOB командной строки, потому что операционные системы, управляющие работой компьюте ров, в этом отношении MOryт значительно различаться. Однако чаще Bcero при вводе apryмeHТOB командной строки их разделяют пробела ми или символами табуляции. При этом обычно запятые или символы точки с запятой не MOryr использоваться в качестве разделителей apry ментов. Например, комбинация apryмeHТOB ОДИН, два, и три образует четыIеe строки, в то время как в комбинации ОДИН, два, И три только две строки  запятая не является допустимым разделите лем. Если вам требуется передать проrpамме apryмeHT командной cтpo ки, который содержит пробелы, вы должны заключить ero в кавычки. Например, следующая фраза будет рассматриваться как один apryмeHT командной строки: "это все есть ОДИН aprYMeHT" Имейте в виду, что приводимые здесь примеры rодятся для широ Koro диапазона операционных сред, но все же не для всех. 
argc И argv: apryMeHTbl ФУНКЦИИ main( ) 219 Обычно вы используете argc и argv для ввода в проrpзмму началь ных режимов или значений (например, имен файлов). В С++ вы MO жете вводить столько apryмeHТOB командной строки, сколько допуска ет ваша операционная система. Использование apryмeHТOB командной строки придаст вашей проrpамме профессиональный вид и позволит запускать ее из командных файлов. Передача числовых apryMeHToB омандной строки Если вы передаете в проrpамму через apryмeHTЫ командной строки числовые данные, они будут принятыI проrpаммой в форме символь ных строк. Ваша проrpамма должна в этом случае преобразовать их во внyrpенний двоичный формат с помощью одной из функций CTaH дартной библиотеки, поддерживаемой С++. Три наиболее часто ис пользуемые для этоro функции приведены ниже: atof( ) atol( ) atoi( ) Преобразует строку в значение doubIe и возвращает результат. Преобразует строку в значение lo"g i"t и возвращает результат. Преобразует строку в значение int и возвращает результат. Все эти функции принимают в качестве apryмeHTa строку, co держащую числовое значение; все они требуют наличия заrоловка <cstdlib> . В приведенной ниже проrpамме демонстрируется преобразова ние числовоrо apryмeHтa командной строки в ero двоичный эквива лент. Проrрамма вычисляет сумму двух чисел, вводимых на KOMaнд ной строке вслед за именем проrpаммы. Для преобразования число Boro apryмeHTa в ero внутреннее представление используется функция atof( ). /* ЭТа проrрамма ВЫВОДИТ сумму ДВУХ числовых aprYMeHTOB командной строки. */ #include <iostream> #include <cstdlib> using namespace std; int rnain{int argc, char *argv[]) { double а, Ь; 
220 Модуль 5. Введение в функции if(аrgс!=З) { cout « "Использование: add число число\n"; return 1; } Используйте atof( ) для преобразования число- Boro apryмeнTa командной строки в тип doubI.. + аtоf(аrgv[l]);//преобразуем первый apryMeHT командной / / строки Ь = аtоf(аrgv[2]);//преобразуем второй apryMeHT командной / / строки а = cout « а + Ь; return о; } Для сложения двух чисел используйте следующую форму KOMaнд ной строки (в предположении, что проrpамма имеет имя add): C:>add 100.2 231 Минутная тренировка 1. Как обычно называют два параметра функции main()? Объясните, что содержит каждый из них. 2. Чему всеrда соответствует первый apryмeHT командной строки? З. Числовой apryмeHT командной строки поступает в проrpамму в виде строки. Правильно ли это? 1. Два пара метра функции main( ) обычно называются argc и argv. Параметр argc задает число наличных apryмeHТOB командой строЮl, а argv является указателем на массив строк, содеРЖaJllИХ сами apryмeнты. 2. Первым арryментом командной строки всеrда является имя данной проrpаммы. 3. ПравWIЬНО. Цель _ Прототипы функций в начале эroro модуля бьт кратко за'lpOнyr вопрос о пporoпmax функ ций. Теперь наступил момент, коrда мы должны рассмотреть Э1У тему более подробно. В С++ все функции перед тем, как их использовать, должны быть объявлены. Объявление функции выполняется с помо щью ее прототипа. В прототипе задаются три характеристики функции: · Тип возвращаемоrо значения. · Типы параметров функции. · Число параметров функции. 
Прототипы ФУНКЦИЙ 221 Прототипы позволяют компилятору выполнить три важных опе рации: · Они rоворят компилятору, с помощью KaKoro рода кода должна вызываться функция. Различные типы возвращаемых значений должныI поразному обрабатываться компилятором. · Они позволяют компилятору выявить недопустимые преобразо вания типов apryмeHToB, 'hспользуемых при вызове функции, в объявленные типы параметров функции. При обнаружении Heco ответствий типов компилятор вьщает сообщение об ошибке. · Они позволяют компилятору обнаружить несоответствие числа apryмeHТOB, используемых при вызове функции, и числа ее пара метров. Общая форма прототипа функции показан ниже. Она совпадает с определением функции за исключением отсутствия тела функции: тип UМЯФУIl"ции(тuп имяпараметра 1, тип uмяпараметра2, тип имяпараметраN); . . . , Использование имен параметров необязательно. Однако их ис пользование позволяет компилятору при обнаружении расхождений в типах apryмeHToB и параметров включить в сообщение об ошибке имя спорноrо параметра, что облеrчает отладку. Поэтому разумно вклю чать в прототипы имена параметров. Для TOro, чтобы продемонстрировать полезность прототипов Функ ций, рассмотрим следующую проrpамму. Если вы попытаетесь ее OT транслировать, будет выдано сообщение об ошибке, потому что про фамма пытается вызвать функцию sqr..Jt() с целочисленным apryмeH том вместо требуемоro указателя на целое. (Компилятор не выполняет автоматические преобразование целоrо числа в указатель.) /* Эта проrрамма использует прототип функции для активизации строrой проверки типов. */ void sqrit(int *i); // прототип int main ( ) { int Х; Прототип предотвращает несоответст- вив типов зpryментов и napaмeTpoB. sqr Jt( ) ожидает получить указатепь, а вызывается с apryмeнтOM типа int. Несоответствие типов! х = 10; sqrit(x); // Ошибка! return о; } 
222 Модуль 5. Введение в функции void sqrit(int *i) { *i = *i * *i; } Определение функции может одновременно служить ее прототи пом, если оно расположено в проrpамме до первоrо вызова этой Функ ции. Например, это вполне правильная проrрамма: // Использование определения функции в качестве ее прототипа. #include <iostream> using namespace std; Поскольку функция isEven( ) определена перед ее использо- ванием, ее определение служит одновременно и ее прототиrюм. I // Определим, является ли число четным. bool isEven(int nит) { if ( ! (nит %2)) return true; / / nит четно  return false; } int main () { if (isEven(4)) cout « "4 четно\п"; I if(isEven(3)) cout« "это не будет выведено"; return о; } в этой проrpамме функция isEven( ) определена перед ее использо ванием в main( ). В этом случае определение функции может служить и ее прототипом, и в отдельном прототипе нет необходимости. В действительности обычно оказывается проще и лучше опреде лить прототипы всех функций, используемых проrpаммой, чем сле дить за тем, чтобы определения всех функций располаrались в про фамме до их вызовов. Это особенно справедливо для больших про фамм, в которых не всеrда леrко проследить, из каких функций какие дрyrие функции вызываются. Более Toro, вполне возможно иметь две функции, которые вызывают дрyr дрyra. В этом случае прототипы функций необходимы. 3аrоловки содержат прототипы в самом начале этой книrи вы столкнулись со стандартными заrо ловками С++. Вы узнали, что эти зarоловки содержат информацию, необходимую для раБотыI проrрамм. Хотя это неполное объяснение и 
Рекурсия 223 справедливо, в нем сказано далеко не все. Заrоловки с++ содержат прототипы функций стандартной библиотеки. (В них также можно найти различные значения и определения, используемые этими Функ циями.) Как И функции, которые вы пишете сами, функции cтaндapT ной библиотеки должны иметь прототипы, и эти прототипы следует включить в проrpамму до вызова самих функций. По этой причине любая проrpамма,. использующая библиотечную функцию, должна также подключать к проrpамме заrоловок, содержащий прототип этой функции. Чтобы выяснить, какой заrоловок требует та или иная библиотеч ная функция, изучите документацию библиотеки вашеrо компиляro ра. Вместе с описанием каждой функции вы найдете название заrолов" ка, который следует включить в проrpамму для использования этой функции. Минутная тренировка 1. Что такое прототип функции? В чем ero назначение? 2. Должны ли все функции, кроме main( ), иметь свои прототипы? 3. Почему вы должны подключать зarоловок при использовании в про фамме функции из стандартной библиотеки? 1. Прототип объявляет имя функции, тип ВОlвращаемоrо значения и типы параметров. Прототип указывает компилятору, какой код он должен создать для вызова функции, а также обеспечивает правильность вызова. 2. Да. все фУНКIlИИ, 13 исключением maiD(), должны иметь свои прототипы. 3. Помимо дрyrой информаIIИИ, заroловок включает прототипы функций стандартной библиотеки. Цель _ Рекурсия Последняя тема, которую мы рассмотрим в этом модуле, посвяще на рекурсии. Рекурсия, иноrда называемая циклическим определением, представляет собой процесс определения чеrото с помощью caмoro себя. В приложении к проrpаммированию рекурсия..... это процесс BЫ зова функцией самой себя. Функцию, которая вызывает саму себя, Ha зывают рекурсивной. Классическим примером рекурсии может служить функция factr( ), которая вычисляет факториал целоrо числа. Факториалом числа N на.. зывают произведение всех целых чисел между 1 и N. Например, фак ториал числа 3 (или, как rоворят, "три факториал") вычисляется как lх2х3, что равно 6. Ниже приведены как функция factr(), так и ее ите ративный эквивалент. 
224 Модуль 5. Введение в функции / / Демонстрация рекурсии. #include <iostrearn> using namespace std; int factr (int n) ; int fact (int n); int main () { // используем рекурсивный вариант cout « "4 факториал равен" « factr(4); cout « I \n' ; // используем итеративный вариант cout« "4 факториал равен" « fact(4); cout « I \n' ; return о; } // Рекурсивный вариант. int factr (int n) { int answer; if(n==l) return(l); answer = factr(nl)*n;  return(answer); Выполнить рекурсивный вызов factr( ). } // Итеративный вариант. int fact (int n) { int t, answer; answer = 1; for(t=l; t<=n; t++) answer = answer*(t); return(answer); } Действие нерекурсивноrо варианта fact( ) вполне очевидно. Он использует цикл, начинающийся с 1, в котором каждое следующее число умножается на последовательно увеличивающееся произве дение. 
Рекурсия 225 Действие рекурсивной функции factr( ) несколько сложнее. При вызове factr( ) с apryмeHToM 1 функция возвращает 1; в противном случае она возвращает произведение factr(o-I)*o. для вычисления этоrо выражения factr( ) вызывается с apryмeHToM (0-1). Это проис ходит до тех пор, пока о не станет равным 1, и вызов функции при ведет к возврату из нее. Например, коrда вычисляется факториал 2, первый вызов factr( ) породит второй вызов с apryмeHToM 1. Этот вызов вернет 1, которая умножится на 2 (первоначальное значение о). Переменная aoswer будет тоrда равна 2. Вы можете ради любо пытства включить в factr( ) предложения cout, которые покажут, на каком уровне находится вызов и каков промежyrочный результат (переменная answer). Коrда функция вызывает саму себя, для новых локальныIx перемен ных и параметров вьщеляется память (обычно на системном стеке), и код функции выполняется с caмoro начала с использованием этих HO вых переменных. Рекурсивный вызов не создает новой копии Функ ции; только ее apryмeHTЫ будyr новыми. При возврате из каждоrо pe курсивноrо вызова старые локальные переменные и параметры удаля ются из стека, и выполнение продолжается с точки вызова функции изнутри функции. Можно сказать, что рекурсивные функции действу ют подобно раздвижной подзорной трубе, сначала раскладываясь, а затем складываясь. Имейте в виду, что большинство рекурсивных функций не yмeHЬ шают размер кода в заметной степени. Кроме тoro, рекурсивные Ba рианты большинства алrоритмов выполняются несколько более Meд ленно, чем их итеративные эквиваленты, из--за добавления издержек на дополнительные функциональные вызовы. Слишком MHOro peкyp сивных обращений к функции может вызвать переполнение стека. Изза Toro, что параметры и локальные переменные функции разме щаются на стеке, и каждый новый вызов создает новую копию этих переменных, стек может истощиться. Если это произойдет, то MOryт быть разрушены дрyrие данные. Реально, однако, вряд ли вам при дется столкнуться с такой ситуацией, если только рекурсивная Функ ция не зациклится. Основное преимущество рекурсивных функций заключается в том, что их можно использовать для создания вариантов HeKOTO рых алrоритмов, которые оказываются проще и наrляднее, чем их итеративные аналоrи. Например, алrоритм быстроrо упорядоче ния довольно затруднительно реализовать в виде итеративных BЫ зовов. Некоторые задачи, особенно в области искусственноrо ин теллекта, кажутся прямо приспособленными для рекурсивноrо решения. Составляя рекурсивную функцию, вы должны rдето в ней включить условное предложение, например, if, чтобы заставить функцию осуществить возврат без выполнения рекурсивноrо вызо ва. Если вы пренебрежете таким условным предложением, то BЫ 
226 Модуль 5. Введение в функции звав однажды эту функцию, вы никоrда из нее не вернетесь. Это довольно распространенная ошибка. Разрабатывая проrраммы с рекурсивными функциями, не стесняйтесь включать в них предло жения cout, которые позволят вам наблюдать, что происходит в проrрамме, и аварийно завершить выполнение, если вы обнаружи ваете ошибку. Ниже приведен дрyrой пример рекурсивной функции, названной reverse( ). Она выводит на экран строку задом наперед. // ВЫВОД строки задом наперед посредством рекурсии. #include <iostream> using namespace std; void reverse(char *s) ; int main ( ) { char str[] = "это просто проверка"; reverse(str); return о; } // ВЫВОД строки задом наперед. void reverse(char *s) { if(*s) reverse(s+l) ; else return; cout « *s; } Функция reverse( ) прежде Bcero проверяет, не передан ли ей указа тель на завершаюший ноль строки. Если нет, то reverse( ) вызывает себя с передачей в качестве apryмeHтa указателя на следующий символ строки. Коrда наконец будет найден завершающий ноль, вызовы Ha чинают "раскручиваться", и символы появляются на экране в обрат ном порядке. Еще одно замечание. Понятие рекурсии часто оказывается слож ным для начинаюших. Не отчаивайтесь, если этот материал пока зался вам слишком запутанным. С течением времени вы освоите и ero. 
Рекурсия 227 Проект 5..1 Быстрое упорядочение в Модуле 4 вы познакомились с простым методом упорядочения, носящим название пузырьковоro. Там же мы упомянули, что имеют 'Ся существенно лучшие алrоритмы упорядочения. В этом проекте вы реализуете один из лучших вариантов: Quicksort (быстрое упорядоче ние). Метод Quicksort, изобретенный Хором (C.A.R. Hoare), который и дал ему это имя, является лучшим алrоритмом упорядочения обше ro назначения из имеющихся в настоящее время. Причина, по KOTO рой он не Mor быть продемонстрирован в Модуле 4, заключается в том, что реализация Quicksort опирается на рекурсию. Вариант, KOTO рый мы рассмотрим, будет упорядочивать символьный массив, хотя лоrику алrоритма MOO приспособить для упорядочения объектов любоrо типа. Quicksort построен на идее разделов. Общая процедура заключается в том, что выбирается значение, называемое компарандом, а затем Mac сив разбивается на две секции. Все элементы, большие или равные компаранда, перемещаются на одну сторону, а все меньшие  на дpy ryю. Этот процесс затем повторяется для каждой оставшейся секции, пока весь массив не окажется упорядоченным. Пусть, например, дан массив fedacb, и значение d используется в качестве компаранда. Пер вый проход Quicksort изменит последовательность символов в массиве следующим образом: Исходное состояние Проход 1 fedacb bcadef Этот процесс повторяется затем для каждой секции  т. е. Ьса и def. Леrко видеть, что процесс по самой своей природе существенно peкyp сивен, и, очевидно, лучше Bcero реализовать Quicksort в виде peKyp сивной функции. Найти значение, которое будет выступать в качестве компаранда, MOO двумя способами. Вы можете выбрать компаранд случайным образом, или можно получить ero, усреднив небольшой набор зна чений, взятых из массива. Для получения оптимальноrо упорядоче ния вы должны выбрать такое значение, которое будет точно в cepe дине диапазона данных, составляющих упорядочиваемый массив. Однако, для большинства коллекций данных это сделать непросто. В худшем случае выбранное значение окажется с одноrо края Mac сива. Даже в этом случае Quicksort работает правильно. Вариант Quicksort, который мы реализуем, выбирает в качестве компаранда средний элемент массива. Еще одно замечание: библиотека С++ включает функцию с именем qsort( ), которая также реализует алrоритм Quicksort. Вам будет инте ресно сравнить ее с вариантом, приведенным здесь. 
228 Модуль 5. Введение в функции Шаr за marOM 1. Создайте файл с именем QSDemo.cpp. 2. Алrоритм Quicksort будет реализован с помощью двух функций. Первая, с именем quicksort( ), предоставляет удобный интерфейс для пользователя и орrанизует вызов qs( ), которая осуществляет фактиче ское упорядочение. Прежде Bcero, создайте функцию quicksort( ), как это показано ниже: // Орrанизуем вызов функции упорядочения. void quicksort(char *items, int len) { qs(items, О, lenl); } Здесь «еms указывает на упорядочиваемый массив, а len задает число элеменroв в эroм массиве. как показано на следующем шare, qs( ) требует определения начальноro раздела, что и осуществляет quicksort( ). Преиму щеС1ВО в использовании quicksort( ) заключается в том, что ее можно BЫ звать С указанием только двух очевидных apryмeHТOB: указателя на упоря дочиваемый массив и числа элеменroв в массиве. Она затем предоставляет начальный и конечный индексы для упорядочиваемоrо раздела. З. Добавьте собственно упорядочивающую функцию с именем qs( ), как это показано ниже: // Рекурсивный вариант алrоритма Quicksort для упорядочения // символов. void qs(char *iterns, int left, int right) { int i, j; char х, у; i = lefti j = right; х = items[( left+right) /2]; do { while«items[i] < х) && (i < right» i++; wh i 1 е ( (х < i t ems [j ]) && (j > 1 е f t» j   ; if(i<=j) { у = items[i]; iterns[i] = iterns[j]; items [j] == у; i++; j; } } while (i <= j) ; 
Рекурсия 229 if(left < j) qs(iterns, left, j); if(i < right) qs(items, i, right); } эту функцию следует вызывать с указанием индексов упорядочи BaeMoro раздела. Левый параметр должен соответствовать началу (ле вой rpанице) раздела. Правый параметр должен соответствовать концу (правой rpанице) раздела. При первом вызове раздел представляет co бой весь массив. Каждый рекурсивный вызов последовательно упоря дочивает все меньшие и меньшие разделы. 4. Чтобы активизировать алrоритм Quicksort, просто вызовите quicksort( ) с именем упорядочиваемоrо массива и ero длины. После возврата из функции массив будет упорядочен. Не забудьте, что этот вариант rодится только для символьных массивов, но вы може те адаптировать ero лоrику для упорядочения нужных вам типов массивов. Вот проrpамма, демонстрирующая алrоритм Quicksort: /* Проект 51 Вариант алrоритма Quicksort для упорядочения символов. */ #include <iostream> #include <cstring> using nаmеерасе std; void quicksort(char *items, int len); void qs(char *items, int left, int right); i n t та i n () { char str[] = "jfmckldoelazlkper"; int i; cout « "Исходный массив: h « str « d\n"; r quicksort(str, strlen(str»; cout « "Упорядоченный массив: " « str « "\n"; return о; } 
230 Модуль 5. Введение в функции // Вызовем фУНКЦИЮ, ВЫПОЛНЯЮЩУЮ фактическое упорядочение. void quicksort(char *items, int len) { qs(items, О, lenl); } // Рекурсивный вариант алrоритма Quicksort // для упорядочения символов. void qs(char *iterns, int left, int right) { int i, j; char х, у; i = left; j = right; х = items[( left+right) /2]; do { while((items[i] < х) && (i < right» i++; while( (х < items[j]) && (j > left» j; if(i<=j) { у = items[i]; items[i] = items[j]; i tems [j] = у; i++; j; } } while(i <= j); if(left < j) qs(items, left, j); if(i < right) qs(iterns, i, right); } Ниже приведен вывод проrpаммы: Исходный массив: jfmckldoelazlkper Упорядоченный массив: acdeefjkklllmoprz Спросим у эксперта Вопрос: Я слышал чтото о правиле "по умолчанию int". Что это такое и применимо ли это правило к с++? OrвeT: В исходном языке с, а таюке в ранних версиях С++, если в объяв лении отсутствовал спецификатор типа, по умолчанию предполаrался тип int. Например, с использованием cTaporo стиля приведенная ниже функция будет написана правильно; она вернет результат типа int: 
Рекурсия 231 f() { // по умолчанию тип возврата устанавливается int { int Х; // return Х; } в этом фрarменте МЯ значения, возвращаемоrо функцией ( ), по умолча нию устанавливается тип int, поскольку никакой дрyrой тип возврата не yкa зан. Однако, правило "по умолчанию int" (которое также называют прави rJOM "неявноrо int") не поддерживается современными версиями С++. ХОТЯ 50ЛЬШИНСТВО компиляторов продолжает подцерживать правило "по умолча нию int" ради обратной совместимости, вам следует явным образом специ фицировать тип возврата каждой создаваемой вами функции. Поскольку старые проrpаммы нередко использовали тип возврата по умолчанию, все сказанное следует иметь в виду, если вам придется модифицировать унасле дованные nporpaMMbl. . ....P.I. . ... .P..P. . . 1. Приведите общую форму определения функции. 2. Создайте функцию bypot( ), которая вычисляет rипотенузу прямо yrольноrо треyrольника по двум ero катетам. Продемонстрируйте использование этой функции в проrpамме. Вам понадобится биб лиотечная функция sqrt( ), которая возвращает квадратный корень из ero apryMeHтa. Прототип этой функции таков: double sqrt(double значение); Эта ФУНКЦИЯ использует заrоловок <cmatb>. З. Может ли функция вернуть указатель? Может ли ФУНКЦИЯ вернуть массив? 4. Создайте собственный вариант функции strlen() стандартной биб лиотеки. 5. Сохраняет ли локальная переменная свое значение при последова тельных вызовах функции, в которой она объявлена? 6. Приведите одно преимущество rлобальных переменных. Приведи те один их недостаток. 7. Создайте функцию byТbrees( ), которая возвращает последователь ностъ чисел, каждое из которых на 3 больше предыдущеrо. Начни те последовательность с О. Тоrда первые пять чисел, возвращенных функцией byТbrees( ), будут О, 3, 6, 9 и 12. Создайте дрyryю функ цию, reset( ), которая заставит byТhrees( ) начать создавать новую 
232 Модуль 5. Введение в функции последовательность снова с О. Продемонстрируйте использование этих функции в проrpамме. Подсказка: Вам понадобится rлобальная переменная. 8. Напишите проrpамму, которая требует ввод пароля, указываемоrо на командной строке. Ваша проrрамма может не выполнять ника кой полезной работы, а только выводить сообщение, правильный ли пароль введен. 9. Прототип предотвращает вызов функции с неправильным числом apryмeHТOB. Справедливо ли это утверждение? 10. Напишите рекурсивную функцию, которая выводит числа от 1 до 10. Продемонстрируйте ее использование в проrpамме. 
Модуль 6 Подробнее о функциях Це!'Иt достиrаемые в ЭТОМ модупе 6.1 Осознать два подхода к передаче apryMeHToB 6.2 Понять, как С++ передает aprYMeHTbI в функции 6.3 Узнать, как с помощью указателя создается вызов по ссылке 6.4 Научиться передавать ссылки в функции 6.5 Рассмотреть возврат ссылок 6.6 Освоить независимые ссылки 6.7 Познакомиться с понятием переrрузки функций 6.8 Начать использовать apryMeHTbI функции по умолчанию 6.9 Узнать, как избежать неоднозначности при переrрузке функций 
234 Модуль 6. Подробнее о функциях В этом модуле мы продолжим изучение функций. Здесь будут paCCMOT рены три наиболее важные темы, связанные с функциями: ссьтки, пе реrpузка функций и apryмeHTЫ по умолчанию. Эти средства сущест венно расширяют возможности функций. Ссьтка представляет собой неявный указатель. Переrpузка функций позволяет реализовывать одну и ту же функцию разными способами, каждый из которых пред назначен для решения своей задачи. Переrpузка функций является oд ним из проявлений полиморфизма в С++. При использовании apry ментов по умолчанию возникает возможность задать для параметра значение, которое будет автоматически присвоено параметру, если co ответствующий apryмeHT не указан. Мы начнем с рассмотрения двух способов передачи в функции ap ryмeHToB и с тoro, какие возможности возникают при их применении. Понимание механизма передачи apryмeHToB необходимо для освоения понятия ссьтки. Цель _Два подхода к передаче aprYMeHToB в принципе в компьютерном языке существуют два способа пере дачи подпроrpамме apryмeнтa. Первый называется передачей по значе нию (или вызовом по значению). Он заключается в копировании значе нил apryмeHTa в параметр подпроrpаммы. Очевидно, что в этом случае, если подпроrpамма изменит свои параметры, это никак не повлияет на apryмeHTЫ, с которыми она вызывалась. Передача по ссылке (или вызов по ссылке) является вторым спо СО бом передачи aprYMeHToB подпроrрамме. В этом случае в пара метр копируется адрес aprYMeHTa (не ero значение). Внутри под проrраммы этот адрес используется для обращения к фактическо МУ aprYMeHTY, указанному в вызове. Это значит, что изменения параметра повлияют на apryмeHT, использованный при вызове подпроrраммы. Цель _ Как С++ передает apryMeHTbI По умолчанию С++ использует при вызове функции передачу apry ментов по значению. Это значит, что код внутри функции не может изменить apryмeнты, указанные при ее вызове. В этой книrе все про 
Как С++ передает apryMeHTbl 235 фаммы до сих пор использовали метод передачи по значению. Pac смотрим в качестве примера функцию reciprocal( ) в приведенной ниже проrpамме: /* Изменение переданноrо по значению параметра не изменяет apryмeHT. */ #include <iostream> using namespace"std; доиЫе reciprocal(double х); int main () { double t = 10.0; cout « "Обратное значение от 10. О составляет " « reciprocal(t) « "\n"; cout « "Значение t все еще равно: " « t « It \n 11 ; return о; } // Возврат обратноrо значения. double reciprocal(double х) { х = 1 / х; // создадим обратное значение  Это не изменяет значе- ния t внyrpи main( ). return Х; } Вывод этой проrpаммы выrлядит следующим образом: Обратное значение от 10. О составляет 0.1 Значение t все еще равно: 10 Как видно из вывода проrpаммы, коrда внyrpи reciprocal( ) выпол няется присваивание х = 1/х; единственное, что изменяется  это локальная переменная х. Пе ременная t, использованная как apryмeHT, будет все еше иметь значение 10; операции внутри функции никак на нее не повлия ют. 
236 Модуль 6. Подробнее о функциях Цель _ Использование указателя для создания вызова по ссылке Несмотря на то, что в С++ по умолчанию используется вызов по значению, можно вручную создать вызов по ссылке, передав в функцию адрес apryмeHTa (т. е. указатель). В этом случае возникает возможность изменения значения apryмeHTa вне функции. Вы виде ли пример такой операции в предыдущем модуле, rде обсуждался вопрос о передаче в функцию указателей. Как вы знаете, указатели передаются в функцию в принципе точно так же, как и любые дpy rие значения. Разумеется, необходимо объявить параметры функ ции как указатели. Чтобы убедиться в том, что передача указателя позволяет вруч ную создать вызов по ссылке, рассмотрим функцию, названную swap( ), которая обменивает значения двух переменных, на KOTO рые указывают ее apryмeHTbI. Один из способов реализации функ ции приведен ниже: / / Обмен значений переменных, на которые указывают х и у. void swap(int *х, int *у) { int tempi temp = *х; // сохраним значение с адресом х *х = *у i / / отправим значение из у в х *у = temp; // отправим значение из х в у Обмен значений пе- ременных, на кото- рые указывают х и у. } Функция swap( ) объявляет два параметрауказателя х и у. Она ис пользует эти параметры для обмена значений переменных, на которые указывают apryмeHTЫ, передаваемые функции. Вспомним, что .х и *у обозначают переменные, находящиеся по адресам х и у. Таким обра зом, предложение *х = *у; помещает объект, на который указывает У, в объект, на который указывает х. В результате, коrда функция завершается, значения переменных, использованных при вызове функции, поменяются местами. Поскольку sawp( ) ожидает получения двух указателей, вы должны вызывать swap( ) с указанием адресов переменных, у которых надо 
Использование указателя для создания вызова по ссылке 237 обменять значения. Как это делается, показано в приведенной ниже проrpамме: / / Демонстрация варианта swap () с указателями. #include <iostream> using narnespace stdi / / Объявим, что swap () использует указатели. void swap (int *х, int *у) i int main ( ) { int i, ji i = 10 i j = 20; swap(&j, &i); // вызов swap() с адресами i ВЫЗОВ _ар( ) с адресами переменных, У которых мы хотим обменять значения. Иj cout « "Исходные значения i и j: "; cout « i « ' , « j « I \n' ; cout « "Новые значения i и j: "; cout « i « I I « j « ' \n I ; return О i } / / Обменяем значения, на которые указывают х и у. void swap(int *х, int *у) { int tempi Обменяем значения переменнbIX, на которые указыва- ют х и у, посредством явных операций с указателями. *у = tempi / / сохраним' значение с адресоJvl х / / поместим значение с адрес=ом у / / в переменную с адресом х / / поместим значение с адресом х / / в переменную с адресом у temp = *Х; * Х = *у; } в Функиии main( ) переменной i присваивается значение 10, а переменной j  20. Затем вызывается swap( ) с адресами i и j в Ka честве apryмeHToB. Унарный оператор & позволяет получить адреса переменных. В результате в swap( ) передаются не значения пере менных, а их адреса. После возврата из swap( ) значения i и j поме 
238 Модуль 6. Подробнее о функциях няются местами, как это видно из приведенноrо ниже вывода про rpaMMbI: Начальные значения i и j: 10 20 Новые значения i и j: 20 10 Минутная тренировка 1. Объясните понятие вызова по значению. 2. Объясните понятие вызова по ссьтке. З. Какой механизм передачи параметров используется в С++ по умолча нию? 1. При вызове по значению в параметры подпроrpаммы копируются значения apryмeнтoB. 2. При вызове по ссьтке в параметры подпроrраммы копируются адреса apryмeHTOB. 3. По умолчанию С++ использует ВЫ10В по значению. Цель _ Параметры-ссылки Как мы видели, с помощью операций над указателями можно вруч ную орrанизовать передачу по ссылке, однако такой подход несколько неуклюж. Вопервых, вам приходится выполнять все операции по средством указателей. BOBТOPЫX, вы должны помнить, что при вызове функции ей следует передавать не значения переменных, а их адреса. К счастью, в С++ имеется возможность заставить компилятор aBTOMa тически использовать для одноrо или нескольких параметров KOHкpeT ной функции передачу по ссьтке вместо передачи по значению. Это достиrается с помошью параметрассыл"u. Если вы используете пара Meтpccьткy, функции автоматически передается адрес (не значение) apryмeHтa. Внутри функции при операциях над параметромссылкой автоматически выполняется снятие ссылки, в результате чеrо исчезает необходимость в использовании указателей. Параметрссылка объявляется помещением перед именем парамет ра в объявлении функции знака &. Операции с использованием пара метрассылки фактически выполнятся над ар ryментом, указанным при вызове функции, а не над самим параметромссылкой. Чтобы освоить использование параметровссылок, начнем с про cтoro примера. В приведенной ниже проrpамме функция f( ) требует один параметрссьтку типа int: // Использование параметрасеылки. #include <iostrearn> 
Параметры-ссылки 239 using namespace std; Объявим napaметр-ссылку. oid f(int &i); // здесь i является параметромссылкой  int main () { int val = 1; cout « "Старое значение val: " « val « I \n I ; f(val); // передадим f() адрес val cout « "Новое значение val: " « val « I \n' ; return о; } voidf(int&i) { i = 10; // это модифицирует передаваемый aprYMeHT } Эта nporpaмMa выведет на экран следующее: Старое значение val: 1 Новое значение val: 10 Обратите особое внимание на определение f( ): . . . Присвоим значение "еремен- v01d f (lnt &1) ной, на которую ссылается i. { i = 1 о; / / это модифицирует передаваемый арryмеит ...J } Посмоорите, как объямяется i. Перед napaмe'lpOM помещается знак &, кoroрый превращает эroт параметр в napaмeтpccьmкy. (Такое же обьявле ние используется и в пporoпmе функции.) ВнyIpи функции предложение i = 10; не присваивает i значение 10. Напротив, это значение присваивается переменной, на которую ссылается i (в данном случае это переменная \111). Заметьте, что в этом предложении не используется оператор yкa зателя *. В тех случаях, коrда вы используете параметрссылку, компи лятор С++ понимает, что вы имеете в виду адрес переменной, и при обращении к apryмeHТY сам снимает ссылку. Указание здесь оператора * было бы серьезной ошибкой. 
240 Модуль 6. Подробнее о функциях Поскольку i объявлена как параметрссылка, компилятор aBTOMa тически передаст функции С( ) адрес любоrо apryмeHTa, с которым она вызывается. Так, в функции main( ) предложение f(val); // передадим f() адрес val передает в функцию С() адрес val (не значение этой переменной). Здесь нет необходимости предварять val оператором &. (Указание этоrо опе ратора бьто бы серьезной ошибкой.) Поскольку С( ) получает адрес val (в форме ссылки), она может модифицировать значение val. Чтобы показать применение параметровссылок (и для дeMOHcтpa ции их преимуществ) в приведенную ниже проrpамму включен вари аит функции swap( ), использующей ссылки. Обратите особое внима ние на объявление и вызов функции swap( ): / / Использование функции swap () с параметрамиссылками. #include <iostrearn> using narnespace std; / / Объявим swap () с использованием параметровссылок. void swap (int &х, int &у) ; int main ( ) { int i, j; i = 10; j = 20; cout « " Начальные значения i и j: "; сои t < < i « I I « j < < I \n ' ; swa р (j, i);  Здесь в функцию _ар( ) автома- тически передаются адреса i и j. cout « "Новые значения i и j: "; cout « i « I I « j « I \n ' ; return о; } / * Здесь swap () определена как использующая передачу по ссылке, а не передачу по значению. В результате функция может обменять значения двух apryмeHTOB, указанных при ее вызове. */ void swap (int &х, int &у) 
Параметрыссылки 241 { int temp i / / используем ссылки для обмена значений apryмeHToB ternp = Х; Х = У;  У = temp; } т enерь обмен значений происходит автоматически посредством ссылок. Вывод этой проrpамм будет такой же, как в предыдущем варианте. Еще раз обратите внимание на то, что при обращении к параметрам х и у с целью обмена их значений нет необходимости указывать опе ратор *. Компилятор автоматически определяет адреса apryмeHТOB, используемых при вызове swap( ), и автоматически снимает ссылки схиу. Подытожим сказанное. Коrда вы создаете параметрссылку, этот параметр автоматически ссылается на apryмeHT, используемый при вызове функции (т. е. явным образом указывает на Hero). Далее, OT падает необходимость указывать при apryмeHтe оператор &. Так же и внутри функции параметрссылка используется непосредственно; оператор * не нужен. Все операции с параметромссылкой aBTOMa тически воздействуют на apryмeHT, указанный при вызове функции. Наконец, коrда вы присваиваете параметруссылке некоторое зна чение, фактически вы присваиваете это значение переменной, на которую указывает ссылка. Применительно к функциям это будет та переменная, которая указана в качестве apryмeHTa при вызове функции. Последнее замечание. Язык С не подцерживает ссылки. Таким об разом, орrанизовать передачу параметра по ссьтке в С МОЖНО только с помощью указателя, как это было показано в первом варианте функции swap( ). Переводя проrpамму с языка С на язык С++, вы можете, rде это будет оправдано, преобразовать параметрыуказатели в параметрыссылки. Спросим у эксперта Вопрос: В некоторых проrpаммах на С++ я сталкивался с объявлениями, в которых знак & указывается при имени типа, как в этом предложении: int& i; а не при имени переменной, как здесь: int &i; Есть ли разница в этих объявлениях? 
242 Модуль 6. Подробнее о функциях Ответ: Короткий ответ будет отрицательным: никакой разницы в этих объявлениях нет. Например, прототип функции swap() можно записать Ta ким образом: void swap(int& х, int& у); Здесь знак & примыкает вплотную к int, и не к х. Более Toro, некоторые проrpаммисты, объявляя указатели, предпочитают помещать знак * при типе, а не при переменной, например, так: :float* р; Такой стиль объявлений отражает желание некоторых проrpаммистов, чтобы С++ содержал отдельные типы ссылок или указателей. Однако, при отнесении знаков & и * к типу, а не к переменной, возникает некоторое неудобство, связанное с тем, что соrласно формальному синтаксису е++ оба эти оператора не обладают свойством дистрибутивности при использо вании в списке переменных, и это может привести к неправильным объяв лениям. Например, в следующем объявлении создается один, а не два YKa зателя на int: int* а, Ь; Здесь Ь объявлено, как целое (а не указатель на целое), потому что, в co ответствии с синтаксисом е++, при использовании в объявлениях знаки * и & связываются с индивидуальной переменной, перед которой они указа ны, а не с типом, указываемым перед ними. е дрyrой стороны, с точки зрения компилятора не имеет никакоrо зна чения, будете ли вы писать int *р или int* р. Поэтому если вам больше Hpa вится относить знаки * и & к типу, а не к переменной, вы вполне можете так и делать. Однако, чтобы избежать неясностей, в этой книrе мы всеrда будем связывать знаки * и & с именем переменной, которую они модифи цируют, а не именем типа. Минутная тренировка 1. Как объявляется параметрссылка? 2. Если вы вызываете функцию, использующую параметрссылку, долж ны ЛИ вы предварять apryмeHT знаком &? 3. Если функция принимает параметрссылку, следует ли внутри функ ции В операциях над этим параметром указывать знаки * или &? 1. Параметрссылка объявляется указанием перед именем параметра знака &. 2. Нет, если в функции используется параметрссылка, apryмeHT автоматически передает ся по ссылке. 3. Нет, операции с параметром осуществляются, как со значением. Однако изменения па раметра отражаются на apryMeHTe, указанном при вызове функции. 
Возврат ссылок 243 Цель _ Возврат ссылок Функции MOryr возвращать ссылки. При проrpаммировании на С++ для возврата ссылок можно найти несколько применений. С He которыми вы познакомитесь позже в этой книrе. Однако ссылка в кa честве значения возврата имеет и дрyrие важные применения, которые вы можете использовать уже сейчас. Korдa функция возвращает ссылку, она фактически возвращает yкa затель на возврашаемое значение. это при водит к совершенно удиви тельной возможности: функцию можно использовать с левой стороны предложения присваивания! Рассмmpим, например, такую проrpамму: // Возврат ссылки. #include <iostream> using namespace std; double &f (); / / функция возвращает ссылку.  Здесь '( ) возвращает ссылку на doubI.. double val = 100.0; int rnain ( ) { double Х; cout « f() « '\n'; // выведем значение val х = f(); // присвоим переменной Х значение val cout« Х« '\п'; // выведем значение х f() = 99.1; // изменим значение val cout « f () « '\n'; t/ / выведем новое значение val return о; } // Эта функция возвращает ссылку на double. double &f () { return val; // возврат ссылки на val  Это пpeдnoженив возвра- щает ссыпку на rлобаль- ную пвременную val. } Вывод этой проrpаммы выrлядит таким образом: 100 100 99.1 
244 Модуль 6. Подробнее о функциях Рассмотрим ход выполнения проrpаммы. В начале проrpаммы 06ъявлена функция С( ), которая возвращает ссылку на тип doubIe, а также rло6альная переменная \111, инициализируемая значением 100. В функции main( ) следующее предложение выводит на экран исход ное значение \111: cout « f() « '\n'; // выведем значение val Коrда вызывается С( ), она возвращает ссылку на val (не значение val!). Эта ссьтка затем используется в предложении cout для вывода на экран значения val. В строке х = f(); // присвоим переменной х значение val ссьтка на val, возвращенная функцией С( ), присваивает значение val переменной х. Самая интересная строка в проrpамме приведена ниже: f() = 99.1; // изменим значение val Это преможение при водит к изменению значения уаl на 99.1. Вот почему: поскольку С( ) возвращает ссылку на val, эта ссьтка становится мишенью для предложения присваивания. В результате значение 99.1 присваивается переменной Уаl косвенным 06разом, через ссылку на эту переменнyIO, возвращаемую функцией С( ). Вот друrой пример простой проrраммы, использующей возврат ссылки: // Возврат ссылки на элемент массива. #include <iostream> using namespace std; double &changeit(int i); // возвращает ссылку double vals[] = { 1.1, 2.2, 3.3, 4.4, 5.5 }; int main () { int i; cout « "Вот исходные значения: 11; for(i=O; i < 5; i++) cout« vals[i] « ' '; cout « ' \n' ; 
Возврат ссылок 245 changeit(l) = changeit(3) = 5298.23; // изменим 2й элемент 98.8; // изменим 4й элемент cout « "Вот измененные значения: "; for(i=O; i < 5; i++) cout «vals[i] « ' '; cout « I \n' i return о; } double &changeit(int i) { return vals(i]i // возвращает ссылку на iй элемент } Эта nporpaмMa изменяет значения Bтoporo и четвертоrо элементов массива vals. Проrpамма выводит следующее: Вот исо,дные значения: ,1.1 ?2 з.з 4.4 5.5 Вот измененные значения: 1.1 5298.23 з.з 98.8 5.5 Посмотрим, как эта nporpaMMa работает. Функция changeit( ) объявлена как возвращающая ссылку на doubIe. Конкретно она возвращает ссылку на элемент массива vals, задаваемый параметром функции i. Ссылка, возвращаемая функцией changeit( ), затем используется в функции main( ) для присваивания значения этому элементу. Используя ссылку в качестве возвращаемоrо значения, следите за тем, чтобы объект, на который указывает эта ссылка, находился в об ласти видимости. Например, рассмотрим такую функцию: // Ошибка, нельзя вернуть ссылку на локальную переменную. int&f() { in t i = 1 О ; return i;  Ошибка! После возврата из функции f( ) переменная i будет вне области ВИДИМOCfи. } Коrда произойдет возврат из функции ( ), переменная i будет вне области видимости. В результате ссьтка на i, возвращаемая С( ), будет не определена. Некоторые компиляторы вообще не будут компилиро вать приведенный выше вариант ( ). Однако TaKoro рода ошибка MO жет возникнуть косвенным образом, поэтому внимательно следите за тем, на какой объект вы возвращаете ссылку. 
246 Модуль 6 Подробнее о функциях Цель _ Независимые ссылочные переменные Несмотря на то, что ссылки включены в С++ rлавным образом для ПОдl1ержки передачи параметров по ссылке при вызове функций, а также для использования в качестве возвращаемоrо функцией значе ння, имеется возможность объявить отдельную переменную ссылоч Horo типа. Такие переменные называются незавuсuмымu ссылочными перемеинымu. Следует, правда, сразу сказать, что такие переменные используются относительно редко, так как они усложняют проrpамму и делают ее трудно читаемой. Имея в виду эту oroBopKY, рассмотрим кратко использование переменных TaKoro типа. Независимая ссылочная переменная должна указывать на какойто объект, поэтому ее необходимо инициализировать при объявлении. В общем случае это означает, что ей должен быть присвоен адрес пред варительно объявленной переменной. После Toro, как это сделано, имя ссылочной переменой может быть использовано всюду, rде допус.. тимо использовать переменную, на которую она указывает. Фактиче ски эти переменные нельзя различить. Рассмотрим, например, такую проrpамму: // Использование независимой переменной ссылочноrо типа. #include <iostream> using namespace std; int main () { Независимая CCblЛочная переменная. int j, k; I int &i = j; // незвисимая ссылочная переменная  j = 10; cout « j « " " « i; // выводит 10 10 k = 121; i = k; // копирует значение k в j (не адрес k) cout « "\n" « j; // выводит 121 return о; } Проrpамма выводит следующее: 10 10 121 
Переrрузка ФУНКЦИЙ 247 Адрес, на который указывает ссылочная переменная, фиксирован; ero нельзя изменять. Поэтому, коrда выполняется предложение i = k; в переменную j (на которую указывает i) копируется значение k (не адрес k). Как уже отмечалось ранее, обычно использование независимых пе ременных ссыочноro типа нельзя назвать разумным, потому что в них нет 6ео6ходимости и они только замусоривают вашу проrpамму. Наличие двух имен для одной и той же переменной не приведет ни к чему, кроме путаницы. Несколько оrраничений при использовании ссылочных переменных в использовании ссьточных переменных необходимо иметь в виду следующие оrpаничения: · Нельзя ссылаться на ссьточную переменную. · Недопустимо создавать массивы ссьточных переменных. · Нельзя создать указатель на ссылку. Дрyrими словами, к ссылке нельзя приложить оператор &. Минутная тренировка 1. Может ли функция BepHyrb ссылку? 2. Что такое независимая ссылочная переменная? З. Можно ли создать ссьтку на ссылку? 1 Да, функuия может вернуть ссылку 2 Не )ависимая ссылочная переменная  зто просто друrое имя тoro же объекта 3 Нет, вы не може re создать ссылку на ссылку Цель _ Переrрузка функций в этом подразделе вы познакомитесь с одной из самых захватыIаю щих средств С++: переrpузкой функций. В С++ две или даже несколько функций MOryr иметь одно и то же имя при условии, что различаются объявления их параме1РОВ. Такие функции называются переzруженны.ми, а само это средс1ВО называют переzрузкой функций. Переrpузка функций ЯВJ1Яется одним из способов реализации полиморфизма в с++. 
248 Модуль 6 Подробнее о функциях для TOrO, чтобы переrpузить функцию, достаточно объявить дрyrой вариант функции с тем же именем. Компилятор возьмет на себя все oc тальное. Вы только должны соблюсти одно важное условие: типы или число параметров (или и ТО, и дpyroe) каждой из переrpуженных Функ ций должны различаться. Для переrpуженных функций недостаточно различия только в типе возврашаемых значений. Они должны разли чаться типом или числом их параметров. (Типы возврата не во всех слу чаях предоставляют достаточную информацию для С+ +, чтобы тот Mor решить, какую из функций использовать.) Разумеется, переrpуженные функции MOZym различаThCЯ также и типами возвращаемых значений. Korдa вызывается переrpуженная функция, реально выполняется тот вариант, у Koтoporo параметры соответствуют apryмeнтaм вызова. Начнем с простоrо проrpаммноrо примера: // Переrрузим ФУНКЦИЮ трижды. #include <iostrearn> using namespace std; void f (int i) ; void f(int i, int j); void f (double k) ; int rnain ( ) { / / параметр типа int / / два параметра типа int  //один параметр типа double I Для каждоrо варианта переrpyженной функции требуется отдельный прототип f(10); // вызов f(int) f(10, 20); // вызов f(int, int) f(12.2З); // вызов f(double) return о; } void f (int i) { cout « "В f(int) i равно" « i « '\n'; } void f(int i, int j) { Здесь опредеяены три различающихся реали- зации '( ) cout « "В f(int, int) i равно h « i; cout « 11, j равно 11 « j « I \n' ; } void f(double k) 
Переrрузка ФУНКЦИЙ 249 { cout « "В f (double) k равно" « k « I \n' ; } Эта профамма выведет на экран следующее: В f(int) i равно 10 В f(int, int) i равно 10, j равно 20 В f(double) k равно 12.23 Итак, С( ) переrружена трижды. Первый вариант требует один целочисленный параметр, второй вариант требует два целочис ленных параметра, и третий вариант требует один параметр типа doubIe. Изза Toro, что списки параметров каждой функции раз личаются, компилятор имеет возможность выбрать требуемый Ba риант функции исходя из типа aprYMeHToB, указанных при вызове функции. Для Toro, чтобы осознать ценность переrpузки функций, paCCMOT рим функцию, названную neg( ), которая возвращает отрицание CBO ero apryмeHTa. Например, при вызове с числом  10 функция возвра щает 10. При вызове с числом 9 функция возвращает  9. Не имея средства переrpузки функций, вы должны были бы для данных с ти пами int, doubIe и loog разработать три отдельные функции с разли чающимися именами, например, ineg( ), Ineg( ) и fneg( ). Блаrодаря переrpузке мы можете для всех функций, возвращающих отрицание cBoero apryмeHTa, использовать одно имя, например, neg( ). Таким образом, переrрузка поддерживает концепцию полиморфизма "один интерфейс, MHoro методов". Приведенная ниже nporpaMMa дeMOHCT рирует эту концепцию: // Создание различных вариантов функции neg(). #include <iostream> using namespace std; int neg(int n); // neg() для int. double neg (double n); / / neg () для double. long neg (long n) ; / / neg () для long. int main () { cout « "neg(10): " «neg(10) « "\n"; cout « "neg(9L): " « neg(9L) « "\n"; 
250 Модуль 6. Подробнее о функциях cout « "nеg(11.2З): " « nеg(11.2З) « "\n"; return о; } / / n eg ( ) для in t . int neg (int n) { return n; } / / neg ( ) для double. double neg(double n) { return n; } // nеg()для long. long neg (long n) { return n; } Вот вывод этой профаммы: neg (  1 о): 1 О n eg ( 9 L):  9 nеg(11.2З): 11.2З в проrpамме предусмотрены три схожие, но все же разные Функ ции с именем neg, каждая из которых возвращает обратное значение cBoero apryмeHтa. Компилятор определяет, какую функцию вызывать в каждой ситуации, по типу apryмeHTa Вblзова. Ценность переrpузки заключается в том, что она позволяет обра щаться к набору функций с помощью одноro имени. В результате имя neg описывает обобщенное действие. Обязанность выбрать конкретный вариант для конкретной ситуации возлаrается на компилятор. Вам, про rpаммисту, нужно только запомнить имя обобщенноro действия. Таким образом, блarодаря применению полиморфизма число объектов, о KO торых надо помнить, сокращается с трех до одноrо. Хотя приведенный пример крайне прост, вы можете, развив эту концепцию, представить, как переrpузка помоrает справляться с возрастающей сложностью. Друrое преимущество переrрузки функций заключается в воз можности определять слеrка различающиеся варианты одной и той же функции, каждый из которых предназначен для опреде ленноrо типа данных. В качестве примера рассмотрим функцию с 
Переrрузка функций 251 именем min( ), которая находит меньшее из двух значений. He трудно написать варианты тlп( ), которые будут выполняться по разному для данных различных типов. Сравнивая два целых чис ла, min( ) вернет меньшее из них. Сравнивая два символа, min( ) может вернуть букву, стоящую в алфавите ранее друrой, незави симо от Toro, прописные эти буквы или строчные. В таблице ASCII прописные буквы имеют значения, на 32 меньшие, чем co ответствующие строчные. Таким образом, иrнорирование реrист ра букв может быть полезным при упорядочении по алфавиту. Сравнивая два указателя, можно заставить min( ) сравнивать зна чения, на которые указывают эти указатели, и возвращать указа тель на меньшее из них. Ниже приведена nporpaMMa, реализую щая все эти варианты min( ): // Создадим различные варианты min(). #include <iostream> using namespace std; int min (int а, int Ь) ; / / min () для int char min (char а, char Ь); / / min () для char int * min(int *а, int *Ь); // min() для int* int main () { int i=10, j=22; cout « "min( 'Х', 'а'): " « min( 'Х', 'а') « "\n"; cout« "min(9, 3): 11« min(9, З)« "\n"; cout « "*min(&i, &j): 11 « *min(&i, &i) « "'n"; return о; } / / min () для int. Возвращает меньшее значение. in t mi n ( i n t а, i n t Ь)  Каждый вариант min( ) может иметь paзnи- { чия Этот вариант возвращает мetiЫ1Jee из i f (а < Ь) return а; двух значений Int. else return Ь; } // min() для char  иrнорирование реrистра букв. char min (char а, char Ь)  Этот вариант min( ) иrнoрирует perистр букв { if(tolower(a) < tolower(b» return а; else return Ь; } 
252 Модуль 6. Подробнее о функциях /* min () для указателей на int. Сравнивает значения и возвращает указатель на меньшее значение. */ int * min{int *а, int *Ь)  { if(*a < *Ь) return а; else return Ь; Этот вариант min( ) возвращает указатель на менЬШее значение } Вот ВЫВОД этой проrpаммы: min ( , Х', , а ' ): а min (9, з): 3 *min(&i, &j): 10 Коrда вы переrpужаете функцию, каждый вариант этой функции может выполнять любые нужные вам действия. Нет никаких правил, устанавливающих, что переrpуженные функции должны быть похожи дpyr на дрyrа. Однако с точки зрения стиля переrpузка функций предполаrает их взаимосвязь. Поэтому, хотя вы и можете дать ОДНО и то же имя переrpуженным функциям, выполняющим совсем разные действия, этоrо делать не следует. Например, вы можете вы6рать имя sqr( ) для функций, одна из которых возвращает квадрат int, а дрyrая  квадратный корень из значения doubIe. Однако эти две операции фундаментально различаются, и использование для них понятия пе реrpузки функций противоречит исходной цели этоrо средства. (Ta кой способ проrpаммирования считается исключительно дурным стилем!) В действительности переrpужать следует только тесно свя занные операции. Автоматическое преобразование типов и переrрузка Как уже отмечалось в Модуле 2, С++ в определенных случаях BЫ полняет автоматическое преобразование типов. Эти прео6разования также применимы к параметрам переrpуженных функций. PaCCMOT рим следующий пример: /* Автоматическое преобразование типа может влиять на выбор переrруженной функции. */ 
Переrрузка функций 253 #include <iostream> using namespace stdi void f (int х) ; void f (double х) ; i n t та i n () { int i = 10; double d = 10.1; short s = 99; float r = 11.5F; f(i); // вызывается f(int) f(d); // вызывается f(double) f(s); // вызывается f(int)  преобразование типа  f(r); // вызывается f(double)  преобразование T I return О. Здесь осущестВJIЯется автомати- , ческое преобразование типов } void f (int х) { cout « "Внутри f (int): .. « х « .. \n" ; } void f(double х) { cout « tI Внутри f (double): .. « Х « tI \n 11 ; } Вывод проrpамм выrлядит следующим образом: Внутри f(int): 10 Внутри f(double): 10.1 Внутри f(int): 99 Внутри f(double): 11.5 в этом примере определены только два варианта функции ( ): одии, принимающий параметр типа int, и дрyrой для параметра типа doubIe. Однако вполне возможно передавать f( ) значения типа sbort или float. Параметр типа short с++ автоматически преобразует в int и вызовет f(int). Значение типа float будет автоматически преобразовано в doubIe, что приведет к вызову f(doubIe). Важно, однако отдавать себе отчет в том, что автоматическое пре образование действует лишь в тех случаях, коrда компилятор не Haxo дит переrpуженной функции с точным соответствием типов параметра 
254 Модуль 6 Подробнее о функциях и apryмeHTa. Ниже приведен предыдущий проrpаммный пример с дo бавлением варианта f( ) для параметра типа sbort: // Теперь добавим f(short). #include <iostream> using namespace std; void f(int х); void f (short х);  void f (double х) ; Добавлена функция f(short) int main () { int i = 10; double d = 10.1; short s = 99; f 1 оа t r = 11. 5 F ; f(i); // вызвается f(int) f(d)i // вызвается f(double) Теперь преобразование f (s); / / теперь вызвается f (short)  типа не происходит, так как имеется '(Мort) f(r); // вызвается f(double)  преобразование типа } void f (int х) { cout« "Внутри f(int): .. «х« "\n"; } void f(short х) { cout « .. Внутри f (short): " « х « .. \n" ; } void f(double х) { сои t « .. Внутри f (double): " « х « .. \п h ; } Теперь, запустив профамму, вы получите на экране следующее: Внутри f(int): 10 Внутри f(double): 10.1 Внутри f(short): 99 Внутри f(double): 11.5 в этом варианте, поскольку здесь добавлена переrpуженная ФУНК ция С( ), принимающая apryмeHT типа short, при вызове С( ) со значени ем short будет активизирована f(short), и автоматическоrо преобразова ння short в int не происходит. 
Переrрузка ФУНКЦИЙ 255 Минутная тренировка 1. Какое условие необходимо выполнить при переrpузке функции? 2. Почему переrpуженные функции должны выполнять схожие действия? 3. Участвует ли тип возвращаемоrо функцией значения в выборе компи лятором варианта переrpуженной функции? Переrруженные функции имеют одно имя, но объявления их параметров различа ются. 2 Переrpуженные функции должны выполнять схожие действия, потому что переrpузка функций предполаrает взаимосвязь между функциями 3 Нет, тип возвращаемоrо значения переrpуженных функций может различаться, однако он не влияет на выбором компилятором KOHKpeTHoro варианта функции Проект 6..1 Создание переrруженныx функций для вывода на экран в этом проекте вы создадите набор переrpуженных функций, BЫBO дящих на экран данные различных типов. Хотя предложение cout дoc таточно удобно, такой набор функций вывода предоставит альтерна тиву, которая может прельстить некоторых проrpаммистов. В действи тельности и Java, и С++ используют не операторы вывода, а функции вывода. Создав переrpуженные функции вывода, вы сможете пользо ваться обоими методами. Кроме тoro, вы сможете придать вашим функциям вывода конкретные характеристики, удобные для решения ваших задач, например, при выводе булевых переменных выводить не 1 и О, а слова "true" и 'false". Вам надо будет создать два набора функций с именами println( ) и print() Функция println( ) будет отображать свой apryмeHT и вслед за тем переводить курсор наследующую строку. Функция print() будет простс выводить свой apryмeHT, без перевода строки. Например, фраrмент: print(l); println( 'Х'); print ("Переrрузка ФУНКЦИЯ  мощное средство. ") i print(18.22); выведет на экран следующее: lХ Переrрузка ФУНКЦИЯ  мощное средство. 18.22 
256 Модуль 6. Подробнее о функциях в этом проекте println( ) и print( ) будут переrpужены для данных ти пов bool, char, int, long, char* и doubIe, однако вы сможете добавить пе реrpуженные функции для ваших собственных типов. Шаr за шаrом 1. Создайте файл с именем Print.cpp. 2.Начните проект со следующих строк: /* Проект 61 Создание переrруженных функций print() и println(), которые выводят на экран данные разных типов. */ include <iostream> using namespace std; З. Добавьте прототипы для функций println( ) и print( ), как это по казан о ниже: // Эти функции осуществляют перевод строки. void println(bool Ь); void println(int i); void println(long i) ; void println (char ch) ; void println(char *str); void println(double d) ; // Эти функции не пере водят строку. void print (bool Ь) ; void print(int i); void print(long i); void print (char ch) ; void print(char *str); void print(double d); 4. Реализуйте функции println( ), как показано ниже: // Вот набор функций println(). void println(bool Ь) { if(b) cout « "true\n"; else cout« "false\n"; } 
Переrрузка ФУНКЦИЙ 257 void println(int i) { cout « i « "\n"; } void println(long i) { cout « i « ., \n It ; } void println(char ch) { cout « сЬ « "\n"; } void println(char *str) { cout « str « 11 \n 11 ; } void println(double d) { cout « d « ., \n It ; } Заметьте, что каждая функция присоединяет к выводу СИМВОЛ HO вой строки. Обратите внимание также на то, что println(bool), OT06pa жая значения булевых переменных, выводит на экран слова "true" или "false". Это иллюстрация Toro, как леrко можно настроить форму BЫ вода, исходя из ваших потребностей и вкусов. 5. Реализуйте функции print( ), как показано ниже: / / Вот набор ФУНКЦИЙ prin t ( ) void print (bool Ь) { if(b) cout « Ittrue"; else cout « "false"; } void print(int i) { cout « i; } void print(long i) { 
258 Модуль 6 Подробнее о функциях } со; « i; void print(char ch) { cout « ch; } void print(char *str) { cout « str; } void print(double d) { cout « d; } Эти функции практически совпадают со своими аналоrами из Ha бора println( ), за исключением Toro, что они не пере водят строку. В pe зультате последующий вывод появляется на той же строке. 6. Ниже приведен полный текст проrpаммы Print.cpp: /* Проект б  1 Создание переrруженных функций print () и println ( ) , которые выводят на экран данные разных типов. */ #include <iostream> using namespace std; // Эти функции осуществляют перевод строки. void println (bool Ь) ; void println(int i); void println(long i); void println (char ch) ; void println(char *str); void println(double d) ; / / Эти функции не переводят строку. void print (bool Ь) ; void print(int i); void print(long i); void print (char ch) ; void print(char *str); void print (double d) ; 
Переrрузка функций 259 int rnain ( ) { println(true)i println(10); println("3TO про верка"); println( 'х'); println(99L)i рrintln(12З.23)i print ("Вот некоторые значения: "); print(false); print (1 '); print(88); print (1 1); print(100000L); print (1 1); print(100.01); println(" Все!"); return о; } // Вот набор функций println(). void println(bool Ь) { if(b) cout « "true\n"; else cout « "false\nn; } void println(int i) { cout « i « "\n"; } void println(long i) { cout« i« "\n"; } void println(char ch) { cout« ch« "\n"; } void println(char *str) { 
260 Модуль 6. Подробнее о функциях cout « str « It \n It ; } void println(double d) { cout< d « "\n"; } // Вот набор функций print(). void print(bool Ь) { if(b) cout « "true"; else cout « "false"; } void print(int i) { cout « i; } void print (long i) { cout « i; } void print(char ch) { cout « ch; } void print(char *str) { cout « str; } void print(double d) { cout « d; } Ниже показан вывод проrpаммы: true 10 Это проверка х 
ApryMeHTbl ФУНКЦИЙ с инициализацией по умолчанию 261 99 123.23 Вот некоторые значения: false 88 100000 100.01 Все! Цель _ApryMeHTbI функций с инициализацией по умолчанию Следующее относящееся к функциям средство, которые мы здесь рассмотрим  это apZYMeптbl с инициализацией по умолчанию. В С++ вы можете задать параметру значение по умолчанию; оно будет автомати чески использоваться, коrда в вызове функции отсутствует apryмeHT, соответствующий этому параметру. Такие apryмeHTЫ с инициализзци ей по умолчанию можно использовать для упрошения вызова сложных функций. Они также иноrдз MOryт выполнять те же задачи, что и nepe rpузка функций, но более простыми средствами. ApryмeHT с инициализацией по умолчанию указывается синтакси чески так же, как и переменная с инициализацией. Рассмотрим сле дующий пример, в котором объявляется функция myfunc( ), прини мающая два арryментз типа int. Первому apryмeHТY по умолчанию дa ется значение О, второму  100: vo i d ту f ипс ( in t х = О, in t у = 1 О О) ; Теперь myfunc( ) может быть вызвана любым из трех приведенных ниже способов: myfunc(l, 2); //оба значения передаются явным образом myfunc (10) ; / / для х передается значение, у использует / / умолчание myfunc ( ) ; / / пусть их, и у используют умолчание в первом вызове функции параметру х передается значение 1, а пара метру у  2. во вropoM вызове Х получает значение 10, а у будет иметь зна чение 100 по умолчанию. В третьем вызове оба параме'фа получают значе ния по умолчанию. Следующая проrpамма демонстрирует эror процесс: // Демонстрация aprYMeHToB с инициализацией по умолчанию. #include <iostream> 
262 Модуль 6 Подробнее о ФУНКЦИЯХ using namespace std; УО i d ту f ипс ( i n t х = О, i n t у = 1 О О);  в myfunc( ) заданы apryмeH- ты с инициализацией по умол- чанию для обоих параметров int main () { Пlу f иn с ( 1, 2); myfunc (10) ; myfunc(); return о; } void myfunc(int х, int у) { cout« "х: "« х«" у: " «у« "\n"; } Приведенный здесь вывод проrpаммы подтверждает использование умолчания для apryмeHToB: х: 1, у: 2 х: 10, у: 100 Х: О, у: 100 При создании функции с инициализацией apryмeHToB по умолча нию значения по умолчанию должны указываться только один раз, и это надо сделать при первом объявлении функции в файле. В предьшу щем примере умолчание для apryмeHТOB бьто задано в прототипе myfunc( ). Если вы попытаетесь задать новые (или даже те же самые) значения по умолчанию в определении myfunc( ), компилятор сообщит вам об ошибке и не будет компилировать проrpамму. Несмотря на то, что значения apryмeHToB по умолчанию не MO ryт быть переопределены внутри проrраммы, вы можете задать различные значения по умолчанию для каждоrо варианта переrру женной функции; друrими словами, различные варианты переrру женной функции Moryт иметь различные умолчания для своих ap rYMeHTOB. Важно понимать, что все параметры, принимающие значения по умолчанию, должны находиться справа от тех, для которых умолчание не используется. Так, следующий прототип неверен: / / Неверно! void f(int а = 1, int Ь); 
ApryMeHTbl функций с инициализацией по умолчанию 263 Начав определять параметры, принимающие значения по умол чанию, вы уже не можете указать параметр без умолчания. Объяв ление вроде следующеrо так же неверно и компилироваться не будет. int myfunc(float f, char *str, int i=10, int j); // Неверно! Поскольку параметру i бьто дано значение по умолчанию, пара.. метр j требует Toro же. Одна из причин, по который в С++ включена инициализация по умолчанию для apryмeHТOB функций, заключается в том, что она уп.. рощает задачу проrраммиста при работе со сложными проrpаммами. Очень часто функция, ради Toro, чтобы ее можно бьшо использо.. вать в широком диапазоне ситуаций, имеет больше параметров, чем это требуется для ее вызова в наиболее типичных и распространен.. ных случаях. Тоrда при наличии у нее apryмeHToB с умолчанием вам нужно помнить и указывать только те apryмeHThI, которые требуют.. ся в конкретной ситуации, а не все те, которые покрывают самый общий случай. ApryMeHTbI с инициализацией по умолчанию или переrрузка? Одним из приложений apryмeHToB с инициализацией по умолча.. нию является создание упрощенноrо аналоrа переrpуженных функ.. ций. Чтобы выяснить, почему это так, представьте себе, что вы хотите создать два специфических варианта стандартной функции strcat( ). Один вариант будет действовать наподобие strcat( ) и присоединять все содержимое одной строки к концу дрyrой. Во втором варианте функция будет требовать еще один (третий) apryмeHT, указывающий число присоединяемых символов. Таким образом, второй вариант бу.. дет присоединять только указанное число символов одной строки к концу дрyrой. Наши специфические функции (назовем их mystrcat(» будyr иметь следующие прототипы: void mystrcat(char *sl, char *s2, int len); void mystrcat(char *sl, char *s2); Первый вариант будет копировать leB символов из s2 в конец sl. Второй вариант скопирует всю строку с адресом s2 в конец строки с адресом sl и, таким образом, будет действовать анало.. rично strcat( ). Хотя не будет ошибкой реализовать два варианта mystrcat( ) для ре.. шения поставленной задачи, имеется и более простой способ. Исполь.. зуя apryмeHT с инициализацией по умолчанию, вы можете создать 
264 Модуль 6 Подробнее о функциях только один вариант mystrcat( ), который будет выполнять обе опера ЦИН. Следующая проrpзмма демонстрирует эту возможность: // Использование специфическоrо варианта 8trcat(). #include <io8tream> #include <cstring> u8ing nаmеерасе 8td; void mY8trcat(char *81, char *82, int len = int main ( ) { char 8tr1 [80] = "Это npоверка" ; char str2[80] = "0123456789"; 0);1 ЗДесь 18" по умолчанию прирав- нивается нулю mY8trcat (str1, 8tr2, 5); / / npисоединить 5 символов  cout « 8tr1 « ' \n' ; I ЗДесь len указано, и копируются только 5 символов strcpy (8tr1, "это проверка 11); / / повторная инициализация 8trl mY8trcat (8trl, 8tr2); / / npисое,Щ,..нить всю строку +j cout « 8tr1 « ' \n' ; I ЗДесь len по умолчанию равно return о; нулю и присоединяется ВСЯ строка } // Специфический вариант 8trcat(). void mY8trcat(char *81, char *82, int len) { / / найдем конец 81 while (*81) 81 ++; if(len==O) len = 8trlen(82); while(*82 && len) { *81 = *82; // копируем символы 81++; 82++; len; } *81 = '\0'; // завершающий О для 81 } Вывод проrpаммы выrлядит следующим образом: 
ApryMeHTbl ФУНКЦИЙ с инициализацией по умолчанию 265 Это проверкаО1234 это проверкаО123456789 Как видно из текста nporpaмMbI, mystrcat( ) присоединяет от 1 до len символов строки, на которую указывает s2, к концу строки, на KOTO рую указывает 51. Однако, если len равно О, что произойдет, если mystrcat( ) вызывается без последнеrо apryмeHTa, который в этом слу чае получит значение по умолчанию, mystrcat( ) присоединит всю строку s2 к концу sl. (Таким образом, при len == О функция действует так же, как и стандартная функция strcat( ).) Используя инициализа.. цию len по умолчанию, становится возможным скомбинировать обе операции в одной функции. Как показывает этот пример, иноrда ис пользование apryмeHToB с инициализацией по умолчанию позволяет получить упрощенный аналоr переrpуженных функций. Правильное использование apryMeHToB с инициализацией по умолчанию Хотя apryмeHTЫ с инициализацией по умолчанию являюrся при их правильном использовании чрезвычайно удобным среДС1ВОм, они мо" ryт привести и к неприятностям. Назначение apryмeHToB с инициализа.. цией по умолчанию заключается в обеспечении эффективности иудоб.. ства при работе с функцией при сохраненЩt значительной rибкости. С этой целью apryмeHТЫ с умолчанием должны отражать способ наиболее частоro использования функции или предоставлять способы разумноrо альтернативноrо использования. Если для данноrо параметра не суще.. ствует определенноrо наиболее часто используемоrо значения, то нет причин объявлять умолчание для соответствующеro apryмeHтa. Факти чески объявление умолчания для apryмeHТOB, Korдa для этоrо нет доста.. точных оснований, только ухудшает проrpамму, поскольку умолчания будyr лишь сбивать с толку любоro, кто будет читать вашу проrpамму. Кроме Toro, инициализация apryмeHтa по умолчанию не должна Hapy шать работу проrpаммы. Нельзя допускать, чтобы случайное использо вание умолчания для apryмeнтa имело необратимые отрицательные по.. следствия. Представьте себе, что вы забьти указать apryмeHT, а в ре.. зультате проrpамма стерла файл с важным данными! Минутная тренировка 1. Покажите, как объявить vоidфункцию с именем count, которая требует двух параметров типа int с именами а и Ь, причем каждому дается зна.. чение по умолчанию, равное О. 2. Можно ли apryмeHTЫ с инициализацией по умолчанию объявить и в прототипе функции, и в ее определении? 
266 Модуль 6. Подробнее о функциях З. Правильно ли следующее объявление? Если нет, то почему? int f(int х=10, double Ь); 1 vOld count(lnt а=О, lnt bO}; 2. Нет, apryмeHTЫ с инициализацией по умолчанию должны быть объявлены в первом объявлении функции, которое обычно является ее прототипом 3 Объявление неправИJIЬНО, поскольку пара метр бе1 умолчания не может следовать за па раметром с умолчанием Цель _ Переrрузка функций инеоднозначность Перед тем, как завершить этот модуль, необходимо обсудить вид ошибки, специфический для языка С++: неодНОЗllаЧll0сть. Возможно создать ситуацию, в который компилятор не может выбрать между двумя (или большим числом) правильно переrpуженных функций. Если такое случается, то rоворят, что возникла llеодll0ЗllаЧll0сть. Heoд нозначные предложения являются ошибочными, и проrpамма, coдep жащая неоднозначность, компилироваться не будет. Наиболее часто причиной неоднозначности оказывается автомати ческое преобразование типов, выполняемое С++. Как известно, С++ автоматически пытается преобразовать типы apryмeнтoB, используемых при вызове функции, в типы параметров, определенные этой функци ей. Вот пример этоrо: int myfunc(double d); // ... cout «myfunc('c'); // не ошибка, выполняется преобразование Как показывает комментарий, такое предложение не является оши бочным, так как С++ автоматически преобразует символ с в ero эквива лент типа doubIe. Фактически в С++ лишь очень немноrие виды преоб разований тaKoro рода запрещены. Автоматическое преобразование ти пов является очень удобным средством, однако оно также оказывается основной причиной возникновения неоднозначности. Рассмотрим сле дующую проrpамму: // Неоднозначность при переrрузке. #include <iostream> using namespace std; float myfunc(float i); double myfunc(double i); 
Переrрузка функций и неоднозначность 267 int rnain ( ) { // однозначно, вызов rnyfunc(double) cout « myfunc (10.1) « .. "; /1 неоднозначно cout« myfunc(lO); // Ошибка!  Какой вариант myfunc( ) следует использовать? return о; } float myfunc(float i) { return i; } double myfunc(doub]e i) { return i; } в приведенном примере функция myfunc( ) переrpужена так, что она может принимать apryмeнт как типа IImt, так и типа doubIe. В однознач ной строке вызывается myfunc(doubIe), потому чro всем константам с плавающей точкой в с++ автоматически назначается тип doubIe (если только они не объявлены явным образом типа float). Однако, коrда myfunc() вызывается с целочисленным apryмeНТOM 10, возникает Heoд нозначность, так как компилятор не может определить, в какой тип следует пре06разоватъ KOHcтamy: lloat или doubIe. И то, и дpyroe преоб разование в данном случае допустимо. Такая сmyация вызывает сооб щение об ошибке и предотвращает компиляцию проrpаммы. Следуer особо подчеркнyIЬ, чro неоднозначность в предыдущем приме ре не является слеДCIВием переrpузки myfunc( ) orносиreльно типов dOUbIe и tb1t. Неясность возникла в результате KOнкpe1НOro вызова тyfunc( ) с ис пользованием apryмeнтa не определеIOlОro типа. Иначе roворя, ОllIИбкой является не переrpузка тyfunc( ), а вид ее конкретноro вызова. Вот дрyrой пример неоднозначности, вызванной автоматическим преобразованием в с++: // Друrой пример неоднозначности при переrрузке. #include <iostream> using narnespace stdj char myfunc(unsigned char ch) ; char myfunc (char ch) ; 
268 Модуль 6. Подробнее о функциях int main () { cout «myfunc(' C '); // cout « myfunc (88) « 11 здесь вызывается myfunc(char) 1 11; // Ошибка, неоднозначность! Следует ли преобразовать 88 в ch8r или в unsigned char'1 return о; } char myfunc(unsigned char ch) { return chl; } char myfunc (char ch) { return ch+l; } в С++ unsigned char и char не являются внутренне неоднозначными. (Это разные типы.) Однако, коrда туСопс( ) вызывается с числом 88 в качестве apryмeHTa, компилятор не знает, какую функцию вызвать. Следует ли преобразовать 88 в char или в unsigned char? Оба преобразо вания вполне допустимы. Дрyrая ситуация, которая может привести к неоднозначности, воз никает при использовании в переrpуженной функции apryмeнтa с инициализацией по умолчанию. Чтобы убедиться в этом, рассмотрим такую проrpамму: // Еще пример неоднозначности при переrрузке функций. #include <iostream> иsing namespace std; int rnyfunc(int i); int myfunc(int i, int j=l); int main () { cout «myfunc(4, 5) « " 11; // однозначно cout« myfunc(10); // Ошибка, неоднозначность! 1 return о; Предnолaraется ли j по умолча- } нию, или вызывается вариант myfunc( ) с одним параметром? int myfunc(int i) { 
Переrрузка ФУНКЦИЙ инеоднозначность 269 return i; } int myfunc(int i, int j) { return i*j; } в первом вызове туСопс( ) указаны два apryмeHтa; следовательно, никакой неоднозначности нет, вызывается myfunc(int i, int j). Однако второй вызов тyfппс( ) приводит к неоднозначности, поскольку KOM пилятор не знает, вызвать ли вариант myfunc( ) с одним apryмeHToM, или использовать умолчание в варианте с двумя арryментами. В своих проrpаммах на с++ вы наверняка будете сталкиваться с ошибками неоднозначности. При этом, пока вы не приобретете ДOCTa точный опыт, таких ошибок в ваших проrpаммах будет MHoro, так как допустить их леrко, а обнаружить заранее  трудно. . ....P.I.. ... ..PP... 1. Какими двумя способами можно передать apryмeHT в подпроrpам му? 2. Что называется ссылкой в с++? Как создается параметрссылка? З. Дан фрarмент проrpаммы: int f(char &с, int *i); // ... cha r ch = I х' ; int i = 10; Покажите, как вызвать f( ) для операций над переменными сЬ и i. 4. Создайте voidфункцию с именем round( ), кoroрая окрyrляет значе ние CBoero apryмeHTa типа doubIe до ближайшеro целоrо значения. Пусть round( ) использует параметрссылку и возвращает окрyrлен ный результат через этот параметр. Продемонстрируйте вызов round( ) в проrpамме. Для решения этой задачи вам понадобится функция modf( ) из стандартной библиотеки. Она имеет такой про тотип: double modf(double пит, double *i); Функuия modf( ) разлаrает пот на целую и дробную части. Она воз вращает дробную часть и помещает uелую часть в переменную, на которую указывает i. Функция требует заrоловок <cmatb>. 
270 Модуль 6. Подробнее о функциях 5. Модифицируйте вариант swap( ) со ссьшкой, чтобы помимо обмена значений ее apryмeHToB, она еше возвращала ссылку на меньший apryмeHT. Назовите эту функцию minswap( ). 6. Почему функция не может вернуть ссьтку на локальную перемен ную? 7. Чем должны различаться списки параметров двух переrpуженных функций? 8. В Проекте 6 1 вы создали набор функций print( ) и println( ). Дo бавьте в эти функции второй параметр, который будет задавать Be личину отступа. Например, при вызове print( ) таким образом: рriпt("проба",18); строка "проба" будет выведена на экран после 18 пробелов. Ha значьте параметру отступа значение О по умолчанию, чтобы при ero отсутствии текст выводился без отступа. 9. Покажите, какими способами можно вызвать myfunc( ), имеющую такой прототип: bool myfunc(char ch, int а=10, int Ь=20); 10. Кратко объясните, почему переrрузка функций может привести к неоднозначности. 
Модуль 7 Подробнее о типах данных и операторах 7.1 Познакомиться с описателями const и volatile 7.2 Узнать об описателях классов памяти 7.3 Начать работать со статическими переменными 7.4 Научиться повышать производительность с помощью реrистровых переменных 7.5 Познакомиться с перечислимыми типами 7. б Рассмотреть оператор typedef 7 .7 Освоить побитовые операции 7.8 Начать использовать операции сдвиrа 7.9 Познакомиться с оператором? 7.10 Понять, как используется оператор.. запятая 7. 11 Освоить операции COCTaBHoro присваивания 
272 Модуль 7. Подробнее о типах данных и операторах В этом модуле мы возвращаемся к понятиям типов данных и операто ров. В дополнение к типам данных, которые вы уже использовали, с++ поддерживает целый ряд дрyrих. Некоторые из них создаются с помощью модификаторов, добавляемых к типам, о которых вы уже знаете. Кроме Toro, имеются еще перечислимые типы данных, а с по мощью оператора typedef можно определять собственные типы. с++ также предоставляет несколько дополнительных операторов, сущест венно расширяющих диапазон проrpаммистских задач, которые удоб но решать с помощью этоrо языка. К ним относятся операторы поби товые и сдвиrов, а также операторы ? и sizeof. Цель _Описатели const и volatile В с++ имеются два типа описателей, влияющих на способы дocтy па к данным и их модификации. Это описатели const и volatile. Вместе они называются сvописателями, и в предложении объявления пере менной они указываются перед базовым типом. const Если переменная объявлена с описателем const, ее значение не MO жет быть изменено в процессе выполнения проrpаммы. Таким обра зом, сопst"переменная" в действительности совсем не является пере менной! Однако вы можете назначить переменной, объявленной как const, начальное значение. Например, предложение const int maxusers = 9; создает переменную maxusers типа int, которая содержит значение 9. эту переменную можно использовать в выражениях, как и любую дpy ryю переменную, однако ее значение не может быть модифицировано вашей проrpаммой. Обычно const используется для создания uменоваНIlОЙ константы. Часто проrpаммы используют одно и то же значение для мноrих цe лей. Например, в проrрамме может быть объявлено несколько различ ных массивов одноrо размера. В этом случае вы можете задать размеры всех массивов с помощью сопstпеременной. Достоинство TaKoro приема заключается в том, что если позже вам потребуется изменить размеры этих массивов, то достаточно только задать новое значение сопstпеременной и перекомпилировать проrpамму. Вам не надо будет изменять размер каждоrо массива в ero объявлении. Такой подход по MoraeT избежать ошибок и, кроме Toro, он проще и наrляднее обычно 
Описатели const и volatlle 273 ro объявления. Приведенный ниже пример иллюстрирует это приме нение описателя const: #include <iostrearn> using namespace std; const int numemployees = 100; 4 Здесь создается именованная кон- станта с именем num..employeea, имеющая значение 100. int rnain ( ) { int empNums[numemployees]; double salary [numernployees] ;  char *names[nurnemployees] / / ... num.mployees использует- ся здесь дnя задания разме- ров массивов } В этом примере, если вам понадобится изменить размер массивов, вам надо только изменить объявление numemployees и перекомпилиро вать проrpамму. Все три массива автоматически изменят свои размеры. Дрyroе важное применение описателя cODSt заключается в защите объекта от модификации посредством указателя. Например, вы хотите предотвратить изменение значения объекта, на который указывает па раметруказатель некоторой функции. Для этоrо достаточно объявить этот параметруказатель с описателем const. После этоrо функция не сможет модифицировать объект, адресуемый через этот указатель. Дрyrими словами, если параметруказатель предваряется описателем const, никакое предложение в функции не сможет модифицировать переменную, на которую указывает этот параметр. Например, в приве денной ниже проrpамме функция negate() возвращает отрицание зна чения, на которое указывает ее параметр. Использование const в объ явлении параметра запрещает коду внyrpи функции каклибо изме нять значение, на которой указывает параметр. // Использование const с параметромуказателем. #include <iostream> using namespace std; int negate(const int *val); int main () { int result; int v = 10; result = negate (&v) ; 
274 Модуль 7. Подробнее о типах данных и операторах cout « v « " отрицание: " « result; сои t « "\n"; return о; } int negate(const int *val)  { Здесь параметр val объявлен как соnst-указаrель return  *val; } Поскольку параметр VaI объявлен, как соnstуказатель, функция не может каклибо изменить значение, на которое указывает val. Посколь ку приведенная выше функция negate( ) не пытается изменить val, про фамма будет компилироваться и выполняться правильно. Если, OДHa ко, negate( ) написана так, как показано в следующем примере, компи лятор сообщит 06 ошибке: // Это работать не будет! int negate(const int *val) { *val =  *val; // ошибка, изменение недопустимо return *val; } в этом случае проrpамма пытается изменить значение переменной, на которую указывает \'81, что недопустимо, так как m объявлена, как const. Описатель const может также использоваться с параметромссыл кой, чтобы предотвратить модификацию функцией объекта, на KOTO рый ссылается этот параметр. Например, приведенный ниже вариант функции negate( ) неверен, потому что в нем делается попытка моди фицировать переменную, на которую ссьтается val: / / и это работать не будет! int negate(const int &val) { val = val; // ошибка, изменение недопустимо return val; } volatile Описатель volatile сообщает компилятору, что значение переменной может изменяться, хотя в проrpамме нет предложений, явным образом модифицирующих эту переменную. Например, проrpамма обработки 
Описатели const и volatlle 275 прерываний от таймера может, получив адрес rлобальной переменной, обновлять ее с каждым тактом таймера. В этой ситуации содержимое переменной изменяется, хотя в проrpамме нет предложения присваи вания этой переменной какихлибо значений. Такое внешнее измене ние переменной может привести к неприятным последствиям, потому что компилятору С++ разрешено автоматически оптимизировать оп ределенные выражения в предположении, что содержимое перемен ной остается неизменным, если ее имя не встречается в левой части выражений присваивания. Если, однако, факторы за пределами про фаммы изменяют значение переменной, MOryr возникнуть проблемы. Для их предотвращения вы должны объявить такую переменную с описателем volatile (изменяемая), как это показано ниже: volatile int currentusers; Поскольку переменная currentusers объявлена как volatile, ее зна чение будет считываться из памяти при каждом обращении к ней. Минутная тренировка 1. Может ли проrpамма изменить значение сопstпеременной? 2. Если значение переменной изменяется какимилибо событиями за пределами проrpаммы, как следует объявить такую переменную?: 1. Нет. значение СОDstпеременной проrpамма изменить не может. 2. Если значение переменной изменяется событиями за пределами проrpаммы, она дол.ж  на быть объявлена с модификатором volatile Описатели классов памяти Bcero имеется пять описателей классов памяти, поддерживаемых С++: auto extern register static mutable эти описатели сообщают компилятору, как следует хранить пере менную в памяти. Описатели классов памяти должны предшествовать остальной части объявления переменной. Описатель mutable приложим только К объектам классов, которые будут обсуждаться в последующих модулях этой книrи. Остальные описатели рассматриваются в этом модуле. 
276 Модуль 7. Подробнее о типах данных и операторах auto Описатель auto объявляет локальную переменную. Он, однако, ис пользуется редко (если вообще используется), потому что локальным переменным описатель auto назначается по умолчанию. Почти невоз можно найти проrpамму, в которой использовалось бы это ключевое слово. Оно является пережитком, оставшимся от языка С. Цель _extern Все проrpаммы, с которыми вы до сих пор работали, были невелики по размеру. Однако реальные компьютерные проrpаммы обычно OKa зываются очень большими. По мере тoro, как файл с проrpаммой yвe личивает свой размер, время компиляции становится настолько боль шим, что это вызывает раздражение. Если это происходит, вы должны разбить свою проrpамму на два или даже несколько файлов. Тоrда из менения в кaкOMТO одном файле не потребуют перекомпиляции всей проrpаммы. Вы можете просто перекомпилировать измененный файл, а затем скомпоновать ("слинковать") ero с существующим объектным кодом, содержащим результат компиляции остальных файлов. Такой "мноrофайловый" подход может дать существенную экономию BpeMe ни при работе с большими проrpаммными проектами. Ключевое слово extem помоrает реализовать этот подход. Посмотрим, как это делается. В проrpаммах, состоящих из двух или большеrо числа файлов, каж дый файл должен знать имена и типы rлобальных переменных, ис пользуемых проrpаммой. Однако вы не можете просто объявить копии rлобальных переменных в каждом файле. Каждая rлобальная перемен ная в проrpамме должна быть только в одном экземпляре. Поэтому если вы попытаетесь объявить rлобальные переменные во всех фай лах, на этапе компоновки возникнет ошибка. Компоновщик (про фамма, имеющая обычно имя LINK или TLINK) обнаружит дублика тыI rлобальных переменных и откажется компоновать проrрамму. Pe шением этой проблемы служит объявление всех rлобальных переменных в одном файле и их использование в дрyrих повторных объявлениях с ключевым словом extem, как это показано на рис. 7  1. Первый файл объявляет переменные х, у и сЬ. Во втором файле име ется копия списка rлобалъных переменных из первоro файла, но к 06ъяв лениям добавлен описатель extem. Эroт описатель делает переменную известной данному модулю, однако фактически не создает ее. Дрyrими словами, extem дает возможность комmmятoру узнать имена и типы rло бальных переменных без фактическоro (повторноrо) вьщеления под них памяти. Korдa компоновIЦИК будет компоновать оба модуля вместе, все ссьтки на внешние перемеЮfые оказывaюrcя разрешенными. 
extern 277 Пе D выА сЬ аАп int х, У; char ch; Вто о оА сЬ аАп extern int х, у; extern char ch; int main () { / / ... void func22 ( ) { х = у/10; } } void func ( ) { х = 123; void func23() { у = 10; } } Рис. 7..1. Использование rлобальных переменных в отдельно компилируемых модулях До сих пор нас не заботил вопрос о различии меЖдУ объявлением и определением переменной, однако здесь он приобретает важность. Обь явление объявляет имя и тип переменной. Определение выделяет под пере менную память. В большинстве случае объявление переменной одновре.. менно является и ее определением. Однако предварив имя переменной описателем extem, вы можете объявить переменную без ее определения. Спецификация компоновки extern Ключевое слово extem предоставляет возможность задавать cпeци фuкацuю компоновки, которая представляет собой инструкцию для компилятора относительно TOro, как функция должна обрабатываться компоновщиком. По умолчанию все функции компонуются как C++ функции, однако спецификация компоновки позволяет вам скомпо" новать функцию по правилам дрyrorо языка. Обшая форма специфи катора компоновки ВЫfЛЯДИТ таким образом: extern "язык" прототипфункцuи Здесь язык определяет требуемый в данном случае язык проrpамми рования. Например, следующее предложение указывает, что функция туССопс( ) должна быть скомпонована как Сфункция: extern "СП void rnyCfunc(); Все компиляторы С++ поддерживают оба языка, и С, и С++. Не.. которые также допускают использование спецификаторов компонов" ки для языков FORTRAN, Pascal или BASIC. (Сверьтесь с дoкyмeHтa цией к вашему компилятору.) Вы можете задать способ компоновки 
278 Модуль 7 Подробнее о типах данных и операторах сразу для нескольких функций, используя такую форму специфика ции компоновки: extern "язы,," { прототипы } Для большинства проrpаммистских задач вам не потребуется ис пользовать спецификацию компоновки. Цель _ Статические переменные Переменные шпа static  это постоянно существующие переменные, действующие ВнyIpи своей функции или файла. or rл06альных перемен ных они отличаются тем, что вне своей функции или файла они неиз вecтны. Описатель static поразному влияет на локальные и rл06альные переменные, поэтому те и дрyrие будут здесь рассмотрены отдельно. Локальные статические переменные Коrда локальной переменной придается описатель static, для нее вьщеляется постоянная память практически так же, как и для rлобаль ных переменных. Это позволяет статической переменной сохранять свое значение при повторных обращениях из функций. (Друrими сло вами, ее значение не теряется при возврате из функции, в отличие от обычной локальной переменной.) Основным отличием локальной пе ременной класса static от rл06альной переменной является то, что CTa тическая локальная переменная известна только в том блоке, в KOTO ром она объявлена. Чтобы объявить переменную класса static, следует предварить ее тип ключевым словом static. Например, следующее предложение объ являет count как статическую переменную: static int count; Статической переменной можно присвоить начальное значение. Например, в следующем предложении статической переменной count дается начальное значение 200: static int count = 200; Локальная статическая переменная инициализируется только один раз, коrда начинается выполнение проrpаммы, а не каждый раз при входе в блок, rде она бьша объявлена. 
Статические переменные 279 Статические локальные переменные используются в тех случаях, коrда требуется сохранять новое значение переменной при повторных вызовах функции. Если бы статических переменных не было, во всех таких случаях пришлось бы использовать rлобальные переменные, что чревато возможными побочными эффектами. Пример статической переменной можно увидеть в приведенной ниже проrpамме. Она вычисляет текущее среднее значение для чисел, вводимых пользователем с клавиатуры: // Вычисление текущеrо среднеrо для чисел, вводимых // пользователем. #include <iostream> using namespace std; int runningavg(int i) ; int main ( ) { int пит; do { cout « "Вводите числа (1 для завершения): "; cin » num; if(num 1= 1) cout « "Текущее среднее равно: " « runningavg (num) ; cout « I \n' ; } while(num > 1); return о; } int runningavg(int i) { static int sum = о, count = о;  Поскольку пвременные sum и count являются статическими, они сохраня- ЮТ СВОИ значения при повторных вы- зовах функции running avg( ) sum = sum + i; count++; return вит / count; } в этой проrpамме локальные переменные sum и count объявлены с ключевым словом static и инициализированы значением О. Вспомним, что переменные класса static инициализируются только один раз, а не 
280 Модуль 7. Подробнее о типах данных и операторах при каждом входе в функцию. Проrpамма использует переменную runningavg( ) для подсчета и вывода на экран текущеrо среднеrо из всех чисел, введенных к этому моменту пользователем. Поскольку пе ременные sum и count являются статическими, они сохраняют свои значения при повторных вызовах runninL.avg( ), позволяя функции Ha капливать текущую сумму и количество введенных чисел. Чтобы убе диться в необходимости для этих переменных описателя static, попро буйте удалить ero из проrpаммы и запустить ее снова. Вы увидите, что проrpамма теперь работает неправильно, так как текущая сумма теря ется при каждом выходе из функции runningavg( ). fлобальиые статические перемеииые Коrда описатель static придается rлобальной переменной, компи лятор создает rлобальную переменную, известную только в том файле, в котором эта переменная объявлена. Таким образом, даже несмотря на то, что эта переменная rлобальная, дрyrие функции в дрyrих файлах ничеrо о ней не знают и не MOryт изменить ее значение. Тем самым устраняются возможные побочные эффектыI. Для решения частной за дачи, если локальная статическая переменная вас не устраивает, вы может создать небольшой файл, содержащий только функции, KOTO рым нужна rлобальная статическая переменная, отдельно скомпили ровать этот файл и использовать ero затем, не опасаясь побочных эф фектов. В качестве примера использования rлобальной статической пе ременной мы модифицируем проrpамму вычисления текущеrо среднеrо из предыдущеrо подраздела. В новом варианте проrрамма разбита на два файла, тексты которых приведены ниже. Проrрам ма также дополнена функцией reset( ), которая сбрасывает в О среднее значение. //  Первый файл  #include <iostream> using nатеерасе std; int runningavg(int i) ; void reset(); int main () { int num; do { cout « "Вводите числа (1 для завершения, 2 для сброса): 11; cin » num; if (num == ....2) { 
Статические переменные 281 reset(); continue; } i f (пит ! =  1) cout « "Текущее среднее равно: .. « runningavg (num) ; cout « I \n' ; } while(num != 1); return о; } //  Второй файл  static int sum = о, count = о;  Эти пвременные известны только в файле, в котором они объявлены int runningavg(int i) { sum = sum + i; count++; return sum / count; } void reset ( ) { sum = о; count = о; } Теперь sum и count стали rлобальными статическими переменны ми, область видимости которых оrpаничена вторым файлом. К ним допустимо обращаться из функций runniL.avg( ) и rest( ) из Bтoporo файла, но больше ниоткуда. Их, таким образом, можно сбросить вызо вом reset() и начать усреднять второй набор чисел. (Запустив проrpам му, вы можете сбросить текушее среднее, введя 2.) Однако, никакая функция вне BToporo файла не может обратиться к этим переменным. Если вы попытаетесь выполнять какиелибо операции над sum и count из первоrо файла, вы получите сообщение об ошибке. Подытожим: имя локальной статической переменной известно только в функции или блоке кода, rде она объявлена, а имя rлобаль ной статической переменной известно только в файле, rде она Haxo дится. В сущности, описатель static позволяет создавать переменные, видимые лишь в такой области, в которой они используются, что уменьшает возможность возникновения побочных эффектов. Пере менные типа static позволяют вам, проrpаммисту, скрывать одни об ласти вашей проrpаммы от дрyrих областей. Эта возможность может иметь офомное значение, если вы работаете с очень большими и сложными проrpаммами. 
282 Модуль 7 Подробнее о типах данных и операторах Спросим у эксперта Вопрос: Я слышал, что некоторые проrpаммисты не используют статиче ские rлобальные переменные. Это так? OrBeT: Хотя статические rлобальные переменные вполне допустимы и широко используются в С++проrpаммах, стандарт с++ не рекомендует их применение. Вместо них предлаrается дрyrой метод управления дocтy пом к rлобальным переменным, именно, использование пространств имен, которые также будут описаны в этой книrе. Однако статические rло бальные переменные широко используются проrpаммистами на с, потому что С не поддерживает пространства имен. По этой причине вы еще долrо будете сталкиваться со статическими rлобальными переменными. Цель _ Реrистровые переменные Возможно, наиболее широко используемым описателем класса па мяти является ключевое слово register. Этот описатель требует от KOM пилятора сохранить переменную таким образом, чтобы к ней можно бьто обращаться с максимальной скоростью. Как правило, это озна чает, что переменная будет храниться либо в реrистре процессора, либо в кешпамяти. Как вы, возможно, знаете, реrистры (и кешпа мять) работают существенно быстрее, чем основная память компьюте ра. Поэтому, если переменная хранится в реrистре, обращение к ней потребует rораздо меньше времени, чем если она находилась в памяти. Время обращения к переменным оказывает orpoMHoe влияние на CKO рость выполнения вашей проrpаммы, поэтому продуманное использо вание класса register является важным проrpаммистским приемом. Cтporo rоворя, описатель register только ставит перед компилято ром запрос, который компилятор вполне может проиrнорировать. Причину этоrо понять леrко: в процессоре имеется лишь оrpаничен ное число реrистров (и оrpаниченный объем памяти с быстрым дocтy пом), причем это число зависит от вычислительной среды. Таким об разом, если компилятор уже использовал всю наличную быструю па мять, он просто сохранит переменную обычным образом. Как правило, это не при водит к какимли60 отрицательным последствиям, но, разумеется, преимущества класса register оказываются неиспользо ванными. Как правило, вы можете рассчитывать на возможность оп тимизации по скорости по меньшей мере двух переменных. Поскольку реально быстрый доступ может быть предоставлен толь ко оrpаниченному числу переменных, важно тщательно выбрать те, которым вы назначите класс register. (Только правильно выбрав пере менные, вы добьетесь ощутимоrо эффекта в производительности.) В 
Реrистровые переменные 283 общем можно сказать, что чем чаще переменная используется в про фамме, тем больше будет выиrpыш в cKopocm при назначении ее pe rистровой переменной. Так, например, переменные, управляющие циклами или используемые внутри циклов, являются разумными KaH дидатами на включение их в класс register. Ниже приведен пример использования реrистровой переменной для повышения производительности функции summation( ), вычис ляющей сумму значений элементов массива. В этом примере предпо лаrается, что только две переменных будyr действительно оптимизи рованы по скорости доступа к ним: // Демонстрация использования реrистровой переменной. #include <iostream> using namespace stdi int summation(int nums[], int n); int main () { int vals[] = { 1, 2, З, 4, 5 }; int resulti result = summation(vals, 5); cout« "Сумма равна"« result« "\n"; return о; } // Возвращает сумму значений элементов массива чисел int. int summation(int nums[], int n) { register int i; register int surn = о; Эти переменные будyr оптимизированы по скорости доступа. for(i = о; i < n; i++) sum = sum + nurns[i]; return sum; } в этом примере переменная i, управляющая циклом for, а также sum, используемая внyrpи цикла, объявлены с указанием класса register. Поскольку обе они используются в цикле, вы получите выиr рыш от убыстрения доступа к ним. В этом примере предполаrалось, 
284 Модуль 7. Подробнее о типах данных и операторах что оптимизировать по скорости доступа фактически можно только две переменные, поэтому n и ппIПS не назначены реrистровыми, так как обращение к ним осуществляется не так часто, как к i и sum. OДHa ко, в среде, rде можно оптимизировать более двух переменных, для них также можно было задать описатель register с целью дальнейшеrо повышения производительности. (просим у эксперта Вопрос: Коrда я попьпался добавить описатель register к некоторым перемен ным в проrpамме, я не ощyrил выиrpыша в производителъности. Почему? Ответ: Блаrодаря достижениям в технолоrии компиляторов, большин ство современных компиляторов автоматически выполняют оптимизацию вашеrо кода. Поэтому во мноrих случаях добавление описателя register не увеличит производительность, так как переменные уже оптимизированы компилятором. Однако в некоторых случаях использование описателя register оказывается полезным, поскольку тем самым вы сообщаете KOM пилятору, какие переменные с вашей точки зрения наиболее важно опти мизировать. Это может быть важным в функциях, использующих большое число переменных, коrда все их оптимизировать невозможно. Таким обра зом, описатель register все еще выполняет важную роль, несмотря на успе хи в разработке компиляторов. Минутная тренировка 1. Локальная переменная класса static свое значение при повторных вызовах функции. 2. Вы используете ключевое слово extem для объявления переменной без определения этой переменной. Правильно ли это? 3. Какой описатель требует от компилятора оптимизировать переменную по скорости? 1. сохраняет 2. Правильно. 3. Описатель register требует, чтобы компилятор оптимизировал переменную по скорости. Цель _ Перечиспимые типы в С++ вы можете определить список именованных целочисленных констант. Такой список называется перечислением, а сами KOHcTaHТbI образуют перечислимый тип. Эти KOHcTaHТbI MOryт быть затем исполь зованы в любом месте, rде допустимо использование целоro числа. 
Перечислимые типы 285 Перечислимый тип определяются с помощью ключевоrо слова еппт следующим образом: епит uмятuпа {спuсо,,значенuй} cпuco,,пepeMeHHЫX; Здесь uмятuпа определяет новый перечислимый тип, а cпUCO"3Ha ченuй является перечнем разделенных запятыми имен констант дaHHO ro перечислимоrо типа. cпиco,,пepeMeHHЫX не обязателен, потому что переменные MOryт быть объявлены позже с указанием для них имени данноrо перечислимоrо типа. Следующий фраrмент определяет перечислимый тип с именем transport, а также две переменные tl и t2 этоrо типа: еnит transport { car, truck, airplane, train, boat } tl, t2.; После тoro, как вы определили перечислимый тип, вы можете объ являть дополнительные переменные этоro типа. Например, следуюшее предлоiteние объявляет одну переменную типа transport с именем how: transport how; Это предложение можно записать и иначе: enum transport how; . Однако использование здесь ключевоrо слова еппт является избы точным. Язык С (который тоже поддерживает перечислимые типы) требует использования именно этой второй формы, поэтому вы CMO жете встретить ее в некоторых проrpаммах. Считая, что перечислимый тип transport объявлен указанным выше образом, следующее предложение присваивает переменной how значе ние airplane: how = airplane; При использовании перечислимых типов существенно понимать, что каждая константа перечислимоrо типа (или просто перечислимая константа) обозначает целое число и может быть использована в лю бом целочисленном выражении. Если перечислимые KOHcTaHТbI не инициализированы отдельно, первое значение из списка обозначает О, второй 1 и т. д. Таким образом, предложение cout « car « I I « train; выводит на экран О 3. Хотя перечислимые KOHcтaHТbI автоматически преобразуются в цe лочисленные значения, последние автоматически не преобразуются в 
286 Модуль 7. Подробнее о типах данных и операторах перечислимые константы. Например, следуюшее предложение непра вильно: how = 1; // Ошибка Такое предложение приведет к ошибке на этапе компиляции, так как компилятор не выполняет автоматическоrо преобразования int в transport. Вы можете устранить ошибку в предыдущем предложении, использовав приведение типа, как это показано ниже: how = (transport) 1; //теперь правильно, но, возможно, / / плохой стиль Приведенное предложение присваивает переменной bow значение truck, так как именно эта константа transport имеет значение 1. как yкa зано в комментарии, хorя это предложение вполне правильно, оно будет рассматриваThCЯ, как пример IUloxoro стиля проrpаммирования, если только оно не используется в силу какихто необычных обстоятельств. Имеется возможность задать значения одной или нескольким KOH стантам перечислимоrо типа с помощью инициализатора. Для этоrо после имени константы следует указать знак равенства и целое число. Если используется инициализатор, каждая следующая константа при обретает значение на 1 большее, чем предыдущая. Например, приве денное ниже предложение присваивает значение 10 константе airplane: enum transport { car, truck, airplane = 10, train, boat }; Теперь значения констант будут следующими: car О truck 1 airplane 10 train 11 boat 12 Часто можно встретиться с предположением (неверным), что пере числимые константы MOryт быть введены или выведены в виде строк, отображающих их имена. Это не так. Например, следующий фрarмент не будет выполняться так, как задумано: // Этот фраrмент не выведет "train" на экран. how = train; cout « how; Не забывайте, что обозначение train представляет собой просто имя целоrо числа; это не строка. Поэтому приведенный выше фраrмент 
Перечислимыетипы 287 выведет на экран числовое значение train, а не строку "train". Между прочим, создание кода, который будет вводить и выводить перечисли мые константы в виде строк, оказывается довольно утомительным за нятием. Например, для Toro, чтобы вывести на экран словами вид транспортноrо средства, которому отвечает значение переменной bo потребуеreя следующий код: swi tch (how) { case car: cout « "Autornobile"; break; case truck: cout « "Truck"; break; case airplane: cout « "Airplane"; break; case train: cout « "Train"; break; case boat: cout « ".Боаt 11 ; break; } Иноrда оказывается возможным объявить массив строк и исполь зовать значения перечислимых констант в качестве индексов для пре образования значений в соответствующие им строки. Например, при веденная ниже проrpамма выводит на экран имена трех транспортных средств: // Демонстрация работы с перечислимым типом. #include <iostream> using namespace std; еnиrn transport { car, truck, airplane, train, boat }; char nаmе[] [20] = { "car [автомобиль)", "track [rрузовик)", "airplane [самолет]", "train [поезд]", "boat [лодка]" . } ; int rnain ( ) 
288 Модуль 7. Подробнее о типах данных и операторах { transport how; how = car; t cout «name[how] « '\n / ; Значение перечислимой константы ИСnOЛlЩeтся в качестве индекса массива. how = airplane; cout « name[how] « '\n/; how = train; cout « name [how] « I \n I ; return о; } Вот вывод этой проrpаммы: car [автомобиль] airplane [самолет] train [поезд] Метод, использованный в этой проrpамме для преобразования значения перечислимой константы в строку, может быть приложен к любому перечислимому типу, если только он не содержит ини uиализаторов. Для правильной индексаuии массива строк значения перечислимых констант должны начинаться с нуля и располаrаться далее cтporo в восходящем порядке, каждое точно на 1 больше пре дыдущеrо. Использование перечислимых констант в качестве индексов cтpo ковых массивов широко распространено в проrpаммировании. Напри мер, перечисления MOryт использоваться в компиляторах для опреде ления таблиц символических обозначений. Цель _typedef С++ позволяет вам определять новые имена типов данных с помо щью ключевоrо слова typedef. Коrда вы используете typedef, вы факти чески не создаете новый тип данных, а просто даете новое имя сущест вующему типу. Это средство помоrает повысить переносимость Ma шиннозависимых проrpамм; при переносе проrpаммы в дрyryю среду достаточно изменить только предложения typedef. Новые имена типов также способствуют самодокументированию вашеrо кода, позволяя использовать в нем описательные имена для стандартных типов дaн ных. Общая форма предложения typedef выrлядит так: 
Побитовые операторы 289 typedef тип имя; Здесь тип  это любой допустимый тип данных, а имя обозначает новое имя для этоrо типа. Новое имя не заменяет старое, существую щее имя, а является дополнением к нему. Например, вы можете соз дать новое имя для типа Ooat: typedef float balance; Это предложение сообщит компилятору, что он должен распозна вать слово balance как новое имя для типа lloat. Далее вы можете соз дать переменную типа t10at с помощью обозначения balance: balance overdue; Здесь over...due является переменной с плавающей точкой типа balance, т. е. фактически типа lloat. Минутная тренировка 1. Перечислимый тип представляет собой список именованных констант 2. С KaKoro целоrо числа начинаются значения перечислимых констант? 3. Покажите, как объявить BigInt дрyrим именем типа 1оng int. 1. целочисленных 2. Значения перечислимых констант начинаются с О. 3. typedef long int BigInt; Цель _ Побитовые операторы Поскольку С++ разрабатывался с целью предоставить полный дoc туп к аппаратуре компьютера, в нем предусмотрены средства для рабо ты с отдельными битами внутри байта или слова. эти средства реали зованы в виде побитовых операторов. Под побитовыми операциями подразумеваются анализ, установка или сдвиr отдельных битов в таких единицах памяти, как байт или слово, которым в С++ соответствуют типы char и int. Побитовые операции не MOryr применяться к типам 0001, lloat, doubIe, long doubIe и void, как и к дрyrим более сложным ти пам данных. Побитовые операции имеют большое значение для широ Koro кpyra задач проrраммирования системноrо уровня, в которых анализируется или устанавливается информация о состоянии устрой ства. В табл. 7..1 перечислены побитовые операторы. Далее каждый из них будет рассмотрен отдельно. 
290 Модуль 7. Подробнее о типах данных и операторах Таблица 7-1 Побитовые операторы Оператор & I А Действие И (AND) ИЛИ (ОА) Исключающее ИЛИ (ХОА) Дополнение до единицы, НЕ (NOT) Сдвиr вправо Сдвиrвлево > < Операторы И, ИЛИ, исключающее ИЛИ и НЕ Побитовые операции И, ИЛИ, исключающее ИЛИ и дополнение до единицы (НЕ) определяются теми же таблицами истинности, что и их лоrические эквиваленты, за исключением Toro, что они действуют на битовом уровне. Исключающее ИЛИ (XOR) действует в cooтвeTCT вии со следуюшей таблицей истинности: р q p"q о о о 1 О 1 1 1 О О 1 1 Как показывает эта таблица, результат операции XOR истинен, только если в точности один из операндов истинен; в противном слу чае результат ложен. С точки зрения наиболее обычноrо использования операция поби TOBoro И представляет собой способ сброса (Т. е. установки в О) OT дельных битов. Дрyrими словами, любой бит, равный О в любом из операндов установит в О соответствующий бит результата. Например: 11010011 1 О 1 О 1 О 1 О & 1 О О О О О 1 О Приведенная ниже проrpамма демонстрирует использование опе ратора & для преобразования строчных букв латинскоrо алфавита в прописные путем сброса шестоrо бита их кода в О. в наборе символов ASCII строчные буквы идут в том же порядке, что и прописные, но коды строчных букв точно на 32 больше кодов прописных. Таким об разом, для преобразования строчных букв в прописные можно просто установить в О шестой бит, что и делает эта проrpамма: 
Побитовые операторы 291 // Преобразование в прописные буквы с помощью побитовоrо И. #include <iostream> using namespace std; int main () { char ch; for(int i = О ; i < 10; i++) { ch = I а' + i; cout « сЬ; // Это предложение сбрасывает 6й бит. сЬ = сЬ & 223; 11 буква сЬ теперь прописная I cout « ch « " ": ИсполlЩeМ побитовое И дnя сброса 6-ro бита. } cout « .. \n" : return о; } Вывод профаммы выrлядит таким образом: аА ЬВ сС dD еЕ fF gG hH iI jJ Значение 223, использованное в предложении с оператором &, пред ставляет собой десятичное представление числа 1101 1111. Таким обра зом, операция И оставляет все биты в сЬ неизменными, кроме шестоrо, который устанавливается в О. Оператор & полезен также в тех случаях, коrда вы хотите опреде лять состояние TOro или иноrо бита. Например, следующее предложе иие проверяет, установлен ли 4й бит в переменной status: if(status & 8) cout « "4й бит установлен": Здесь используется число 8, потому что в двоичном коде оно co ставляет 0000 1000. Дрyrими словами, число 8, транслируясь в двоич ный код, устанавливает в байте только один четвертый бит. Поэтому предложение if дает истину, только если четвертый бит переменной status установлен. Любопытный при мер этой операции можно увидеть в приведенной ниже функции showbinary, которая выводит на экран в двоичном формате содержимое cBoero apryмeHTa. Позже в этом модуле вы будете использовать sbowbinary для изучения действия дрyrих по битовых операций. 
292 Модуль 7. Подробнее о типах данных и операторах / / Отображение битов байта. void showbinary (unsigned int и) { int t; for(t=128; t > о; t = t/2) if(u & t) cout « "1 "; else cout « "о "; cout « "\n"; } Функция showbinary с ПОМОЩЬЮ операции побитовоro И выполняет последовательный анализ каждоrо бита в младшем байте переменной u с целью определения, установлен этот бит или сброшен. Если бит ycтa новлен, на экран выводится символ 1, если сброшен  выводится О. Побитовая операция ИЛИ, как противоположная И, может быть использована для установки отдельных битов. Любой бит, установлен ный в 1 в любом из операндов, установит соответствующий бит pe зультата в 1. Например: 11010011 1 О 1 О 1 О 1 О I 11111011 с помощью операции ИЛИ можно заставить приведенную выше проrpамму преобразования строчных букв в прописные работать Ha оборот, пре06разуя прописные буквы в строчные: // Преобразование в строчные буквы с помощью побитовоrо ИЛИ. #include <iostream> using namespace std; int main ( ) { char ch; for(int i = О ; i < 10; i++) { ch = I А' + i; cout « ch; Используем побитовое ИЛИ ДЛЯ установки 6-ro бита. / / Это прдложение устанавливает 6й бит. J ch = ch I 32; // буква ch теперь строчная 
Побитовые операторы 293 cout « сЬ « " "; } cout « "\п"; return о; } Вот вывод этой nporpaMMbl: Аа ВЬ Сс Dd Ее Ff Gg Hh Ii Jj Установкой шестоrо бита каждая прописная буква преобразуется в свой строчный эквивалент. Операция исключающеrо ИЛИ, обычно обозначаемая как XOR, yc тановит бит, только если сравниваемые Биты различны, как это про иллюстрировано ниже: 01111111 10111001 л 11000110 Оператор л имеет интересное свойство, которое позволяет простым способом шифровать сообщения. Если над некоторым значением Х BЫ полняется операция XOR с дрyrим значением  а затем над результатом опять выполняется операция XOR с тем же  то в итоre получается пер воначальное значение Х. Дрyrими словами, после выполнения операций Rl == Х л У: , R2 == R 1 л у. , R2 имеет то же значение, что и Х. Таким образом, результат ПOCJ1едова тельности из двух операций XOR, использующих то же значение в качест ве BТOporo операнда, дает первоначальное значение. Эrим обстоятельством можно воспользоваться Д1IЯ еоздания простой шифрующей npoфаммы, в кoroрой HeKoropoe целое число служит ключом, выcryпaющим В качестве операнда при шифровании и расшифровке сообщения посредcrвoм опе рации XOR над символами сообщения. При шифровании операция XOR применяется первый раз, давая в результате зашифрованный текст. При расшифровке та же операция XOR применяется второй раз, давая в pe зулътате исхоДНЫЙ 'reKCТ. ниже приведен простой npимер, используюIЦИЙ Э1ОТ подход для шифрования и расшифровки KOpoткOro сообщения: // Использование XOR для шифрования и расшифровки сообщения. #include <iostream> using narnespace std; 
294 Модуль 7. Подробнее о типах данных и операторах int main () { char msg [] = "This is а test"; char key = 88; cout « "Исходное сообщение: n « msg « "\n"; for(int i = О ; i < strlen(msg); i++) msg [i] = msg [i] л key;  Здесь создается зашифрованная строка cout « "Зашифрованное сообщение: " « msg « "\п"; for(int i = О ; i < strlen(msg); i++) rnsg [i] = msg [i] л key;  Здесь создается расшифрованная строка cout « "Расшифрованное сообщение: .. « msg « "\n"; return о; } Вот вывод этой nporpaMMbI:: Исходное сообщение: This is а test Зашифрованное сообщение: +Оl+хl+х9х,=+, Расшифрованное сообщение: This is а test Как и подтверждает этот вывод, в результате двух операций исклю чающеrо ИЛИ с одним и тем же ключом образуется исходное расшиф рованное сообщение. Оператор НЕ (дополнение до единицы, или обратный код) изменяет состояние всех битов операнда на обратное. Например, если некоторое целое под названием А имеет двоичный код 1001 0110, тоrда  А 06разу ет код 0110 1001. Приведенная ниже проrpамма демонстрирует действие оператора НЕ, выводя на экран число и ero обратный код в двоичной форме с помощью рассмотренной ранее функции show binary( ): #include <iostream> using namespace std; void showbinary (unsigned int и) ; int main () { unsigned и; cout « "Введите число между О и 255: "; cin » и; cout « "Вот двоичный код числа: "; 
Операторы сдвиrа 295 showbinary(u); cout « "Вот обратный код числа: "; show.....binary(u)j return О i } // Отображение битов байта. void showbinary(unsigned int и) { int ti for(t=128; t>Oi t = t/2) if(u & t) cout « "1 "; else cout « "О "; сои t « "\n" i } Ниже приведен результат проrона проrpаммы: Введите число между О и 255: 99 Вот двоичный код числа: О 1 1 О О О 1 1 Вот обратный код числа: 1 О О 1 1 1 О О Все рассмотренные операторы, &, 1, л и ""', выполняют свои опера ции непосредственно над каждым битом значения независимо от oc тальных битов. Поэтому побитовые операторы обычно не используют ся в условных предложениях так, как это имеет место для операторов лоrических и отношения. Например, если х равно 7, то х && 8 дает ис тину, а х & 8 дает ложь. Цель _ Операторы сдвиrа Операторы сдвиrа, » и «, сдвиrают все биты переменной вправо или влево на указанное число битов. Общая форма оператора сдвиra вправо: nеремеННQЯ » чuслобuтов Общая форма оператора сдвиrа влево: nеремеННQЯ < < чuслобuтов 
296 Модуль 7. Подробнее о типах данных и операторах Значение чuслобuтов определяет, на сколько битовых мест следует сдвинуть операнд. Каждый сдвиr влево при водит к тому, что все биты указанной переменной сдвиrаются влево на одну позицию, а в самый правый бит переменной записывается ноль. Каждый сдвиr вправо при водит к тому, что все биты указанной переменной сдвиrаются вправо на одну позицию, а в самый левый бит переменной записывается ноль. Если, однако, переменная представляет собой целое со знаком, coдep жащее отрицательное значение, тоrда каждый сдвиr вправо записывает с левой стороны числа 1, что сохраняет знаковый бит числа. Учтите, что сдвиrи влево и вправо не являются циклическими. Дрyrими словами, биты, вьщвиraемые с одноrо конца числа, не переходят в ero начало. Операторы сдвиrов используются только с целыми типами, такими как int, char, long int и short int. Их нельзя применять, например, к зна чениям с плавающей точкой. Побитовые сдвиrи MOryт быть весьма полезны при расшифровке ввода из внешних устройств вроде аналоrоцифровых преобразовате лей и при чтении информации о состоянии устройства. Эти операторы можно также использовать для выполнения очень быстроrо умноже ния и деления целых чисел. Сдвиr влево весьма эффективно умножает число на 2, а сдвиr вправо делит ero на 2. Приведенная ниже проrpамма иллюстрирует действие операторов сдвиrа: // Пример операций сдвиrа. #include <iostream> using namespace std; void showbinary(unsigned int и); int main () { int i=l, ti / / сдвиr влево for(t=O; t < 8; t++) { showbinary(i); i = i « 1;  Сдвиr влево переменной i на одну позицию } cout « 11 \n" ; / / сдвиr вправо for(t=O; t < 8; t++) { i = i » 1;  showbinary(i); Сдвиr вправо переменной i на O/J1fY позицию } 
Операторы сдвиrа 297 return о; } // Отображение битов байта. void showbinary(unsigned int и) { int t; for(t=128; t>O; t=t/2) if(u & t) cout « "1 "; else cout « "О "; cout« "\n"; } Проrpамма ВЫВОДИТ на экран следующее: о О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О 1 О О О О О О О 1 О О О О О О О О 1 О О О О О О О О 1 О О О О О О О О 1 О О О О О О О О 1 О О О О О О О О 1 О О О О О О О О 1 О Минутная тренировка 1. Как записываются операторы для побитовых операций И, ИЛИ, НЕ и исключающее ИЛИ? 2. Побитовый оператор воздействует на каждый бит в отдельности. Пра вильно ли это? З. Покажите, как сдвинyrь целочисленную переменную х на два бита влево. 1. Побитовые операторы имеют обозначения &, 1,  и л. 2. Правильно. 3. х« 2 
298 Модуль 7. Подробнее о типах данных и операторах Проект 7..1 Создание функций цикпическоrо побитовоrо сдвиrа в С++ имеются два оператора сдвиra, но нет оператора циклическооо сД8иra. Циклический сдвиr аналоrичен рассмотренным выше операциям сдвиra влево и вправо, за исключением тooo, что бит, выдвинуrый из пе ременной с одноrо конца, переносится на дрyrой конец. Таким образом, биты не теряюrcя, а roлько перемещаются. В принципе существуют два циклических сдвиra, влево и вправо. Например, число 1010 0000, цикли чески сдвинyroe влево на одну позицию, дает 0100 0001. То же число, циклически сдвинyroe вправо на одну позицию, дает 0101 0000. В каждом случае бит, вьщвинуrый из числа, вcraвляется в крайнюю позицию с дpy roй croроны. Можно подумать, что orcyrcтвие в С++ операторов цикли ческих сдвиrов является eoo недоработкой, однако в действительности это не так, поскольку, используя дрyrие побитовые операторы, леrко сконструировать как лево, так и правосторонний циклический сдвиr. В данном проекте создаются две функции: rrotate( ) и Irotate( ), цик лически сдвиrающие байт в правом или в левом направлении. Каждая функция принимает два параметра. Первый определяет сдвиrаемое значение. Второй задает число битов сдвиrа. Каждая функция возвра щает результат. Проект использует несколько видов манипуляции с битами и демонстрирует побитовые операции в действии. Шаr за шаrом 1. Создайте файл с именем rotate.cpp. 2. Включите в файл приведенную ниже функцию Irotate(). Она BЫ полняет циклический сдвиr влево. // Циклический сдвиr байта влево на n позиций. unsigned char lrotate(unsigned char val, int n) { unsigned int t; t = val; for(int i=O; i < n; i++) { t = t « 1; /* Если бит выдвиrается из байта, это будет бит 8 целоrо числа t. В этом случае поместим этот бит с правой стороны t. */ 
Операторы сдвиrа 299 if (t & 256) t = t I 1; // поместим 1 в самый правый бит } return t; // вернем младшие 8 bit. } Функция Irotate() работает следующим образом. Через параметр \111 функции передается сдвиrаемое значение, а через параметр n  число позиций сдвиrа. Функция присваивает значение val переменной t, KO торая объявлена как unsigned int. Перенос значения в unsigned int необ ходим, он позволяет получить биты, вьшвиrаемые с левой стороны. И вот почему. Поскольку число unsigned int больше, чем байт, коrда бит выдвиrается с левой стороны байтовоrо числа, он просто смещается в бит 8 числа int. Значение этоrо бита может быть затем скопировано в бит О байтовоrо числа, что и обеспечивает циклический сдвиr. Фактический сдвиr осуществляется следующим образом. Запуска ется цикл, который выполняет заданное число сдвиroв, по одному в каждом шarе. Внутри цикла значение t сдвиrается влево на одну пози цию. В результате этой операции в правый бит t записывается О. OДHa ко, если значение бита 8 результата (а это как раз бит, вьшвинутый из байта) равно 1, бит О устанавливается в 1. В противном случае бит О oc тается со значением О. Бит 8 анализируется предложением if (t & 256) Значение 256  эro десятичное число, в KOI'OpoM установлен один бит 8. Таким образом, t & 256 будет истинно, только если t имело 1 в бите 8. После завершения сдвиra функция возвращает t. Поскольку Irotate( ) объявлена с типом возврата unsigned cbar, фактически возвращаются только младшие 8 бит переменной t. З. Добавьте в файл функцию rrotate( ), приведенную ниже. Она BЫ полняет циклический сдвиr вправо: // Циклический сдвиr байта вправо на n позиций. unsigned char rrotate(unsigned char val, int n) { unsigned int t; t = val; // Сначала сдвинем значение на 8 бит влево. t = t « 8; for(int i=O; i < n; i++) { t = t » 1; 
300 Модуль 7. Подробнее о типах данных и операторах /* Если бит выдвиrается из байта, это будет бит 7 целоrо числа t. В этом случае поместим этот бит с певой стороны t. */ if (t & 128) t = t I 32768; // поместим 1 в самый левый бит } /* Наконец сдвинем результат назад в младшие 8 бит t. */ t = t » 8; return t; } ЦИЮIИЧеский сдвиr вправо несколько более сложен, чем сдвиr влево, так как значение, передаваемое в vaI, должно быть сначала перемещено во вroрой байт t, чтобы не потерять биты, вьшвиrаемые из этоro байта с правой croроны. После завершения операции циклическоro сдвиra полу чеIOlое значение должно быть сдвинyro назад в младший байт t, чтобы ero можно бьшо использовать в качестве возвращаемоro значения. По скольку бит, вьшвиraемый из старшеro байта, перемещается в бит 7, сле дующее предложение проверяет, равно ли ero значение 1: if(t & 128) Десятичное число 128 имеет установленным только бит 7. Если бит 7 t установлен, над t выполняется операция побитовоrо ИЛИ с числом 32768, в котором установлен один только бит 15, а биты 14...0 сброше ны. В результате в t устанавливается бит 15, а остальные БитыI осТают ся без изменения. 4. Ниже приведен полный текст проrpаммы, которая демонстрирует использование функций Irotate( ) и rrotate( ). Проrpамма использует для вывода результата каждоro сдвиra на экран функцию showbinary( ). /* Проект 71 Функции циклическоrо сдвиrа байта влево и вправо. */ #include <iostream> using namespace std; unsigned char rrotate(unsigned char val, int n); unsigned char lrotate(unsigned char val, int n); void showbinary(unsigned int и); int main ( ) 
Операторы сдвиrа 301 { char ch = 'Т'; cout « "Исходное значение в двоичном коде:\п"; showbinary (ch) ; cout « "Циклический сдвиr вправо 8 раз:\п"; for(int i=O; i < 8; i++) { ch = rrotate(ch, 1); showbinary(ch); } cout « " Циклический сдвиr влево 8 раз:\п"; for(int i=O; i < 8; i+t) { ch  lrotate(ch, 1); sh()wblIlary (ch) ; } return о; } // Циклический сдвиr байта влево на n позиций. unsigned char lrotate(unsigned char val, int п) { unsigned int t; t :;: val; for(int i=-O; i < п; i++) { t = t « 1; /* Если бит выдвиrается из байта, это будет бит 8 целоrо числа t. В этом случае поместим этот бит с правой стороны t. */ if(t & 256) t = t I 1; // поместим 1 в самый правый бит } return t; // вернем младшие 8 бит. } // Циклический сдвиr байта вправо на n позиций. unsigned char rrotate(unsigned char val, int п) { unsigned int t; t = val; // Сначала сдвинем значение на 8 бит влево. 
302 Модуль 7. Подробнее о типах данных и операторах t = t « 8: for{int i=Oi i < ni i++) { t = t » 1: /* Если бит выдвиrается из байта, это будет бит 7 целоrо числа t. В этом случае поместим этот бит с левой стороны t. */ if (t & 128) t = t I 32768; // поместим 1 в самый левый бит } /* Наконец сдвинем результат назад в младшие 8 бит t. */ t = t » 8: return t; } // Выведем на экран биты байта. void show.....binary (unsigned int и) { int t i for{t=128; t>Oi t = t/2) if{u & t) cout « "1 "; else cout « "О "; cout « "\n": } Ниже приведен вывод проrpаммы: Исходное значение в двоичном коде: О 1 О 1 О 1 О О Циклический сдвиr вправо 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 О О Циклический сдвиr влево 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 О О Цель _Оператор? Одним из самых удивительных операторов с++ является оператор ? Этот оператор часто используется вместо предложений if-else в Ta кой общей форме: if (условие) var == выражение 1; else var == выражение2; Здесь значение, присваиваемое переменной va1; зависит от резуль тата вычисления условия, управляющеrо предложением if. Оператор? называется тернарным (троичным) оператором, потому что он требует трех операндов. Ero общая форма: Ехр 1 ? Ехр2 : ЕхрЗ; Здесь Ехрl, Ехр2 и ЕхрЗ  выражения. Обратите внимание на ис пользование и местоположение двоеточия. Значение выражения ? определяется следующим образом. Оценива ется выражение Ехрl. Если оно истинно, тоrда выражение Ехр2 вычис ляется и становится значением вcero выражения? Если же Ехрl ложно, тоrда выражение ЕхрЗ вычисляется и становится значением вcero Bыpa жени я ? Рассмотрим пример, в котором пере мен ной absvalue присваи вается абсолютное значение переменной val: absval = val < О ? Ya! : уа! i / / получим абсолютное значение уа! Здесь переменной absval будет при с вое но значение vaJ, если val больше или равно О. Если же уаl отрицательно, тоrда absval будет при свое но значение  val, что даст, разумеется, положительное значение. Тот же код, написанный с использованием предложения if-else, выrля ДИТ следующим образом: if(val < О) absval = val; else absval = vali 
304 Модуль 7. Подробнее о типах данных и операторах Вот дрyrой пример использования оператора ? Приведенная ниже проrpамма делит два числа, но не допускает деления на ноль: /* Эта проrрамма использует оператор? для предотвращения деления на ноль. */ #include <iostream> using namespace std; int di v.....zero ( ) ; int main () { int i, j, result; cout « "Введите делимое и делитель: "; cin » i » j; // Это предложение предотвращает ошибку деления на ноль. resul t = j ? i / j : di v.....zero ();  Оператор? используется для предот- вращения ошибки деления на ноль. cout « "Результат: " « result; return о; } int di v.....zero ( ) { cout « "Не Mory делить на ноль. \n"; . return о; } Здесь, если j не равно нулю, то i делится на j, а результат присваива ется переменной result. В противном случае вызывается обработчик дe ления на ноль div.....zero(), и переменной result присваивается значение О. Цель _ Оператор-запятая Дрyrи:м интересным оператором с++ является запятая. Вы уже виде ли несколько примеров применения этоro оператора в цикле for, rде он позволял выполнить множесmенные инициализации или приращения. В действительности запятая может быть использована как часть любоro вьфажения. Она сцепляет вместе несколько вьфажений. Значение спи 
Оператор-запятая 305 ска выражеНИЙ, рaзnеленных запятой, совпадает со значением caмoro правоrо выражения. Значения дрyrиx выражений будyr отброшены. это значит, что самое правое выражение становится значением вcero Bыpa жения с ра:шеляющими запятыми. Например, в пре.можении var = (count=19, incr=10, count+1)j сначала переменная count получает значение 19, затем переменной incr присваивается значение 10, далее к count прибавляется 1 и, наконец, пе ременная var получает значение Bcero выражения с запятыми, в данном случае 20. Скобки здесь необходимы, потому Ч1О операторзапятая имеет более низкий относительный приоритет, чем оператор присваивания. Для Toro, чтобы наrлядно наблюдать действие операторазапятая, попробуйте запустить такую проrpамму: #include <iostream> using namespace std; int main () { int i, jj j = 10; i = (j++, j+l00, 999+j);  Запятая обозначает "делать это, и это, и это". cout « i; return о; } Эта проrpамма выводит на экран "1010". И вот почему. Начальное значение переменной j равно 10. Затем j увеличивается на 1. Далее j прибавляется к числу 100. Наконец, j (которая все еще равна 11) при бавляется к числу 999, что дает в результате 1010. Существенно, что наличие запятой приводит к тому, что выполня ется последовательность операций. Если запятая указывается с правой стороны в предложении присваивания, присваиваемое значение опре деляется, как значение caмoro правоrо выражения в списке выраже ний, разделяемых запятыми. Вы можете в какойто степени считать, что операторзапятая имеет то же значение, что и союз "и", если ero использовать во фразе "сделай это, и это, и это". Минутная тренировка 1. Каково будет значение х после оценки этоrо выражения: х = 10 > 11 ? 1 : о; 
З06 Модуль 7. Подробнее о типах данных и операторах 2. Оператор ? называют троичным оператором, потому что он имеет операнда. 3. Для чеrо предназначена запятая? 1. Значение х будет равно О. 2. три 3. Запятая заставляет выполниться последовательности пре.пложений. Множественное присваивание с++ допускает удобный способ присваивания нескольким пере менным одно и TOro же значения, что достиraется использованием He скольких операций присваивания в одном предложении. Например, этот фраrмент присвоит переменным count, incr и index значение 10: ount = incr = index = 10; в профессионально написанных проrpаммах вы часто увидите при сваивание переменным общеrо значения таким образом. Цель _Составное присвивание в с++ предусмотрен специальный оператор COCTaBHoro присваива ния, который упрощает вид предложений присваивания определенно ro рода. Например, предложение х = х+ 1 О ; может быть написано с использованием оператора cocтaBHoro при сваивания следующим образом: х += 10; Пара операторов +== требует от компилятора присвоить х значение х плюс 10. Операторы cocтaBHoro присваивания существуют для всех би нарных операций в с++ (т. е. операций, требующих двух операндов). Общая форма операторов cocтaBHoro присваивания выrлядит так: nеременная оператор == выражение; Вот дрyrой пример: х = x100; 
Использование оператора sizeof 307 записывается проще как x=100; Поскольку предложение cocтaвHoro присваивания позволяет эко номить на вводимых знаках, ero иноrда называют кратким пpucвaивa нием. В профессионально написанных проrpаммах на с++ краткое присваивание используется чрезвычайно широко, и вы должны уметь им пользоваться. Цель _ Использование оператора sizeof Иноrда бывает нужно знать размер в байтах HeKoтoporo типа дaн ных. Поскольку размеры встроенных типов с++ различаются для раз ных компьютерных сред, невозможно заранее указать их для всех си туаций. Для решения этой проблемы в с++ включен оператор BpeMe ни компиляции sizeof, который имеет следующие общие формы: sizeof (тип) sizeof имяпеременной Первый вариант возвращает размер указанноrо типа данных, а BTO рой  размер указанной переменной. Таким образом, если вы хотите определить размер какоrолибо типа данных, например, int, вы долж ны поместить имя типа в крyrлые скобки. Если же вам нужен размер переменной, скобки не нужны, хотя при желании вы можете их ис пользовать и здесь. Чтобы посмотреть, как работает sizeof, запустите эту короткую про фамму. Для мноrих 32разрядных сред она выведет значения 1,4,4 и 8: / / Демонстрация sizeof. #include <iostream> using namespace std; int main () { char Chi int i; cout «sizeof сЬ« ' '; // размер char cout « sizeof i « I '; // размер int 
З08 Модуль 7. Подробнее о типах данных и операторах cout« sizeof (float) « I '; // размер float cout « sizeof (double) « ' '; // размер double return о; } Вы можете использовать оператор sizeof с любыми типами данных. Если, например, применить ero к массиву, он вернет число байтов в этом массиве. Рассмотрим такой фраrмент: int nums [4] ; cout « sizeof nums; Полаrая тип int 4байтовым, этот фраrмент выведет на экран число 16 (т. е. 4 элемента по 4 байта). Как уже отмечалось выше, sizeof является оператором времени KOM пиляции. Вся информация, необходимая для вычисления размера пе ременной или типа данных, становится известна во время компиля ции. Оператор sizeof прежде Bcero помоrает вам создавать переноси мый код, который будет зависеть от размеров типов данных С++. Не забывайте, что поскольку размеры типов с++ определяются реализа цией вычислительной среды, делать заранее предположения об этих размерах в своих проrpаммах  плохой стиль. Минутная тренировка 1. Покажите, каким образом присвоить переменным tl, t2 и tЗ значение 10 в одном предложении присваивания. 2. Как можно записать подрyroму такое предложение: х = х +10; 3. Оператор sizeof возвращает размер переменной или типа в 1. t1 = t2 = tЗ = 10; 2. х += 1 О о; 3. байтах Обзор относительных приоритетов В табл. 72 приведен порядок относительных приоритетов, от BЫC шеro в низшему, для всех операторов С++. Большинство операторов выполняются слева направо. Унарные операторы, операторы присваи вания и оператор? выполняются справа налево. Заметьте, что таблица 
Использование оператора sizeof 309 включает несколько операторов, с которыми вы еще не сталкивались. В основном это операторы, используемые в объектноориентирован ном проrpаммировании. Таблица 7 -2. Относительный приоритет операторов С++ Относительный приоритет Операторы Высший ()[]->::. !  ++  .. * & sizeof new delete typeid при.. ведения типов * ..>* * /% +- « » < <= > >= == ,= & А I && 11 ?:  +  * / % > < & A I    _... ....  ....  ...    Низший . ....P.I.. .. . .PP. ., 1. Покажите, как следует объявить переменную test типа int, чтобы ее нельзя было изменить в проrpамме. Дайте ей начальное значение 100. 2. Описатель volatile сообщает компилятору, что переменная может быть изменена какимилибо событиями вне проrpаммы. Справед ливо ли это yrверждение? 3. Какой описатель вы используете в мноrофайловом проекте, чтобы один файл узнал о rлобальных переменных, объявленных в дрyrом файле? 4. Каково наиболее важное свойство статической локальной перемен ной? 5. Напишите проrpамму, содержащую функцию с именем counter( ), которая просто отсчитывает число своих вызовов. Функция дoтк на возвращать текущее число вызовов. 
310 Модуль 7. Подробнее о типах данных и операторах 6. Рассмотрите приведенный ниже фраrмент. Какую переменную сле дует объявить реrистровой, чтобы получить максимальный выиr рыш в скорости? int myfunc () { int х; int у; int z; z = 10; у = о; for(x=z; х < 15; х++) у += х; return у; } 7. Чем & отличается от &&? 8. Что делает это предложение? х *= 10; 9. Используя функции rrotate( ) и rrotate( ) из Проекта 7  J, возможно шифровать и расшифровывать строку. Для шифрования строки выполните циклический сдвиr каждой буквы влево на некоторое число битов, задаваемое ключом. Для расшифровки выполните циклический сдвиr каждой буквы вправо на то же число битов. Ис пользуйте ключ, состоящий из строки символов. Можно приду мать MHoro способов вычисления числа сдвиrов, исходя из coдep жимоrо ключа. Проявите творческий подход. Решение, показанное в Приложении А, является лишь одним из мноrих возможных. 10. Самостоятельно усовершенствуйте функцию showbinary( ), чтобы она показывала в значении unsigned int все биты, а не только пра вые восемь. 
Модуль 8 Классы и объекты Ц-Др,qиr...8, TO. PelЛ, 8.1 Познакомиться с общей формой класса 8.2 Приобрести навыки в создании классов и объектов 8.3 Освоить добавление в класс членов"функций 8.4 Научиться использовать конструкторы и деструкторы 8.5 Узнать о параметрических конструкторах 8.6 Освоить создание встроенных функций 8.7 Получить представление о массивах объектов 8.8 Научиться инициализировать массивы объектов 8.9 Изучить взаимодействие указателей и объектов 
312 Модуль 8. Классы и объекты П роrpаммы, которые вы составляли до сих пор, не использовали объ ектноориентированные возможности с++. Все рассмотренные про rpaMMbI использовали метод cТPYктypHoro, а не объектноориентиро BaHHoro проrpаммирования. Для Toro, чтобы начать писать объектно ориентированные проrpаммы, необходимо изучить понятие класса. Класс в с++ является базовой единицей инкапсуляции. Классы ис пользуются для создания объектов. Классы и объекты являются Ha столько фундаментальными понятиями для с++, что почти вся OCTaB шаяся часть книrи будет посвящена именно им. OCHOBbl классов Давайте начнем с обзора понятий класса и обьекта. Класс  это шаблон, который определяет форму обьекта. Класс описывает и коды, и данные. с++ использует описание класса для конструирования объ ектов. Объекты являются экземплярами класса. Таким образом, класс по существу представляет собой комплект планов, по которым строит ся объект. Важно усвоить, что класс  это лоrическая абстракция. Лишь после Toro, как будет создан объект класса, в памяти появится и начнет существовать физическое представление этоrо класса. Определяя класс, вы объявляете данные, которые будyr в Hero BXO ДИТЬ, а также код, который будет работать с этими данными. Хотя очень простые классы MOryr содержать только код или только данные, в боль шинство реальных классов входят и те, и дрyrие. Данные содержатся в nеременных экземпляра, определенных в классе, а код содержится в Функ циях. Код И данные, составляющие класс, называются членами класса. Цель _ Общая форма класса Класс создается с помощью ключевоrо слова class. Общая форма объявления простоrо класса выrлядит следующим образом: class имякласса { закрытые даuные и функции public: открытые данные и функции } список обьектов; Здесь имякласса задает имя этоrо класса. Это имя становится име нем HOBoro типа, который может быть использован для создания объ ектов этоrо класса. Вы можете также создать объекты класса, указав их непосредственно после объявления класса в списке обьектов, хотя это 
)пределение класса З1З не обязательно. После тoro, как класс объявлен, объекты можно созда вать по мере необходимости. Класс может содержать как закрытые, так и открытые члены. По умолчанию все члены, определенные в классе, являются закрытыми. Это означает, что к ним можно обращаться только из дрyrих членов этоrо класса, но не из дрyrих частей вашей проrpаммы. Закрытость членов класса является одним из способов достижения инкапсуля ции  вы можете жестко контролировать доступ к определенным дaH ным, объявив их закрытыми. Чтобы сделать часть членов класса открытыми (т. е. разрешить дос1УП к ним из дрyrих частей вашей проrpаммы), вы должны объявить их после ключевоrо слова pubIic. Все переменные и функции, определенные после описателя pubIic, доступны из дрyrиx частей вашей проrpаммы. Обратите внимание на то, что после описателя роыic стоит двоеточие. Хотя нет определенных синтаксических правил, определяющих состав класса, однако разумно разработанный класс должен определять одну и только одну лоrическую сущность. Например, класс, содержащий имена и телефонные номера, не должен, как правило, включать в себя дaнНbIe о кorиpoвках фондовой биржи, помесячном КОJшчестве осадков, солнечных пяrnах и дрyryю не mносящуюся в делу информацию. Помещение в класс чуждой ДТIЯ Hero информации бысlpO зaпyraет вашу проrpамму! Подытожим сказанное. В с++ класс определяет новый тип дaH ных, который можно использовать для создания объектов. Класс пред ставляет собой лоrический шаблон, определяющий взаимоотношения между ero членами. Объявляя переменную класса, вы создаете объект. Объект имеет физическое воплощение и является конкретным экзем пляром класса. Дрyrими словами, объект занимает место в памяти, в то время как определение типа в памяти не существует. Цель _ Определение класса и создание объектов Для иллюстрации техники работы с классами мы разработаем класс, который инкапсулирует информацию о транспортных cpeДCT вах, конкретно, о некоторых типах автомобилей: леrковых автомоби лях, фурrонах и rpузовиках. Назовем наш класс Vehicle, и пусть в нем будет содержаться три единицы информации об автомобилях: допус тимое число пассажиров, объем бензобака и среднее потребление топ лива (в милях на rаллон rорючеrо). Ниже показан первый вариант класса Vebicle. Он определяет три переменных экземпляра: passengers, fuelcap и mpg. Заметьте, что Vehicle не содержит никаких функций; пока что он является классом, вклю чающим только данные. (В последующих подразделах мы добавив в Hero и функции.) 
314 Модуль 8. Классы и объекты class Vehicle { public: int passengers; int fuelcap; int mpg; } ; / / класс транспортных средств //число пассажиров / / емкость бензобака в rаллонах / / потребление топлива в милях на rаллон Переменные экземпляра, определенные в Vehicle, иллюстрируют об щий принцип объявления переменных. Общая форма для объямения переменной экземrтяра выrлядит так: тип uмяперемеН1l0Й; Здесь тип определяет тип переменной, а u.м.яперемеН1l0Й  ее имя. Ta ким образом, вы объявляете переменные экземrтяра точно так же, как вы это делали для обычных переменных. В классе Vehicle перед перечнем переменных указан описатель ДОС1УПа pubIic. как уже roворилось, это дает возможность обращаться к переменным из кода вне Vehicle. Определение класса создает новый тип данных. В нашем случае этот новый тип имеет имя Vehicle. Вы будет использовать это имя для объявления объектов типа Vehicle. Не забывайте, что объявление клас са представляет собой лишь описание типа; оно не создает реальных объектов. Таким образом, приведенный выше фраrмент проrpаммы не приводит к появлению какихлибо объектов типа Vehicle. Для Toro, чтобы создать реальный объект Vehicle, просто используй те предложения объявления, например, так: Vehicle minivan; // создание объекта Vehicle с именем minivqn После выполнения этоrо предложения minivan будет экземrтяром Vehicle. Тем самым мы получаем "физическую" реальность. Каждый раз, коrда вы создаете экземпляр класса, вы создаете объ ект, содержащий свою собственную копию всех переменных экземп ляра, определенных в классе. Так, каждый объект Vehicle будет coдep жзть свои собственные копии переменных passengers, fuelcap и mpg. Для доступа к этим переменным мы будем использовать операторточ ку (.). Операторточка соединяет имя объекта с именем ero члена. Об щая форма оператораточки такова: объект. член Мы видим, что объект указывается с левой стороны от точки, а член  с правой стороны. Например, чтобы присвоить переменной fuelcap объекта minivan значение 16, следует использовать такое пред.. ложение: minivan.fuelcap = 16; 
Определение класса 315 в обшем случае операторточка используется как для обрашения к переменныM экземпляра, так и ШIЯ вызова функций. Вот полная проrpамма, использующая класс Vebicle: // Проrрамма, использующая класс Vehicle. #include <iostream> using namespace std; // Объявим класс Vehicle. class Vehicle {  Объявим класс Vehide. public: int раssеngеrs;//число пассажиров int fuelcap; //емкость бензобака в rаллонах int mpg; / / потребление топлива в милях на rаллон } j Создадим экземпляр Vehicle с именем minivan. in t та in () { I Vehicle minivan; // создадим объект класса Vehicle  int rangej // Присвоим значения полям объекта minivan (фурrон). minivan.passengers = 7; minivan.fuelcap = 16;  minivan.mpg = 21; // Вычислим дальность пробеrа для полноrо бензобака. range = minivan.fuelcap * minivan.mpg; Обратите внимание на ИСПОЛЬЗОВаНие оператора- точки при обращении к членам. cout « "Фурrон может везти " « mini van. passengers « " на расстояние" « range « "\n"; return о; } Рассмотрим текст проrpаммы. Функция тain( ) СОЗlIает экземпляр класса bicle с именем minivan. Код внyrpи main( ) обращается к пере MeHныM экземпляра, относящимся к объеIcry minivan, присваивает им значения и затем использует эrи значения. Код внyrpи шaiп( ) имеет дoc 1YII к членам bicle, так как они 06ъямены с описателем роыi.. Если бы этот описатель не был указан, обращаться к членам Vebicle можно было бы только из этою класса, и main( ) не смоrла бы их использовать. Запустив проrpамму, вы получите следующий вывод: Фурrон может везти 7 на расстояние ЗЗ6 Перед тем, как пойти дальше, еще раз остановимся на фундамен тальном принципе: кaждый объект имеет собственные копии пере 
316 Модуль 8. Классы и объекты менных экземпляра, определенных в ero классе. Поэтому содержимое переменных одноrо объекта может отличаться от содержимоrо пере менных дpyroro объекта. Между двумя объектами нет никакой связи, за исключением тoro факта, что оба объекта принадлежат одному типу. Например, если у вас есть два объекта Vehicle, в каждом есть соб ственные копии passengers, fuelcap и mpg, причем содержимое этих пе ременных для двух объектов может различаться. Приведенная ниже проrpамма демонстрирует это положение: // Эта проrрамма создает два объекта Vehicle. #include <iostream> using namespace std; // Объявим класс Vehicle class Vehicle { public: int passengers; int fuelcap; int mpg; } ; int main () { Vehicle minivani Vehicle sportscar; / / число пассажиров //емкость бензобака в rаллонах / / потребление топлива в милях на rаллон Каждый объекr, и minivan, и spol18car, имеют собственные копии neременных экземпляра класса Vehicl.. / / создадим объект класса Vehicle / / создадим друrой объект int rangel, range2i // Присвоим значения полям minivan (фурrон). minivan.passengers = 7; minivan.fuelcap = 16; minivan.mpg = 21; // Присвоим значения полям sportscar (спортивный автомобиль). sportscar.passengers = 2; sportscar.fuelcap = 14; sportscar.mpg = 12; // Вычислим дальность пробеrа для полноrо бензобака. rangel = minivan.fuelcap * minivan.mpg; range2 = sportscar.fuelcap * sportscar.mpg; cout « "Фурrон может везти" « minivan.passengers « " на расстояние" « range1 « "\n"; cout « "Спортивный автомобиль может везти " « sportscar.passengers « 
Добавление в класс функций 317 " на расстояние " « range2 « "\n"; return о; } Леrко видеть, что данные по спортивному автомобилю (объект sportscar) никак не связаны с данными по фурrону (объект minivan). Рис. 8 1 отображает эту ситуацию. minivan passengers .. fuelc трв 7 16 21 sportscar .. passengers 2 fuelcap 14 тР8 12 Рис. 8 1. Перемснныс экземпляра двух объектов никак не связаны дрyr с дрyrом Минутная тренировка 1. Какие две составляющие входят в класс? 2. Какой оператор используется для доступа к членам класса посредством объекта ? З. Каждый объект имеет собственные копии класса. 1. Класс может содержать коды и данные. 2. ,Для доступа к членам класса посредством объекта используется оператор"точка. 3. переменных ')кзеМWJяра Цель .Добавление в класс функцийчленов Пока что класс Vebicle содержит только данные, в нет функций. Хотя классы без функций вполне имеют право на существование, в действительности большинство классов имеют функциичлены. Как правило, функциичлены манипулируют с данными, определенными в классе, и во мноrих случаях предоставляют доступ к этим данным. Обычно дрyrие части вашей проrpаммы взаимодействуют с классом посредством ero функuий. Чтобы продемонстрировать пример определения и использования функцийчленов, мы добавим одну такую функцию в класс Vebicle. Вспомним, что main( ) в последнем примере вычисляла дальность про беrа транспортноrо средства путем умножения потребления топлива на емкость бензобака. Хотя формально такой подход правилен, это не 
318 Модуль 8. Классы и объекты лучший способ работы с данными класса. Вычисление дальности про беrа транспортноrо средства  это операция, которую лучше поручить самому классу Vebicle. Основание для TaKoro подхода очевидно: даль ность пробеrа транспортноrо средства зависит от емкости бензобака и потребления топлива, а обе эти величины инкапсулированы в Vebicle. Добавив в класс Vebicle функцию, вычисляющую дальность пробеrа, вы улучшите ero объектноориентированную структуру. Для добавления функции в Vebicle, определите ее прототип в объяв лении Vebicle. Например, в приведенном ниже варианте Vebicle опреде лена функциячлен с именем range( ), возвращающая дальность пробе ra тpaнcnopmoro средства: // Объявим класс Vehicle. class Vehicle { public: in t passengers; / / число пассажиров int fuelcapi //емкость бензобака в rаллонах int mpgi //потребление топлива в милях на rаллон int range ( ) i / / вычислить и вернуть дальность пробеrа...., } ; Объявление функции-члена range( ) Поскольку прототипы функцийчленов, в частности, range( ), BXO дят В определение класса, их больше ниrде описывать не надо. Для реализации функциичлена вы должны сообщить компилято ру, какому классу она принадлежит. Это делается путем добавления к имени функции имени ее класса. Ниже приведен один из способов реализации функции range( ): // Реализация функциичлена range. int Vehicle::range() { return mpg * fuelcapi } Обратите внимание на знак двойноrо двоеточия ::, разделяющий имя класса Vebicle и имя функции range(). Знак :: называется oпepaтo ром разрешения видимости. Этот оператор связывает имя класса с име нем члена и тем самым сообщает компилятору, какому классу принад лежит этот член. В нашем случае он связывает range( ) с классом Vebicle. Дрyrими словами, знак :: устанавливает, что range( ) находится в области видимости Vebicle. Различные классы вполне Moryт иметь функции с одинаковыми именами. Компилятор определяет, какая функция принадлежит какому классу, блаrодаря оператору разреше ния видимости и имени класса. Тело функции range( ) состоит из единственной строки: return mpg * fuelcap; 
Добавление в класс функций 319 это предложение возвращает дальность пробеra транспортноrо cpeд ства, которое определяется как произведение fuelcap на mpg. Поскольку каждый объект класса Vehicle имеет собственную копию fuelcap и mpg, вычисление в range( ) использует копии этих переменных, принадлежа щие вызывающему объекту. Внутри range( ) обращение к переменным экземпляра fuelcap и mpg осуществляется непосредственно, без предварения их именем объекта и оператором1ОЧКОЙ. Коrда функциячлен использует переменную эк земпляра, определенную в ее классе, она обращается к ней непосредст венно, без явноro указания имени объекта и без использования опера тораточки. Леrко понять, почему это так. Функциячлен всеrда вызы вается относительно HeKoтoporo объекта cBoero класса. Коrда функuия начинает выполняться, объект уже известен. Поэтому внутри функции члена нет необходимости второй раз специфицировать объект. это зна ЧИТ, что fuelcap и трк внутри range() неявным образом ссылаются на KO пии переменных, входящих в roт объект, который активизировал range( ). Разумеется, код вне Vehicle должен обращаться к fuelcap и mpg, только ссылаясь на конкретный объект и используя операторточку. Функциючлен можно вызывать только относительно конкретноro объекта. Эroт вызов может осуществляться двумя способами. Вопервых, функциячлен может быть вызвана кодом вне ее класса. В этом случае вы должны указать имя объекта и операторточку. Например, в следующем пре.можении функция range( ) вызывается для объекта minivап: range = minivan.range(); Активизация функциичлена minivan.range( ) заставляет range( ) pa ботать с копией переменных экземпляра, при надлежащих minivan. По этому функция возвращает дальность пробеrа для minivan. Во втором случае функциячлен может быть вызвана из дрyrой функциичлена тoro же класса. Коrда одна функциячлен вызывает дрyryю функциючлен TOro же класса, она может сделать это непосред ственно, без использования оператораточки. В этом случае компиля тор уже знает, над каким объектом осуществляются действия. Только коrда функциячлен вызывается кодом, не принадлежащим классу, He обходимо указывать имя объекта и операторточку. Приведенная ниже проrpамма собирает вместе все рассмотренные фраrменты и иллюстрирует использование функuии range( ): // Проrрамма, использующая класс Vehicle. #include <iostream> using namespace std; // Объявим класс Vehicle. class Vehicle { 
320 Модуль 8 Классы и объекты public: int раssеngеrs;//число пассажиров int fuelcap; //емкость бензобака в rаллонах int mpg; / / потребление топлива в милях на rаллон int range (); / / вычислить и вернуть дальность пробеrа  } ; Объявление range( ) // Реализация функциичлена range. int Vehicle: : range () {  return mpg * fuelcapi Реализация range( ). } int main () { Vehicle minivan; / / создадим объект класса Vehicle Vehicle sportscar; //создадим друrой объект int rangel, range2; // Присвоим значения полям minivan. minivan.passengers = 7; minivan.fuelcap = 16; minivan.mpg = 21; // Присвоим значения полям sportscar. sportscar.passengers = 2; sportscar.fuelcap = 14; sportscar.mpg = 12; // Вычислим дальность пробеrа для полноrо бензобака. range1 = mini van. range ( );  Вызов range( ) для объекrов VehidE range2 = sportscar.range(); cout « " Фурrон может везти " « mini van. passengers « " на расстояние" « range1 « "\n"i cout « .. Спор't'ивный автомобиль может везти " « sportscar.passengers « " на расстояние" « range2 « "\n"; return о; } Эта профамма ВЫВОДИТ следующее: Фурrон может везти 7 на расстояние 336 Спортивный автомобиль может везти 2 на расстояние 168 
Добавление в класс функций 321 Минутная тренировка 1. как называется оператор ::? 2. Каково назначение оператора ::? 3. Если Функциячлен вызывается извне cBoero класса, она должна вызы ваться с указанием объекта и с использованием оператораточки. Это правильно или нет? I Оператор' называется оператором ра.1решения видимости 2 Опера10Р связывает Юlасс с членом. 3 ПраВJ.тьно При вызове извне CBoero класса функция..член должна вызыва1ЬСЯ с указа.. нием объекта и с использованием оператора"точки. Проект 8..1 Создание класса справочника Если бы ктoтo попытался обобщить сущность класса в одном предложении, оно бы выrлядело примерно так: класс инкапсулирует функциональные возможности. Конечно, часто сразу не определишь, rде кончаются одни "функциональные возможности" и начинаются дрyrие. Как правило, вы хотите, чтобы ваши классы служили строи тельными блоками вашею большоrо приложения. Чтобы удовлетво рять этому требованию, каждый класс должен представлять единую функциональную единицу, выполняющую четко разrpаниченные функции. Получается, что вы должны разработать свои классы так, чтобы они были такими маленькими, как это только возможно  но не меньше! Действительно, классы, содержащие избыточные функцио нальные возможности, будут запyrывать код и нарушать ero cтpyктyp ную CтpOroCTb. но классы, содержащие слишком мало функциональ ных возможностей, создадут фраrментарность. [де золотая середина? Именно в этом месте наука проrpаммирования переходит в искусство проrpаммирования. К счастью, большинство проrраммистов быстро обнаруживает, что поиски золотой середины становятся все менее об ременительными по мере накопления опыта раБотыI. Начните приобретать этот опыт с преобразования справочной сис темы из Проекта 33 в Модуле 3 в класс справочника. Посмотрим, по чему такое преобразование имеет смысл. Вопервых, справочная сис тема определяет одну лоrическую единицу. Она просто выводит на эк ран синтаксис управляющих предложений С++. Функциональные возможности этой системы компактны и четко определены. BOBTO рых, помещение справочника в класс  эстетически красивый подход. Желая преДJ10ЖИТЬ справочную систему пользователю, вы просто соз даете экземrтяр объекта этой системы. Наконец, поскольку справоч ник инкапсулирован, он может расширяться и изменяться без нежела тельных побочных эффектов в использующей ею проrpамме. 
322 Модуль 8. Классы и объекты Шаr за шаrом 1. СОЗlIайте новый файл с именем HelpClass.cpp. Чтобы не тратить время на ввод текста с клавиатуры, вы можете скопировать файл НеlрЗ.срр из Проекта 3 3 в HelpClass.cpp. 2. Для преобразования справочной системы в класс вы должны прежде Bcero точно определить, что именно составляет эту систему. Например, в НеlрЗ.срр имеется код, выводящий меню, вводящий за прос пользователя, проверяющий ero правильность и выводящий на экран информацию о выбранном пункте. В проrpамме также преду смотрено циклическое выполнение до нажатия клавиши q. Леrко co образить, что меню, проверка правильности запроса и вывод содержа тельной информации являются существом справочной системы. А вот каким образом получается запрос пользователя, как и циклическое повторение проrpаммы, прямоrо отношения к справочной системе не имеет. Таким образом, вам надо создать класс, который выводит спра вочную информацию и меню справочника, а также проверяет пра вильность запроса пользователя. Эти функции мы назовем COOTвeTCT венно belpon( ), sbowmenu( ) и isvalid ( ). 3. Объявите класс Help, как это показано ниже: // Класс, инкапсулирующий справочную систему. class Help { public: void helpon(char what); void showmenu ( ) ; Ьооl isvalid(char сЬ); } ; Обратите внимание на то, что это чисто функциональный класс; в нем не требуются переменные экземпляра. Как уже отмечалось ранее, класс только с данными или только с функциями вполне допустимы. (Вопрос 9 в Вопросах для самопроверки добавляет в класс Help пере менную экземпляра.) 4. Создайте функцию belpon( ), как это показано ниже: // Вывод справочной информации. void Help: :helpon(char what) { switch (what) { case 11': cout « "Предложение if:\n\n"; cout « "if(условие) предложение;\п"; cout « "else предложение;\п"; break; case I 2 ' : cout « "The switch:\n\n"; 
Добавление в класс функций 323 cout « "switсh(выражение) (\n"; cout « " case KOHCTaHTa:\n"; cout « " последовательность предложений\п"; cout « " break;\n"; cout « " / / ... \n" ; cout « "}\n"; break; case I 3 ' : cout « "Цикл for:\n\n"; cout« "fоr(инициализаЦИЯi условие; приращение)"; cout « .. предложение; \n" ; breaki case 14': cout« "Цикл while:\n\n"; cout « "while (условие) продолжение; \n" i break; case I 5 ' : cout « "Цикл dowhile: \n\n"; cout « "do {\n"i cout « " предложение;\п"; cout « "} while (условие); \n" ; break; case I 6 ' : cout « "Предложение break:\n\n"; cout « "break;\n"i break; case I 7 ' : cout « "Предложение continue: \n\n" i cout « "continue;\n"; break; case 18/: cout « "Предложение goto:\n\n"; cout « "goto MeTKa;\n"; break; } cout « "\n"; } 5. Создайте функцию sbowmenu( ): // Вывод на экран меню справочной системы. void Help: : showmenu () { cout « "Справка по: \n" ; cout « " 1. if\n"; cout « " 2. switch\n"; cout « " з. for\n"; cout« " 4. while\n"; 
324 Модуль 8. Классы и объекты cout « cout « cout « cout « cout « " 5. dowhile\n"; " 6. break\n"; " 7. continue\n"; " 8. goto\n"; "Выберите один из пунктов (q для завершения): "; } 6. Создайте функцию isvald( ), приведенную ниже: // Возвращает true, если выбор допустим. bool Help::isvalid(char сЬ) { i f (ch < I l' I I сЬ > ' 8' && сЬ ! = , q' ) return false; else return true; } 7. Перепишите функцию main() из Проекта 33, чтобы она исполь зовала новый класс Help. Ниже приведен полный текст HelpClass.cpp: /* Проект 81 Преобразование справочной системы из Проекта З3 в класс Help. */ #include <iostream> using namespace std; // Класс, инкапсулирующий справочную систему. class Help { public: void helpon(char what); void showmenu ( ) ; Ьооl isvalid(char сЬ); } ; // Вывод справочной информации. void Help::helpon(char what) { switch(what) { case 11': cout « "Предложение if:\n\n"; cout « "if(условие) предложение;\п"; cout « "else предложение;\п"; break; case I 2 ' : cout « "Предложение switch:\n\n"; cout « "switсh(выражение) {\п"; 
Добавление в класс функций 325 cout « " case константа:\n"; cout « " последовательность предложений\п"; cout « " break; \п 11 i cout« " // . ..\п"; cout « "} \n l ' ; break; case 13/: cout « "Цикл for:\n\n"; cout« "fоr(инициализация; условие; приращение)"; cout « " предложение;\n"; break; case 14': cout « "Цикл while: \п\п" ; cout « "whilе{условие) проДолжениеi\n"; break; case I 5 ' : cout « "Цикл dowhile: \п\п"; cout « "do {\п"; cout « " предложение;\n"; cout < "} whi 1е (условие); \п" ; break; case I 6 ' : cout « "Предложение break:\n\n"; cout « "break;\n"; break; case I 7 ' : cout « "Предложение continue:\n\n"; cout « "continue;\n"; break; case 18': cout « "Предложение goto:\n\n"; cout« "goto метка;\п"; break; } cout« "\п"; } // ВЫВОД на экран меню справочной системы. void Help::showmenu() { cout « "Справка по: \п" ; cout « 11 1 . if\n"; cout « " 2 . swi tch \п 11 ; cout « " 3 . for\n" ; cout « 11 4 . while \п" ; cout « " 5 . dowhile\n" ; cout « " 6. break\n"; 
326 Модуль 8 Классы и объекты cout « .. 7. continue\n"; cout « " 8. goto\n"; cout « "Выберите один из пунктов (q для завершения): "; } // Возвращает true, если выбор допустим. bool Help::isvalid(char сЬ) { i f (сЬ < 11' I I ch > 18' && ch ! = I q' ) return false; else return true; } int main () { char choice; Help hlpob; // создание экземпляра класса Help. // Используем объект Help для вывода информации. for(;;) { do ( hlpob.showmenu(); cin » choice; } while(!hlpob.isvalid(choice)); if(choice == 'q') break; cout« "\n"; hlpob.helpon(choice); } return о; } Коrда вы испытаете эту проrpамму, вы обнаружите, что функцио нально она не отличается от своей предшественницы из Модуля 3. Преимущество HOBoro подхода в том, что теперь у вас есть компонент справочной системы, который можно повторно использовать, rде бы он ни понадобился. Цель _ Конструкторы и деструкторы в предыдущих примерах переменные экземпляра в каждом объекте Vehicle должны бьти устанавливаться вручную с помощью предложе ний, подобных этим: 
Конструкторы и деструкторы 327 minivan.passengers = 7; minivan.fuelcap = 16; minivan.mpg = 21; в профессионально написанном коде на с++ такой подход никоrда не применяется. Помимо возможных ошибок (ВЫ можете позабыть yc тановить ОДНО из полей), просто существует более удобный способ BЫ полнить задачу инициализации. Эта задача возлarается на конструктор. Конструктор инициализирует объект при ero создании. KOHCТPYК тор имеет то же имя, что и класс, а синтаксически сходен с функцией. Однако конструкторы не возвращают никакоrо значения. Общая фор ма конструктора выrлядит так: uмякласса( ) { //код конструктора } Обычно конструктор используется для придания переменным эк земпляра, определенным в классе, начальных значений, а также для выполнения дрyrих начальных процедур, требуемых для создания пол ностью сформированноro объекта. В не котором роде прorивоположностью конструктору является дe структор. Во мноrих случаях объекту при ero удалении требуется BЫ полнение HeKoтoporo действия или последовательности действий. Ло кальные объекты создаются при входе в свой блок и уничтожаются при выходе из блока. fлобальные объекты уничroжаются, коrда завершает ся проrpамма. Может быть MHoro причин, приводящим: к необходимо сти иметь деструктор. Например, объекту может по надобиться освобо дить ранее выделенную память, или закрыть открытый им файл. в с++ тaкoro рода операции выполняет деструктор. Деструктор имеет то же имя, что и конструктор, но перед именем указывается знак ...... как и конструкторы, деструкторы не имеют возвращаемоrо значения. Вот простой прим:ер использования конструктора и деструктора: // Простой конструктор и деструктор. #include <iostream> using namespace std; class MyClass { public: int Х; // Объявим конструктор и деструктор. MyClass(); // конструктор  MyClass(); // деструктор  } ; Объявим конструктор и деструк - тор для пасса МYCI_ 
328 Модуль 8 Классы и объекты к// Реализация конструктора MyClass. MyClass::MyClass() { х :: 10; } // Реализация деструктора MyClass. MyClass::MyClass() { cout« "Уничтожаем...\п"; } int main () { MyClass оЫ; MyClass оЬ2; cout « оЫ.х « .. .. « оЬ2.х « .. \n"; return о; } Вывод этой проrpаммы ВЫfЛЯДИТ таким образом: 10 10 Уничтожаем. . . Уничтожаем. . . В этом примере конструктор класса MyClass имеет такой вид: // Реализация конструктора MyClass. MyClass::MyClass() { х :: 10; } Заметьте, что KOHcтpyкrop объямен после описателя public. Эro ecтecт венно, так как KOHcтpyкrop будет вызываться из кода вне ею класса. Эroт KOHcтpyкrop присваивает переменной экземrтяpа х из MyClass значение 10. KOHCТPyкrop вызывается, Korдa соЗдается объект. Например, в строке MyClass оЫ; конструктор MyClass () вызывается для объекта obl, присваивая obl.x значение 10. То же справедливо для объекта оЫ. После конструирова ния оЬ2.х так же будет иметь значение 10. Деструктор для MyClass показан ниже: // Реализация деструктора MyClass. MyClass::MyClass() { cout « "Уничтожаем... \n" ; } 
Параметрические конструкторы 329 Этот деструктор просто выводит сообщение, но в реальных про rpaMMax деструктор используется для освобождения ресурсов (напри мер, дескриптора файла или памяти), используемых классом. Минутная тренировка 1. Что такое конструктор и коrда он выполняется? 2. Имеет ли конструктор возвращаемое значение? 3. Коrда вызывается деструктор? Конструктор  это функция, которая выполняется, коrда создаеlСЯ экземrmяр объекта данноrо класса Коне rpyктop используется мя инициализации создаваемоrо объекта 2 Нет, конструктор не имее f возвращаемоrо .значения 3 lleclPYKTOP вызываеlСЯ при уничтожении объекта Цель _ Параметрические конструкторы в предьшущем примере использовался конструктор без парамет ров. Хотя в некоторых ситуациях именно такой конструктор и нужен, однако чаше нам требуется конструктор с одним или несколькими па раметрами. Параметры добавляются в конструктор точно так же, как они добавляются в функцию: просто объявите их внутри крyrлых CKO бок после имени конструктора. Вот, например, параметрический KOH структор для класса MyClass: Myclass::MyClass(int i) { х = i; } Для передачи конструктору apryмeHTa вы должны сопоставить передаваемое значение или значения с объявляемым объектом. с++ предоставляет для этоrо два способа. Первый способ выrля дит так: MyClass оЫ = MyClass(101); в этом объявлении создается объект класса MyClass с именем оЫ, и этому объекту передается значение 101. Однако такая форма исполь зуется редко (во всяком случае, в этом контексте), потому что второй способ короче и наrляднее. При использовании второй формы apry мент или apryмeHTЫ, заключенные в крyrлые скобки, указываются вслед за именем объекта. Приведенное ниже предложение выполняет то же, что и предыдущее: 
ззо Модуль 8. Классы и объекты MyClass оЫ(101); Это наиболее распространенный способ объявления параметриче CKOro объекта. Общая форма ero такова: т иn  класса nеремеНllая( сп исок ap2YMellтoв); Здесь cnиCOKap2YMellтoв представляет собой перечень apryмeHТOB, передаваемых конструктору. ApryмeHТЫ разделяются запятыми.  Замечание с формальной точки зрения между двумя приведенными форма.. ми имеется небольшое различие, о которым вы узнаете позже. Это различие, впрочем, не будет влиять на проrраммы в этом модуле, да и вообще на большинство проrрамм, которые вам придется пи.. сать. Вот полная проrpамма, демонстрирующая параметрический KOHCT руктор MyClass: // Параметрический конструктор. #include <iostream> using namespace std; class MyClass { public: int х; // Объявим конструктор и деструктор. MyClass(int i); // конструктор  MyClass(); // деструктор } ; Добавим naраметр к MyClass( ). // Реализация параметрическоrо конструктора. MyClass::MyClass(int i) { х = i; } // Реализация деструктора MyClass. MyClass::MyClass() { cout « "Уничтожение объекта, у KOToporo значение х равно" « х « " \n"; } in t та in () { 
Параметрические конструкторы ЗЗ1 MyClass tl(5); Мус 1 а s s t 2 (19) ; Передадим apryм8НТЫ конструктору МycI_( ) cout « tl.x « " " « t2. х « 11 \n 11 ; return о; } Вывод этой проrpаммы будет таким: 5 19 Уничтожение объекта, у KOToporo значение х равно 19 Уничтожение объекта, у KOToporo значение х равно 5 В этом варианте проrpаммы KOHCтpyкrop MyClass( ) определяет один параметр с именем i, который используется ШIЯ инициализации пере менной экземrтяpа х. Таким образом, коrда выполняется строка MyClass оЫ () ; значение 5 передается в i, а затем присваивается переменной х. В отличие от конструкторов, деструкторы не MOryr иметь парамет ров. Причину этоrо леrко понять: не существует способа передачи пара метров уничтожаемому объекту. Если вашему объекту при вызове ero дecтpyкropa требуется обращение к какимлибо данным, определяемым по ходу проrpаммы, вам придется создать для этою специальную пере менную. Тоrда перед самым уничтожением объекта вы сможете обра титься к этой пере мен ной. Впрочем, такая ситуация возникает редко. Добавление конструктора в класс Vehicle Мы может улучшить класс Vebicle, добавив в Hero конструктор, KO торый будет автоматически инициализировать поля passenger, fuelcap и mpg в процессе конструирования объекта. Обратите особое внимание, как создаются объекты Vebicle. // Добавление конструктора в класс транспортных средств. #include <iostream> using namespace std; // Объявим класс Vehicle. class Vehicle { public: int раssеngеrs;//число пассажиров int fuelcap; //емкость бензобака в rаллонах int mpg; / / потребление топлива в милях на rаллон 
ЗЗ2 Модуль 8 Классы и объекты // Это конструктор для Vehicle. Vehicle (int р, int f, int m) ; int range(); //вычислить и вернуть дальность пробеrа } ; // Реализация конструктора Vehicle. Vehicle: : Vehicle (int р, int f, int m) {  passengers = р; fuelcap = f; mpg = m; } KOHcTpyкrop Vehicle ини- циализирует passengers, fuelcap и тРа // Реализация функциичлена range. int Vehicle::range() { return mpg * fuelcapi } int main () { // Передадим значения конструктору Vehicle. Vehicle mini van (7, 1 б, 21);  Передача информации о транс- Vehicle sportscar (2, 14, 12); портном средстве классу Vehicle посредством ero KOHcTPYкropa int range1, range2; // Вычислим дальность пробеrа для полноrо бензобака. rangel = minivan.range(); range2 = sportscar.range(); cout « "Фурrон может везти" « minivan.passengers « " на расстояние" « range1 « "\n"; cout « "спортивный автомобиль может везти " « sportscar.passengers « " на расстояние" « range2 « "\n"; return о; } Оба объекта, и minivan, и sportscar, инициализируются KOHCТPYКTO ром при их создании. Каждый объект инициализируется параметрами, указанными при вызове ero конструктора. Например, в строке Vehicle minivan(7, 16,21); значения 7, 16 и 21 передаются конструктору Vebicle, коrда создаетс объект minivan. В результате копии переменных passengers, fuelcap l' mpg, при надлежащие объекту minivan, будут содержать значения 7, 16 ., 21 соответственно. Вывод этой проrpаммы будет, разумеется, тем же что и у предыдущеrо варианта. 
Параметрические конструкторы ззз Друrой способ инициализации Если конструктор принимает только один параметр, вы можете ис пользовать для инициализации объекта альтернативный способ. Pac смотрим такую проrрамму: // Альтернативный способ инициализации. #include <iostream> using namespace std; class MyClass { public: int Х; / / Объявим конструктор и десТрУКТОр. MyClass(int i); // конструктор MyClass(); // деструктор } ; // Реализация параметрическоrо конструктора. MyClass::MyClass(int i) { х = i; } // Реализация деструктора MyClass. MyClass::MyClass() { cout « "Уничтожение объекта, у KOToporo значение Х равно" « х <" \n"; } i n t та i n () { MyClass оЬ = 5; // вызов MyClass(5)  Альтернативный синтаксис для инициализации объекта cout « оЬ.х « "\n"; return о; } Здесь конструктор класса MyClass принимает один параметр. Обра тите особое внимание на способ объявления оЬ в main( ): MyClass оЬ = 5; При такой форме инициализации 5 автоматически передается па раметру i в конструкторе MyClass. Предложение инициализации BOC принимается компилятором, как если бы оно было написано таким образом: 
З34 Модуль 8. Классы и объекты MyClass оЬ = MyClass(5); Всеrда, если у вас есть конструктор, требующий только один apry мент, вы можете использовать для инициализации объекта либо оЬ(х), либо оЬ ==х . Объясняется это просто: если вы создаете конструктор с oд ним apryмeНТOM, вы также неявно создаете преобразование типа этоrо apryмeHтa в тип класса. Не забывайте, что описанный здесь альтернативный способ rодит ся только для конструкторов, имеющих в точности один арrумент. Минутная тренировка 1. Покажите, как объявить конструктор для класса Test, который прини мает один параметр типа int с именем count. 2. Приведите дрyryю форму написания этоrо предложения: Test оЬ = Test(10); 3. Как еще можно объявить объект из предыдущеrо вопроса? 1. Test: :Test (int count) (... 2. Test оЬ (10) ; З. Тее t оЬ = 1 О ; Спросим у эксперта Вопрос: Можно ли объявить один класс в друrом? Друrими словами, MO ryт ли классы быть вложенными? OrвeT: Да, вполне возможно объявить один класс внутри дрyrоrо. Ta кая операция создает вложенные классы. Поскольку объявление class фак тически создает область видимости, вложенный класс действителен только в области видимости внешнеrо класса. Честно rоворя, блаrодаря боrатству и rибкости дрyrих средств с++, например, наследования, о котором речь будет идти ниже, нужда во вложенных классах возникает крайне редко. Цель _ Встроенные функции Перед тем, как мы продолжим исследование понятия класса, нам следует сделать небольшое, но важное отступление. В с++ имеется одно чрезвычайно полезное средство, называемое встроенными ФУНJ(, цuями, которое, хотя и не относится исключительно к объектноори ентированному проrpаммированию, часто используется в объявлениях классов. Встроенная функция  это функция, которая фактически не вызывается, а развертывается в свой полный текст в месте cBoero BЫ зова. Встроенную функцию можно создать двумя способами. Первый 
Встроенные функции ЗЗ5 заключается в использовании описателя inline. Например, для созда ния встроенной функции с именем f, которая возвращает int и не тpe бует параметров, вы объявляете ее следующим образом: inline int f () { // ... } Описатель inline указывается перед всеми остальными элементами объявления функции. Ценность встроенных функций заключается в их эффективности. Каждый раз при вызове обычной функции должна выполняться по следовательность команд, как собственно для ее вызова, включая про талкивание apryмeHТOB функции в стек, так и для возврата из Функ ции. В ряде случаев для выполнения этих операций требуется большое количество тактов процессора. Если же функция развертывается еще на этапе компиляции в свой полный текст, включаемый в текст OCHOB ной проrpаммы, то никаких издержек не возникает, что повышает скорость выполнения вашей проrpаммы. В то же время, если текст встроенной функции велик, общий размер вашей проrpаммы также может существенно увеличиться. Поэтому лучше Bcero объявлять встроенными небольшие по размеру функции. Большие же функции следует, как правило, объявлять обычным образом. Приведенная ниже проrpамма демонстрирует использование опи сателя inline: // Демонстрация описателя inline. #include <iostream> using namespace std; class сl { int i; // закрыта по умолчанию public: . in t get.....i ( ) i void put.....i(int j); } ; inline int cl::get.....i()  { I Функции getJ( ) и putJ( ) являются встроенными I return i; } inl ine void cl: : put.....i (int j)  { 
ЗЗ6 Модуль 8. Классы и объекты i = j; } int main () { сl s; s.puti(10)i cout« s.geti(); return о; } Важно отдавать себе отчет в том, что, cтporo rоворя, описатель inline является лишь запросом, а не командой, чтобы компилятор сrенериро вал встроенный код. Имеется ряд ситуаций, в которых компилятор не сможет выполнить запрос на создание встроенной функции. Вот He сколько примеров таких ситуаций: · Некoroрые компиляторы не будут компилировать встроеННЫЙ код для функции, содержащей циклы, а 13ЮКе предложения switcb и goto. · Часто оказывается невозможным создать встроенную рекурсив ную функцию. · Moryт быть запрещены встроенные функции, содержащие стати ческие переменные. Не забывайте, что оrpаничения на создание встроенных функций зависят от реализации вычислительной системы, поэтому для выясне ния применимости тех или иных оrpаничений в вашей ситуации вы должны свериться с документацией к вашему компилятору. Создание встроенных функций внутри класса Дрyrой способ создания встроенной функции заключается в опре делении кода функциичлена внутри определения класса. Любая функция, определенная внyrpи определения класса, автоматически делается встроенной. В этом случае нет необходимости в использова нии ключевоrо слова inline. Например, предыдущую проrpамму можно переписать следующим образом: #include <iostream> using namespace std; class cl { int i; // закрыта по умолчанию public: // Автоматическое создание встроенных функций. 
Встроенные ФУНКЦИИ ЗЗ7 int get.....i () { return i; }  void put.....i (int j) { i = j; }  } ; Определение getj( ) и putj( ) внyrри класса int main ( ) { cl s; s.put.....i(10); cout« s.get.....i()i return о; } Обратите внимание на то, как записаны тексты функций. для очень коротких функций такое расположение отражает общепринятый стиль с++. Однако вы моrли написать их и таким образом: class cl { int i; // закрыта по умолчанию public: // встроенные функции int get.....i ( ) { return ii } void put.....i(int j) { i = j; } } ; Короткие функции вроде тех, что использованы в нашем примере, обычно определяются внутри объявления класса. Такие встроенные функции широко используются в объектноориентированном про rpаммировании, rде часто открытая функция предоставляет доступ к закрытой переменной. Такие функции носят название функций дocтy па. Одним из условий успешноrо объектноориентированноro про rpаммирования является контроль ДОС1УПа к данным посредством функuийчленов. Поскольку большинство проrpаммистов на с++ оп ределяют функции ДОС1УПа и дрyrие короткие функциичлены внутри их классов, такой стиль будет использоваться в дальнейших примерах этой книrи. Вам также следует привыкнуть к этому стилю. В приведенной ниже проrpамме класс Vebicle записан так, что ero конструктор, деструктор и функция range( ) определены внутри класса. 
З38 Модуль 8. Классы и объекты Далее, поля passenger, fuelcap и mpg сделаны закрытыми, и для получе ния их значений добавлены функции доступа: // Определяем конструктор, деструктор / / и функцию range (), как встроенные. #include <iostream> using namespace std; // Объявляем класс Vehicle. class Vehicle { / / Эти члены теперь закрыты. int passengers; / / число пассажиров int fuelcap; / / емкость бензобака в rаллонах int mpg; / / потребление топлива в милях на rал лон public: // Это конструктор для Vehicle. Vehicle(int р, int f, int m) { passengers = р; fuelcap = f; mpg = m; Сделаем эти пвременные закрытыми } Определим ФУНКЦИИ встро- енНЫМИ И получим ДОС1У" к закрыrым neременным по- средством ФУНКЦИЙ доступа / / Вычислим и вернем дальность пробеrа. int range() { return mpg * fuelcap; } / / Функции доступа. int getassengers() { return passengers; } int getfuelcap() { return fuelcap; } int getmpg () { return mpg; } } ; int main () { // Передадим значения конструктору Vehicle. Vehicle minivan(7, 16,21); Vehicle sportscar(2, 14, 12); int range1, range2; // Вычислим дальность пробеrа при полном бензобаке. range1 = minivan.range(); range2 = sportscar.range(); cout « "Фурrон может везти " « minivan.getassengers () « " на расстояние" « range1 « "\n"; cout « "спортивный автомобиль может везти" « 
Встроенные функции 339 sportscar.getassengers() « "на расстояние " « range2 « "\n" i return о; } Поскольку переменныечлены Vebicle теперь закрыты, ШIЯ получе ния в функции main( ) числа пассажиров, которые может перевозить данное транспортное средство, следует использовать функцию доступа get....passengers( ). Минутная тренировка 1. Для чеrо предназначен описатель inline? 2. Можно ли встроенную функцию объявить внyrpи объявления class? 3. Что такое функция доступа? I Функuия, объявленная с описателем inline, не вызывается, а разворачивается внyrpи проrpаммноrо кода. 2 Да, встроенная функция может быть объявлена внyrpи объявления cla55 3. Фуню(ия доступа  310 короrкая функция, которая получает ми устанавливает значе иие закрытой переменной экзеМIUIЯра Проект 8..2 Создание класса очереди Как вы, наверное, знаете, структура данных  это способ орrаниза ции данных. Простейшей структурой данных является массив, KOTO рый представляет собой линейный список, поддерживающий случай ный доступ к ero элементам. Массивы часто используются как OCHOBa ние для создания более изошренных струюур данных, например, стеков и очередей. Стек  это список, в котором доступ к элементам осуществляется исключительно в порядке "первым вошел, последним вышел" (FirstIn, LastOut, FILO). Очередь  это список, в котором доступ к элементам осуществляется исключительно в порядке "пер вым вошел, первым вышел" (FirstIn, FirstOut, FIFO). Стек напоми нает стопку тарелок на столе; первая (нижняя) тарелка будет исполь зована последней. Примером очереди являIOТСЯ люди, ждущие обслу живания у кассира банка: первый в очереди и обслужен будет первым. Что делает струюуры данных типа стека или очереди особенно ин тересным, так это совмещение в них функции хранения информации с функциями доступа к этой информации. С этой точки зрения стеки и очереди представляют собой процессоры данных, в которых хранение и извлечение предоставляются самими струюурами данных, а не вашей 
340 Модуль 8. Классы и объекты проrpаммой, в которой это надо делать своими руками. Такая комби нация, очевидно, превосходно отвечает идее класса, и в этом проекте вы создадите простой класс очереди. Очереди в принципе поддерживают две операции: "положить" и "взять". Каждая операция "положить" помещает новый элемент в KO нец очереди. Каждая операция "взять" извлекает следующий элемент из roловы очереди. Операции с очередью носят разрушающий xapaK тер. После тoro как элемент извлечен из очереди, ero нельзя извлечь повторно. Очередь может также переполниться, если в ней нет свобод Horo места для сохранения элемента; она также может оказаться пус той, если из нее извлечены все элементы. Последнее замечание: в принципе существуют два вида очередей, кольцевая и некольцевая. Кольцевая очередь повторно использует по зиции лежащеrо в ее основе массива, из которых были удалены эле MeHТbI; некольцевая не делает этоrо и в конце концов все позиции в ней исчерпываются. Ради простоты в этом примере будет создана He кольцевая очередь, но затратив незначительные усилия, вы сами CMO жете преобразовать ее в очередь кольцевоrо типа. Шаr за maroM 1. Создайте файл с именем Queue.cpp. 2. Существуют разные способы создания очереди; метод, которым мы воспользуемся, основан на массиве. Дрyrими словами, память для xpaнe ния элементов, помещаемых в очередь, будет представлять собой массив. Обращение к этому массиву будет выполняться с помощью двух индек сов. Индекс putloc определяет, куда должен быть помещен следуюЩИЙ элемент. Индекс getloc указывает, из кaKoro места массива должен ИЗRЛе каться следуюЩИЙ элемент даЮIЫХ. Не забудьте, чro операция ИЗRЛече ния данноrо действует разрушаюшим образом; один и тот же элемент нельзя извлечь дваждыI. Очереш>, которую мы будем создавать, предна значена для хранения символов, но та же самая лоrика используется ШIЯ объеIcrOВ любых типов. Начните создавать класс Queue с таких строк: const int maxQsize = 100; class Queue { char q[maxQsize]; // массив для хранения очереди int size; // максимальное число элементов, // которые MOryT находиться в очереди int putloc, getloc; // индексы "положить" и "взять" Переменная maxQsize с описателем const определяет максимальный размер создаваемой очереди. Фактический размер очереди хранится в поле с именем size. 
Встроенные функции 341 3. Конструктор для класса Queue создает очередь заданноrо разме ра. Вот ero текст: public: // Сконструируем очередь конкретной длины. Queue(int len) { // Размер очереди должен быть меньше тах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } Если затребованный размер очереди больше, чем maxQsize, то соз дается очередь максимально возможной длины. Если затребованный размер равен нулю или отрицателен, создается очередь длиной в 1 эле мент. Размер очереди сохраняется в поле size. Индексам "положить" и "взять" присваивается начальное нулевое значение. 4. Функция put( ), заносящая элементы в очередь, имеет такой вид: // Поместим символ в очередь. void put(char сЬ) { if(putloc == size) { cout « "  Очередь полна.\п"; return; } putloc++; q[putloc] = сЬ; } Функция начинается с проверки, не заполнена ли очередь. Если putloc равен размеру очереди, следовательно, в очереди больше нет места, куда можно было бы занести новый элемент. В противном слу чае putloc увеличивается на единицу, и новый элемент заносится в эту ячейку. Таким образом, putloc всеrда является индексом последнеrо coxpaHeHHoro элемента. 5. для извлечения элементов из очереди предусмorpeна функция get( ): // Извлечем символ из очереди. char get () { if(getloc == putloc) { cout « "  Очередь пуста. \n" ; return о; } 
342 Модуль 8. Классы и объекты getloc++; return q[getloc]; } Прежде вcero обратите внимание на проверку, не пуста ли очередь. Если getlock и putlock оба указывают на один и тот же элемент, очереш> считается пустой. Именно поэтому в конструкторе Queue оба индекса, и getlock, и putlock, были инициализированы нулевыми значениями. Дa лее getlock увеличивается на единицу и из очереди извлекается очеред ной элемент. Таким образом, getlock всеrда ЯRЛяется индексом ячейки, из которой был извлечен последний элемент. 6. Ниже приведен полный текст проrpаммы Queue.cpp: /* Проект 82 Класс очереди символов. */ #include <iostream> using namespace std; const int maxQsize = 100; class Оиеие { char q[maxQsize]; int size; / / массив для хранения очереди // максимальное число элементов, / / которые MOryT находиться в очереди / / индексы .. положить" и .. взять" int putloc, getloci public: / / Сконструируем очередь конкретной длины. Queue(int len) { // Размер очереди должен быть меньше mах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } / / Поместим символ в очередь. void put (char ch) { if(putloc == size) { cout « "  Очередь полна. \n"; returni } putloc++; 
Встроенные функции З4З q[putloc] = ch; } / / Извлечем символ из очереди. char ge t () { if(getloc == putloc) { cout « " ... Очередь пуста. \n" ; return о; } getloc++; return q[getloc]; } } ; / / Демонстрация класса очереди Оиеие. int main () { аиеие bigQ (100) ; аиеие smallQ(4); char ch; int i; cout « "Используем bigQ для хранения латинскоrо алфавита.\n"; / / поместим в bigQ буквы алфавита for(i=O; i < 26; i++) bigQ. put ( I А I + i); / / извлечем и выведем на экран элементы из bigQ cout « "Содержимое bigQ: "; for(i=O; i < 26; i++) { ch = bigQ.get(); if(ch 1= О) cout « сЬ; } cout « 11 \n\n" ; cout « "Используем smallQ для демонстрации ошибок. \n"; / / Теперь используем smallQ для демонстрации ошибок for(i=O; i < 5; i++) { cout « "ПЫтаемся записать 11 « (char) (1 Z I  i); smallQ.put{'Z'  i); cout « "\В"; } 
344 Модуль 8. Классы и объекты cout « "\n"; / / друrая ошибка в smallQ cout « "Содержимое smallQ: "; for{i=O; i < 5; i++) { ch = smallQ.get{); if{ch 1= О) cout « сЬ; } cout « "\n"; } 7. ВЫВОД проrpаммы выrлядит следующим образом: Используем bigQ для хранения латинскоrо алфавита. Содержимое bigQ: ABCDEFGHIJKLMNOPQRSTUVWXYZ Используем smallQ для rенерации ошибок. Пытаемся записать Z Пытаемся записать У Пытаемся записать Х Пытаемся записать W Пытаемся записать V  Очередь полна. Содержимое smallQ: ZYXW  Очередь пуста. 8. Попробуйте самостоятельно модифицировать Queue таким обра зом, чтобы в очередь можно было записывать объекты дрyrих типов. Например, создайте очередь для переменных int или doubIe. Цель _ Массивы объектов Вы может создавать массивы объектов точно так же, как и массивы любых дрyrих типов данных. Например, приведенная ниже проrpамма создает массив объектов MyClass. Обращение к объектам, составляю щим элементы массива, осуществляется обычным образом с помощью индексов: / / Создание массива объектов. #include <iostream> using namespace std; 
Инициализация массивов объектов 345 class MyClass { int Х; public: va id s е tx ( in t i) { х = i; } int getx () { return Х; } } ; int main () { MyClass obs[4];  int i; Соэдание массива объектов for(i=O; i < 4; i++) obs[i] .setx(i); for(i=O; i < 4; i++) сои t < < "о bs [" < < i < < "]. ge tx (): " < < obs[i] .getx() « "\n"; return о; } Эта проrpамма выводит на экран следующее: obs[O] .getx(): О obs[l] .getx(): 1 obs[2] .getx(): 2 оЬs[З] .getx(): 3 Цель .. Инициализация массивов объектов Если класс включает в себя параметрический конструктор, массив объектов может быть инициализирован. В приведенном ниже примере MyClass является параметрическим классом, а obs представляет собой инициализированный массив объектов этоrо класса: // Инициализация массива объектов. #include <iostream> using namespace std; class MyClass { int Х; public: 
346 Модуль 8 Классы и объекты MyClass(int i) { х = i; } in t get.....x () { ret urn х; } } i int main ( ) { MyClass obs[4] = { 1, 2, 3, 4 };  int i; ОДИН из способов инициа- лизации массива объектов. for(i=O; i < 4; i++) сои t « .. оЬв [" « i « "] . get.....x (): " « obs [i] . get.....x () « "\n"; return о; } в этом примере конструктору MyClass передаются значения от  1 до 4. Проrpамма выводит на экран следуюшее: оЬв[О] .get.....x(): 1 оЬв[l] .get.....x(): 2 obs[2] .get.....x(): 3 оЬs[З] .get.....x(): 4 Синтаксис, использованный в списке инициализаторов последнеrо примера представляет собой сокращение более пространной формы: Друrой способ ини- Мус 1 а s s obs [ 4] = { МуС 1 а s s (  1), Мус 1 а s s ( 2) ,............. циализации массива. MyClass (3), MyClass (4) }; Как уже объяснялось выше, если конструктор принимает только один apryмeHT, то выполняется неявное преобразование типа этоro ap ryмeHTa в тип класса. Более длинный вариант инициализации просто вызывает конструктор явным образом. При инициализации массива объектов, конструктор которых при нимает более одноrо apryмeHтa, вы должны использовать длинный Ba риант инициализации. Например: #include <iostream> using namespace std; class MyClass { int х, у; public: MyClass(int i, in t j) { х = i; у = j ; } in t get.....x ( ) { return х; } in t get.,..y ( ) { return у; } 
Указатели на объекты 347 } ; int main () { MyClass obs[4] [2] = { MyClass(1, 2), МуСlаss(З, 4), MyClass(5, 6), MyClass(7, 8),  MyClass(9, 10), МyClass(11, 12), MyClass(13, 14), MyClass(15, 16) } ; Если конструктор объекта тре- бует двух или нескольких зpry- ментов, требуется использовать длинную форму инициализации. int i; for(i=O; i < 4; i++) { cout « obs[i] [О] .getx() cout « obs[i] [О] .get() cout « obs[i] [1] .getx() cout « obs [i] [1] . get () } « I I . , « "\n"; « I I . , « ., \n" ; return о; } в эroм примере KOHC1pyкrop класса MyCIass требует два apryмeнтa. В функции main( ) массив obs объявляется и инициализируется с использо ванием прямых вызовов KOHcrpyкropa MyCIass. Инициализируя массивы, вы можете вcerдa использовать ШIЮfНYIO форму инициализации, даже если объект принимает 'ЮЛько один apryмeнт. Пpocro Iфаткзя форма более удобна, если apryмeнт вcero один. Проrpамма выводит следующее: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Цель _Указатели на объекты Вы можете обратиться к объекту либо непосредственно (как это дe лалось во всех предыдущих примерах), либо посредством указателя на 
348 Модуль 8. Классы и объекты этот объект. При обращении к конкретному элементу объекта посред ством указателя на объект вы должны использовать операторстрелку: >. Для включения в текст проrpаммы этоrо оператора надо сначала ввести знак "минус", а затем знак "больше". для объявления указателя на объект вы исполъзуere ror же синтаксис, посреДС1ВОм коюроro вы будere объявлять указатель на любой дрyroй тип данных. Приведенная ниже проrpамма СОЗlIает простой Юlасс Р ....example и определяет объект эroro класса с именем 00, а также указатель р на объект mпа Р ....еxamplе. Далее в проrpaмме иллюcrpируется непосредственное об ращение к 00, а 13IOКe косвенное обращение посреДС1ВОм указателя: // Простой пример использования указателя на объект. #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 ( ) { Pexample оЬ, *р; 11 объявим объект и указатель на Hero  ob.setnиm(l); 11 вызов функции непосредственно через оЬ I оЬ. show....num () ; Указатели на объекrы ис- пользуются так же, как и р = &оЬ; / / присвоим р значение адреса оЬ  указатели друrих типов p>set....num(20); // вызов функции посредством указателя на оЬ p>shownum();  return о; Обратите внимание на использование оператора-стрелки. } Обратите внимание на то, что для получения адреса оЬ использует ся оператор & (получение адреса переменной) точно так же, как это делается для получения адреса переменной любоrо дрyrоrо типа. Как вы уже знаете, Korдa указатель инкрементируется или дeкpeMeH тируется, он фактически увеличивается или уменьшается таким обра.. зом, чтобы указывать на следующий элемент ero базовоro типа. То же самое происходит при операциях инкремента или декремента указателя на объект: указатель начинает указывать на следующий объект. для ил- люстрации этою правила мы модифицировали предыдущую проrpамм) так, что оЬ стал двухэлементным массивом типа Р .....example. ОбраТИТЕ 
Указатели на объекты 349 внимание, как ШIЯ обращения к двум элементам массива над указателем выполняются операции инкремента и декремента: // Инкремент и декремент указателя на объект. #include <iostream> using namespace std; class Pexample { int num; public: void set.....num (int val) { num = val i } void show.....num ( ) { cout « num «. "\n"; } } i int main () { P.....example оЬ[2], *р; оЬ[О] .set.....num(10); // непосредственное обращение к объекту ob[l] .set.....num(20); р = &оЬ [ О]; / / получим указатель на первый элемент p>show.....num(); // выведем значение оЬ[О] через указатель р++; // продвинемся к следующему объекту p>show.....num{); // выведем значение ob[l] через указатель P; // вернемся к предыдущему объекту p>show.....num(); // снова выведем значение оЬ[О] return О i } Проrpамма выведет на экран 10, 20, 10 Как будет показано в дальнейших разделах этой книrи, указатели на объекты иrpают центральную роль в одной из наиболее важных концепций с++: полиморфизме. Минутная тренировка 1. Можно ли присвоить начальные значения массиву объектов? 2. Если в проrpамме имеется указатель на объект, какой оператор следует использовать для обращению к члену? I Да, массиву объектов MOIyf быть даны начальные значения 2 При обраlцении к члену через указатель следует использовать оператор..стрелку 
350 Модуль 8. Классы и объекты ссыкии на объекты На объекты можно ссылаться точно так же, как и на данные любых дрyrих типов. Здесь нет никаких особых оrpаничений или правил. v" Вопросы для самопроверки 1. В чем разница меЖдУ классом и объектом? 2 Какое ключевое слово используется для объявления класса? 3. Копия чеrо содержится в каждом объекте? 4. Покажите, как объявить класс с именем Test, содержащий две за крытые переменные типа int с именами count и тах. 5. Какое имя имеет конструктор? А какое имя имеет деструктор? 6. Класс объявлен следующим образом: class Sample { int i; public: Sample(int х) { i = х } // } ; Покажите, как объявить объект Sample, чтобы i получило началь ное значение 10. 7. Если функциячлен объявлена внутри объявления класса, кaKoro рода оптимизация выполняется автоматически? 8. Создайте класс Тriangle, в котором хранятся длина основания и BЫ сота прямоyrольноrо треyrольника в двух закрытых переменных экземпляра. Включите в класс конструктор, устанавливающий значения этих переменных. Определите две функции. Первая функция hypot( ) должна возвращать длину rипотенузы треуroль ника, а вторая функция area( )  ero площадь. 9. Расширьте класс Help, чтобы он хранил целочисленный номер ID каждоrо пользователя класса. Выводите ID на экран при уничто жения объекта справочника. Предусмотрите функцию getID( ), KO торая при вызове будет возвращать значение ID. 
Модуль 9 Подробнее о классах Цели, достиrаемые в TOM модуле 9. 1 Познакомиться с переrрузкой конструкторов 9.2 Освоить присваивание объектов 9.3 Научиться передавать объекты функциям 9.4 Узнать о возврате объектов из функций 9.5 Рассмотреть конструкторы копий 9.6 Начать использовать дружественные функции 9.7 Изучить структуры и объединения 9.8 Понять, что такое this 9.9 Освоить основы переrрузки операторов 9.1 О Рассмотреть переrрузку операторов с помощью функций-членов 9.11 Познакомиться с переrрузкой операторов с помощью функций"не членов 
352 Модуль 9. Подробнее о классах В этом модуле мы продолжим обсуждение классов, начатое в Модуле 8. Мы рассмотрим целый ряд тем, связанных с классами, включая пере rpузку конструкторов, передачу объектов функциям и возврат объек тов. Мы также остановимся на специальном типе конструктора, назы ваемом конструктором копий, который используется, коrда требуется получить копию объекта. Далее будут описаны дружественные Функ ции, структуры И объединения и, наконец, ключевое слово tbis. Mo дуль завершается обсуждением переrpузки операторов, одним из ca мых замечательных средств С++. Цель _ Переrрузка конструкторов Конструкторы, хотя они и предназначены для решения вполне оп ределенных задач, мало отличаются от дрyrих функций, и их, в част ности, можно переrpужать. Для переrpузки конструктора класса про сто объявите разные формы этоrо конструктора. Например, приведен ная ниже проrpамма определяет три конструктора: // Переrрузка КОНСТРУКТОРОВ. #include <iostream> using namespace std; class Sample { public: int Х; int у; // Переrрузим конструктор по умолчанию. Sample () { Х = у = о; } . // Конструктор с одним параметром. Sample(int i) { х = у = i; }  I ]енные конструкторы Sample // Конструктор с двумя параметрами. Sample(int i, int j) { х = i; у = j; } } ; int main () { Sample t; // активизация конструктора по умолчанию Sample t1(5); // используем Sample(int) Sample t2(9, 10); // используем Sample(int, int) cout « "t.X: " « t.x « ", t.y: " « t.y « "\n"; 
Ilрисваивание объектов 35З cout « "tl.x: " « tl.x « " cout « "t2.x: " « t2.x « " tl.y: " « t1.y« "\n"; t2.y: " « t2.y« "\n"; return о; } Вот вывод этой проrpаммы: t.x: О, t.y: О t1.x: 5, t1.y: 5 t2.x: 9, t2.y: 10 В проrpамме создаются три конструктора. Первым объявлен KOHCT руктор без параметров; он инициализирует х и У, присваивая им значе ние О. Этот конструктор становится конструктором по умолчанию, за мещая собой конструктор по умолчанию, автоматически предоставляе мый С++. Второй конструктор принимает один параметр, присваивая ero значение одновременно х и у. Третий конструктор принимает два параметра, инициализируя х и у индивидуально. Переrpуженные конструкторы имеют целый ряд достоинств. Bo первых, они повышают rибкость создаваемоrо вами класса, позволяя конструировать объектыI разными способами. BOBTOPЫX, они создают удобства для пользователя вашеrо класса, позволяя ему конструиро вать объекты способом, наиболее естественным для решения ero KOH кретной задачи. В третьих, определяя и конструктор по умолчанию, и параметрический конструктор, вы получаете возможность создавать как инициализированные, так и неинициализированные объекты. Цель _Присваивание объектов Если у вас есть два объекта одноro типа (дрyrими словами, два объ екта одноrо класса), тоrда один объект можно присвоить дрyrому. При этом недостаточно, чтобы два класса были физически одинаковы  они должны иметь еше и одно имя. По умолчанию, если один объект присваивается дрyrому, второму объекту фактически присваивается побитовая копия данных первоrо объекта. В этом случае после при сваивания оба объекта будут идентичны, но существовать независимо. Приведенная ниже проrpамма демонстрирует присваивание объектов: // Демонстрация присваивания объектов. #include <iostream> using namespace std; 
354 Модуль 9. Подробнее о классах class Test { int а, Ь; public: void setab(int i, int j) { а = i, Ь = j; } void showab () { cout « Па равно" « а « '\n'; cout « "Ь равно " « Ь « I \n' ; } } ; int main () { Test оЫ, оЬ2; obl.setab(10, 20); ob2.setab(0, О); cout « "оЫ перед присваиванием: \n"; оЫ . showab ( ) ; cout « "оЬ2 перед присваиванием: \n"; ob2.showab(); cout « ' \n' ; Присваивание одноrо объекта дpyroмy оЬ2 = оЫ; / / присвоим объект оы объекту оЬ2  cout « "оЫ после присваивания: \n"; оы. showab () ; cout « "оЬ2 после присваивания: \n"; оЬ2 . showab ( ) ; cout « I \n ' ; obl.setab(l, 1); // изменим оЫ cout « "оЫ после изменения оЫ: \n"; оЫ . showab ( ) ; cout « "оЬ2 после изменения оЫ: \n"; ob2.showab(); return о; } ВОТ ВЫВОД этой nporpaмMbI: оЫ перед присваиванием: а равно 10 Ь равно 20 оЬ2 перед присваиванием: 
Передача объектов ФУНКЦИЯМ 355 а равно О Ь равно О оЫ после присваивания: а равно 10 Ь равно 20 оЬ2 после присваивания: а равно 10 Ь равно 20 оЫ после изменения оЫ: а равно 1 Ь равно 1 оЬ2 после изменения оЫ: а равно 10 Ь равно 20 как видно из этой проrpаммы, присваивание одноro объекта дpyroмy соЗдает два объекта, содержащих одни и re же значения. Однако в осталъ ном Э1И два объекта совершенно независимы. Так, последующая модифи кация данных одноro объекта никак не отражается на данных дpyroro. OД нако вам следует опасаться побочных эффекroв, кoroрые все же MOryr воз никнyrь. Например, если объект А содержит указатель на некoroрый дрyroй объект В, тоrда и копия объекта А будет содержать поле с указате лем на ror же объект В. В ЭIOм случае изменение объеюа В поRЛИЯет на обе копии. В тaкoro рода сmyaцияx лучше обойти побитовое копирование, осущеСТВ1IЯемое по умолчанию, пyreм определения для дaннoro класса собственноro оператора присваивания, как это будет описано ниже. Цель _ Передача объектов функциям Объект можно передать функции точно так же, как и данное любо ro дрyrоrо типа. Объекты передаются функциям посредством обычно ro соrлашения С++ о передаче параметров по значению. Эrо означает, что в функцию передается не сам объект, а ero копия. Следовательно, изменения объекта внyrpи функции не отражаются на объекте, ис пользованном в качестве apryмeHTa функции. Приведенная ниже про rpaмMa иллюстрирует сказанное: // Передача функции объекта. #include <iostream> using namespace std; 
356 Модуль 9. Подробнее о классах class MyClass { int val; public: MyClass(int i) { val = i; } int getval () { return val; } void setval(int i) { val = i; } } ; void display(MyClass оЬ)  { cout« ob.getval() « '\n'; Функция di8p1ay( ) принимает объекr класса МycIass в качестве параметра } void change (MyClass оЬ)  { Функция change( ) таюке принимает объ- ект класса MyClass в качестве naраметра ob.setval(100); // не влияет на apryмeHT coиt « "Значение оЬ внутри change (): "; display (оЬ) ; } int main () { МуС 1 а s s а ( 1 О) ; cout « "Значение а перед вызовом change(): "; display (а); . Передача объекта класса MyClass функциям display( ) и change( ) change (а);  coиt « "Значение а после вызова change(): "; display (а) ; return о; } Ниже показан вывод проrpаммы: Значение а перед вызовом change(): 10 Значение оЬ внутри change(): 100 Значение а после вызова change(): 10 Как видно из вывода nporpaMMbI, изменение значения оЬ внyrpи change( ) не влияет на значение а внутри main( ). 
Передача объектов функциям 357 Конструкторы, деструкторы и передача объектов Хотя передача простыХ объектов в качестве apryмeHТOB функций является очевидной процедурой, в отношении конструкторов и дeCT рукторов MOryт возникать некоторые неожиданные явления. Чтобы понять, что здесь происходит, рассмотрим следующую проrpамму: // Конструкторы, деструкторы и передача объектов. #include <iostream> using namespace std; class MyClass { int val; public: MyClass(int i) { val = i i coиt « "Внутри конструктора \n"i } ....MyClass () { cout « " Уничтожаем\n" i } int getval() { retиrn val; } } ; void display(MyClass оЬ) { cout« ob.getval() « '\n'; } int main () { MyClass а (10) ; cout « "Перед вызовом display () . \n" ; display(a)i cout « "После возврата из display () . \n" i return о; } Результатом работы проrpаммы будет этот неожиданный вывод: Внутри конструктора Перед вызовом display(). 10 Уничтожаем После возврата из display() . Уничтожаем  Обратите внимание на второе сообщение "Уничтожаем", 
358 Модуль 9. Подробнее о классах Вы видиre, что по ходу проrpаммы конструктор вызывается один раз (что происходит, коrда создается объект а), но имеются два вызова деструктора. Посмотрим, почему так получается. Коrда объект передается функции, создается копия этоrо объекта. (И эта копия становится параметром функции.) Это значит, что начи нает существовать новый объект. Коrда функция завершается, копия apryмeHTa (т. е. параметр функции) уничтожается. Здесь возникает два фундаментальных вопроса. Первый: вызывается ли конструктор объ екта коrда создается ero копия? И второй: вызывается ли деструктор объекта, коrда копия уничтожается? OтвeТbI вполне MOryт, по всяком случае поначалу, удивить вас. Korдa при вызове функции СОЗlIЗется коrmя apryмeнтa, обычный KOH С1руктор не вызывается. BMecro эroro вызывается конструктор копий объ еюа. КОНС1руктор копий определяет, каким образом должна создаваться коШIЯ объекта. (Позже в эroм модуле будет рассказано, как создать собст венный KOHC'lpYКТOP копий.) При эroм, если в классе конструктор копий не оlШСан явным: образом, С++ предоставляет такой конструктор по умолчaнmo. Конструктор коШIЙ по умолчaнmo СОЗдает побитовую (т. е. полностью идентичную) копию объеюа. Причина, по кoroрой СОЗдается именно побиroвая коШlЯ, если вдуматься, очевидна. Поскольку оБычный конструктор используется для той или иной ИНШJ.ИЗЛИзации объекта, он не должен вызываться ради копироВЗIOfЯ уже существующеro объекra. Ta кой вызов изменит содержимое объеюа. Передавая объект функции, вы хorитe использовать текущее, а не начальное СОС1Ояние объекта. Однако, коrда функция завершается, и копия объекта, использо ванная в качестве apryмeнтa, уничтожается, вызывается функция дeCT руктора. Это необходимая операция, так как объект уходит из области видимости. Вот почему в последнем примере мы видели два вызова дe структора. Первый вызов был сделан, коrда параметр функции display( ) вышел из области видимости. Второй  коrда при завершении про rpaмMbI был уничтожен объект а внутри main( ). Подытожим: Коrда СОЗдается копия объекта ради использования ее в качестве apryмeHтa функции, обычный конструктор не вызывается. Вместо Hero вызывается конструктор копий по умолчанию, который создает побитовую, идентичную копию объекта. Однако при уничтоже нии копии (обычно это происходит при завершении функции, коrда объект уходит из области видимости) вызывается обычный деструктор. Передача объектов по ссылке Дрyrим способом передачи объекта в функцию является передача по ссылке. В этом случае в функцию передается ссылка на объект, и Функ ция выполняет оперзuЮl непосредственно над объеIcrOм, используемым в качестве apryмeнтa. Соответственно, изменения, вносимые функцией в параметр, будут изменять apryмeнт, из чеrо следует, что передача по 
Передача объектов ФУНКЦИЯМ 359 ссылке применима не во всех случаях. Однако, в тех случаях, Korдa она допустима, возникают два преимущества. Вопервых, поскольку в Функ цию передается только адрес объекта, а не целый объект, передача по ссылке должна осуществляться значительно быстрее и эффекmвнее пе редачи объекта по значению. Boвтopыx, коrда объект передается по ссьтке, не появляется никаких новых объеIcrOв И, coorвeтcтвeннo, не тратится время на конструирование и уничroжение временною объекта. Ниже приведен пример, ИЛЛЮС1рируюЩИЙ передачу объекта по ссылке: // Конструкторы, деструкторы и передача объектов. #include <iostream> using namespace stdi class MyClass { int val; public: MyClass(int i) { val = i; cout'« nВнури конструктора\n"; } MyClass() { cout « "Уничтожаем\n"; } int getval() { return val; } void setval(int i) { val = i; } } ; void display(MyClass &оЬ)  { cout« ob.getval() « '\n'; Эдесь объект класса МYCI_ передается rю СCblлке. } void change(MyClass &оЬ) { ob.setval(100); } int main () { MyClass а (10) ; cout « "Перед вызовом display() .\n"; display (а) ; cout « "После возврата из display() .\n"i change (а) ; cout « "После вызова change() .\n A ; 
360 Модуль 9. Подробнее о классах display (а) ; return О i } Ниже приведен вывод этой проrраммы: Внутри конструктора Перед вызовом display(). 10 После возврата из display(). После вызова change(). 100 Уничтожаем в этой проrpамме обе функции, и display( ), и change( ), используют передачу параметра по ссьтке. Следовательно, в функцию передается не копия apryмeHTa, а ero адрес, и функция выполняет операции непо средственно над apryмeHToM. Например, коrда вызывается функция change( ), ей по ссылке передается а Изменения параметра оЬ, выпол ненные в change( ), влияют на а в main( ). Заметьте также, что выполня ются только по одному вызову конструктора и деструктора. Так проис ходит потому, что создается и уничтожается только один объект, а Во временных объектах эта проrpамма не нуждается. Потенциальные проблемы при передаче объектов Даже коrда объекты передаются в функции обычным способом пе редачи по значению, что теоретически защищает и изолирует переда ваемый apryмeHT, MOryт возникать побочные эффектыI, которые MOryт влиять на объект, используемый в качестве apryмeHTa, вплоть до ero уничтожения. Например, если объект при своем создании выделяет некоторые системные ресурсы (скажем, память), а при уничтожении эти ресурсы освоБОЖдает, тоrда локальная копия внyrpи функции, KO rда будет вызван деструктор, освободит эти ресурсы. Это вызовет про блемы, так как исходный объект все еше использует эти ресурсы. Обычно такая ситуация завершается разрушением исходноrо объекта. Одно из решений этой проблемы состоит в передаче объекта по ссылке, как это было показано в предыдущем подразделе. В этом случае не создается копии объекта, и, соответственно, объект не уничтожается при выходе из функции. Как уже отмечалось ранее, передача объекroв по ссьтке может также ускорить вызов функции, поскольку фактиче ски в функцию передается только адрес объекта. Однако передача объ екта по ссылке применима не во всех случаях. К счастью, существует 
Возврат объектов 361 более общее решение: вы можете создать собственный конструктора копий. В этом случае вы можете точно определить, как должна созда ваться копия объекта и, тем самым, избежать описанных выше про блем. Однако перед тем, приступить к изучению конструктора копий, рассмотрим дрyroй связанный с копированием вопрос, rде также MOryт проявиться преимущества собственноro конструктора копий. Цель _ Возврат объектов Объекты MOryт не только передаваться в функции, но и возвра щаться из них. Для возврата объекта прежде Bcero необходимо при объявлении функции указать, что она возвращает тип класса. В самой функции следует вернуть объект, используя обычное предложение return. Приведенная ниже проrpамма имеет Функциючлен с именем mkBigger( ). Эта функция возвращает объект, который придает пере менной val значение вдвое большее, чем вызывающий объект: // Возврат объектов. #include <iostream> using namespace std; class MyClass { int val; public: / / Обычный конструктор. MyClass(int i) { val = i; cout « "Внутри KOHCTpYKTopa\n"; } MyClass () { cout « "Уничтожаем\n"; } int getval() { return val; } // Возврат объекта. MyClass mkBigger () {  MyClass o(val * 2); return о; mkВigger( ) ВОЗВJВЦaeТ объект кпасса МyCIass } } ; void display(MyClass оЬ) { 
362 Модуль 9 Подробнее о классах cout« ob.getval() « '\n'i } int main ( ) { cout « "Перед конструированием а. \n" ; MyClass а (10) ; cout « "После конструирования а. \n\n" ; cout « "Перед вызовом display() .\n"; display(a); cout « "После возврата из display() .\n\n"i cout « "Перед вызовом mkBigger() .\n"i а = a.mkBigger()i cout « "После возврата из mkBigger() .\n\n"; cout « "Перед вторым вызовом display () . \n" i display(a); cout « "После возврата из display () . \n \n" ; return о; } Проrpамма ВЫВОДИТ следующее: Перед конструированием а. Внутри конструктора После конструирования а. Перед вызовом display() . 10 Уничтожаем После возврата из display() . Перед вызовом mkBigger(). Внутри конструктора Уничтожаем Уничтожаем  Обратите внимание на второе сообщение "Уничтожаем" После возврата из mkBigger(). Перед вторым вызовом display(). 20 Уничтожаем После возврата из display() . Уничтожаем 
Создание и использование конструктора копий З6З В Э1Ом примере функция mkBigger( ) СОЗlIает локальный объект с име нем О, у Koroporo значение m вдвое больше, чем у вызывающею объекта. Эroт объект затем возвращается функцией и присваивается объекту а внyrpи тain( ). Далее о уничтожается, в результате чеro появляется первое сообщение "Уничroжаем". Но orкyдa берется второй вызов деструктора? Коrда функция возврашает объект, то автоматически создается Bpe менный объект, содержащий возвращаемое значение. Именно Э1ОТ объ ект фактически возвращается функцией. После тoro, как значение воз вращено, этот объект уничтожается. Вот почему на экран было выведено второе сообщение "Уничтожаем" перед сообщением "После возврата из mkBigger()". Эro индикация уничтожения временноro объекта. Как было и в случае передачи функции объекта, при возврате объ екта из функции возникают потенциальные проблемы. Уничтожение этоrо BpeMeHHoro объекта может в некоторых ситуациях привести к неожиданным побочным эффектам. Например, если объект, возвра щаемый функцией, имеет деструктор, освобождающий ресурс (память или дескриптор файла), этот ресурс будет освобожден, несмотря на то, что объект, которому присвоено возвращаемое значение, все еще ис пользует ero. Решение этой проблемы требует создания конструктора копий, который будет описан в следующем подразделе. Последнее замечание: функция может вернуть объект по ссылке, но нужно проявлять осторожность, чтобы объект, на который указы вает ссылка, не ушел из области видимости при завершении функции. Минутная тренировка 1. Конструкторы не MOryт переrpужаться. Правильно ли это? 2. Коrда объект передается в функцию по значению, создается ero копия. Уничтожается ли эта копия, коrда происходит возврат из функции? 3. Коrда функция возвращает объект, создается временный объект, co держащий возвращаемое значение. Правильно ли это? 1. Неправильно, конструкторы MOIyf переrpужаться 2. Да, копия apryмeHтa УНИЧfожается, коrда происходит возврат из функции 3 Правильно, возвращаемое из функции значение представляет собой временный объект Цель _Создание и использование конструктора копий Как показали предыдущие примеры, коrда объект передается Функ ции или возвращается из функции, создается копия этоro объекта. По умолчанию эта копия есть побитовый дубликат исходноro объекта. Ta кое копирование по умолчанию часто оказывается приемлемым, oднa 
364 Модуль 9. Подробнее о классах КО В тех случаях, коrда оно вас не устраивает, вы можете указать точный способ создания копии, явным образом определив KOHCтpyкrop копий для класса. KOHCТPyкrop копий (ero еше называют копирующим KOHCT pyкroPOM или KOHCтpyкropOM копирования) представляет собой специ альный тип переrpуженноrо KOHcтpyкropa, кoroрый активизируется aв томатически, как только потребуется копия объекта. Для начала вспомним, зачем нам понадобилось явно определять конструктор копий. Коrда объект передается в функцию, создается побитовая (т. е. точная) копия этоrо объекта, которая становится па раметром, принимающим объект. Однако имеются случаи, коrда такая идентичная копия нежелательна. Например, если объект использует ресурс, скажем, открытый файл, тоrда копия будет использовать тот же самый ресурс, что и исходный объект. В результате если копия KaK то изменяет ресурс, он будет изменен также и для исходноrо объекта! Более Toro, коrда функция завершается, копия объекта будет уничто жена с вызовом деструктора объекта. Это также может привести к He желательному воздействию на исходный объект. Схожая ситуация возникает, если объект возвращается функцией. Компилятор в этом случае создаст временный объект, содержащий KO пию значения, возвращаемоrо функцией. (Это делается автоматиче ски, и мы никак на эту процедуру повлиять не можем.) Этот BpeMeH ный объект уходит из области видимости, как только значение BepHY лось в вызывающую проrрамму, что приводит к вызову деструктора BpeMeHHoro объекта. Если, однако, деструктор уничтожает чтото, тpe буемое вызывающей проrрамме, возникнут неприятности. В основе этих проблем лежит создание побитовой копии объекта. Для тoro, чтобы от проблем избавиться, необходимо точно опреде лить, что должно происходить, коrда создается копия объекта, чтобы избежать нежелательных побочных эффектов. Для этоrо надо создать собственный конструктор копий. Перед тем, как мы приступим к исследованию возможностей KOH структора копий, важно подчеркнуть, что с++ определяет два вида ситуаций, в которых значение одноrо объекта придается дрyrому. Пер вая ситуация  присваивание. Вторая  инициализация, причем она может про исходить тремя способами: · коrда один объект явным образом инициализирует дрyrой, как это имеет место при объявлении; · Kor.na создается копия объекта с целью передачи ее в функцию; · коrда создается временный объект (обычно в качестве возвращае Moro значения). Конструктор копий активизируется только при инициализации. Операция присваивания не вызывает конструктор копий. Наиболее общая форма конструктора копий выrлядит так: uмя"ласса (const uмя"ласса& объе"т) { //тело конструктора } 
Создание и использование конструктора копий 365 Здесь объект представляет собой ссылку на объект, используемый для инициализации дрyrоrо объекта. Пусть, например, у нас есть класс MyClass, и у есть объект типа MyClass. Тоrда следующие предложения будyr вызывать конструктор копий класса MyClass: MyClass х = у; / / у явным образом инициализирует х funcl (у) ; / / у передается как параметр у = func2 ( ) ; / / у принимает возвращаемый объект в первых двух случаях конструктору копий будет передана ссылка на у. В третьем случае конструктору копий будет передана ссылка на объект, возвращаемый функцией func2(). Таким образом, в тех случа як, коrда объект передается в качестве параметра, возвращается Функ цией или используется в операции инициализации, вызывается KOHCT руктор копий С целью дублирования объекта. Не забывайте, что конструктор копий не вызывается, коrда один объект присваивается дpyrOMY. Например, в приведенном ниже фрar менте конструктор копий вызван не будет: MyClass х; MyClass у; х = у; // здесь конструктор копий не вызывается. Последнее предложение обслуживается оператором присваивания, а не конструктором копий. Приведенная ниже проrpамма демонстрирует создание KOHCтpYК тора копий: /* Конструктор копий активизируется, коrда объект передается функции. */ #include <iostream> using namespace std; class MyClass { int val; int copynumber; public: // Обычный конструктор. MyClass(int i) { val = i; copynumber = о; cout « n Внутри обычноrо конструктора \n" ; } 
366 Модуль 9. Подробнее о классах // Конструктор копий MyClass (const MyClass &0) {  Это KOHCTpyкrop копий класса МYCI_ val = o.val; copynumber = o.copynumber + 1; cout « "Внутри конструктора копий. \n" ; } .....MyClass () { if(copynumber == О) cout « "Уничтожение ориrинала.\п"; else cout « "Уничтожение копии " « copynumber « "\n"; } int getval () { return val; } } ; void display(MyClass оЬ) { cout« ob.getval() « '\n'; } int main () { MyClass а (10) ; display(a)  Конструктор копий вызывается, Korдa функции display( ) передается зpryмент а return о; } Проrрамма выводит на экран следующее' Внутри обычноrо конструктора Внутри конструктора копий. 10 Уничтожение копии 1 Уничтожение ориrинала. Вот что происходит при запуске проrpаммы. Коrда внутри main( ) создается объект а, значение ero перем;енной copynumber устанавливает ся в О обычным конструктором. Далее а передается параметру оЬ Функ ции display( ). В процессе передачи параметра вызывается конструктор копий, и создается копия а Конструктор копирования, выполняя свой код, инкрементирует значение copynumber. Коrда display( ) завершается, 
Jружественные функции 367 оЬ уходит из области ВИДИМОСТИ. Это приводит к вызову ero дecтpyктo ра. Наконец, коrда завершается main( ), а уходит из области ВИДИМОСТИ. ВЫ можете попробовать поэкспериментировать с этой проrpаммой. Например, создайте функцию, которая возвращает объект класса MyClass, и посмотрите, коrда будет вызван конструктор копий. Минутная тренировка 1. Каким образом создается копия объекта, коrда вызывается KOHCтpYК тор копий по умолчанию? 2. Конструктор копий вызывается, коrда одному объекту присваивается значение дpyroro. Правильно ли это? 3. Почему может возникнуть необходимость в явном определении KOHCT руктора копий для класса? I Конструктор копий по умолчанию создает побитовую (т е идентичную) копию 2 Неправильно, конструктор коIШЙ не вызывается, Korдa один объект присваивается дрyroму 3 Вам может понадобиться явное определение конструктора копий, Korдa копия объекта не должна быть идентичной ориrиналу, например, для преДОТВРaIцения нанесения по.. вреж:дений объектуориrиналу Цель _Дружественные функции в принципе к закрытым членам класса имеют доступ только дрyrие члены этоrо класса. Однако имеется возможность орrанизовать доступ к закрытым членам класса из функции, не входящей в данный класс, посредством объявления ее дРУ20М класса. Чтобы сделать функцию дpyrOM класса (создать дружественную классу функцию), вы включае те ее прототип в открытую (pubIic) секцию объявления класса и пред варяете ее ключевым словом friend. Например, в приведенном ниже фраrменте функция fmd( ) объявлена дpyrOM класса MyClass: class MyClass { // ... public: friend void frnd (MyClass оЬ) ; // } ; Вы видите, что ключевое слово friend предваряет остальные элемен ты прототипа. Функция может быть дpyroM более чем одноro класса. Ниже приведен короткий пример использования дружественной функции для выяснения тoro, имеют ли закрытые поля класса MyClass общий знаменатель: 
368 Модуль 9. Подробнее о классах 1/ Демонстрация дружественной функции. #include <iostream> using narnespace std; сотDenom является с 1 а s s Мус 1 а s s { дpyrOM класса МyClа. int а, Ь; 1 public: MyClass{int i, int j) { a=i; b=j; } friend int comDenom(MyClass х); // дружественная функция } ; / * Обратите внимание на то, что comDenom ( ) не является функциейчленом какоrолибо класса. */ int comDenom(MyClass х) { /* Поскольку comDenom() дружественна классу MyClass, она может непосредственно обращаться к а и Ь. */ int тах = х.а < х.Ь ? х.а : х.Ь; for(int i=2; i <= mах; i++) if( (x.a%i)==O && (x.b%i)==O) return i; return о; } int main ( ) { MyClass n(18, 111); if(comDenom(n))  cout « "Общий знаменатель равен comDenom(n) « "\n"; сотDenот вызывается обычным образом без указания объекта и оператора-точки I " « else cout« "Общеrо знаменателя HeT.\n"; return о; } в этом примере функция сошОепот( ) не является членом MyClass. Однако она имеет полный доступ к закрытым членам MyClass. KOH кретно, она может обратиться к х.а и х.Ь. Заметьте также, что comDenom( ) вызывается обычным образом, т. е. без связи с какимли бо объектом и без оператораточки. Поскольку сотОепот( ) не являет ся функциейчленом, ее не надо уточнять именем объекта (собственно rоворя, ее нельзя уточнять именем объекта). Обычно дружественной 
J!ружественные ФУНКЦИИ 369 функции передаются один или несколько объектов класса, которому она является дpyrOM, как это И имеет место в случае сотОепот( ). Хотя мы не получили никаких преимуществ, объявив сотОепот( ) дpyrOM, а не членом MyClass, существуют обстоятельства, при которых дружественная функция оказывается весьма ценным средством. Bo первых, друзья MOryт быть полезны при переrpузке некоторых типов операторов, как это будет показано ниже в этом же модуле. BOBTOPЫX, дружественные функции упрощают создание некоторых типов Функ ций BBoдaBЫBoдa, как это будет показано в Модуле 11. Третья причина, оправдывающая введение в язык дружественных функций, заключается в том, что в некоторых случаях два или He сколько классов MOryт содержать члены, взаимосвязанные с дрyrими частями вашей проrpаммы. Представьте себе, например, два различ ных класса, СпЬе и Cylinder, которые определяют характеристики куба и цилиндра, причем одной из характеристик является цвет объекта. Чтобы получить возможность леrко сравнивать цвета куба и цилиндра, вы можете определить дружественную функцию, которая сравнивает цветовые характеристики двух объектов, возврашая истину, если цвета совпадают и ложь, если они различаются. Приведенная ниже проrpам  ма иллюстрирует эту идею: // Дружественные функции MorYT быть друзьями двух или более // классов. #include <iostream> using namespace stdi class Cylinder; // упреждающее объявление enum colors { red, green, yellow }; class СиЬе { colors color; public: Cube(colors с) { color = с; } friend bool sameColor(Cube х, // } ; 8ameColor( ) является дpyrOM Cube Cylinder у); .. I class Cylinder { с о 1 о r s с о 1 о r ; sameColor( ) является также дpyroM Cylinder. public:  Cylinder(colors с) { color= с; } friend bool sameColor(Cube х, Cylinder у); // } ; 
370 Модуль 9 Подробнее о классах bool sameColor(Cube х, Cylinder у) { if(x.color == y.color) return true; else return falsei } int main () { СиЬе сиЬеl (red) i СиЬе cube2(green)i Cylinder cyl(green)i if(sameColor(cubel, cyl») cout « "кубl and ЦИЛИНДР одноrо цвета. \n"; else cout« "кубl and ЦИЛИНДР разных цвеТОВ.\n"i if(sameColor(cube2, cyl» cout « "куб2 and ЦИЛИНДр одноrо цвета. \n" ; else cout « "куб2 and цилиндр разных цветов. \n" ; return О i } Вот вывод этой проrpаммы: кубl and цилиндр разных цветов. куб2 and ЦИЛИНДР одноrо цвета. Обратите внимание на то, что эта проrpамма использует упреждаю щее объявление (называемое также упреждающей ССЬUlКОЙ, или ссылкой вперед) в отношении класса MyClass. Такая ссылка необходима изза Toro, что объявление функции sameColor( ) внутри СпЬе ссылается на класс Cylinder еще перед ero объявлением. Для Toro, чтобы создать уп режцающее объявление класса, просто используйте форму, показан ную в этой проrрамме. Дрyr одноrо класса может быть членом дpyroro. Например, преды дущую проrpамму можно переписать так, чтобы функция sameColor( ) была членом СпЬе. Обратите внимание на оператор разрешения види мости при объявлении sameColor( ) дрyrом Cylinder: /* ФУНКЦИЯ может быть членом одноrо класса И друrом BToporo. */ #include <iostream> using namespace std; 
Дружественные функции 371 class Cylinder; /z/ упреждающее объявление епиm colors { red, green, yello }; class СиЬе { colors color; public: Cube(colors с) { color= с; } bool sameColor(Cylinder у);  // } i sameColor( ) теперь член класса Cube class Cylinder { colors color; public: Cylinder(colors с) { color = с; } friend bool СиЬе:: sameColor (Cylinder у) ; // } i Cube::sameColor( ) являет- r bool СиЬе: : sameColor(Cylinder у) { if(color == y.color) return true; else return false; } int main () { СиЬе сиЬеl (red) ; СиЬе cube2(green); Cylinder cyl(green); if(cubel.sarneColor(cyl)) сои t « n кубl and цилиндр одноrо цвета. \n n ; else cout « "кубl and ЦИЛИНДр разных цветов. \п"; if(cube2.sameColor(cyl)) cout « "куб2 and цилиндр ОДНоrо цвета. \п" ; else cout « акуб2 and цилиндр разных цвеТОВ.\П"i return о; } Поскольку функция sameColor( ) является членом СоЬе, она может непосредственно обращаться к переменной color объектов типа СоЬе. Только объекты типа Cylinder должны передаваться этой функции. 
372 Модуль 9. Подробнее о классах Минутная тренировка 1. Что такое дружественная функция? С помощью кaKOrO ключевоrо сло ва она объявляется? 2. Если дружественная функция вызывается для объекта, используется ли операторточка ? 3. Может ли дружественная функция одноrо класса быть членом дpyroro? 1. Дружественная функция  это функция, не являющаяся членом класса, но имеющая доступ к закрытым членам класса, для KOToporo она является дрyrом Дружественная функuия объявляется с помощью ключевоro слова friend 2 Нет, дружественная функция вызывается как обычная функция, не являющаяся членом класса 3 Да, дрyr одноrо класса может быть членом дрyrоro. Цель _Структуры И объединения в дополнение к ключевому слову class, с++ предоставляет еще две возможности создать структуру данных классовоrо типа. Вопервых, можно создать структуру. BOBТOPЫX, можно создать объединение. Ниже описываются обе эти возможности. Структуры структуры унаследованы из язьп(а с; они объявляются с помощью ключевоrо слова struct. Описатель struct синтаксически схож с class, оба создают классовый тип. В языке С struct может содержать только данныечлены, но в С++ это оrpаничение снято. В С++ struct  это, в сущности, просто дрyrой способ задать класс. Фактически в С++ единственное различие между class и struct заключается в том, что по умолчанию все члены в struct OТКPbIТbI, а в class закрытыI. Во всех oc тальных отношениях структуры и классы эквивалентны. Вот пример структуры: #include <iostream> using namespace std; struct Test { int geti() { return i; } // эти члены public void puti(int j) { i = j; } }/ по умолчанию  pri va te : Члены cтpyкrypы по умолчанию открыты. int i; } ; int main () { 
Структуры и объединения З7З Test Si s.put....i(10); cout« s.geti(); return о; } Эта простая проrpамма определяют структурный тип с именем Test, в котором get.....i( ) и put....i( ) открыты, а i закрыта. Обратите внимание на использование ключевоrо слова private для определения закрытых эле ментов структуры. Ниже приведена проrpамма, эквивалентная предьщущей, но в KO торой вместо struct использовано ключевое слово class: #include <iostream> using namespace std; class Test { int i; // private по умолчанию public: int geti() { return i; } void puti (int j) { i = j i } } ; int main () { Test Si s.puti(10); cout « s.geti(); return о; } в большинстве своем проrpаммисты на с++ используют class для определения формы объекта, содержащеrо функциичлены, а struct используют традиционным образом, для описания объектов, содержа щих только данныечлены. Иноrда для описания структуры, которая не содержит членовФункций, используется аббревиатура "POD", оз начающая "plain old data", что приблизительно можно перевести как "чистые данные, как в старину". Спросим у эксперта Вопрос: Если struct и class так похожи, почему в с++ имеются оба ключе вые слова? 
374 Модуль 9. Подробнее о классах OrвeT: На первый взrляд наличие в языке структур и классов, имеющих фактически одинаковые возможности, является избыточным. Мноrие начи нающие работать на С++ удивляются, зачем существует эта очевидное дуб лирование. Нередко можно даже услышать предложение устранить одно из этих ключевых слов, либо class, либо stnJct. Ответ на такие рассуждения заключается в том, что разработчики С++ старались обеспечить совместимость с С. в С++, как он определен на cero ДНЯ, стандартная структура С является вполне допустимой. В С, rде OтcyrcT вует концепция открытых или закрытыIx членов структуры, все члены cтpyк туры по умолчанию открыты. Поэтому и в С++ члены структуры по умолча нию объявляются роыi,, а не private. Ключевое слово class было введено с отчетливой идеей подцержки инкапсуляции, и представляется разумным, что члены класса по умолчанию объявляются закрытыми. Однако, чтобы не потерять совместимость с С в этом отношении, умолчание для структур надо было оставить, поэтому и было добавлено новое ключевое слово. OДHa ко имеется и более серьезное основание для разделения структур и классов. Поскольку class является сущностью, синтаксически отделенной от struct, определение класса имеет возможность развиваться в направлениях, KOTO рые уже не будут синтаксически совместимыI с Сподобными структурами. Раз эти два понятия разделены, будущее развитие С++ не будет связано за ботами о совместимости с Сподобными структурами. Объединения Объединение представляет собой область памяти, совместно исполь зуемую двумя или большим числом переменных. Объединение созда ется посредством ключевоrо слова union, и ero объявление схоже с объ явлением структуры, как это показано в следующем примере: union utype { short int i; char сЬ; } ; Здесь определено объединение, в котором значения short int и сЬм занимают одну и ту же ячейку памяти. Однако следует внести ясность: невозможно, чтобы объединение содержало обе переменные, целочис ленную и символьную, в одно и т.о же время, потому что i и сЬ перекры вают дрyr друт. Проrpамма может в любой момент трактовать данные в объединении либо как целочисленную переменную, либо как символ. Таким образом, объединение дает вам возможность рассматривать ДBY мя ИЛИ большим числом способов одну и ту же порцию данных. Вы можете объявить переменную типа объединения, поместив ее имя в конец объявления union, или использовав отдельное предложе 
Структуры и объединения 375 ние объявления. Например, для объявления объединенной перемен ной оуп типа utype, можно написать: utype uvar; в uvar обе переменные, и целочисленная i, и символьная сЬ, ис пользуют одну и ту же память. (Разумеется, i занимает два байта, а сЬ использует только один.) На рис. 91 показано, как располаrаются i и сЬ по одному и тому же адресу. Рис. 9-1. Как переменные i и сЬ размещаются по одному и Тому же адресу Соrласно идеолоrии с++, объединение представляет собой класс, в котором все элементы занимают одну и ту же память. Фактически union определяет классовый тип. Объединение может содержать функции конструктора и деструктора, так же, как и функциичлены. Поскольку объединение унаследовано от С, ero члены по умолчанию OТКPblТbI. Ниже приведена проrpамма, использующая объединение для BЫBO да на экран символов, составляющих младший и старший байты KO роткоrо целоrо (в предположении, что короткое целое занимает два байта) : // Демонстрация объединения. #include <iostream> using nаrnеврасе std; union utype { utype(short int а) { i = а; }; u.....type (char х, char у) { сЬ [ О] = х; сЬ [ 1] = у; } void showchars(){ cout « ch[O] « cout « ch[l] « .. ". , "\n"; } short int i; char сЬ[2]; } ; Данные-члены объединения расno- naraются в одной и той же памяти 
376 Модуль 9 Подробнее о классах int main () { utype u (1000) ; utype и2 ( I Х', 'У'); cout « "и как целое: "; cout « u.i « "\n": cout « "и как симво лы: "; u.showchars(): Данное в объекте utype мо- ryr рассматриваться как 8hon int или как два char cout « "и2 как целое: ": cout « и2. i « "\n"; cout « "и2 как симв олы: "; u2.showchars(): return о: } Вывод проrpаммы выrлядит так. u как целое: 1000 u как символы: Ш . и2 как целое: 22872 и2 как символы: Х У как ЭТО видно из вывода проrpаммы, используя объединение utype, можно рассматривать одни и те же данные разными способами. Как и структуры, объединения с++ произошли от cBoero предше ственника в с. Однако в С объединения MOryт включать только дaH ныечлены; функции и конструкторы недопустимы. В с++ объеди нения имеют расширенные возможности класса. Однако из Toro, что с++ придает объединениям большие возможности и rибкость, не следует, что вы должны ИХ использовать. Часто объединения coдep жат только данные. Однако в случаях, коrда вы сможете инкапсули ровать объединение вместе с манипулирующими им проrpаммами, вы получите возможность добавить в свою проrрамму весьма слож ную структуру. В с++ существует ряд оrpаничений на использование объедине ний. Большая их часть связана со средствами С++, которые будут об суждаться в последующих разделах этой книrи, поэтому мы упомянем их здесь только ШIЯ полноты картины. ПреЖде Bcero, union не может наследовать класс. Далее, union не может выполнять роль базовоrо класса. union не может включать виртуальные функциичлены. Пере менные static не MOryт быть членами union. Нельзя использовать член ссылку. union не может иметь в качестве члена никакой объект, пере rpужающий оператор ==. Наконец, никакой объект не может быть чле ном union, если этот объект имеет явный конструктор или деструктор. 
труктуры И объединения 377 Безымянные объединения В С++ имее1СЯ специальный тип объединения, называемый безы.мян ны.м объедuненuем. Безымянное объединение не включает имя типа, и для такою объединения нельзя объявить переменные. Такое объединение просто указывает компилятору, что ею члены должныI размещаться в об щей памяти. Однако обращение к самим переменным осушествляе1СЯ непосредственно, без использования обычноro синтаксиса с оператором точкой. Рассмотрим в качестве примера такую проrpамму: // Демонстрация безымянноrо объединения. #include <iostream> #include <cstring> using namespace std; int main () { // объявим безымянное объединение union { 4 Это безымянное объединение long 1; double d; char s ( 4] ; } // теперь обратимся к элементам объединения непосредственно 1 = 100000; 4 cout « 1 « " "; d = 123.2342;  cout « d« " "; s t rcpy ( s, " h i " );  cout « s; I Обращение к элементам бвэымянноrо объе- динения осущестВlКleТСЯ непосредственно I return о; } Вы видите, что обращение к элементам объединения осуществляет ся, как если бы они были объявлены обычными локальными перемен ными. Собственно, в своей проrpамме именно так вы и будете их ис пользовать. Далее, несмотря на то, что они определены в объявлении union, они находятся в той же области видимости, что и остальные ло кальные переменные в том же блоке. Отсюда следует, что имена чле нов безымянноrо объединения не должны конфликтовать с дрyrими идентификаторами, известными в той же области видимости. Все оrpаничения обычных объединений относятся так же и к безы мянным, со следующими добавлениями. Вопервых, элементами бе 
378 Модуль 9 Подробнее о классах зымянноrо объединения MOryт быть только данные; функциичлены не допускаются. Безымянные объединения не MOryт содержать элементы private и protected. (Описатель protected будет обсуждаться в Модуле 10.) Наконец, rлобальные безымянные объединения должны быть объявле ны, как static. Цель _ Ключевое слово this Перед тем как перейти к переrpузке, необходимо описать еще одно ключевое слово с++: tbis. Каждый раз, коrда активизируется Функ циячлен, она автоматически получает указатель с именем tbis на объ ект, для Koтoporo она вызвана. Указатель tbis является llеЯ81lЫМ пара метром всех Функцийчленов. Таким образом, внутри функциичлена tbis может быть использован для обращения к данному объекту. Как вы знаете, функuиячлен может непосредственно обращаться к закрытым данным cBoero класса. Например, если описан такой класс: class Test { int i; vo i d f () { ... }; // } ; то внутри функции f( ) для присваивания переменной i значения 1 О может быть использовано такое предложение: i = 10; Фактически, однако, это предложение является сокращенным Ba риантом следующеrо: this>i = 10; Чтобы освоиться с указателем tbis, рассмотрите следующую KOpOТ кую проrpамму: // Используем указатель "this". #include <iostream> using namespace std; class Test { int i; 
Ilереrрузка операторов 379 public: void loadi(int val) { this>i = val;  ТО Ж8 самое, ЧТО I = VaI; } int get.....i () { return this>i;  ТО Ж8 самое, что return 1, } } int main () { Tes t о; o.load.....i(100); cout « o.get.....i(); return о; } Эта проrpамма выводит число 100. Пример, конечно, тривиален, и никому не придет в rолову использовать указатель tbis таким образом. Вскоре, однако, вы увидите, почему указатель tbis так важен в про rpаммировании на С++. Еше одно замечание. Дружественные функции не получают указа теля tbis, потому что они не являются членами класса. Указатель tbis доступен только Функциямчленам. Минутная тренировка 1. Может ли struct содержать Функциичлены? 2. Что является определяющей характеристикой структуры union? 3. На что ссылается tbis? 1. Да. struct может содержаrь функции..члены 2 Данные..члены структуры uDion занимают одну и ry же память 3 this являе1СЯ указателем на объект, ШIЯ KOToporo бьта вызвана функция..tтен Цель _ Переrрузка операторов Оставшаяся часть этоrо модуля посвящена исследованию одноrо из самых замечательных и мощных средств С++: nереzрузк,е операторов. В с++ операторы MOryт быть переrpужены относительно создаваемоrо вами классовоrо типа. Принципиальная ценность переrpуженных опе 
380 Модуль 9. Подробнее о классах раторов заключается в том, что они позволяют вам эффективно интеr рировать новые типы данных в вашу проrраммную среду. Коrда вы переrpу:жаете оператор, вы определяете смысл этоrо опе ратора для KOHкpeTHoro класса. Например, класс, который определяет связный список, Mor бы использовать оператор + для добавления в список новых объектов. Класс, реализующий стек, Mor бы использо вать оператор + для проталкивания объекта в стек. Дрyrой класс Mor бы использовать тот же оператор + для совершенно дрyrиx целей. Ko rда оператор переrpужается, ero исходный смысл не теряется. Просто относительно KOHKpeTHoro класса возникает новая операция, обозна чаемая этим оператором. Поэтому переrpузка оператора +, скажем, для работы со связным списком, никак не затронет ero назначения OT носительно целых чисел (rде он выполняет операцию сложения). Переrpузка операторов тесно связана с переrpузкой функций. Для переrpузки оператора вы должны определить, что означает эта опера ция относительно Toro класса, rде осуществляется переrpузка. Для этоrо вы создаете функцию operator (операторную функцию). Общая форма функции operator выrлядит так: тип имя класса: :operator# (список apZYMeH тов) { / /операции } Здесь переrpужаемый оператор должен заместить знак #, а тип  это тип значения, возвращаемоrо определяемой операцией. Хотя пе реrpу:женный оператор может возвращать любой тип по вашему жела НИЮ, однако часто возвращаемое оператором значение имеет тот же тип, что и класс, для Koтoporo оператор переrpу:жается. В этом случае обеспечивае1СЯ использование переrpу:женноrо оператора в составных выражениях. Конкретная природа списка apryмeHToB определяется несколькими факторами, описанными в последующих подразделах. Операторные функции MOryт быть как членами, так и не членами класса. Однако операторные Функциине члены класса часто объявля ются друзьями класса. Переrpузка операторных функций, являющихся и не являющихся членами класса, выполняется схоже, однако с HeKO торыми различиями, которые будут описаны ниже.  Замечание Большое количество операторов в С++ делает тему переrрузки опе.. раторов весьма объемной, и в этой книrе нет возможности изложить все ее аспекты. Желающих познакомиться с исчерпывающим описани.. ем переrрузки операторов адресую к моей книrе: С++: The Coтp/ete Refereпce, Osborne/McGraw..HiII. 
Переrрузка операторов с использованием функцийчленов 381 Цель _ Переrрузка операторов с использованием функций- членов Начнем изучение операторных функцийчленов с простоro примера. Приведенная ниже проrpамма создает класс 1breeD, который обслужива ет координаты объек:m в трехмерном пространстве. Проrpамма переrpу жает операторы + и == для этоro класса. Давайте рассмотрим ее в деталях. // Определение + и = для класса ThreeD. #include <iostream> using namespace std; class ThreeD { int х, У, z; // Трехмерные координаты public: ThreeD () { х = у = z = о; } ThreeD(int i, int j, int k) { х = i; у = j; z = k; } ThreeD operator+(ThreeD ор2); // орl подразумевается ThreeD operator=(ThreeD ор2); // орl подразумевается void show () ; } ; / / Переrрузим +. ThreeD ThreeD::operator+(ThreeD ор2)  { Переrpyзка + для ТhreeO ThreeD temp; temp.x = х + ор2.х; // Это сложение целых чисел temp.y = у + ор2.у; // и по отношению к ним знак + сохраняет temp.z = z + op2.z; // свое первоначальное назначение. return temp;  } Возврат нoвoro объекта. Ap остались без изменений // Переrрузим присваивание. ThreeD ThreeD::operator=(ThreeD ор2)  { Пеperpyзкa = для ТhreeD х = ор2.х; // Это присваивание целых чисел у = ор2.у; // и по отношение к ним знак = сохраняет z = op2.z; // свое первоначальное назначение. 
382 Модуль 9. Подробнее о классах return *this;  } Возврат модифицированноrо объекта. / / Вывести координа ты Х, У, z. void ThreeD: : show ( ) { cout « х « " " . , coиt « у « 11 " . , coиt « z « "\n"; } int main () { Thr е е D а ( 1, 2, З), Ь ( 1 О, 1 О, 1 О), с; coиt « "Исходное значение а: "; а . show () ; cout « " Исходное значение Ь: "; Ь. show ( ) ; coиt « "\n"; с = а + Ь; // сложим а и Ь cout « "Значение с после с = а + Ь: ее; c.show(); coиt « "\n"; с = а + Ь + с; // сложим а, Ь и с coиt « "Значение с после с = а + Ь + с: ее; С . S how ( ) ; coиt « "\n"; с = Ь = а; // демонстрация множественноrо присваивания coиt « "Значение с после с = Ь = а: ее; c.show(); cout « "Значение Ь после с = Ь = а: "; b.show() ; return о; } Проrpамма ВЫВОДИТ на экран следую шее: Исходное значение а: 1, 2, 3 Исходное значение Ь: 10, 10, 10 
Переrрузка операторов с использованием функцийчленов З8З Значение с после с = а + Ь: 11, 12, 13 Значение с после с = а + Ь + с: 22, 24, 26 Значение с после с = Ь = а: 1, 2, 3 Значение Ь после с = Ь = а: 1, 2, 3 Изучая текст проrpаммы, вы моrли удивиться тому, что обе опера торные функции имеют только по одному параметру, хотя они переrpу жают бинарные операции. Причина эrorо явноro противоречия заклю чае1СЯ в том, что коrда бинарный оператор переrpужается с помощью функциичлена, явным образом в нее предается только один apryмeHT. Дрyrой apryмeHT передается неявным образом посредством указателя tbis. Таким образом, в строке temp.x = х + ор2.х; х обозначает tbis..>x, т. е. переменную х тoro объекта, для KOToporo вызвана операторная функция. Во всех случаях операторная функция вызывается для объекта, указанноrо с левой стороны знака операции. Объект с правой стороны передается функции в качестве apryMeHTa. В обшем случае операторная функция не нуждается в параметрах при переrpузке yнapHoro оператора и требует указания только одноrо параметра при переrpузке бинарноrо оператора. (Вы не можете пере rpузить тернарный оператор ?) В любом случае объект, для Koтoporo вызывается операторная функция, неявным образом передается в функцию посредством указателя tbis. Чroбы уяснить себе, как работает переrpузка оперaroров, рассмотрим последнюю проrpамму детально, начиная с переrpуженноro оператора +. Коrда над двумя объектами пma 'ПtreeО вьmолняется операция +, значе ния их сOOIВeТCТВyI01.I.ПfX координат склад:ывaюrcя, как это видно из текста функции operator+( ). Заметьте, однако, что эта функция не изменяет зна чений ни одноro из операндов. BMecro этоro она возвращает объект rnпа 'ПtreeО, содерЖ3WИЙ результат операции. Чтобы понять, почему операция + не изменяет содержимоro обоих объекroв, подумайте, как выполняе1СЯ стандартная арифметическая операция + в выражеmm 10 + 12. Резуль1ЗТ этой операции 22, однако ни 10, ни 12 не изменились. Хorя и нет опреде ленноro правила, запрещающеro переrpуженному оператору + изменять значение одноro из ero операндов, лучше, чтобы действия переrpуженноro оператора соответствовали ero первоначальному назначению. Обратите внимание на то, что operator+( ) возвращает объект типа ThreeD. Хотя функция в принципе моrла бы возвращать любой допус тимый в с++ тип, возврат объекта типа TbreeD позволяет использовать оператор + в составных выражениях вроде а+Ь+с. В этом выражении сложение а+Ь дает результат типа ТhreeO. это значение затем прибав ляе1СЯ к с. Если бы сложение а+Ь давало результат любоrо дpyroro типа, составное выражение не моrло бы выполняться должным образом. 
384 Модуль 9 Подробнее о классах в противоположность оператору +, оператор присваивания моди фицирует один из своих apryмeHToB. (В этом, впрочем, и состоит сущ ность присваивания.) Поскольку функция operator=() вызывается для объекта, который указывается с левой стороны знака ==, то именно этот объект модифицируется операцией присваивания. Чаще Bcero возвращаемое значение переrpуженноrо оператора присваивания по сле Toro, как присваивание выполнено, становится объектом с левой стороны знака ==. (Это соответствует традиционному действию опера тора ==.) Например, для Toro, чтобы можно было написать предложе ние вроде а = ь = с = d; необходимо, чтобы operator=( ) возвращал объект, на который указы вает tbis, а это как раз объект, указанный с левой стороны предложе ния присваивания. В результате возникает возможность выполнять присваивание по цепочке. Использование указателя tbis в операции присваивания является одним из ero важнейших применений. В рассматриваемой проrpамме в сущности не было необходимости в переrpузке операции ==, поскольку оператор присваивания по умол чанию, предоставляемый С++, вполне rодится для класса ThreeD. (Как уже отмечалось ранее, операция присваивания по умолчанию представляет собой побитовое копирование.) В нашем примере опера тор == был переrpужен просто чтобы продемонстрировать процедуру переrpузки. В принципе вам надо будет переrpужать == только в тех случаях, коrда действующее по умолчанию побитовое копирование даст неправильный результат. Поскольку оператор == по умолчанию работает удовлетворительно для класса ThreeD, в последующих приме рах этоro модуля он переrpужаться не будет. Друrие вопросы Переrpужая бинарные операторы, имейте в виду, что во мноrих случаях имеет значение порядок операндов. Так, хотя А + В представ ляет собой коммутативную операцию, А  В таковой не является. (А  В не то же самое, что В  А!) Поэтому, реализуя переrpуженные вари анты некоммyrативных операторов, помните, какой операнд стоит с левой стороны, и какой с правой. Например, вот как надо переrpузить минус для класса ThreeD: // Переrрузка вычитания. ThreeD ThreeD::operator(ThreeD ор2) { ThreeD temp; 
Ilереrрузка операторов с использованием функций..членов 385 temp.x = х  ор2.х; temp.y = у  ор2.у; temp.z = z  op2.z; return temp; } Не забывайте, что вызывает операторную функцию операнд, стоя щий С левой стороны знака операции. Правый операнд предается яв ным образом. Использование функций..членов для переrрузки унарных операторов Унарные операторы, такие как ++,   или унарные  и +, также MO ryт быть переrpужены. Как уже отмечалось выше, если унарный оператор переrpужается посредством функциичлена, операторной функции яв ным образом не передаются никакие объекты. Просто операция выпол няется над объекroм, который вызывает функцию посредством неявно передаваемою указателя tbis. Ниже приведена для примера проrpамма, КО1Орая определяет операцию инкремента ШIЯ объекroв типа 1breeD: // Переrрузка YHapHoro оператора ++. #include <iostream> using namespace std; class ThreeD { int х, у, z; // Трехмерные координаты public: ThreeD () { х = у = z = о; } ThreeD(int i, int j, int k) {х = i; у = j; z = k; } ThreeD operator++(); // префиксный вариант ++  void show ( ) Пеperрузим ++ ДI1Я класса ТhreeO } ; // Переrрузим префиксный вариант ++. ThreeD ThreeD::operator++() { х++; // инкремент х, у и z У++; z++; return *thisi  Возврат инкрементированноrо объекта } 
386 Модуль 9 Подробнее о классах / / Выведем координаты Х, У, z. void ThreeD: : show () { cout « х«" "; cout « у «" "; cout « z « "\n"; } int main ( ) { ThreeD а(l, 2, 3); cout « "Исходное значение а: "; а . show ( ) ; ++а; // инкремент а cout « "Значение после ++а: "; a.show(); return о; } Вывод этой проrpаммы выrлядит таким образом: Исходное значение а: 1, 2, 3 Значение после ++а: 2, 3, 4 Как это удостоверяет вывод проrpаммы, operator++( ) выполняет инкремент каждой координаты в объекте и возвращает модифициро ванный объект. Как и раньше, эта процедура соответствует традици онному пониманию смысла оператора ++. Как вы знаете, операторы ++ и   имеют префиксные и пост фиксные формы. Например, предложения ++х; и х++; являются допустимыми применениями оператора инкремента. Как бьто указано в комментариях в последней проrpЗММе, функция operator++( ) определяет префиксную форму ++ для класса ThreeD. Однако, пост фиксная форма этоro оператора также может быть переrpужена. ПрО1О 1ИП постфиксной формы оператора ++ для класса ThreeD выrлядит так: ThreeD operator++(int notused); 
Ilореrрузка операторов с использованием функций..членов 387 Параметр notused не используется функцией и на Hero не следует обращать внимания. Этот параметр просто предоставляет способ ШIЯ компилятора различить префиксную и постфиксную формы операто ра инкремента. (Постфиксный декремент использует тот же подход.) Вот один из способов реализации постфиксноrо варианта операции ++ для класса ТhreeD: // Переrрузим постфиксный вариант ++. ThreeD ThreeD::operator++(int notused)  { Обратите внимание на napaметр notused ThreeD temp = *this; // сохраним исходное значение х++; // инкремент х, у и z у++; z++; return temp; // вернем исходное значение } Обратите внимание на сохранение этой функцией исходноrо co стояния операнда с помощью предложения ThreeD temp = *this; и возврата, перед завершением, объеюа temp. Обычный смысл пост фиксноro инкремента заключается в том, что сначала определяется зна чение операнда, и лишь затем выполняется ero инкремент. Следователь но, перед тем, как исходное значение операнда будет инкрементировано, необходимо сохранить это исходное значение и использовать в качестве возвращаемоro значения именно ею, а не модифицированное значение. Приведенная ниже проrpамма реализует обе формы оператора ++: // Демонстрация префиксноrо и постфиксноrо ++. #include <iostream> using namespace std; class ThreeD { int х, У, z; / / Трехмерные координаты public: ThreeD () { х = у = z = О i } ThreeD(int i, int j, int k) {х = i; у = ji Z = k; } ThreeD operator++ (); / / префиксный вариант -+ + ThreeD operator++(int notused)i // постфиксный вариант ++ void show () ; } i 
388 Модуль 9 Подробнее о классах 1/ Переrрузим префиксный вариант ++. ThreeD ThreeD::operator++() { х++; // инкремент х, у и z у++; z++; return *this; // вернем измененное значение } // Переrрузим постфиксный вариант ++. ThreeD ThreeD::operator++(int notused) { ThreeD temp = *this; // сохраним исходное значение х++; // инкремент х, у и z у++; z++; return tempi // вернем исходное значение } // Выведем координаты Х, У, z. void ThreeD: : show ( ) { cout « х « " " . , cout « у « " " . , cout « z « "\n"; } int main ( ) { ThreeD a(l, 2, 3); ThreeD Ь; cout « "Исходное значение а: ее; a.show(); cout « "\n"; ++а; / / префиксный инкремент  cout « "Значение после ++а: R; а . show ( ) ; Вызов функции префиксноrо инкремента а + +; / / постфиксный инкремент  cout « "Значение после а++: h; а . show ( ) ; Вызов функции постфиксноrо инкремента cout « "\n R ; 
Переrрузка операторов с использованием функцийчленов 389 ь = ++а; / / Ь получает значение а после инкремента cout « nЗначение а после Ь = ++а: n; а . show ( ) i cout « "Значение Ь после Ь = ++а: "; b.show(); cout« "\n"; ь = а++; / / Ь получает значение а перед инкрементом cout « "Значение а после Ь = а++: "; a.show(); cout « "Значение Ь после Ь = а++: "; Ь. show ( ) ; return О i } Вывод этой проrpаммы выrлядит следующим образом: Исходное значение а: 1 , 2, 3 Значение после ++а: 2 , 3 , 4 Значение после а++: 3 , 4, 5 Значение а после Ь = ++а: 4, 5 , 6 Значение Ь после Ь = ++а: 4, 5, 6 Значение а после Ь = а++: 5, 6 , 7 Значение Ь после Ь = а++: 4, 5, 6 Не забывайте, что если знак ++ указан перед своим операндом, BЫ зывается operator++( ). Если он указан после cBoero операнда, вызыва ется operator++(int notused). Тот же самый подход используется для пе реrpузки префиксноrо или постфиксноrо оператора декремента для любоrо класса. Вы можете в качестве упражнения попробовать опре делить оператор декремента для ТhreeD. Любопытно отметить, что ранние версии с++ не делали различия между префиксной и постфиксной формами операторов инкремента и декремента. В этих старых версиях для обоих вариантов оператора BЫ зывалась префиксная форма операторной функции. Работая со CTapы ми С++проrpаммами, имейте в виду это обстоятельство. Минутная тренировка 1. Операторы должны переrpужаться для каждоrо класса отдельно. Пра вильно ли это? 
390 Модуль 9 Подробнее о классах 2. Сколько параметров имеет операторная функциячлен для бинарных операторов? 3. При использовании бинарной операторной функциичлена левый опе ранд передается в функцию посредством I Правильно, оператор должен переrpу:жаться МЯ KOHкpeTHoro класса 2 Бинарная операторная функция..член имеет один параметр, который принимает правый операнд 3 this  Цель _Опера торные функции-не члены Вы можете переrpузить оператор ШIЯ конкретноrо класса, используя функцию, не являющуюся членом этоrо класса. Часто эта функция оп ределяется, как дружественная этому классу. как вы уже знаете, дру:же ственные функции не имеют указателя tbis. Следовательно, если дру:же ственная функция используется для переrpузки оператора, то для би HapHoro оператора в нее должны передаваться явным образом оба операнда, а при переrpузке yнapHoro оператора  один операнд. С по мощью дружественных функций MOryr быть переrpу:жены все операто ры, за исключением ==, ( ), [ ] и >. Приведенная ниже проrpамма использует для переrpузки опера ции + в классе ThreeD вместо функциичлена дружественную функцию: // Использование дружественной операторной функции. #include <iostream> using namespace std; class ThreeD { int х, у, z; / / Трехмерные координаты public: ThreeD () { х = у = z = о; } ThreeD(int i, int j, int k) { х = i; у = j; z = k; } friend ThreeD operator+(ThreeD орl, ThreeD ор2); void show () ; } ; I Здесь ФУНКЦИЯ operator+( ) является дpyrOM ТlveeO. Заметьте, что ей требуются два napaметра // + теперь является дружественной функцией. I ThreeD operator+(ThreeD орl, ThreeD ор2)  { 
Операторные функциине члены 391 ThreeD temp i temp.x = орl.х + ор2.х; temp.y = орl.у + ор2.у; temp.z = opl.z+ op2.z; return temp; } // Выведем координаты Х, У, z. void ThreeD: : show() { cout « х « " ,. . , cout « у « 11 n . I , cout « z « "\n"i } int main () { ТЬ re е D а ( 1, 2 I 3) I Ь ( 1 О, 1 О, 1 О) I с; coиt « "Исходное значение а: "; а . show () ; cout « 11 Исходное значение Ь: "; Ь. show ( ) i coиt « "\n"; с = а + Ь; // сложим а и Ь cout « "Значение с после с = а + Ь: 11; с . S how ( ) ; cout « "\n' l i с = а + Ь + с; // сложим а, Ь и с cout « "Значение с после с = а + Ь + с: "; С . show ( ) ; cout « ., \n" ; с = ь = а; // демонстрация множественноrо присваивания cout « "Значение с после с = Ь = а: "; с . show ( ) ; cout « "Значение Ь после с = Ь = а: "; Ь . show () ; return о; } 
392 Модуль 9 Подробнее о классах Вывод этой проrpаммы выrлядит таким образом: Исходное значение а: 1, 2, 3 Исходное значение Ь: 10, 10, 10 Значение с после с = а + Ь: 11, 12, 13 Значение с после с = а + Ь + с: 22, 24, 26 Значение с после с = Ь = а: 1, 2, 3 Значение Ь после с = Ь = а: 1, 2, 3 Вы видите, что теперь в функцию operator+( ) передаются оба опе ранда. Левый операнд передается в орl, правый  в ор2. Во мноrих случаях использование для переrpузки оператора друже ственной функции вместо функциичлена не дает никаких преиму ществ. Однако, имеется одна ситуация, в которой дружественная функ ция оказывается весьма полезной: если вы хотите, чтобы с левой cтopo ны бинарной операции был указан объект BcтpoeHHoro типа. Для тoro, чтобы понять, почему это так, поразмыслите над следующим. Как вы знаете, указатель на объект, активизирующий операторную функцию член, передается через tbis. В случае бинарноrо оператора функция aк тивизируется объекroм, стоящим слева. Если для объекта, стоящеrо слева, интересующая нас операция определена, то все хорошо. Пусть, например, для HeKOТOporo объекта Т определены операции присваива ния и целочисленноrо сложения. Тоrда приведенное ниже предложение Т = Т + 10; // будет работать вполне допустимо. Поскольку объект Т указан слева от оператора +, он активизирует переrpуженную операторную функцию, которая (оче видно) умеет прибавлять целочисленное значение к не которому эле менту Т Однако, следующее предложение работать не будет: Т = 10 + Т; // не будет работать Проблема здесь в том, что объект, указанный слева от оператора +, является переменной типа int, встроенным типом, для KOToporo не оп ределены операции над целым числом и объектами типа, которому принадлежит объект Т Решение поставленной проблемы заКЛIQчается в переrpузке + с ис пользованием двух дружественных функций. В этом случае оператор ной функции явным образом передаются оба apryмeHTa, и она активи зируется, как и любая дрyrая переrpуженная функция, исходя из типов ее apryмeНТOB. Один вариант операторной функции + выполняет опе рации объект + целое, дрyrой  целое + объект. Переrpузка + (или лю боrо дpyroro бинарноrо оператора) с помощью дружественных функций 
Операторные функции"не члены З9З позволяет помещать встроенный тип как с левой, так и с правой CTOpO ны оператора. Приведенная ниже проrpамма иллюстрирует эту технику. Она определяет два варианта функции operator+( ) для объектов типа ТhreeD. Оба прибавляют целочисленное значение к каждой из перемен ных экземпляра класса ТbreeD. Целочисленная переменная может быть указана как с левой, так и справой стороны оператора. // Переrрузка int + объект и объект + int. #include <iostream> using namespace stdi class ThreeD { int х, у, z; // Трехмерные координаты public: ThreeD() { х = у = z = о; } ThreeD(int i, int j, int k) { х = ii У = ji Z = ki } friend ThreeD operator+{ThreeD орl, int op friend ThreeD operator+{int орl, ThreeD op I void show () i Эти функции обеспечивают опера- } i ЦИИ объекr + Int и Int + объекr // Эта функция обеспечивает ThreeD + int ThreeD operator+(ThreeD орl, int ор2) { ThreeD tempi temp.x = орl.х + ор2; temp.y = орl.у + ор2; temp.z = opl.z + ор2; return tempi } // Эта функция обеспечивает int + ThreeD ThreeD operator+{int орl, ThreeD ор2) { ThreeD temp i temp.x = ор2.х + орl; temp.y = ор2.у + орl; temp.z = op2.z + орl; return tempi } // Выведем координаты Х, У, z. 
394 Модуль 9. Подробнее о классах void ThreeD: : show ( ) { cout « х «" "; cout «у« ", "; cout « z « "\n"; } int main ( ) { ThreeD a(l, 2, 3), Ь; cout « "Исходное значение а: "; а. show ( ) ; cout « "\n"; ь = а + 10; // объект + int cout « "Значение Ь после Ь = а + 10: "; Ь. show ( ) ; сои t « "\ n" ; ь = 10 + а; / / int + объект  cout « "Значение Ь после Ь = 10 + а: "; Ь. show ( ) ; Здесь встроенный тип указан с левой стороны оператора + return о; } ВЫВОД проrpаммы выrлядит так: Исходное значение of а: 1, 2, 3 Значение Ь после Ь = а + 10: 11, 12, 13 Значение Ь после Ь = 10 + а: 11, 12, 13 Поскольку функция operator+( ) переrpу:жена дважды, она обеспе чивает оба варианта размещения целочисленной переменной и объек та типа ThreeD в операциях сложения. Использование дружественной функции ДЛЯ переrрузки YHapHoro оператора Унарный оператор тоже можно переrpузить с помощью дружест венной функции. Однако, если вы переrpу:жаете ++ или  , вы долж ны передавать операнды в функцию в качестве параметровссылок. 
Операторные функции-не члены 395 Поскольку параметрссьтка представляет собой неявный указатель на apryмeнт, изменения параметра приведут к изменениям apryмeHтa. Использование параметра"'ссылки позволит функции выполнять ин кремент или декремент объекта, указанноrо в качестве операнда. Если для переrpузки операторов инкремента или декремента исполь зуется дружественная функция, префиксная форма принимает один па раме1)) (кoroрый является операндом). ПОС1фиксная форма принимает два параметра. Второй параметр имеет тип int и не используется. Вот как можно переrpузить обе формы дружественной функции operator++( ) для класса ТhreeD: /* Переrрузка префиксной операции ++ с помощью дружественной функции. Такая переrрузка требует использования параметрассылки. */ ThreeD operator++(ThreeD &орl) { орl.х++; орl.у++; opl.z++; return орl; } /* Переrрузка постфиксной операции ++ с помощью дружественной функции. Такая переrрузка требует использования параметрассылки. */ ThreeD operator++(ThreeD &орl, int notused) { ThreeD temp = орl i орl.х++; орl.у++; opl.z++; return tempi } (просим у эксперта Вопрос: Возникают ли какиелибо специфические проблемы при переrpузке операторов отношения? orвeт: Переrpузка оператора отношения, например, == == или <, выпоJПiЯет ся обычным образом. Однако, имеется одна сложность. Как вы знаете, пере 
396 Модуль 9. Подробнее о классах rpуженная операторная функция часто возвращает объект класса, для KOТOporo она переrpужена. Однако переrpуженныIe операторы О1Ношения должны воз вращать tnJe или false. В этом случае они будyr работать так же, как и обычные операторы отношения, и их можно будет использовать в условных выражениях. Те же рассуждения справедливы и для лоrических операторов. Чтобы показать вам, как можно реализовать переrpуженныIй оператор oтнo шения, приведем функцию, переrpужающую оператор =: =: для класса ThreeD: / / Переrpузка == . Ьооl ThreeD:: operator== (ThreeD ор2) { if «х == ор2.х) && (у == ор2.у) && (z  ор2 .z» return true; else return false; } Если в проrpамме реализована операторная функция operator= =:( ), то сле дуюЩИЙ фраrмент ВПОJП-Iе допустим: ТhreeO а(l, 1, 1), Ь(2, 2, 2) i // if(a == Ь) cout « "а равно b\n"; else cout « "а не равно b\n"; Минутная тренировка 1. Сколько параметров имеет бинарная операторная функцияне член? 2. Как следует передавать параметр при использовании операторной функ ции нечлена для переrpузки оператора ++? 3. Одно из преимуществ использования дружественных операторных функuий заключается в том, ЧТО они позволяют использовать с левой стороны операнда встроенный тип (например, int). Справедливо ли это утверждение? I Бинарная операторная функция..не член имеет два параметра 2 При использовании операторной функции нечлена лля переrpузки оператора ++ опе.. ранд должен передаваться по ссылке 3 Справе1U1ИВО, использование дружественных операторных функций позволяет исполь.. зоватъ встроенный тип (например, mt) с левой стороны операнда. Советы и оrраничения при переrрузке операторов Действие переrpуженноrо оператора применительно к классу, для Koтoporo он определен, может не иметь никакоrо отношения к ero 
Операторные функции"не члены 397 использованию по умолчанию, коrда он применяется к встроенным типам С++. Например, операторы « и», коrда они используются с объектами cout и cin, имеют мало общеrо с теми же операторами, ис пользуемыми с целочисленными переменными. Однако, ради ясности и удобства чтения вашей проrpаммы переrpуженный оператор дол жен, если это возможно, отражать дух cBoero традиционноro исполь зования. Например, оператор +, переrpуженный для класса ТbreeD, концептуально схож с тем же оператором, используемым с целочис ленными переменными. Было бы мало смысла определить оператор + для HeKoтoporo класса таким образом, чтобы он выполнял действия, которые вы ожидаете от, например, оператора 11. Наша основная мысль заключается в том, что хотя вы можете придать переrpу:женному опе ратору любой смысл, какой вы только пожелаете, ради ясности про rpaмMbI лучше Bcero, если новое значение оператора схоже с ero ис ходным назначением. При переrpузке операторов возникают некоторые оrpаничения. Bo первых, вы не можете изменить относительный приоритет оператора. Boвтopыx, вы не можете изменить число операндов, требуемых опера тором, хотя, с дрyroй croроны, ваша операторная функuия может иrно рировать операнд. Наконец, за исключением оператора вызова функ ции, операторные функции не MOryт иметь apryмeНТOB по умолчанию. Почти все операторы с++ MOryт быть переrpужены. это относится и к таким специальным операторам, как оператор индексирования массива [ ], оператор вызова функции ( ), оператор >. Bcero четыре оператора не MOryr быть переrру:жены. Это: . :: .* ? Оператор .* имеет особое назначение, и ero использование выходит за рамки данной книrи. Проект 9..1 Создание класса, определяющеrо множество Переrpузка операторов помоrает вам создавать классы, которые дo пускают полную интеrpацию в проrpаммную среду с++. Подумайте о следующем: определив необходимые операторы, вы можете использо вать любой классовый тип в проrpамме в точности так же, как исполь зуете встроенные типы. Вы можете воздействовать на объектыI этоrо класса посредством операторов и использовать объекты этоrо класса в выражениях. Для иллюстрации создания и интеrpации HOBoro класса в 
398 Модуль 9. Подробнее о классах среду С++ в этом проекте создается класс Set, который определяет HO вый тип множества. Перед тем, как мы приступим к разработке класса, важно точно оп ределить, что мы будем понимать под множеством. В плане данноrо проекта мы будем считать множеством коллекцию уникальных эле ментов. Это значит, что никакие два элемента в данном множестве не MOryт быть одинаковыми. Порядок членов множества значения не имеет. Так, множество { А, В, С } совпадает с множеством { А, С, В } Множество может быть также пустыI.. Множество поддерживает ряд операций. Мы реализуем следующие: · Прибавление элемента к множеству · Удаление элемента из множества · Объединение множеств · Получение разности множеств Добавление элемента к множеству и удаление элемента из множе ства не требуют пояснений. Оставшиеся операции не столь очевидны. Объединением двух множеств является множество, содержащее все элементы из обоих множеств. (Разумеется, дублирование элементов недопустимо.) Для выполнения операции объединения множеств мы воспользуемся оператором +. Разностью двух множеств является множество, содержащее те эле менты первоrо множества, которые не являются частью Bтoporo MHO жества. Для выполнения операции разности над множествами мы бу дем использовать оператор . Например, если даны два множества 51 и 82, тоrда следующее предложение удаляет из 81 все элементы 82, по мещая результат в 83: 83 == s 1  82; Если 81 и 82 совпадают, тоrда 83 будет пустым множеством. Класс Set будет также включать функцию IsMember(), которая оп ределяет, является ли указанный элемент членом данноrо множества. Разумеется, над множествами можно выполнять и дрyrие опера ции. Некоторые из них вы сможете реализовать, отвечая на Вопросы для самопроверки. Дрyrие вы можете попытаться разработать caMO стоятельно. Ради простотыI наш класс Set будет содержать множество символов, однако те же самые общие принципы можно использовать ШIЯ создания класса Set, предназначенною для хранения элементов дрyrих типов. 
Операторные функции"не члены 399 Шаr за maroM 1. Создайте файл с именем Set.cpp. 2. Начните создавать Set с разработки объявления этоrо класса, как показано ниже: const int MaxSize = 100; class Set { int len; // число членов char members[MaxSize]; // этот массив будет содержать // множество / * Функция f ind () закрыта, потому что она не используется вне класса Set. */ int Set::find(char сЬ); // найти элемент public: // Конструируем пустое множество. Set() { len = О; } // Возвращает число элементов множества. int getLength() { return len; } void showset()i // выводит множество на экран Ьооl isMember(char сЬ); // проверяет на членство Set operator + (char сЬ) ; // добавляет элемент Set operator  (char ch) ; // удаляет элемент Set operator + (Set оЬ2); // образует объединение Set operator  (Set оЬ2) i // образует разность } i Каждое множество хранится в массиве cbar с именем members. Чис ло членов, фактически входящих в множество, хранится в переменной 'еп. Максимальный размер множества оrpаничен величиной MaxSize, которая имеет значение 100. (Вы можете увеличить это значение, если хотите работать с большими множествами.) Конструктор Set создает пустое множество, т. е. множество, в KOTO ром нет членов. Создавать какиелибо еще конструкторы нет необхо димости, как и определять явный конструктор копий для класса Set, поскольку побитовое копирование по умолчанию в данном случае вполне приемлемо. Функция getLengtb( ) возвращает значение 'еп, KO торое представляет собой число элементов, находящихся в множестве в данный момент. 
400 Модуль 9. Подробнее о классах 3. Начнем определять функциичлены, начав с закрытой функции find( ), показанной ниже: /* Возвращает индекс элемента, задаваемоrо параметром сЬ, или 1, если элемент не найден. */ int Set::find(char сЬ) { int i; for{i=O; i < len; i++) if(members[i] == сЬ) return i; return 1; } Эта функция определяет, является ли элемент, переданный в каче стве параметра сЬ, членом множества. Она возвращает индекс найден Horo элемента или  1, если заданный элемент не входит в множество. Эта функция закрыта, так как они не используется вне класса Set. Как было объяснено ранее, функциичлены MOryт быть закрытыми в своем классе. Закрытая функциячлен может быть вызвана только дрyrими функциямичленами данноrо класса. 4. Добавьте функцию showset( ), текст которой приведен ниже: // Вывести множество на экран. void Set::showset{) { сои t « "{ 11; for(int i=O; i<leni i++) cout « members [i] « 11 "; cout« "}\n"; } Эта функция выводит на экран содержимое множества. 5. Добавьте функцию IsMember( ), которая определяет, является ли указанный элемент членом множества. Текст функции приведен ниже: /* Возвращает true, если ch есть член множества. В противном случае возвращает false. */ bool Set::isMember(char ch) { if(find(ch) 1= 1) return true; return false; } Эта функuия вызывает find( ) для определения Toro, является ли сЬ членом множества. Если является, IsMember() возвращает true; в про тивном случае она возвращает false. 
Операторные функции"не члены 401 6. Теперь начните включать в проrpамму операторы для работы с множеством, начав с оператора +. Для этоro следует переrpузить + ШIЯ объектов типа Set, как это показано ниже. Этот вариант добавляет эле менты в множество. // Добавление уникальноrо элемента в множество. Set Set::operator + (char ch) { Set newset; if(len == MaxSize) { cout « "Множество полно. \n" ; return *thisi // вернуть существующее множество } newset = *this; // дублируем существующее множество / / СМОТРИМ, не существует ли уже этот элемент if(find(ch) == 1) { // если не найден, можно добавить // добавим новый элемент в множество newset.members [newset. len] = Chi newset.len++i } return newset; // возвращаем обновленное множество } Эта функция достойна более внимательноrо рассмотрения. Прежде вcero создается новое множество, которое содержит элементы сущест вующеrо плюс символ, полученный в сЬ. Перед тем, как символ в сЬ будет добавлен, выполняется проверка, есть ли в множестве свободное место для помещения в Hero HOBoro элемента. Если ШIЯ HOBoro эле мента место есть, исходное множество присваивается множеству newset. Далее вызывается функция find( ) для определения, нет ли уже в множестве символа сЬ. В любом случае возвращается множество newset. Таким образом, исходное множество в результате этой опера ции остается без изменений. 7. Переrрузите , чтобы можно было удалять элемент из множе ства: // Удаляет элемент из множества. Set Set::operator (char ch) { Set newset; int i = find(ch)i // i будет равно 1, если элемент не найден // копирование и уплотнение оставшихся элементов for(int j=Oi j < len; j++) if(j 1= i) newset = newset + members[j]; 
402 Модуль 9 Подробнее о классах return newset; } эта функция начинает с создания HOBoro пустоrо множества. Затем вызывается find( ), чтобы определить индекс элемента сЬ в исходном множестве. Вспомним, что find( ) возвращает  1, если сЬ не является членом. Далее элементыI исходноrо множества добавляются к новому множеству за исключением элемента, индекс Koтoporo равен индексу, возвращенному функцией find( ). В результате образуемое множество содержит все элементыI исходноrо за исключением сЬ. Если сЬ не BXO ДИЛ В исходное множество, тоrда оба множества будут эквивалентны. 8. Еще раз переrpузите + и, как это показано ниже. Эти варианты реализуют объединение и разность множеств: // Объединение множеств. Set Set::operator +(Set оЬ2) { Set newset = *this; // копируем первое множество // Добавляем уникальные элементы из BToporo множества. for(int i=O; i < ob2.1en; i++) newset = newset + ob2.members[i]; return newset; // возвращаем обновленное множество } // Разность множеств. Set Set::operator (Set оЬ2) { Set newset = *this; // копируем первое множество // Вычитаем элементы BToporo множества. for(int i=O; i < ob2.1en; i++) newset = newset  ob2.members[i]; return newset; // возвращаем обновленное множество } Леrко заметить, что эти функции при выполнении своих операций используют предварительно определенные варианты операторов + и . В случае объединения создается новое множество, которое coдep жит элементы первоrо множества. Затем к нему добавляются элемен тыI Bтoporo множества. Поскольку оператор + добавляет элемент толь ко в том случае, если ero еще нет в множестве, результирующее MHO жество является объединением (без дублирования) двух исходных. Оператор разности множеств вычитает совпадающие элементы. 9. Ниже приведен полный текст класса Set вместе с функцией main( ), демонстрирующей работу с ним: 
Операторные функциине члены 403 /* Проект 9  1 Класс множества для символов. */ #include <iostream> using namespace std; const int MaxSize = 100; class Set { int len; / / число членов char memЬers [MaxSize]; / / этот массив будет содержать / / множество / * Функция f ind () закрыта, потому что она не используется вне класса Set. */ int Set: : find(char сЬ); / / найти элемент public: / / Конструируем пустое множество. Set() { len = о; } // Возвращает число элементов множества. int getLength () { return len; } void showset(); // выводит множество на экран 0001 isMember(char ch); / / проверяет на членство Set operator +(char сЬ); Set operator (char сЬ); / / добавляет элемент / / удаляет элемент Set operator +(Set оЬ2); Set operator (Set оЬ2); } ; / / образует объединение / / образует разность /* Возвращает индекс элемента, задаваемоrо параметром ch, или 1, если элемент не найден. */ int Set::find(char сЬ) { int i; for(i=O; i < 1en; i++) if(members[i] == сЬ) return i; return "'1; } 
404 Модуль 9. Подробнее о классах // Вывести множество на экран. void Set::showset{) { cout « "{ "; for{int i=O; i<len; i++) cout « mernbers [ i] « " 11 i cout « 'I} \n 11 ; } /* Возвращает true, если сЬ есть член множества. В противном случае возвращает false. */ bool Set::isMember{char сЬ) { if(find(ch) 1= 1) return true; return false; } // Добавление уникальноrо элемента в множество. Set Set::operator + (char сЬ) { Set newset; if(len == MaxSize) { cout « "Множество полно. \n" ; return *this; // вернуть существующее множество } newset = *this; / / дублируем существующее множество / / смотрим, не существует ли уже этот элемент if(find(ch) == 1) { // если не найден, можно добавить // добавим новый элемент в множество newset.members[newset.len] = сЬ; newset .len++ i } return newset; // возвращаем обновленное множество } / / Удаляет элемент из множества. Set Set::operator (char сЬ) { Set newset; int i = find(ch); // i будет равно 1, если элемент не найден // копирование и уплотнение оставшихся элементов for(int j=O; j < len; j++) if(j 1= i) newset = newset + members[j]i return newseti } 
Операторные функции"не члены 405 // Объединение множеств. Set Set::operator +(Set оЬ2) { Set new8et = *thi8; // копируем первое множество // Добавляем уникальные элементы из BToporo множества. for(int i=O; i < ob2.1en; i++) new8et = new8et + ob2.member8[i]; return new8et; // возвращаем обновленное множество } // Разность множеств. Set Set::operator (Set оЬ2) { Set new8et = *thi8; / / копируем первое множество // Вычитаем элементы BToporo множества. for(int i=O; i < ob2.1en; i++) new8et = new8et  ob2.member8[i]; return new8et; // возвращаем обновленное множество } // Демонстрация класса Set. in t та in () { / / сконструируем 10элементное пустое множество Set 81; Set 82; Set 83; 81 81 81 = 81 + ' А' ; = 81 + ' В' ; = 81 + ' С' ; cout « "81 после добавления А В С: "; 81.8how8et(); cout « "\n"; cout« "Проверяем на членство, используя isMember() .\n"; if(81.i8Member('B'» cout « "В является членом 81. \n"; e18e cout « "В не является членом 81. \n"; if(81.i8Member('T'» cout « "Т является членом 81. \n" ; 
406 Модуль 9. Подробнее о классах else cout « "Т не является членом 81. \n" i cout « 11 \n" ; 81 = 81  I В' ; cout « "81 после 81 = 81  'В': "; 81.8how8et(); 81 = 81  I А' ; cout « "81 после 81 = 81  I А' : .. . , 81.8how8et(); 81 = 81  'С' ; cout « "81 после а1 = 81  'С' : " . , 81.8how8et(); cout « "\n"; 81 = 81 + I А' ; 81 = 81 + I В' ; 81 = 81 + I С' ; cout « "81 после добавления А В С: "; 81.8how8et(); cout « "\n"; 82 = 82 + I А' i 82 = 82 + I Х' ; 82 = 82 + 'W'; cout « "82 после добавления А Х W: "; 82.8how8et(); cout « "\n"; 83 = 81 + 82; cout « "83 после 83 = 81 + 82: "; 83.8hOW8et(); 83 = 83  81; cout « "83 после 83  81: "; S3.8hOW8et(); cout « "\n"; cout « "82 после 82 = 82  82: "; 82 = 82  82; // очистка 82 
Операторные функции"не члены 407 82.8how8et(); cout « "\n"; 82 = 82 + I С'; / / добавим АВС in обратном порядке 82 = 82 + I В' ; 82 = 82 + I А' ; cout « "82 после добавления С В А: "; 82.8howaet(); return о; } Вывод проrpаммы выrлядит таким образом: 81 после добавления А В С: { А В С } Проверяем на членство, используя i8Member(). В является членом 81. т не является членом 81. 81 после 81 = 81 I В I : { АС } 81 после 81 = 81 I А I : { С } 81 после аl = 81 I С' : { } 81 после добавления А В С: { АВС } s2 после добавления А Х W: { AXW } 8З после s3 = 81 + 82: {ABCXW } аЗ после 8З  81: { Х W } 82 после 82 = 82 ... 82: { } 82 после добавления С В А: { С В А } . ...P. . ... .PP.. . 1. Что такое конструктор копий и коrда он вызывается? Приведите общую форму конструктора копий. 2. Объясните, что происходит, коrда объект возвращается функцией. Конкретно, коrда вызывается деструктор? 
408 Модуль 9. Подробнее о классах 3. Дан такой класс: class Т { int i, j; public: in t s ит () { ret urn i + j; } } ; Покажите, как можно иначе написать функцию sum( ), чтобы она использовала указатель this. 4. Что такое структура? Что такое объединение? 5. К чему относится *this внутри функции? 6. Что такое дружественная функция? 7. Приведите общую форму переrpузки бинарной операторной Функ циичлена. 8. Что вы должны сделать, чтобы моrли выполняться операции, в KO торых участвует как классовый тип, так и встроенный? 9. Можно ли переrpузить операторзнак вопроса (?)? Можно ли из менить относительный приоритет оператора? 10. Для класса Set, разработанноrо в Проекте 91, определите операто ры < и > так, чтобы они определяли, является ли одно множество подмножеством или надмножеством дрyrоrо. Пусть оператор < возвращает true, если левое множество является подмножеством множества с правой стороны, и false в обратном случае. Пусть> возвращает true, если левое множество является надмножеством множества с правой стороны, и false в обратном случае. 11. Для класса Set определите оператор & так, чтобы он возвращал пе ресечение двух множеств. 12. Попытайтесь самостоятельно дополнить Set друrими операторами. Например, попробуйте определить оператор I так, чтобы он возвра щал строzую дизъюнкцию двух множеств. Строrая дизъюнкuия co стоит из всех элементов обоих множеств за исключением совпа дающих. 
Модуль 10 Наследование, виртуальные функции и полиморфизм Цели. достиrаемые в ЭТОМ модуле 1 О 1 Познакомиться с основами наследования 1 О 2 Понять, как производные классы наследуют от базовых 10.3 Узнать, зачем используется защищенный доступ 1 О 4 Освоить вызов конструкторов базовых классов 1 О 5 Научиться создавать мноrоуровневую иерархическую аруктуру классов 1 О 6 Познакомиться со случаями наследования от нескольких базовых классов 1 О 7 Узнать, коrда вызываются конструкторы и деструкторы, входящие в иерархию классов 10.8 Рассмотреть применение указателей на базовый класс в качестве указателей на производные классы 1 О 9 Научиться создавать виртуальные функции 1 О 1 О Освоить использование чистых виртуальных функций и абстрактных классов 
410 Модуль 10. Наследование, виртуальные функции и полиморфизм В этом модуле обсуждаются три средства с++, имеющих непосредст венное отношение к объектноориентированному проrpаммирова нию: наследование, виртуальные функции и полиморфизм. Наследо вание  это средство, позволяющее одному классу наследовать xapaK теристики дрyrоrо. Опираясь на понятие наследования, вы можете создать базовый класс, в котором будут определены характерные чер ты, общие для определенноrо кpyra объектов. Эти общие черты MOryт наследоваться дрyrими, более специфическими классами, каждый из которых добавит к ним более частные средства, характерные для дaH Horo npоизводноrо класса. Возможности наследования находят свое применение в понятии виртуальной функции. Виртуальные функции поддерживают полиморфизм, философию "одноrо интерфейса, MHO rих методов", являющуюся одним из краеyrольных камней объектно ориентированноrо проrраммирования. Цель _Основы наследования Соrласно языку С++, наследуемый класс называется базовым клас сом. Класс, который наследует от базовоrо, называется проuзводным классом. Таким образом, производный класс является специализиро ванным вариантом базовоrо. Производный класс наследует все члены, определенные в базовом классе, и добавляет к ним свои собственные специфические элементыI. В с++ наследование реализуercя пyreм включения одноro класса (ба зовоro) в объявление дpyroro (производноro). Дрyrими словами, в объяв ленин производноro класса указываercя, какой класс являercя Д1ТЯ Hero ба зовым. давайте начнем с простоro примера, ИЛЛlOC1pирующеro HeKoropbIe ключевые чеprыI наследования. Приведенная ниже проrpамма соЗlIает ба зовый класс 1\юDShаре (двумерная фиrypа), кoroрый содержит IШфШIY И выСО1у двумерноro объекra, а также производныIй класс 1iiangle (треyrоль ник). Обратите особое внимание на способ объявления класса Тrianglе: // Простая иерархия классов. #include <iostream> #include <cstring> using namespace std; / / Класс для двумерных объектов. class TwoDShape { public: double width; doubl е height; 
Основы наследования 411 void showDim () { cout « "Ширина and высота составляют " « width« " и " « height « "\п"; } } ; // Triangle является производным от TwoDShape. class Triangle : public TwoDShape {  public: char style [20] ; I TrlangIe наследует от TwoDShape Обратите внимание на синтаксис double area () { return width * height / 2;  Tnangle может работать с членами ТwoDShape. как если бы они были ero собственными. } void showStyle() { cout « "Треуrольник " « style « "\п"; } } ; in t та in () { Triangle tl; Triangle t2; t 1 . width = 4. о;  tl.height = 4.0; strcpy(tl.style, "равнобедренный"); все члены Tnangl. дocrynны объек- там Тпапа'.. p;rк.e те, который на- следованы от ТwoDShape t2 . width = 8. О ; t2.height = 12.0; strcpy (t2. style, "прямоуrольный"); cout « "Данные о tl:\n"; tl.showStyle(); tl.showDim(); cout « "Площадь равна" « tl.area() « "\п"; cout « .. \п" ; cout « "Данные о t2:\n"; t2.showStyle(); t2.showDim(); cout « "Площадь равна" « t2. area () « .. \п" ; return о; } 
412 Модуль 1 О Наследование. виртуальные функции и полиморфизм Вывод этой проrpаммы выrлядит следующим образом: Данные о t1: Треуrольник равнобедренный Ширина и высота составляют 4 and 4 Площадь равна 8 Данные о t2: Треуrольник прямоуrольный Ширина и высота составляют 8 and 12 Площадь равна 48 в этой проrpамме 1WoDShape определяет атрибуrы "обобщенной" двy мерной фиrypы, например, квадрата, прям:оyroлъника, треyroльника и т. д. Класс 1iiangle созцает специфический тип класса 1WoDShape, в данном случае треyroльник. Класс 1iiangle включает все членыI 1WoDShape и добав ляет поле styIe, функцию area( ) и функцию showStyle( ). Описание вида треyroльника хранится в поле style, area( ) вычисляет и возврашает пло щадь треyroльникз, а showStyle( ) выводит на экран вид треyroльника. Следующая строка показывает, как 1Папglе наследует от 1\voDShape: class Triangle : public TwoDShape { Здесь 1\voDShape  это базовый класс; от Hero наследует класс 1Пangle, ЯRЛяющийся производным классом. как показывает приведенный при мер, синтаксис для описания наследования весьма прост. Поскольку 1Папglе наследует все члены cBoero базовоro класса, 1\voDShape, он может внутри функции area( ) обращаться к переменным width и height. Кроме тoro, внутри main( ) объектыI tl и t2 MOryт обра щаться к width и height непосредственно, как если бы они бьти частью 1Папglе. На рис. 101 схематически показано, как класс 1\voDShape BXO дит в 1Папglе. Еще одно замечание: хотя 1\voDShape является базовым для 1Папglе, он также является полностью независимым, самостоятельным классом. То, что некоторый класс является базовым для производноrо класса, никак не значит, что он не может быть использован сам по себе. DS { width height showDun() style area() showStyle() Triangle Рис. 101. Схемаrическое изображение ЮIасса TriaDgle 
Основы наследования 413 Общая форма наследования ориведена ниже: class пРОUЗ80дный--класс : доступ базовый--класс { / / тело производноrо класса } Здесь описатель доступ не является обязательным. Если он присут-- ствует, он должен быть pubIic, private или protected. Ниже в этом же мо-- дуле вы прочитаете больше об этих описателях. Поначалу все наши производные классы будут использовать доступ роЬОс. Указание этоrо вида доступа означает, что все открытыIe члены базовоrо класса будут также открытыIии членами производноrо. Основное преимущество наследования заключается в возможно-- сти, после тoro, как вы создали базовый класс, определяющий общие характеристики rpуппы объектов, использовать ero для создания лю-- боrо числа более специфических производных классов. Каждый про-- изводный класс может детально определить собственные средства. Вот пример друrоro класса, наследуемоrо от тoro же 1\voDShape, который инкапсулирует прямоyrольники: // ПрОИЗВОДНЫЙ от TwoDShape класс для прямоуrольников. class Rectangle : public TwoDShape { public: Ьооl isSquare() { if(width == height) return true; return false; } double area () { return width * height; } } ; Класс Rectangle включает все содержимое 1\voDShape и добавляет функцию isSquare( ), которая определяет, не является ли прямоyrоль-- ник квадратом, а также функцию area( ) для вычисления площади оря-- моyrольника. Доступ к членам и наследование Как вы узнали из Модуля 8, члены класса часто объявляются за-- крытыIи,' чтобы предотвратить неавторизованный доступ или нера-- зумное использование. Наследование от класса не нарушает оrраниче-- ние доступа к закрытыIM членам. Таким образом, хотя производный класс включает все члены базовоrо, он не может обратиться к закры-- 
414 Модуль 10. Наследование, виртуальные функции и полиморфизм тым членам базовоro класса. Например, если переменные width и height объявить в классе 1\voDShape закрытыIи,, как это показано ниже, тоrда 1Папglе не будет иметь к ним доступа. /* ПРОИЗВОДНЫМ классам закрыт доступ к закрытым членам базовоrо класса. */ class TwoDShape { // Эти члены теперь закрыты double width;  width и h_ght теперь объявлены закрытыми double height; public: void showDim () { cout « "Ширина и высота составляют " « width« " и " « height « "\п"; } } ; // Triangle является производным от TwoDShape. class Triangle : public TwoDShape { К закрытым членам базовоrо риыl ic: класса обращаться нельзя char style[20]; I double area () { + return width * height / 2; // Ошибка! Доступ закрыт. } void showStyle() { cout « "Треуrольник " « style « "\п"; } } ; Класс 1Папglе не будет компилироваться, потому что обращение к width и height внутри функции area( ) приведет к нарушению доступа. Поскольку width и height теперь закрытыI, к НИМ можно обратиться только из членов ИХ собственноrо класса. Производные классы дocтy па к ним не имеют. Поначалу вам может показаться, что запрещение доступа к за крытым членам базовоrо класса из производных классов является серьезным оrраничением, потому что это во мноrих случаях лишит нас возможности использовать закрытые члены. К счастью, это не так, поскольку С++ предоставляет ряд средств для решения этой проблемы. Одним из НИХ является атрибут protected (защищен ный), который будет описан в следующем подразделе. Второе средство заключается в использовании ОТКРЫТЫХ функций для обеспечения доступа к закрытым данным. Как вы уже знаете из материала предыдущих модулей, С++проrраммисты обычно пре 
Основы наследования 415 доставляют доступ к закрытым членам класса посредством функ ций. Функции, предоставляющие доступ к закрытым членам, HO сят название функций доступа. Ниже приведен новый вариант класса 1\voDSbape, в который добавлены функции доступа для пе ременных widtb и height: // Обращение к закрытым данным посредством функций доступа. #include <iostream> #include <cstring> using namespace std; / / Класс для двумерных объектов. class TwoDShape { // эти члены закрыты double width; double height; public: void showDim () { cout « "Ширина and высота составляют " « width« а И " « height « "\n"; } / / функции доступа double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = Wi } void setHeight(double Ь) { height = Ь; } } ; Функция доступа для width и height // Triangle является npоизводным от TwoDShape. class Triangle : public TwoDShape { public: char s t у 1 е [ 2 О] ; double area () { return getwidth() * getHeight () / 2;  Использование ФУНКЦИЙ доступа для получения UI1рины и ВЫСОТЫ. } void showStyle() { cout « "Треуrольник " « style « "\n"; } } ; int main() { Triangle tl; Triangle t2 i 
416 Модуль 1 О Наследование. виртуальные функции и полиморфизм tl.setWidth(4.0); tl.setHeight(4.0); strcpy(tl.style, "равнобедренный"); t2.setWidth(8.0); t2.setHeight(12.0); strcpy (t2. style, "прямоуrольный"); cout« "Данные о tl:\n"; tl.showStyle(); tl.showDim(); cout « "Площадь равна" « tl.area() « "\п"; cout « "\n"; cout « "Данные о t2:\n"; t2.showStyle(); t2.showDim(); cout « "Площадь равна" « t2.area() « "\n"; return о; } Спросим у эксперта Вопрос: Я услышал в дискуссии о проrpаммировании на языке Java Tep мины суперкласс и подкласс. Имеют ли эти термины смысл в с++? OrвeT: То, что в Java называется суперклассом, в с++ именуется базо вым классом. Подкласс в Java ..... это производный класс в с++. Вы можете услышать обе пары терминов применительно к обоим языкам, однако в ЭТОЙ книrе мы будем продолжать использовать стандартную терминолоrию с++. Кстати, в языке с# также приняты термины базовый класс и произ водный класс. Минутная тренировка 1. Каким образом указывается, что данный класс является производным от базовоrо? 2. Включает ли в себя производный класс члены базовоrо класса? 3. Имеет ли производный класс доступ к закрытым членам CBoero базово ro класса? 1 Базовый класс указывается через двоеточие после имени производиоrо класса 2 Да, производный класс включает ли в себя все члены cBoero базовоrо класса 3. Нет, производный класс не имеет доступа к закрытым членам cBoero базовоrо класса 
Управление доступом к базовому классу 417 Цель _Управление доступом к базовому классу Как уже бъто сказано, если один класс наследует от дpyroro, все чле ны базовоrо класса становятся членами производноro. Однако доступ ность членов базовоrо класса ВНУ1Ри производноro определяется описа теле м доступа, используемым при наследовании членов базовоrо клас са. Описатель доступа в базовом классе может принимать формы pubIic (открытый), private (закрытый) или protected (защищенный). Если опи сатель доступа не используется, то по умолчанию для структуры class принимается описатель private, а для структуры struct  описатель роыi.. Рассмотрим следствия использования описателей доступа public и private (описатель protected будет описан в следующем подразделе). Если базовый класс наследуется как pubIic, все открытые члены ба зовоrо класса становятся открытыми членами производноro класса. Во всех случаях закрытьrе элементы базовоro класса остаются закры тыIии В этом классе и недоступны для членов производноrо класса. Например, в приведенной ниже проrpамме открытые члены В CTaHO вятся открытыми членами О. Таким образом, все они доступны из дрyrих частей проrpаммы: // Демонстрация наследования public. #include <iostream> using namespace std; class В { int i, j: public: void set(int а, int Ь) { i = а: j = Ь: } void show() { cout« i « " " « j « "\n"; } } ; с 1 а s s D Р иЬ 1 i с В {  кnacc В наследуется как pubIic. int k; public: D(int х) { k = х: } void showk () { cout « k « "\n"; } // i = 10; // Ошибка! i объявлена private в В и недоступна в D. i }; Д " В ОС1УП к члену I запрещен, так как I закрыта в классе int main ( ) 
418 Модуль 10. Наследование, виртуальные функции и полиморфизм { D оЬ ( З) ; оЬ. set (1, 2); ob.show(); / / ФУНКЦИЯ доступа базовоrо класса // функция доступа базовоrо класса ob.showk(); // использует член производноrо класса return о; } Поскольку функции set() и show() объявлены как pubIic в классе В, их можно вызвать для объекта типа D внутри main( ). Поскольку i и j объявлены как private, они остаются закрытыIии в классе В. Поэтому строка // i = 10; // Ошибка! i объявлена private в В и недоступна в D. закомментирована. D не имеет доступа к закрытыIM членам В. Противоположностью открытому является закрытое наследо вание. Если базовый класс наследуется как private, все открытые члены базовоrо класса становятся закрытыми членами в произ водном классе. Например, приведенная ниже проrрамма не будет компилироваться, так как функции set( ) и show( ) теперь являют ся закрытыми членами О, и, следовательно, не MorYT быть вызва ны из main( ): /* Демонстрация наследования private. Эта проrрамма комnилироваться не будет. */ #include <iostream> using namespace std; class В { int i, j; public: void set(int а, int Ь) { i = а; j = Ь; } void show() { cout « i « .. .. « j « "\n"; } } ; / / Открытые элементы в В становятся закрытыми в D. class D : private В { . i n t k;  Теперь В наследуется как pПvве. public: D (int х) { k = х; } voidshowk() {cout«k« "'n";} } ; 
Использование защищенных членов 419 int main () { D оЬ ( 3) ; Теперь set( ) and show( ) нeдocтynHЫ через объекты О. ob.set(l, 2); // Ошибка, нет доступа к set() оЬ. show ( ) ; / / Ошибка, нет доступа к show ( ) return о; } Подытожим: если базовый класс наследуется как private, открытые члены базовоrо класса становятся закрытыми членами производноrо класса. Это значит, что они все еще доступны из членов производно.. ro класса, но из дрyrих частей вашей проrpаммы к ним обратиться нельзя. Цель _ Использование защищенных членов Как вы знаете, закрытый член базовоrо класса недоступен из про.. изводноrо класса. Отсюда можно сделать заключение, что если вы xo тите, чтобы производный класс имел доступ к кaKOMYТO члену базово ro, ero следует объявить открытым. Однако объявление члена с описа телем pubIic сделает ero открытым и для Bcero остальноrо кода, что часто нежелательно. К счастью, наше заключение неправильно, так как С++ позволяет создавать защищенные члены. Защищенный член является открытым внУ1РИ иерархии Ю1ассов, но закрытым вне этой иерархии. Защищенный член создается указанием описателя доступа protected. Если член класса объявлен как protected, этот член, за одним важным исключением, является закрытым. ИСЮ1ючение проявляется, коrда этот защищенный член наследуется. Такой защищенный член базовоro класса оказывается доступным из производноro Ю1асса. Таким образом, используя описатель protected, вы можете создавать члены класса, KOТO рые закрытыI в своем классе, но тем не менее наследуются и доступны из производноrо класса. Описатель protected может также исполъзовать ся со структурами. Рассмотрим такую проrpамму: // Демонстрация защищенных членов. #include <iostream> 
420 Модуль 10. Наследование, виртуальные функции и полиморфизм using namespace std; с 1 а s s В { Переменные i и i объявлены protected protected: . int i, j; // закрыты в В, но доступны из D public: void set (int а, int Ь) { i = а; j = Ь; } void show() {cout« i« 11 11« j« "\n"; } } ; class D : public В { in t k; public: // D может обращаться к i void setk() {k = i*j; } D имеет доступ к i и i, так как они защищенные, а не закрытые и j, принадлежащим В. void showk () { cout « k « 11 \n 11; } } ; int main () { D оЬ; ob.set(2, З); // ОК, set() открыта в В оЬ. show ( ) ; / / ОК, show () открыта в В ob.setk(); ob.showk(); return о; } в этой проrpамме, поскольку В наследуется классом D как OTKpЫ тыIй класс, а i и j объявлены защищенными, функция setk( ) из класса D может к ним обращаться. Если бы i иj бьти объявлены в В заIфыты ми, тоrда класс D не имел бы к ним доступа, и проrpамма не стала бы компилироваться. Коrда базовый класс наследуется с атрибутом pubIic, защищенные члены базовоro класса становятся защищенными членами производ Horo класса. Коrда базовый класс наследуется с атрибутом private, за щищенные члены базовоrо класса становятся закрытыми членами производноrо класса. Описатель доступа protected может быть указан в любом месте в объявлении класса, хотя обычно ero используют после тoro, как объ явлены закрытыIe (по умолчанию) члены класса, но до объявления OT IфытыIx членов. Поэтому наиболее распространенная полная форма объявления класса выrлядит таким образом: 
Использование защищенных членов 421 class uмякласса { / / закрытые члены класса protected: / / защищенные члены pиblic: / / открытые члены }; Разумеется, защищенная секция не является обязательной. В дополнение к определению атрибута защищенности для членов класса, ключевое слово protected может также служить описателем доступа при наследовании базовоrо класса. Если базовый класс Ha следуется как защищенный, все открытые и защищенные члены ба.. зовоrо класса становятся защищенными членами производноrо класса. Например, в последнем примере, если D наследует от В та.. ким образом: class D : protected В { тоrда все члены В станут защищенными членами О. Минутная тренировка 1. Если базовый класс наследуется как закрытый, открытые члены базо.. BOro класса становятся закрытыми членами производноrо класса. Пра вильно ли это? 2. Можно ли сделать закрытый член базовоro класса открытым посредст" вом наследования? З. Чтобы сделать член доступным внутри иерархии, но закрытым в любом дрyrом месте, какой описатель ДОС1УПа следует использовать? 1. Правильно, если базовый класс наследуется как закРЫIЫЙ, orкpЫTыe члены базовоrо ЮIасса становятся закрытыми членами производноrо класса 2 Нет, закрытый член всеrда остается закрытым в своем классе 3 Для Toro, чтобы член был доступен внyrpи иерархии, но закрыт в остальных местах про.. rpaMMbl, следует использовать описатель доступа protected. Спросим у эксперта Вопрос: Не моrли бы вы дать краткий обзор описателям pubIic, protected и private? OrвeT: Если член класса объявлен как pubIic, к нему можно обратить.. ся ИЗ любоrо места в проrрамме. Если член объявлен как private, он дос.. тупен только членам cBoero класса. При этом производные классы не имеют доступа к членам private базовоrо класса. Если член объявлен как 
422 Модуль 10. Наследование, виртуальные функции и полиморфизм protected, он доступен только членам cBoero класса и производных клас сов. Таким образом, атрибут protected позволяет члену наследоваться, но оставаться закрытыIM внутри иерархии классов Если базовый класс наследуется с атрибутом pubIic, ero открытые чле ны становятся открытыми членами производноrо класса, а ero защищен ные члены становятся защищенными членами производноrо класса. Если базовый класс наследуется с атрибутом protected, ero открытыIe и защи  щенные члены становятся защищенными членами производноrо класса. Если базовый класс наследуется с атрибутом private, ero открытые и за щищенные члены становятся закрытыIии членами производноrо класса. Во всех случаях закрытыIe члены базовоrо класса остаются закрытыIии в этом классе. Конструкторы и наследование в иерархии классов вполне возможно, чтобы и базовые, и наследуе мые классы имели собственные конструкторы. В этом случае возника ет важный вопрос: какой конструктор отвечает за построение объекта производноrо класса, тот, который находится в базовом классе, или тот, который находится в производном, или оба вместе? Ответ заклю чается в следующем: конструктор базовоrо класса конструирует часть объекта, относящуюся к базовому классу, а конструктор производноro класса конструирует часть, относяшуюся к производному классу. Это представляется разумным, поскольку базовый класс ничеrо не знает про производный класс и не имеет доступа к ero элементам. Поэтому конструирование двух частей объекта производноrо класса ДОЛJЮНО осуществляться отдельно. Предыдущие примеры опирались на KOHCT рукторы по умолчанию, создаваемые автоматически, поэтому с обсуж даемым здесь вопросом мы не сталкивались. Однако на практике большинство классов имеют конструкторы. Здесь мы рассмотрим BO просы их взаимодействия. Если конструктор определен только в производном классе, про цесс построения объекта очевиден: надо просто сконструировать объект производноrо класса. Часть объекта, относяшаяся к базово му классу, конструируется автоматически конструктором базовоrо класса по умолчанию. Ниже приведен переработанный вариант класса Thiangle, в котором определен конструктор. Кроме Toro, член style объявлен закрытым, поскольку теперь ero будет устанавливать конструктор: // Добавление в класс Triangle конструктора. #include <iostream> #include <cstring> 
Использование защищенных членов 423 using namespace stdi // Класс для двумерных объектов. class TwoDShape { / / эти члены закрыты double width; double height; public: void showDim () { cout « "Ширина and высота составляют .. « width « .. и .. « height « .. \n" ; } / 1 функции доступа double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = Wi } void setHeight(double h) { height = Ь; } } ; 1/ Triangle является производным от ТwoDShape. class Triangle : public TwoDShape { char style[20]; // теперь private public: // Конструктор для класса Triangle. Triangle(char *str, double w, double h) { // Инициа лизация части, относящейся к базовому лассу. setWidth (w) ; I Инциanиэация чacrм Trilllgle, оПtoCЯ- setHeigh t (h) ; щеися к ТwoDShape // Инициализация части, относящейся к производному классу. strcpy(style, str)i  }  Инициализация styI., переменной, специфической для Trlangl. double area () { return getWidth() * getHeight() / 2; } void showStyle() { cout « "Треуrольник .. « style « "\n"i } } ; int та i n () { Triangle tl ("равнобедренный", 4. О, 4. О) i 
424 Модуль 10 Наследование. виртуальные функции и полиморфизм Triangle t2 ("прямоуrольный", 8. О, 12. О) ; cout « "Данные о t1:\n"; tl.showStyle(); tl. showDim () ; cout « "Площадь равна .. « t1. area () « .. \n" ; cout « .. \n" ; cout « "Данные о t2: \n" ; t2.showStyle(); t2.showDim(); cout « "Площадь равна" « t2.area() « "\n"; return о; } в этой проrpамме конструктор класса 1Папglе инициализирует чле ны 1\voDShape, которые 1Папglе наследует от 1\voDShape, а также соб ственное поле style. Коrда оба класса, и базовый, и производный, определяют свои конструкторы, процесс становится несколько более сложным, по тому что при создании объекта должны выполняться оба KOHCT руктора. Цель _ Вызов конструктора базовоrо класса Если в базовом классе есть свой конструктор, производный класс должен вызывать ero явным образом, чтобы инициализировать часть объекта, относящуюся к базовому классу. Производный класс может вызывать конструктор, определенный в базовом классе, по средством расширенной формы объявления конструктора производ Horo класса. Общая форма этоrо расширенноrо объявления выrля дит так: проuзводныйконструктор (cпиCOKapCYMeHтoв) : базовый конструктор (cпиCOKap2YMeHтoв) { тело конструктора производноrо класса } Здесь базовый конструктор представляет собой имя базовоrо клас са, от Koтoporo наследует производный класс. Обратите внимание на двоеточие, отделяюшее объявление конструктора производноrо класса 
Вызов конструктора базовоrо класса 425 от конструктора базовоrо класса. (Если класс наследует от более чем одноrо базовоrо класса, тоrда конструкторы базовых классов отделя ются дрyr от дрyrа запятыми.) Приведенная ниже проrpамма показывает способ передачи apry ментов конструктору базовоrо класса. В ней определен конструктор класса 1\voDShape, который инициализирует переменные width и height: // Добавление конструктора в класс TwoDShape. #include <iostream> #include <cstring> using namespace stdi // Класс для двумерных объектов. class TwoDShape { // эти члены закрыты double width; double height; public: // Конструктор для TwoDShape. ТwoDShape(double w, double h) { width = Wi height = h; } void showDim () { cout « "Ширина и высота составляют " « width « " и " « height « "\п"; } / / функции доступа double getWidth() { return width; } double getHeight() { return heighti } void setWidth(double w) { width = w; } void setHeight(double Ь) { height = Ь; } } i // Triangle является производным от TwoDShape. class Triangle : public TwoDShape { char style[20]; // теперь private public: // Конструктор для класса Triangle. Triangle(char *str, double w, 
426 Модуль 10. Наследование, виртуальные функции и полиморфизм double Ь) : TwoDShape (w, Ь) {  strcpy(style, str); ВЫЗОВ конструктора ТwoDShape } double area () { return getWidth() * getHeight() / 2; } void showStyle() { cout « "Треуrольник .. « style « .. \n" ; } } ; in t та in () { Triangle tl("равнобедренный", 4.0, 4.0); Triangle t2("прямоуrольный", 8.0, 12.0); cout « .. Данные о t1: \п" ; t1.showStyle(); t1.showDim(); cout « "Площадь равна " « tl. area () « "\n"; cout « "\n"; cout « "Данные о t2:\n"; t2.showStyle(); t2.showDim(); cout « "Площадь равна" « t2.area() « "\n"; return о; } в этой проrpамме Тriangle( ) вызывает 1\voDShape с параметрами w и Ь, которые инициализируют переменные width и height этими значениями. Тriangle уже не инициализирует их сам. Ему требуется инициализировать только переменную, специфическую для этоrо класса, именно, style. Такая процедура оставляет за 1\voDShape воз можность конструировать свои подобъекты любым способом, какой ему будет удобен. Более Toro, 1\voDShape может увеличить свои функциональные возможности, о чем существующие производные классы нечеrо не будут знать, и это не приведет к разрушению cy ществующих проrрамм. Конструктором производноrо класса может быть вызвана лю бая форма конструктора из определенных в базовом классе. Bы полняться будет конструктор с соответствующим списком apry ментов. Ниже приведены расширенные варианты обоих классов, 1\voDShape и Тriangle, которые включают дополнительные KOHCT рук торы : 
Вызов конструктора базовоrо класса 427 // Добавление конструктора в TwoDShape. #include <iostream> #include <cstring> using namespace std; // Класс для двумерных объектов. class TwoDShape { // эти члены закрыты аоиЫе width; аоиЫе height; public: / / Конструктор по умолчанию. TwoDShape () { width = height = 0.0; } // Конструктор для TwoDShape. TwoDShape(double w, аоиЫе h) { width = w; height = h; } Различные конструк- торы TwoDShape // Конструируем объект с равными шириной и высотой. TwoDShape(double х) { width = height = х; } void showDim() { cout « "Ширина and высота составляют " « width « " и " « height « 11 \n 11 ; } // функции доступа аоиЫе getWidth() { return width; } аоиЫе getHeight() { return heighti } void setWidth (аоиЫе w) { width = w; } void setHeight(double h) { height = h; } } ; // Triangle является ПРОИЗБОДНЫМ от TwoDShape. class Triangle : public ТwoDShape { char style[20]i // теперь private public: 
428 Модуль 10. Наследование, виртуальные функции и полиморфизм /* Конструктор по умолчанию. Он автоматически вызывает конструктор по умолчанию TwoDShape. */ Triangle () { strcpy (style, "неизвестен" ) ; } // Конструктор с тремя параметрами. Triangle(char *str, double w, double h) : TwoDShape(w, Ь) { strcpy(style, str); Различные КОНСТ- pyкrOPЫ Tnangl. } .// Сконструируем равнобедренный треуrольник. Triangle(double х) : TwoDShape(x) { strcpy(style, "равнобедренный"); } double area () { return getWidth() * getHeight() / 2; } void showStyle() { cout « "Треуrольник " « style « "\n"; } } ; in t та in () { Triangle t1; Triangle t2("прямоуrольный", 8.0,12.0); Triangle tЗ(4.0); tl = t2; cout « "Данные о tl: \n"; tl.showStyle(); tl.showDim(); cout « "Площадь равна" « t1.area() « "\n"; cout « .. \n" ; cout « "Данные о t2: \n"; t2.showStyle(); t2.showDim(); cout « "Площадь равна" « t2.area() « "\n"; cout « .. \n" ; 
Вызов конструктора базовоrо класса 429 cout « .. Данные о t3: \n"; t3.showStyle(); t3.showDim(); cout « "Площадь равна" « t3.area() « "\n"; cout« "\n"; return о; } Вот вывод этой проrраммы: Данные о t1: Треуrольник npямоуrольный Ширина и высота составляют 8 and 12 Площадь равна 48 Данные о t2: Треуrольник прямоуrольный Ширина и высота составляют 8 and 12 Площадь равна 48 Данные о tЗ: Треуrольник равнобедренный Ширина и высота составляют 4 and 4 Площадь равна 8 Минутная тренировка 1. Каким образом производный класс активизирует выполнение KOHCT руктора CBoero базовоro класса? 2. Можно ли передать параметры конструктору базовоrо класса? 3. Какой конструктор отвечает за инициализацию той части объекта про изводноrо класса, которая относится к базовому классу? Конструктор баЗОБоrо класса или производноro класса? I Производный класс указывает конструктор базовоrо класса в своем конструкторе 2 Да, конструктору баЗОDоrо класса можно передать пара метры 3 За инициализацию той части объекта ПРОИЗВОДНОI о класса, которая относится к базово.. му классу, отвечае J конструктор, определенный в базовом классе Проект 10..1 Расширение класса Vehicle В этом проекте создается подкласс класса Vehicle, разработанно ro в Модуле 8. Как вы помните, класс Vehicle инкапсулировал ин 
430 Модуль 10 Наследование. виртуальные функции и полиморфизм формацию о транспортных средствах, включая число перевозимых пассажиров, емкость бензобака и скорость потребления rорючеrо. Мы можем использовать класс Vehicle в качестве отправной точки для разработки более специализированных классов. К примеру, одним из транспортных средств является rрузовик (truck). К важ ным характеристикам rрузовика относится ero rрузоподъемность. Таким образом, создавая класс Тruck, вы можете сделать ero произ водным от Vehicle, добавив переменную экземпляра, содержащую величину rpузоподъемности. В этом проекте мы создадим класс Тruck. Переменные экземпляра Vehicle мы сделаем закрытыми, и включим в класс функции доступа для получения значений этих переменных. Шаr за waroM 1. Создайте файл с именем ТruckDemo.cpp и скопируйте в Hero по следнюю реализацию Vehicle из Модуля 8. 2. Создайте Ю1асс Thuck, как это показано ниже: // Используем Vehicle для создания специализированноrо // класса Truck. class Truck : public Vehicle { int cargocapi // rрузоподъемность в фунтах public: // Это конструктор для Truck. Truck(int р, int f, int т, int с) : Vehicle(p, f, т) { cargocap = с i } // Функция доступа для cargocap. int getcargocap() { return cargocapi } } i Мы видим, что Тruck наследует от Vehicle; кроме Toro, в Hero добав ляется член cargocap. Таким образом, Thuck включает все общие атри буты транспортных средств, определенные в Vehicle. Добавить в Hero надо только элементыI, специфические именно для этоrо класса. 3. Ниже приведен полный текст проrpаммы, демонстрирующей создание и использование класса Тruck: // Создание подкласса Truck, производноrо от Vehicle. #include <iostream> 
Вызов конструктора базовоrо класса 431 using namespace std; // объявим класс Vehicle. class Vehicle { // Эти члены закрыты. int passengersi// число пассажиров int fuelcapi // емкость бензобака в rаллонах int mpgi // потребление топлива в милях на rаллон public: // Это конструктор для Vehicle. Vehicle(int р, int f, int m) { passengers = Р; fuelcap = fi mpg = m: } // Вычислить и вернуть дальность пробеrа. int range() { return mpg * fuelcapi } / / Функции доступа. int getpassengers() { return passengersi } int getfuelcap() { return fuelcap: } int getmpg () { return mpg; } } ; // Используем Vehicle для создания специализированноrо // класса Truck. class Truck : public Vehicle { int cargocapi // rрузоподъемность в фунтах public: // Это конструктор для Truck. Truck(int р, int f, int т, int с) : Vehicle (р, f, т) { cargocap = с i } // Функция доступа для cargocap. int getcargocap() { return cargocapi } } i i n t та i n () { // Сконструируем несколько rрузовиков Truck semi(2, 200,7,44000); // Фурrон 
432 Модуль 10. Наследование, виртуальные функции и полиморфизм Truck pickup(3, 28, 15, 2000); // Пикап int dist = 252; //Расстояние сои t « ,. Фурrон может везти .. « semi. get.....cargocap () « " фунтов rруза. \п ,. ; cout « "Он имеет дальность пробеrа .. « semi. range () « .. миль. \п 11 ; cout « "Пробеr в " « dist « .. миль требует .. « dist / semi.get.....mpg() « " rаллонов топлива.\п\п"; cout « "Пикап может везти" « pickup.get.....cargocap() « " фунтов rруза. \п" ; cout « "Он имеет дальность пробеrа .. « pickup. range () « " миль. \п 11 ; cout « "Пробеr в 11 « dist « 11 миль требует .. « dist / pickup. get.....mpg () « .. rаллонов топлива.\п"; return о; } 4. Вывод проrpаммы будет таким: Фурrон может везти 44000 фунтов rруза. Он имеет дальность пробеrа 1400 миль. Пробеr в 252 миль требует 36 rаллонов топлива. Пикап может везти 2000 фунтов rруза. Он имеет дальность пробеrа 420 миль. Пробеr в 252 миль требует 16 rаллонов топлива. 5. От класса Vehicle можно образовать MHoro друrих типов классов. Например, следующая заrотовка создает класс внедорож ника, который содержит величину дорожноrо просвета для маши ны: // Создадим класс внедорожника class OffRoad : public Vehicle { int groundClearance; // дорожный просвет в дюймах public: // } ; Ключевой момент здесь заключается в том, что как только вы соз дали базовый класс, определяющий общие характеристики объекта, 
Создание мноrоуровневой иерархии классов 4ЗЗ этот базовый класс может наследоваться для образования специализи рованных классов. Каждый производный класс просто добавляет свои специфические характеристики. В этом и заключается cyrb наследова ния. Цель _Создание мноrоуровневой иерархии классов До сих пор мы имели дело с простыми иерархическими cтpyктypa ми, состоящими только из двух классов: базовоrо и производноrо. В действительности можно созцавать иерархические структуры, coдep жащие любое число уровней наследования. Как уже отмечалось, про изводный класс вполне допустимо использовать в качестве базовоrо для следующеrо производноrо класса. Например, если у нас имеются три класса А, В и С, то С может быть производным ОТ В, а В, в свою очередь, производным от А. В случае такой ситуации каждый произ водный класс наследует все содержимое всех своих базовых классов. В нашем примере С наследует все содержимое В и А. Для демонстрации смысла мноrоуровневой иерархии рассмотрим следующую проrpамму. В ней производный класс 1Папglе (треyrоль ник) используется в качестве базовоrо для создания производноrо класса Color1riangle (цветной треyrольник). Соlоr1Папglе наследует все содержимое классов 1riangle и 1\voDShape, и добавляет поле color, в KO тором хранится цвет треyrольника. // Мноrоуровневая иерархия. #include <iostream> #include <cstring> using namespace std; // Класс для двумерных объектов. class TwoDShape { // эти члены закрыты double width; double height; public: / / Конструктор по умолчанию. TwoDShape () { width = height = 0.0; } 
434 Модуль 1 О Наследование, виртуальные функции и полиморфизм // Конструктор для TwoDShape. TwoDShape(double W, double Ь) { width = w; height = h; } // Конструируем объект с равными шириной и высотой. TwoDShape(double х) { width = height = х; } void showDim () { cout « "Ширина и высота составляют " « width« " и " « height « "\0"; } / / функции доступа double getWidth() { returo width; } double getHeight() { returo height; } void setWidth(double w) { width = w; } void setHeight(double h) { height = h; } } ; // Triangle является производным от TwoDShape. class Triaogle : public ТwoDShape { char style[20]; // теперь private public: /* Конструктор по умолчанию. Он автоматически вызывает конструктор по умолчанию TwoDShape. */ Triaogle () { strcpy (style, .. неизвестно" ) ; } // Конструктор с тремя параметрами. Triaogle(char *str, double w, double h) : ТwoDShape(w, h) { strcpy(style, str); } // Конструируем равнобедренный треуrольник. Triangle(double х) : TwoDShape(x) { strcpy(style, "равнобедренный"); } doubl е area () { return getWidth() * getHeight() / 2; } 
с :CI'lдание мноrоуровневой иерархии классов 435 void showStyle() { cout « "Треуrольник .. « style « .. \п" i } } ; ColorTrl8l1gle наследует от Tnangle, который наследует от TwoDShape. I // Расширим Triangle. class ColorTriangle public Triangle {  char color[20] i public: ColorTriangle(char *clr, char *style, double w, double Ь) : Triangle(style, w, h) { strcpy(color, clr)i } // Выведем цвет. void showColor() { cout « "Цвет" « color « "\п"; } } i int rnain () { ColorTriangle tl("Синий", "прямоуrольный", 8.0,12.0); ColorTriangle t2 ("Красный", "равнобедренный", 2. О, 2. О) ; cout « .. Данные о tl: \п n i t1.showStyle()i t1.showDim()i t1.showColor()i cout « "Площадь равна 11 < t1.area() « "\п"; cout « "\;0" i cout « "Данные о t2:\n"; t2.ShowSt Y le( :] ; t2 . showDim () ; , t2.showColor(); cout « "Площадь равна .. « t2. area () « "\п"; Объeкr CoIorTriangle может ВЫ3blвaTb фунКЦИИ, оп- редenенные им самим и ero базовыми классами. return О i } Вывод профаммы ВЫfЛЯДИТ следующим образом: Данные о tl: Треуrольник прямоуrольный Высота и ширина составляют 8 и 12 
436 Модуль 1 О Наследование, виртуальные функции и полиморфизм Цвет СИНИЙ Площадь равна 48 Данные о t2: Треуrольник равнобедренный Ширина и высота составляют 2 и 2 Цвет Красный Площадь равна 2 Блаrодаря наследованию класс Соlоr1Папglе может использовать предварительно определенные классы Тriangle и 1\voDShape, добавив только специфическую информацию, которая нужна ему для ero соб CTBeHHoro, специфическоrо использования. В этом заключается часть ценности наследования; наследование является базой для повторноrо использования кодов. Рассмотренный пример иллюстрирует еще один важный принцип. Если конструктор базовоrо класса в иерархии классов требует парамет ры, тоrда все производные классы должны передавать эти параметры вверх по цепочке классов. Эro правило остается справедливым незави симо от тoro, требует ли сам производный класс параметры или нет. Цель _ Наследование от нескольких базовыx классов в с++ имеется возможность создать производный класс, который будет наследовать одновременно от двух или нескольких базовых классов (так называемое множественное наследование). Например, в этой короткой проrpамме класс D наследует и от Вl, и от В2: // Пример нескольких базовых классов. #include <iostream> using namespace std; class 81 { protected: int х; public: void showx () { cout « х « .. \n"; } } ; class 82 { protected: 
Коrда выполняются функции конструктора и деструктора 437 int у; public: void showy() { cout «у« "\n"; } } ; // Множественное наследование. class D: public 81, public 82 {  public: /* х и у доступны, потому что они объявлены в 81 и 82 защищенными, а не закрытыми. */ void set(int i, int j) { х = i; у = j; } } ; Здесь D одновременно на- следует и от 81, и от 82 int main () { D оЬ; ob.set(10, 20); // берется из D ob.showx(); // из 81 ob.showy(); // из 82 return о; } Как показывает этот пример, для TOro, чтобы наследовать от более чем одноrо базовоro класса, вы должны использовать список базовых классов, разделяемых запятыми. Не забудьте также для каждоrо базо Boro наследуемоrо класса указать описатель доступа. Цель _KorAa выполняются функции конструктора и AeCTpYIqpa Поскольку и базовый класс, и производный класс MOryт содержать конструкторы и деструкторы, важно понимать, в каком порядке они выполняются. Конкретно, в каком порядке вызываются KOHCтpYКТO ры, коrда начинает существовать объект производноrо класса? А коrда объект перестает существовать, в каком порядке вызываются дecтpyк торы? Для тою, чтобы ответить на эти вопросы, начнем с такой про стой проrpаммы: #include <iostream> using namespace std; 
438 Модуль 10 Наследование, виртуальные функции и полиморфизм class В { public: В () { cout « .. Конструируется базовая часть \n"; } B() { cout « "Уничтожается базовая часть\n"; } } ; class D: public В { public: D() { cout « "Конструируется производная часть\n"; } D () { cout « "Уничтожается производная часть \n"; } } ; int main () { D оЬ; // не делаем ничеrо, только конструируем и уничтожаем оЬ return о; } Как указано в комментарии к функции main( ), эта проrрамма просто конструирует и уничтожает объект с именем оЬ, принадле жащий классу О. Если проrpамму запустить, она выведет на экран следующее: Конструируется базовая часть Конструируется производная часть Уничтожается производная часть Уничтожается базовая часть Как показывает этот вывод, сначала выполняется конструктор В, затем конструктор О. Далее (поскольку оЬ в этой проrpамме HeMeд лен но уничтожается) вызывается деструктор О, а вслед за ним ..... дe структор В. результатыI проведенноrо эксперимента можно обобщить следую щим образом. Коrда создается объект производноrо класса, сначала вызывается конструктор базовоrо класса, затем конструктор произ водноrо класса. Коrда объект производноrо класса уничтожается, CHa чала вызывается ero деструктор, затем деструктор базовоrо класса. Дрyrими словами, конструкторы выполняются в порядке наследова ния их классов. Деструкторы выполняются в порядке, обратном по рядку наследования их классов. В случае мноrоуровневой иерархии классов (т. е. коrда производ ный класс ВЫС1УПает в качестве базовоrо класса для дрyrоrо произ водноrо класса), используется то же общее правило: конструкторы 
Указатели на производные классы 439 вызываются в порядке наследования их классов; деструкторы вызы ваются в обратном порядке. Если конструктор наследует более чем от одноro класса одновременно, конструкторы вызываются в порядке их перечисления (слева направо) в списке базовых KOHCТPYКPOB производноrо класса. Деструкторы вызываются в обратном порядке, справа налево. Спросим у эксперта Вопрос: Почему конструкторы вызываются в порядке наследования их классов, а деструкторы в обратном порядке? OrвeT: Если вдуматься, то установленный порядок вызова KOHCтpYКТO ров разумен. Поскольку базовый класс ничеrо не знает о производном классе, то любая требуемая ему инициализация должна выполняться He зависимо и, возможно, как необходимое условие инициализации, выпол няемой производным классом. Поэтому конструктор базовоrо класса дол жен выполняться первым. Аналоrично, вполне разумно, что деструкторы выполняются в поряд ке, обратном порядку образования их классов. Поскольку базовый класс служит основой для производноrо, уничтожение базовоrо класса предпо лаrает уничтожение и производноrо класса. Поэтому деструктор произ I водноrо класса должен быть вызван перед тем, как объект будет полно стью уничтожен. Минутная тренировка 1. Можно ли использовать производный класс в качестве базовоrо для дрyrоrо производноrо класса? 2. В каком порядке вызываются конструкторы в иерархии классов? З. В каком порядке вызываются деструкторы в иерархии классов? Да. производный класс может быть использован в качестве базовоrо для дpyroro произ.. водноrо класса 2 Конструкторы вызываются в порядке наследования их классов 3 ДесrpуктоРЫ вызываются в ПОРЯдке, обратном порядку наследования их классов Цель _Указатели на производные классы Перед тем, как приступить к изучению виртуальных функций и полиморфизма, нам необходимо обсудить некоторые важные аспек 
440 Модуль 10. Наследование, виртуальные функции и полиморфизм ты использования указателей. Указатели на базовый и производный классы связаны дpyr с дрyrом, чеrо мы не наблюдаем для указателей дрyrих типов. Как правило, указатель одноro типа не может указы вать на объект дрyrоrо типа. Однако указатели базовых и производ HbIX классов выпадают из этоrо правила. В С++ указатель базовоrо класса может быть использован в качестве указателя на объект любо ro дрyrоrо класса, производноrо от этоrо базовоro. Предположим, например, что у нас есть базовый класс В и производный от Hero класс О. Любой указатель, объявленный как указатель на В, можно использовать для указания на объект типа О. Таким образом, если имеются объявления: в *р; в В.....оЬ; D D....ob; / / указатель на объект типа В // объект типа В / / объект типа D то следующие предложения вполне правильны: р = &В.....оЬ; // р указывает на объект типа В р = &D.....ob; /* р указывает на объект типа D, который является производным от В. */ Базовый указатель можно использовать только для обращения к тем частям производноrо объекта, которые наследованы от базовоrо класса. Так, в этом примере р можно использовать для обращения ко всем элементам о.....оЬ, унаследованным от В.....оЬ. Однако к элементам, специфическим для о.....оЬ, посредством р обратиться нельзя (если только не применено приведение типа). Еще необходимо усвоить, что хотя посредством базовоrо указателя можно обратиться к производному объекту, обратное несправедливо. Вы не можете обратиться к объекту базовоrо типа, используя указатель на производный класс. Как вы уже знаете, инкремент и декремент указателя осуществляет ся относительно ero базовоrо типа. Поэтому в случае, коrда указатель базовоrо класса указывает на производный объект, операции инкре мента или декремента не приведут к смещению указателя на следую щий объект производноrо класса. Вместо этоrо указатель будет указы вать на ТО, что он считает следующим объектом базовоrо класса. Ta ким образом, если указатель базовоrо класса указывает на производный объект, вы не должны выполнять над ним операции ин кремента или декремента. Тот факт, что указатель на базовый тип может быть использован для указания на любой объект, производный от этоrо базовою типа, является исключительно важным и фундаментальным для С++. Как вы вскоре узнаете, эта возможность позволяет С++ реализовать поли морфизм времени выполнения. 
Виртуальные функции и полиморфизм 441 Ссылки на производные типы Аналоrично описанному только что использованию указателей, ссылка на базовый класс может быть использована для обращения к объекту производноro класса. Чаще вcero эта возможность использу ется в параметрах функций. Параметрссылка базовоrо класса может принимать объекты как базовоrо класса, так и любых классов, произ водных от этоrо базовоrо. Цель .. Виртуальные функции и полиморфизм Фундамент, на котором С++ строит свою поддержку полиморфиз ма, состоит из наследования и указателей на базовые классы. KOH кретным средством, которое фактически реализует полиморфизм, яв ляется виртуальная функция. Оставшаяся часть этоro модуля будет по священа этому важному средству. Основы виртуальных функций Виртуальной называется функция, объявленная в базовом классе с описателем virtual и переопределенная в одном или нескольких произ водных классах. Таким образом, каждый производный класс может иметь собственный вариант виртуальной функции. Свойства вирту альности проявляются у виртуальных функций, коrда для их вызова используется указатель базовоrо класса. Если виртуальная функция вызывается посредством указателя базовоrо класса, С++ определяет, какой из вариантов этой виртуальной функции вызвать, основываясь на типе объекта, на который указывает указатель. Следовательно, если базовый класс содержит виртуальную функцию, и два или боль ше различных классов наследуют от этоrо базовоrо класса, то в зави симости от тoro, на объект KaKoro производноrо класса указывает yкa затель базовоrо класса, будут вызываться различные варианты вирту альной функции. Вы объявляете функцию виртуальной внутри базовоro класса, пред  варяя ее объявление ключевым словом virtual. Коrда виртуальная Функ ция переопределяется в производном классе, нет необходимости повто рять ключевое слово virtual (хотя не будет ошибкой и ero повторение). Класс, который содержит виртуальные функции, называется поли морфны.м классом. Этот термин также относится и к классу, который наследует от базовоrо класса, содержащеrо виртуальную функцию. 
442 Модуль 10. Наследование, виртуальные функции и полиморфизм в следующей проrpамме продемонстрирована работа с виртуальной функцией: // Короткий пример использования виртуальной функции. #include <iostream> using namespace stdi class В { public: virtual void who () { / / Определим виртуальную функцию cout « "Базовый класс\n"; Объявим ВИplyallЬНУЮ фунКЦИЮ J } } ; class Dl : public В { Переопределим ВИр1)'8ЛЬНУЮ фУНКЦИЮ для D1 public: I vo id who () { / / переопределим who () для Dl .......-..J cout « "Первый производный класс\n"; } } ; class D2 : public В { public: void who () { / / переопределим who () для cout « "Второй производный класс\n"; Переопределим ВИр1)'8ЛЬНУЮ ФУНКЦИЮ второй раз для 02. D2J } } ; int main ( ) { в base.....obj ; В *р; Dl Dl.....obj; D2 D2.....obj; р = &base.....obj; p>who ( ); / / обращение к who из В р = &Dl.....0bj; p>who(); // обращение к who из Dl Вызов вир1)'8ЛЬНОЙ ФУНК- цИИ посредством указа- теля базовоrо класса. р = &D2.....0bj; p>who (); / / обращение к who из D2 return о; } 
Виртуальные функции и полиморфизм 443 Эта проrpамма выводит на экран следующее: Базовый класс Первый производный класс Второй про из водный класс Рассмотрим эту проrpамму детально, чтобы понять, как она рабо тает. Как вы видите, функция wbo() объявлена в Ю1ассе В как виртуаль ная. Это означает, что функцию можно пере определить в производ ном классе. Внутри каждоrо из производных классов О1 и 02 функция переопределяется относительно cBoero класса. Внyrpи main( ) объявле ны четыре переменные: baseobJ, представляющая собой объект типа В; р, являющаяся указателем на объектыI класса В; наконец, D1obj и D2obj..... объектыI двух производных классов. Далее переменной р при сваивается адрес объекта base.....obj, после чеro вызывается функция wbo( ). Поскольку wbo( ) объявлена как виртуальная, с++ определяет во время выполнения проrpаммы, какой вариант wbo( ) вызывать, oc новываясь на типе объекта, на который указывает р. В данном случае р указывает на объект типа В, поэтому вызывается вариант wbo( ) из класса В. Далее по ходу проrpаммы р присваивается адрес объекта D1obj. Вспомним, что указатель базовоrо класса может ссътаться на объект любоrо производноrо класса. Теперь при вызове wbo( ) с++ снова анализирует, на объект KaKoro типа указывает р и, в зависимости от этоrо типа, определяет, какой вариант wbo( ) следует вызвать. По скольку р указывает на объект типа Оl, выполняется вариант wbo( ) из этоrо класса. Аналоrично, коrда р присваивается адрес D2obj, выпол няется вариант wbo( ), объявленный внутри О2. Подытожим: если виртуальная функция вызывается посредством указателя базовоrо класса, выбор фактически выполняемоrо варианта виртуальной функции осуществляется во время выполнения, исходя из типа объекта, на КОТОРЫЙ указывает этот указатель. Хотя виртуальные функции обычно вызываются посредством yкa зателей базовоro класса, их можно также вызывать обычным образом, используя синтаксис с операторомточкой. Отсюда следует, что в пре дыдущем примере было бы синтаксически правильно вызвать wbo( ) с помощью тaKoro предложения: Dl.....obj.who(); Однако вызов виртуалЬНОЙ функции таким образом иrнорирует ее полиморфные атриБутыI. Только коrда виртуальная функция вызыва ется посредством указателя базовоrо класса (или ССЫЛКИ на базовый класс), достиrается полиморфизм времени выполнения. На первый взrляд описанное выше переопределение виртуальной функции в производном классе представляется особой формой пе 
444 Модуль 10 Наследование, виртуальные функции и полиморфизм реrрузки функции. Однако это не так. Фактически эти два средства фундаментально различаются. Прежде Bcero, переrруженная Функ ция должна отличаться числом или типом своих параметров, в то время как переопределенная виртуальная функция должна иметь в точности то же число и те же ТИПЫ параметров. Прототипы вирту альной функции и ее переопределений должны в точности совпа дать. Если прототипы различаются, тоrда функция рассматривается просто как переrруженная, и ее виртуальная сущность теряется. Дрyrое оrраничение заключается в том, что виртуальная функция обязана быть членом, а не друrом класса, для Koтoporo она опреде ляется. Однако виртуальная функция может быть дpyrOM дpyroro класса. Наконец, виртуальными MOryт быть деструкторы, но не KOH структоры. Виртуальные функции наследуются После тoro, как функция объявлена виртуальной, она остается тa ковой независимо от ТOI'O, через сколько производных классов она прошла. Например, если класс О2 объявлен производным не от В, а от Оl, как это показано в следующем фрarменте, тоrда функция who( ) все равно остается виртуальной: // D2 ПРОИЗВОДНЫЙ от Dl, а не от В. class D2 : public Dl { public: void who () { / / Определение who () в классе D2 cout « "Второй производный класс\п"; } } ; Если производный класс не переопределяет виртуальную функ  цию, тоrда используется функция в том виде, в каком она определе на в базовом классе. В качестве примера испытайте приведенный ниже вариант предыдущей проrраммы. В нем класс О2 не переопре деляет функцию who( ): #include <iostream> using namespace std; class В { public: virtual void who () { cout « "Базовый класс\п"; } } ; 
Виртуальные функции и полиморфизм 445 class Dl : public В { public: vo id who () { cout « "Первый производный класс\п"; } } ; class D2 : public В { 4 / / who () не определена } ; О2 не neреопределяет who( ). int main () { в baseobj ; В *р; Dl Dlobj; D2 D2obj; р = &baseobj; p>who (); / / обращение к who () из В , р = &Dlobj; p>who (); / / обращение к who () из Dl Здесь Вblзывается who(), которая onредeneна в В. р = &D2obj; I p>who (); /* обращение к who () из В, потому что ....J D2 не переопределил who()*/ return о; } Вот вывод этой проrpаммы: Базовый класс Первый производный класс Базовый класс Изза тoro, что класс О2 не переопределил who( ), используется Ba риант who( ), определенный в базовом классе В. Не забывайте, что наследование виртуальных функций имеет иерар... хический характер. Если последний пример изменить так, чтобы О2 был производным не от В, а от Оl, тоrда при вызове функции who( ) для объекта типа О2 будет вызываться не who( ) из класса В, а вариант who(), объявленный внyrpи Оl, потому что класс Оl является ближай шим классом к О2 в иерархии классов. 
446 Модуль 10 Наследование. виртуальные функции и полиморфизм Зачем нужны виртуальные функции? Как уже отмечалось ранее, виртуальные функции в сочетании с производными классами позволяют с++ поддерживать полимор физм времени выполнения. Полиморфизм весьма важен для объект ноориентированноrо проrpаммирования, потому что он дает воз можность обобщенному классу задать те функции, которые будут об щими для всех классов, производных от этоrо класса, и, в то же время, позволяет каждому производному классу определить специ фическую реализацию всех или некоторых из этих функций. Иноrда эту идею выражают таким образом: базовый класс диктует общий ИН терфейс для любоrо объекта, производноrо от базовоrо класса, но позволяет производным классам определять фактический метод, ис пользуемый для реализации этоrо интерфейса. Именно поэтому для описания полиморфизма часто используют фразу "один интерфейс, множество методов". Успешное практическое приложение полиморфизма требует по нимания Toro, что базовые и производные классы образуют иерар хию, в которой можно проследить движение от большей к меньшей обобщенности (от базовых классов к производным). Правильно раз работанный базовый класс предоставляет все элементы, которые производный класс может использовать непосредственно. Он также определяет те функции, которые производный класс должен реали зовать самостоятельно. Такой подход придает производному классу rибкость в определении собственных методов, и, тем не менее, Ha вязывает соrласованный, единообразный интерфейс. Единообразие означает, что поскольку форма интерфейса определена в базовом классе, все производные классы будут использовать один и тот же общий интерфейс. Таким образом, использование виртуальных функций предоставляет возможность базовому классу определить обобщенный интерфейс, который будет использоваться всеми про изводными классами. Вы, конечно, можете задать вопрос, почему единообразный ин терфейс с множеством реализаций так важен. Ответ опять возвра щает нас к rлавной движущей силе объектноориентированноrо проrpаммирования: оно помоrает проrpаммистам справляться с проrpаммами неуклонно возрастающей сложности. Если, например, вы правильно разработали свою проrрамму, то вы знаете, что ко всем объектам, наследуемым от базовоrо класса, обращение будет происходить единообразно, несмотря на то, что их конкретные дей ствия будут варьироваться от одноrо производноrо класса к дрyrо му. В результате вам надо будет иметь дело только с одним интер фейсом, а не с несколькими. Кроме Toro, ваш ПРОИЗВОДНЫЙ класс может использовать все или любые функциональные возможности, предоставляемые базовым классом. Вам не надо заново изобретать эти возможности. 
Виртуальные функции и полиморфизм 447 Отделение интерфейса от реализации является также основой для создания библиотек клаССО8, которые MOryr разрабатываться CТOpOH ними фирмами. Если эти библиотеки реализованы правильно, они предоставляют общий интерфейс, который вы можете использовать при разработке собственных производных классов, отвечающих Ba шим конкретным требованиям. Например, библиотеки классов Мiсrоsоft Foundation Classes (MFC) и более новая разработка NET Framework Wmdows Forms ПОдl1ерживают проrpаммирование в системе Windows. Используя классы этих библиотек, вы наследуете значитель ную долю функциональных возможностей, требуемых от Windows проrpамм Вам только остается добавить элементы, необходимые для вашеro конкретноrо приложения. При проrpаммировании сложных систем такой подход имеет orpoMHbIe преимущества. Приложение виртуальных функций Для Toro, чтобы продемонстрировать мощь виртуальных Функ ций, мы используем их в классе 1\voDShape. В предыдущих приме рах каждый класс, производный от 1\voDShape, определял функцию с именем area( ). Это наводит на мысль, что было бы лучше сделать area( ) виртуальной функцией класса 1\voDShape, позволив каждому производному классу переопределить эту функцию, поместив в нее формулу для вычисления площади Toro типа фиryp, которые инкап сулирует данный класс. Приводимая ниже проrрамма иллюстрирует этот подход. Кроме Toro, в ней для удобства в класс TwoDShape включено поле с названием фиrypы. (Это облеrчает демонстрацию классов. ) // Использование виртуальных функций и полиморфизма. #include <iostream> #include <cstring> using nаmеэрасе std; // Класс для двумерных объектов. class TwoDShape { // эти члены закрыты double widthi double height; // добавим поле для названия фиrуры char пате [ 2 О] ; public: 
448 Модуль 10. Наследование, виртуальные функции и полиморфизм / / Конструктор по умолчанию. TwoDShape () { width = height = 0.0; strcpy(name, "неизвестно"); } // Конструктор для TwoDShape. TwoDShape(double w, double Ь, char *n) { width = w; height = Ь; strcpy (пате, n); } // Конструируем объект с равными шириной и высотой. ТwoDShape(double х, char *n) { width = height = х; strcpy (пате, n); } void showDim () { cout « "Ширина и высота составляют " « width« n И " «height « "\n"; } / / функции доступа double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) {width = w; } void setHeight(double Ь) { height = Ь; } char *getName() { return пате; } / / Добавим в TwoDShape функцию area ( ) и сделаем / / ее виртуальной. Функция area( ) virtual double area () {  теперь ВИР1)'8l1ьН8Я. cout « "Ошибка: area () должна быть переопределена . \n q ; return 0.0; } } ; // Triangle является производным от TwoDShape. class Triangle : public TwoDShape { char style[20]; // теперь private public: /* Конструктор по умолчанию. ОН автоматически вызывает конструктор по умолчанию ТwoDShape. */ 
Виртуальные функции и полиморфизм 449 Tr iangle () { strcpy (style, "неизвестно"); } // Конструктор с тремя параметрами. Triangle(char *str, double w, double Ь) : TwoDShape(w, Ь, "треуrольник") { strcpy(style, str)i } // Конструируем равнобедренный треуrольник. Triangle (double х) : TwoDShape (х, "треуrольник") { strcpy(style, "равнобедренный"); } // Переопределяем функцию area(), объявленную в TwoDShape. doub] е area () { .. Пepeonредепение return getWidth () * getHeight () / 2; area( ) в Triangle } void showStyle() { cout « "Треуrольник 11 « style « 11 \n" i } } i // Производный от ТwoDShape класс для прямоуrольников. class Rectangle : public ТwoDShape { public: // Конструируем прямоуrольник. Rectangle(double w, double Ь) : TwoDShape (w, Ь, "прямоуrольник") { } // Конструируем квадрат. Rectangle(double х) : TwoDShape(x, "прямоуrольник") { } Ьооl isSquare() { if(getWidth() == getHeight(» return true; return false; } // Это друrое переопределение функции area(). double area() {  return getWidth() * getHeight(); Переопредeneние area( ) в Rectangle } } ; 
450 Модуль 1 О Наследование, виртуальные функции и полиморфизм in t та i n () { // объявим массив указателей на объекты TwoDShape. TwoDShape *shapes[5]; shapes[O] = &Triangle ( "прямоуrольный" , 8. О , 12 . О) ; shapes[l] = &Rectangle (1 О) ; shapes[2] = &Rectangle(10, 4) ; shареs[З] = &Triangle(7.0); shapes[4] = &TwoDShape(10, 20, "обобщенную фиrуру") ; for(int i=O; i < 5; i++) { cout « "объект представляет собой " « shapes[i]>getName() « "\n"; cout « "Площадь равна " « shapes[i]>area() « "\n";  cout « "\n"; I Теперь для каждоro объекrа вызывается правильный вариант функции area( ) } return о; } Ниже приведен вывод этой проrpзммы: объект представляет собой треуrольник Площадь равна 48 объект представляет собой прямоуrольник Площадь равна 100 объект представляет собой прямоуrольник Площадь равна 40 объект представляет собой треуrольник Площадь равна 24.5 объект представляет собой обобщенную фиrуру Ошибка: area() должна быть переопределена. Площадь равна О Рассмотрим проrpамму в деталях. Прежде Bcero, area( ) объявлена как виртуальная в классе 1\voDShape и переопределена в классах Тriangle и Rectangle. Внутри 1\voDShape для функции area( ) предусмот рена реализациязаrотовка, которая просто информирует пользовате ля, что функция должна быть переопределена в производном классе. Каждое переопределение area( ) представляет реализацию, отвечаю щую типу объекта, инкапсулированноrо в данном производном клас 
Чистые виртуальные функции и абстрактные классы 4! се. Таким образом, если бы вы, например, реализовывали класс э липсов, тоrда area( ) должна была бы вычислять площадь эллипса. В рассмorpeнной проrpамме имеется одно важное меcro. Обраm внимание на 10, что перемеIOfЗЯ sbapes в main( ) объявлена как массив ук зателей на объекrы 1\юDSЬаре. Однако элеменmм эroro массива присво ныI указатеJШ на объекты 'Diangle, Rectangle и 'I\voDShape. Эro допустим пoroму чro указатель базовоro класса может указывать на объект прои водноro класса. Проrpамма затем просматривает этот массив в ЦИЮIе, в) водя информaшuo о каждом объекre. Будучи весьма просThlМ, этот фра мент тем не менее иллюстрирует мощь как наследования, так и Вир1у3Л ных функций. Тип объеК'IЗ, на кoroрый указывает указатель базово класса, определяется во время выполнения, и дальнеЙlШfе действия зав сят or эroro 1ИПа. Если объекr ЯR1IЯется производным от 1\юShаре, е площадь можно получить, вызвав функцию area( ), причем интерфе эroй операции одинаков, Д1IЯ кaкoro бы 1ИПа фиrypы он не исполъзовалс Минутная тренировка 1. Что такое виртуальная функция? 2. В чем важность виртуальных функций? 3. Если переопределенная виртуальная функция вызывается посредств( указателя базовоrо класса, какой вариант функции будет выполнятьс 1 Виртуальной называется функuия, объявленная с описателем virtual в базовом класс. переопределенная в производном Юlассе 2 ВИрl уальиые функции предоставляют один из способов поддержки ПОЛИМОрфl ма в С  + l. Вариант виртуальной функции, который будет выполняться, определяется типом объс та, на который указывает указатель базовоrо класса к моменту вызова функuии Так образом, выбор функции осуществляется во время выполнения Цель _ Чистые виртуальные ФУНКЦИI и абстрактные классы Иноrда вам может понадобиться создать базовый класс, КОТОрl определяет только обобщенную форму, доступную всем ПРОИЗВОДНI: классам, имея в ВИДУ, что она затем будет конкретизироваться в ка дом производном классе. Такой класс определяет состав функций, I< торые должны быть реализованы в производных классах, но сам себе не предоставляет реализации всех этих функций или некотор из них. Ситуация тaKoro рода возникает, например, коrда базов) класс не может предложить имеющую смысл реализацию функц} Именно это имело место в последнем варианте 1\voDShape. ОпредеJ 
452 Модуль 10. Наследование, виртуальные функции и полиморфизм ние функции area( ) было просто шаблоном. Этот вариант функции не вычислит и не выведет на экран площадь какой бы то ни бьто фиrypы. как вы убедитесь сами, если будете создавать собственные библио теки классов, такая ситуация, коrда функция в контексте базовою клас са не может иметь разумной реализации, встречается довольно часто. Она может быть разрешена двумя способами. Один способ, продемон стрированный в нашем примере, заключается в реализации функции, которая будет просто выводить предупреждающее сообщение. Хотя в некоторых сmyациях  например, при отладке проrpаммы ..... такой подход может быть полезен, обычно к нему не прибеrают. Вы можете столкнуться с функциями, которые должны быть обязательно переоп ределены в производном классе, чтобы этот класс имел смысл. Вспом ним класс 1Папg1е. Он не имеет смысла, пока в нем не определена функция area( ). В таких случаях вы должны иметь какойто способ, KO торый позволит вам убедиться, что производный класс действительно пере определил все необходимые функции. Решение этой проблемы в С++ заключается в использовании чистых виртуальных функций. Чистая виртуальная функция ..... это функция, объявленная в базо вом классе и не имеющая в этом классе cBoero определения. В этом случае каждый производный класс должен обязательно определить собственный вариант функции..... он не может использовать функцию, которая не имеет определения. Для объявления чистой виртуальной функции используется такая общая форма: virtual тип имяфункции(списокпараметров) == о; Здесь тип представляет собой тип возвращаемоrо функцией значе ння, а u.мяфункции ..... ее имя. Используя чиcIыIe вIф1yзлъныIe функции, вы можете улучшить класс 1WoDShape. Поскольку для неопределенной двумерной фиrypы нельзя предложить разумный способ вычисления IUIощзди, в приведенном ниже вариaнre предыдущей проrpаммы функция area( ) объявлена ВнyIpИ Ю1ас са 1WoDShape как чистая Вlф1Yзльная. Эro, разумее1СЯ, означает, чro все ЮIассы, производные от 1\voDShape, должныI переопреде.лять area( ). // Использование qистой виртуальной функции. #include <iostream> #include <cstring> using namespace stdi / / Класс для двумерных объектов. class TwoDShape { / / эти члены закрыты double width; double height i 
Чистые виртуальные функции и абстрактные классы 453 // добавим поле для названия фиrуры char пате [ 2 о] ; public: / / Конструктор по умолчанию. ТwoDShape () { width = height = 0.0; strcpy(name, "неизвестно"); } // Конструктор для ТwoDShape. TwoDShape(double w, double Ь, char *n) { width = w; height = Ь; strcpy (пате, n); } // Конструктор для объекта с равными шириной и высотой. TwoDShape(double х, char *n) { width = height = х; strcpy (пате, n); } void showDim () { cout « "Ширина и высота составляют " « width« " и " « height « "\n"; } / / функции доступа double getWidth() { return width; } double getHeight() { return height; } void setWidth(double w) { width = W; } void setHeight(double Ь) { height = Ь; } char *getName() { return пате; } / / area ( ) теперь чистая виртуальная функция  virtual double area() = о; } ; area( ) теперь ЯВ- ляется чистой вир. 1УaI1bНОЙ функцией // Triangle является производным от TwoDShape. class Triangle : public TwoDShape { char style[20]; // теперь private public: /* Конструктор по умолчанию. Он автоматически конструктор по умолчанию TwoDShape. */ Triangle () { вызывает 
454 Модуль 10. Наследование, виртуальные функции и полиморфизм strcpy(style, "неизвестно"); } // Конструктор с тремя параметрами. Triangle(char *str, double w, double h) : TwoDShape(w, h, "треуrольник") { strcpy(style, str); } // Конструируем равнобедренный треуrольник. Triangle (double х) : TwoDShape (х, " треуrольник" ) { strcpy(style, "равнобедренный"); } // Теперь это переопределяет area(), объявленную в // TwoDShape. double area () { return getWidth() * getHeight() / 2; } void showStyle() { cout « "Треуrольник " « style « "\п"; } } ; // Производный от TwoDShape класс для прямоуrольников. class Rectangle : public TwoDShape { public: // Конструируем прямоуrольник. Rectangle(double w, double h) : TwoDShape (w, h, .. прямоу rольник ") { } // Конструируем квадрат. Rectangle(double х) : ТwoDShape (х, .. прямоуrольник ") { } bool isSquare() { if(getWidth()  getHeight(» return true; return false; } // Это друrое переопределение функции area(). doubl е area () { return getWidth() * getHeight(); } } ; 
Чистые виртуальные функции и абстрактные классы 455 int main() { // объявим массив указателей на объекты TwoDShape. ТwoDShape *shapes[4]; shapes[O] = &Тriапglе("прямоуrольный", 8.0,12.0); shapes[l] = &Rectangle(lO); shapes(2] = &Rectangle(10, 4); shареs[З] = &Triangle(7.0); for(int i=Oi i < 4; i++) { cout « "объект представляет собой" « shapes[i]>getName() « "\n"; cout « "Площадь равна " « shapes[i]>area() « "\n"; cout« "\n"; } return О i } Если класс имеет хотя бы одну чистую виртуальную функцию, про HerO rоворят, что это абстрактный класс. Абстрактный класс имеет одну важную черту: нельзя создать объект TaKoro класса. Чтобы y6e диться в справедливости этоrо утверждения, попробуйте удалить пере определение area( ) из класса 1Папglе последней проrpаммы. При по пытке создать экземпляр 1Пanglе вы получите сообщение об ошибке. Абстрактный класс может быть использован только в качестве базово ro, от KOToporo будут наследовать дрyrие классы. Причина невозмож ности объявить объект абстрактноrо класса заключается в том, чro одна или несколько ero функций не имеют определений. Именно по этому массив shapes в последней проrpамме уменьшен до четырех эле ментов и не создается обобщенный объект 1\voDShape. Как это видно из проrpаммы, если базовый класс является абстрактным, вы, тем не менее, можете объявить указатель этоrо класса, который будет затем использоваться для указания на объекты производных классов. .'( ....P.I. . . . .PP. . . 1. Класс, который наследуется, называется классом. Класс, который наследует, называется классом 2. Имеет ли базовый класс доступ к членам производноro класса? Име ет ли производныIй класс доступ к членам базовоro класса? 
456 Модуль 1 О Наследование, виртуальные функции и полиморфизм 3. Создайте производный от 1\voDShape класс с именем Circle (кpyr). Включите в Hero функцию area( ), вычисляющую площадь кpyra. 4. Каким образом предотвратить доступ производноrо класса к члену базовоrо класса? 5. Приведите общую форму конструктора, который вызывает KOHCT руктор баЗОБоrо класса. 6. Пусть имеется такая иерархия классов: class Alpha { ... class Beta : public Alpha { class Gamma : public Beta { в каком порядке вызываются конструкторы этих классов, коrда создается объект Gamma? 7. Откуда можно обращаться к членам класса с атрибутом protected? 8. Указатель базовоrо класса может указывать на объект производно ro класса. Объясните, почему это важно применительно к переоп ределению функций. 9. Что такое чистая виртуальная функция? Что такое абстрактный класс? 10. Можно ли создать экземпляр объекта абстрактноrо класса? 11. Объясните, каким образом чистые виртуальные функции помоrа ют реализации принципа полиморфизма "один интерфейс, MHO жество методов". 
Модуль 11 с++ и система ввода-вывода Цели, дости..аемые в этом rv10AYJle 11.1 Понять, что такое потоки ввода"вывода 11.2 Узнать о иерархии классов ввода"вывода 11.3 Освоить переrрузку операторов « и » 11.4 Изучить форматирование ввода"вывода с помощью функций..чле- HOBios 11.5 Научиться форматировать ввод"вывод с помощью манипуляторов 11.6 Начать создавать собственные манипуляторы 11 .7 Научиться открывать и закрывать файлы 11.8 Освоить чтение и запись текстовых файлов 11.9 Освоить чтение и запись двоичных файлов 11.1 О Больше узнать о функциях ввода"вывода 11 . 11 Познакомиться со случайным доступом к файлам 11 . 1 2 Узнать, как можно получить состояние системы ввода.. вывода 
458 Модуль 11 С++ и система ввода"вывода С caMoro начала этой книrи вы использовали систему BBoдaBЫBoдa С++, однако делали это без должноrо формальноrо описания этой системы. Поскольку система BBoдaBЫBoдa основывается на иерархии классов, не бьто возможности представить ее теорию и детали без предварительноrо обсуждения классов и наследования. Теперь Hacтy пило время изучить систему BBoдaBЫBoдa в деталях. Система BBoдaBЫBoдa с++ весьма велика, и невозможно описать в этой книrе все ее классы, функции и средства; здесь мы только позна комим вас с наиболее важными и используемыми чаще дрyrих элемен тами этой системы. В частности, вы узнаете, как можно переrpузить операторы < < и > >, чтобы получить возможность вводить и выводить объекты разработанных вами классов. Вы также познакомитесь с фор матами и манипуляторами BBoдaBЫBoдa. В конце модуля мы обсудим файловый BBOДBЫBOД. Старая и новая системы ввода- BblBoAa в настоящее время используются две версии объектноориентиро ванной библиотеки BBoдaBЫBoдa с++: более старая, основанная на ис ходной спецификации С++, и новая, определенная в стандартном с++. Старая библиотека BBoдaBЫBoдa ПОдl1ерживается заrоловочным файлом <iostream.h>. Новая библиотека BBoдaBЫBoдa поддерживается заrоловком <iostream>. Обе библиотеки с точки зрения проrpаммиста представляются в значительной степени одинаковыми. Эro объясняет ся тем, ЧТО новая библиотека BBoдaBЫBoдa по существу является просто модернизированной и улучшенной версией старой. Фактически боль шая часть различий в этих библиотеках лежит под поверхностью и оп ределяется тем, как они реализованы, а не тем, как они используются. С точки зрения проrpаммиста между старой и новой библиотекой имеются два основных различия. Вопервых, новая библиотека BBoдa вывода содержит несколько дополнительных средств и определяет He которые новые типы данных. Таким образом, новая библиотека опре деленно является надмножеством старой. Практически все проrpам мы, написанные для старой библиотеки BBoдaBЫBoдa, будут компили роваться без существенных изменений в случае использования новой библиотеки. BOBTOPЫX, старая библиотека BBoдaBЫBoдa нходилась в rлобальном пространстве имен. Новая же библиотека находится в пространстве имен std. (Пространство имен std используется всеми библиотеками стандартноrо С++.) Поскольку старая библиотека BBO дaBЫBoдa теперь устарела, в этой книrе описывается только новая библиотека, хотя большая часть приводимых данных будет с равным успехом относиться и к старой. 
Потоки С++ 459 Цель _ Потоки с++ Важнейшей чертой системы BBoдaBЫBoдa С++ является то, что она оперирует над потоками. Поток является абстрактным объектом, KO торый либо создает, либо поrлощает информацию. Поток связывается с физическим устройством посредством системы BBoдaBЫBoдa С++. Все потоки вeдyr себя одинаковым образом, одни и те же функции и операторы BBoдaBЫBoдa применимы практически ко всем типам YCT ройств. Например, метод, с помощью KOToporo вы выводите данные на экран, может быть использован для записи данных на диск или пе чати их на принтере. В наиболее общей форме поток является лоrическим интерфей сом с файлом. Соrласно определению в С++, термин "файл" может относиться к клавиатуре, порту, файлу на ленте и т. д. Хотя файлы различаются по форме и возможностям, все потоки одинаковы. Пре имущество тaKoro подхода заключается в том, что для вас, проrpам миста, одно аппаратное устройство будет выrлядеть в значительной степени так же, как и любое дpyroe. Поток обеспечивает единообраз ный интерфейс. Поток связывается с файлом посредством операции открытия. По ток отсоединяется от файла посредством операции закрытия. Имеются два типа потоков: текстовые и двоичные. Текстовый по ток состоит ИЗ символов. При использовании TeKcToBoro потока может иметь место то или иное преобразование символов. Например, при выводе символа новой строки он может быть преобразован в последо вателъность возврат каретки ..... перевод строки. По этой причине меж ду тем, что посьтается в поток, и тем, что записывается в файл, может и не быть точноrо соответствия. Двоичные потоки MOryт ИСПОЛЬЗО ваться с данными любых типов. В этом случае никакоrо преобразова ния не происходит, и между тем, что посьтается в поток, и тем, что фактически содержится в файле, имеется полное соответствие. Еще одним важным понятием является текущая позиция. Текущая позиция..... это позиция в файле, к которой будет выполняться очеред ное обращение при чтении или записи файла. Если, например, файл имеет длину 100 байт, и половина файла уже прочитана, следующая операция чтения обратится к байту 50, который и является в данный момент текущей позицией. Подытожим. В С++ BBOДBЫBOД осуществляется посредством лоrи ческоro интерфейса, называемоro потоком. Все потоки имеют схожие свойства, и операции над любым потоком выполняются одними и теми же функциями ввода"вывода, независимо от Toro, с кaкoro рода файлом связан поток. Файл является физическим устройством, KOТO рое содержит данные. Хотя файлы и различаются, все потоки одина ковы. (Разумеется, конкретные устройства MOryт и не обеспечивать все 
460 Модуль 11. С++ и система BBoдaBЫBoдa возможные операции, например, операции произвольноrо доступа, и тоrда связанные с ними потоки также эти операции ПОдl1ерживать не будут. ) Предопределенные потоки С++. С++ содержит несколько предопределенных потоков, которые открываются автоматически, коrда начинает выполняться ваша С++проrрамма. Эти потоки называются cin, cout, cerr и clog. Как вы уже знаете, cin  это поток, связанный со стандартным вводом, а COBt ..... поток, связанный со стандартным выводом. Поток cerr также связан со стандартным выводом, как и clog. Разница между двумя этими потоками заключается в том, что clog буферизован, а cerr нет. Это означает, что любые данные, посьтаемые в поток cerr, BЫBO дятся немедленно, а вывод в clog записывается на устройство только после заполнения буфера. Обычно потоки cerr и clog используются проrраммой для записи отладочной информации и информации об ошибках. С++ также открывает варианты стандартных потоков, предназна ченных для раБотыI с "широкими" (16битовыми) символами: wcin, wcout, wcerr и wclog. Эти потоки призваны поддерживать такие языки, как, например, китайский, которые используют большие символьные наборы. Мы не будем использовать их в этой книrе. По умолчанию стандартные потоки С++ связаны с консолью, но ваша проrpамма может пере направить их на дрyrие устройства или файлы. Они MOryт быть также перенаправлены операционной сис темой. Цель _ Потоковые классы С++ Как вы узнали из Модуля 1, с++ обеспечивает поддержку своей системы BBoдaBЫBoдa в заrоловке <iostream>. В нем определена дo вольно сложная система иерархий классов для поддержки операций BBoдaBЫBoдa. Классы BBoдaBЫBoдa начинаются с системы шаблон ных классов. Как вы узнаете из Модуля 12, шаблон определяет фор му класса без полной спецификации данных, над которыми он бу дет производить операции. После Toro, как шаблонный класс опре делен, MOryт быть созданы конкретные экземпляры этоrо класса. Если rоворить о библиотеке BBoдaBЫBoдa, стандартный С++ созда ет два специфических варианта этих шаблонных классов: один для 8битовых символов И друrой для "широких". Эти специфические варианты действуют, как и любые дрyrие классы, и для полноцен 
Потоковые классы С++ 461 Horo использования системы BBoдaBЫBoдa С++ нет необходимости знать о шаблонах. Система BBoдaBЫBoдa С++ построена на двух связанных, но раз личных иерархиях шаблонных классов. Первая наследуется от низко ypoBHeBoro класса BBoдaBЫBoдa, называемоrо basic....streambuf. Этот класс обеспечивает операции элементарноrо, низкоуровневоrо BBoдa вывода и предоставляет базовую поддержку для всей системы BBoдa вывода С++. Использовать класс basic....streambuf непосредственно вам может понадобиться, только если вы будете заниматься систем ным проrpаммированием BBoдaBЫBoдa. Иерархия классов, с которой вам чаще Bcero придется сталкиваться, наследуется от класса basic....ios. Это класс BBoдaBЫBoдa высокоrо уровня, который предос тавляет форматирование, обнаружение ошибок и информацию о co стоянии при работе с потоковым BBOДOMBЫBOДOM. (Базовый класс для basic....los называется los....base; в нем определены некоторые xapaK терные средства, используемые классом basic....ios.) Класс basic....ios ис пользуется в качестве базовоrо для нескольких производных классов, включая basic....istream, basic....ostream и basic....iostream. Эти классы ис пользуются для создания потоков, обеспечивающих ввод, вывод и BBOдBыBOд соответственно. Как уже rоворилось, библиотека BBoдaBЫBoдa содержит два специ фических варианта иерархий классов BBoдaBЫBoдa: одна для 8бито вых символов, а дрyrая для "широких". В этой книre обсуждаются только классы для 8битовых символов, поскольку именно они ис пользуются наиболее часто. Ниже приведен список соответствий имен шаблонных классов их символьноориентированных вариантов. Имя wаБJlонноrо класса Эквивалентное имя СИМВОJllaно-ориентиро- BaHHoro класса baslc  streambuf streambuf baslc IOS 10S baslC Istream Istream baslc  ostream ostream baslC Iostream lostream baslC  fstream fstream baslc ...Ifstream Ifstream baslc  ofstream ofstream На протяжении всей оставшейся части этой книrи будут использо ватъся символъноориентированные имена, поскольку именно этими именами вы будете пользоваться в своих проrpаммах. Эrи же самые имена использовались и в старом варианте библиотеки BBoдaBЫBoдa. Именно поэтому старая и новая библиотеки ввода"вывода совмести мы на уровне исходных текстов проrpамм. 
462 Модуль 11. С++ и система BBoдaBЫBoдa Последнее замечание: класс ios содержит большое количество Функ цийчленов и переменных для управления и контроля фундаменталь HbIX потоковых операций. Мы будем часто обращаться к этому классу. Не забывайте, чro если вы включаете в свою проrpамму зarоловок <iostream>, вы получаете доС1УП к этому важному классу. Минутная тренировка 1. Что такое поток? Что такое файл? 2. Какой поток связан со стандартным выводом? 3. BBOДBЫBOД с++ ПОдl1ерживается сложным набором иерархий классов. Правильно ли это? 1. ПОТОКОМ называется лоrический абстрактный объеКТ, который либо производит, либо потребляет информацию. Файл  это физический объект, содержащий данные 2. ПОТОК, связанный со стандартным выводом, называется cout 3 Правильно Ввод..вывод с++ поддерживается сложным набором иерархий классов Цель _ Переrрузка операторов Boдa - BblBoAa в предыдущих модулях, коrда проrpамме нужно было вывести или ввести данные, связанные с классом, создавались функциичлены, единственным назначением КОТОРЫХ был вывод или ввод данных для этоrо класса. Хотя в этом подходе нет ничеrо неправильноrо, с++ предлаrает rораздо лучший способ выполнения операций BBoдaBЫBo да для классов: переrpузку операторов BBoдaBЫBoдa « и». В языке с++ оператор « называется оператором вставки, потому что он вставляет данные в поток. Аналоrично, оператор > > называет ся оператором извлечения, потому что он извлекает данные из потока. Операторные функции, переrpужающие операторы вставки и извлече ния, называют соответственно функциями вставки и извлечения или просто функциями вывода и ввода. В зaroловке <iostream> имеются переrpуженные операторы ВС1ЗВКИ и извлечения Д1IЯ всех ВC1J>OOННbIX типов с++. Здесь вы увишпе, как можно переrpузитъ эти операторы примеюпелъно к созцаваемым вами Ю1ассам. Создание опера торных функций вывода в качестве простоrо примера давайте создадим оператор вывода (вставки) для приведенноrо ниже варианта класса ThreeD: 
Переrрузка операторов ввода..вывода 463 class ThreeD { public: int х, у, Zi // трехмерные координаты ThreeD(int а, int Ь, int с) { х = а; У = Ь; Z = с; } } i Чтобы СО1дать функцию вывода для объекта типа ТhreeD, необхо димо переrpузить для Hero оператор «о Вот как это делается: // Вывод координат Х, У, Z  функция вывода для ThreeD. ostream &operator«(ostream &stream, ThreeD obj) { stream« obj.x«" "; stream« obj.y«" "; stream « obj. z « .. \n" ; return streami // возврат потока stream } Рассмотрим приведенный фраrмент внимательно, так как мноrие ero детали будyr повторяться во всех функция вывода. Прежде Bcero заметим, что наша функция, в соответствии с ее объявлением, возвра щает объект типа ostream. Такое объявление делает возможным объе динять операторы вывода этоrо типа в составных выражениях BBoдa вывода. Далее, функция имеет два пара метра. Первый представляет собой ссьтку на поток, который указывается с левой стороны опера тора < <. Второй параметр ..... это объект, указываемый с правой CTOpO ны оператора. (Этот параметр может также представлять собой ссылку на объект, если вам ЭТО больше нравится.) Внутри функции выводятся в поток три значения, содержащиеся в объекте типа ThreeD, и локаль ная переменная stream возвращается. Вот короткая проrpамма, демонстрирующая создание и использо вание оператора вывода: /* Демонстрация переrруженноrо оператора вывода для KOHKpeTHoro класса.*/ #include <iostream> using namespace stdi class ThreeD { public: int х, у, z;.// трехмерные координаты ThreeD(int а, int Ь, int с) { х = а; У = Ь; Z = с; } } i // Вывод координат Х, У, Z  оператор вывода для ThreeD. 
464 Модуль 11 . С++ и система BBoдaBЫBoдa 7stream &operator«ostream &stream. ThreeD obj)  stream « obj. х «" n ; Оператор вывода ДЛЯ класса ТhreeD. stream« obj.y«" "; stream« obj.z « "\0"; return streami // возврат stream } int maio () { ThreeD а ( 1, 2, 3), Ь ( 3, 4, 5), с ( 5, 6, 7); cout « а « Ь « с;  Используем оператор вывода класса ТhreeD для вывода координат returo о; } ВЫВОД этой проrpаммы выrлядит таким образом: 1, 2, 3 3, 4, 5 5, 6, 7 Если ВЫ удалите код, специфический для класса ТhreeD, останется шаблон для функции вывода: ostream &operator«ostream &stream, classtype obj) { // здесь располаrается код, специфический для данноrо // класса returo stream; // возврат stream } Разумеется, объект obj вполне допустимо передать по ссьтке. Действия, выполняемые функцией вывода, фактически MOryт быть какими уrодно. Однако хороший стиль проrpаммирования требует, чтобы ваша функция вывода орrанизовывала разумный вывод. Не за бывайте, что она должна возвращать локальную переменную, назван ную в наших примерах stream. Использование дружественных функций ДЛЯ переrрузки операторов вывода В предыдущей проrpамме функция переrpузки оператора вывода не являлась членом ТhreeD. В самом деле, ни функции ввода, ни 
Переrрузка операторов BBoдaBЫBoдa 465 функции вывода не Moryт быть членами класса. Причина TaKoro за прета заключается в том, что если функция operator является чле ном класса, ее левый операнд (передаваемый неявно посредством указателя this) должен быть объектом Toro класса, который вызыва ет эту функцию. Изменить это требование нельзя. Однако, коrда мы переrружаем операторы ввода, левым операндом является поток, а правым  объект класса, для KOToporo осуществляется операция BЫ вода. Поэтому переrруженные операторы вывода должны быть функциямине членами. То, что переrpуженные операторы вывода не MOryт быть членами класса, для KOToporo они определяются, вызывает серьезный BO прос: каким образом переrpуженный оператор вывода может обра титься к закрытым элементам класса? В предыдущей проrpамме пе ременные х, у и z были сделаны открытыми, чтобы оператор вывода Mor к ним обратиться. Однако сокрытие данных является важной частью ООП, и вынужденное открытие данных является серьезным нарушением ero принципов. Для решения возникшей проблемы имеется решение: переrpуженный оператор вывода может быть дpy жественной функцией класса. Как дрyr класса, для KOToporo он оп ределяется, этот оператор имеет доступ к закрытым данным. Ниже приведен новый вариант класса ThreeD и использующей ero про фаммы, rде переrруженный оператор вывода объявлен дружествен ной функцией: // Использование дружественной функции для переrрузки «е #include <iostream> using namespace std; Оператор вывода класса ТhreeD теперь является дpyrOM и имеет Д к закрытым данным. class ThreeD { int х, у, z; // трехмерные координаты  теперь private public: ThreeD(int а, int Ь, int с) { х = а; у = Ь; z = с; } friend ostream &operator«(ostream &stream, ThreeD obj), } ; // Вывод координат Х, У, Z  оператор вывода для ThreeD. ostream &operator«(ostream &stream, ThreeD obj) { stream« obj.x«" "; stream« obj.y«" "; stream « obj. z « "\n"; return stream; // возврат stream } int main ( ) 
466 Модуль 11. С++ и система BBoдaBЫBoдa { ThreeD а ( 1, 2, З), Ь (З, 4, 5), с ( 5, 6 I 7); сои t « а « Ь « с; return о; } Обратите внимание на переменные х, у и z, которые теперь объявлены закрытыми, но, тем не менее, непосредственно доступ ны для переrруженноrо оператора вывода. Объявление переrру женных операторов вывода (и ввода) друзьями классов, для KOTO рых они определяются, хорошо соrласуется с принципами инкап суляции ооп. Переrрузка операторов ввода Для переrpузки оператора ввода используется тот же самый общий подход, продемонстрированный выше применительно к операции BЫ вода. Например, приведенный ниже переrруженный оператор ввода вводит трехмерные координатыI. Обратите внимание на то, что он Taк же выводит запрос пользователю: // Получение трехмерных координат  оператор ввода для // ThreeD. istream &operator»(istream &stream, ThreeD &obj) { cout « "Введите значения X,Y,Z: "; stream» obj.x» obj.y» obj.z; return stream; } Оператор ввода должен возвращать ссылку на объект типа istream. Кроме Toro, в качестве первоrо параметра должна использо ваться ссылка на объект типа istream. Это объект потока, указывае мый с левой стороны оператора > >. ВТОРОЙ параметр представляет собой ссьтку на переменную, которая будет принимать ввод. по скольку это ссылка, второй параметр может модифицироваться в процессе ввода данных. Шаблон для переrpуженноrо оператора ввода выrлядит так: istream &operator»(istream &stream, objecttype &obj) { // здесь размещается код вашей функции ввода 
Переrрузка операторов BBoдaBЫBoдa 467 return streami } Приведенная ниже nporpaMMa демонстрирует создание и исполъзо вание оператора ввода для объектов типа ТhreeD: // Демонстрация прикладноrо переrруженноrо оператора ввода. #include <iostream> using namespace std; class ThreeD { int х, у, Zi // трехмерные координаты public: ThreeD(int а, int Ь, int с) { х = а; у = Ь; z = с; } friend ostream &operator«(ostream &stream, ThreeD obj); friend istream &operator»(istream &stream, ThreeD &obj); } ; // Вывод координат Х, У, Z  оператор вывода для ThreeD. ostream &operator«(ostream &stream, ThreeD obj) { stream« obj.x«" "; stream« obj.y«" "; stream« obj.z « "\n"; return streami // возврат stream } // Получение трехмерных координат  оператор ввода для ThreeD. stream &operator»(istream &stream, ТhreeD &Obj) cout « 11 Введите значения Х, у , z: "; Переrpyженный оператор s tream » obj . х » obj . у » obj . z; ввода для 11neD return stream; } int main () { ThreeD a(l, 2, З)i cout « а; cin » а; cout « а; return о; } 
468 Модуль 11 . С++ и система ввода.. вы вода Вывод одноrо из проrонов проrраммы выrлядит так: 1, 2, 3 Введите значения Х, У, z: 5 6 7 5, б, 7 Как и переrpуженные операторы вывода, переrpуженные операто ры ввода не MOryт быть членами класса, для операций с которым они предназначаются. Они MOryт быть либо друзьями класса, либо просто независимыми функциями. За исключением тoro, что вы должны вернуть ссылку на объект типа istream, вы можете внутри функции ввода делать все, что вам за блаrорассудится. Однако ради ясности и удобства чтения проrpаммы лучше использовать переrpуженные операторы ввода исключительно для операций ввода. Минутная тренировка 1. Что такое функция вывода? 2. Что такое функция ввода? 3. Почему для создания переrpуженных операторов ввода и вывода часто используются дружественные функции? 1. Функция вывода помещает данные в поток 2 Функция ввода извлекает данные из потока 3 Дружественные функции часто используются ШIЯ создания переrpуженных операторов ввода и вывода, потому что они имеют дос1УП к закрытым данным класса Форматированный ввод"вывод До сих пор при вводе и выводе мы неявным образом исполъзова ли форматные данные, поставляемые системой BBoдaBЫBoдa с++ по умолчанию. Вы, однако, можете детально управлять форматом ваших данных, для чеrо имеются два способа. Первый использует Функции члены класса ios. Второй работает со специальными функциями, Ha зываемыми манипуляторами. Мы начнем с рассмотрения функций членов ios. Цель 8 Ф орматирование с помощью функцийчленов ios с КЗ)fЩbIМ потоком связан набор флarов формата, кoroрые задaюr спо соб формаrnpования дaннbIX в пoroке. В Ю1ассе ios объявлен перечисли 
Форматирование с помощью функций..членов 10S 469 мый 1ИП БИ1ОВОЙ маски с именем fmtt1ap, в КО1Ором определены приве денные ниже значения. (Формально они определены ВнyIpИ iosbase, KO roрый, как уже отмечалось, является базовым классом для ios.) adJustf.eld basefleld boolalpha dec flxed floatf.eld hex Internal left oct rlght sClent.f.c showbase showpo.nt showpos sk.pws unltbuf uppercase с помощью этих значений устанавливаются и сбрасываются фла rи формата. Некоторые старые компиляторы MOryт не распознавать перечислимый тип fmtt1ags; в этом случае флаrи формата кодируются в виде длинных целых чисел. Если флаr skipws установлен, то при вводе в поток отбрасываются ведущие пробельные символы (символы пробела, табуляции и новой СТРОки). Если флаr skipws сброшен, то пробельные символы не отбра сываются. Если флаr left установлен, то вывод выравнивается по левому краю. Если установлен right, вывод выравнивается по правому краю. При yc тановке флаrа intemal числовые значения дополняются пробелами до указанной ширины поля, причем пробелы включаются между знаком числа и самим числом. Если НИ один из этих флаrов не установлен, вывод по умолчанию выравнивается по правому краю. Числовые значения выводятся по умолчанию в десятичной форме. Однако имеется возможность изменить систему счисления. Установка флаra oct приводит к выводу чисел в восьмеричной системе; флаr Ьех устанавливает шестнадцатеричную систему, а для возврата к десятич ной форме вывода надо установить флаr dec. Установка showbase включает в вывод обозначение системы счисле ния. Например, если установлена шестнадцатеричная система вывода, то значение lF будет отображаться как OxlF: Если вывод чисел осуществляется в экспоненциальной форме, то по умолчанию выводится строчное е. Кроме тoro, строчной будет бук ва х при выводе шестнадцатеричных чисел. Если установить флаr uppercase, эти символы выводятся прописными буквами. Установка showpos выводит знак плюс перед положительными зна чениями. Установка showpoint при водит к выводу десятичной точки и завер шающих нулей при выводе любых чисел с плавающей точкой, даже если в этом нет фактической необходимости. При УС13Новке флara scientific числа с плавающей точкой будyr BЫВO диться В экспоненциальной форме. Если установлен tixed, числа с пла вающей точкой выводятся обычным образом. Если ни один из этих фла roB не установлен, компиляroр сам выбирает подходящий способ вывода. 
470 Модуль 11. С++ и система ввода..вывода Если установлен unitbuf, после каждой операции вставки буфер очи щается. При установленном boolalpha булевы переменные вводятся и BЫBO дятся С использованием ключевых слов true и false. Поскольку часто приходится ссылаться на поля oct, dec и Ьех, для их совокупности введено обозначение basefield. Аналоrично поля left, right и intemal обозначаются adjustfield. Наконец, к полям scientific и fixed можно обращаться с помощью ключевоrо слова t1oatfield. у стаНО8ка и сброс флаrО8 формата для установки флara используется функция setf( ), являющаяся чле ном класса ios. Ее прототип выrлядит так: fmtflags setf(fmtflags флаzu); Эта функция возвращает предыдущее значение флаrов формата и устанавливает флarи, указанные параметром флаzu. Например, чтобы установить флаr showbase, следует использовать такое предложение: поток.sеtf(iоs::shоwЬаsе); Здесь поток  это тот поток, на который вы хотите воздействовать. Обратите внимание на то, как ios:: квалифицирует showbase. Посколь ку showbase представляет собой константу перечислимоrо типа, опре деленноro в классе ios, ее необходимо квалифицировать, т. е. указы вать принадлежность к классу ios при любом ее использовании. Этот принцип приложим ко всем флаrам формата. Следующая проrpамма использует setf( ) для установки флarов showpos и scientific: // Использование setf(). #include <iostream> using nmespace std; int main () { // Установка флаrов sh owpos и scientific. cout. setf (ios: : showpos) ; I Установка флarов формата cout. setf.( ios: : scient ific) ; . nocpelP8OM нtf() cout « 123 « " " « 123.23 « " "; return о; } 
Форматирование с помощью функцийчленов 10S 471 Проrpамма выведет следующее: +123 +1.232300е+ОО2 с помощью операции ИЛИ можно объединять в одном вызове setf( ) любое число флarов формата. Например, объединив флаrи scientit1c и showpos, как это показано ниже, вы сможете оrpаничиться одним вызо вом функции setf( ): cout.setf(ios::scientific I ios::showpoS)i для сброса флаrов используется функция unsetf( ) со следующим прототипом: void unsetf(fmtflags флаzu); Флаrи, указанные параметром флаzu, сбрасываются. (Все осталь ные флаrи остаются без изменения.) Иноrда оказывается полезным узнать текущее состояние флаrов формата. Вы можете определить текущие значения флarов с помощью функции Оар( ) со следующим прототипом: fmtflags flags( ); Эта функция возвращает текущее значение флаrов в вызвавшем ее потоке. Дрyrая форма той же функции устанавливает флar, заданный пара метром флаzu, и возвращает предыдущее значение этоrо флarа: fmtflags flags(fmtflags флаzu); Приведенная ниже проrpамма демонстрирует использование t1ags( ) и unset( ): #include <iostream> using namespace stdi int main () { ios::fmtflags f; f = cout.flags();  Получим фпаrи формата if(f & ios::showpos) cout « "showpos установлен для cout. \n 1. ; else cout « "showpos сброшен для cout. \n" i 
472 Модуль 11. С++ и система BBoдaBЫBoдa cout « .. \nУстановим showpos для cout. \n" ; cout. setf (ios: : showpos);  Установим флаr showpos f = cout.flags(); if(f & ios::showpos) cout « "showpos установлен для cout. \n '! ; else cout « "showpos сброшен для cout.\n"; cout « "\nСбросим showpos для cout.\n"; cout.unsetf(ios::showpos);  Сбросим флаr showpos. f = cout.flags(); if(f & ios::showpos) cout « II showpos установлен для cout. \n" ; else cout « "showpos сброшен для cout. \n"; return о; } Проrpамма ВЫВОДИТ следующее: showpos сброшен для cout. Установим showpos для cout. showpos установлен для cout. Сбросим showpos для cout. showpos сброшен для cout. Заметьте, что в проrpамме при объявлении переменной f тип fmtOags предварен указанием на класс ios::. Это необходимо делать, поскольку тип fmtRags определен в классе ios. Вообще, коrда вы используете имя типа или перечислимой константы, которое определено в классе, вы должны квалифицировать ero именем класа. Установка ширины поля, точности и символа..зanолнителя Помимо флаrов формата, имеются еще три функциичлена, оп ределенные в ios, которые устанавливают следующие дополнитель ные характеристики формата: ширину поля, точность и символза полнитель. Эти характеристики устанавливаются функциями width( ), precision( ) и fill( ) соответственно. Рассмотрим эти функции по оче реди. 
Форматирование с помоЩЬЮ функций..членов 10S 473 Коrда осуществляется вывод значения, то по умолчанию оно за нимает столько места, сколько требуется для отображения coдep жащеrо в нем числа символов. Однако вы может указать мини мальную ширину поля с помощью функции width( ). Ее прототип выrлядит так: streamsize width(streamsize w); Здесь w становится шириной поля, а возвращаемое значение представляет собой предыдущее значение ширины. В некоторых реализациях ширину поля необходимо устанавливать перед каж дым выводом, иначе будет использоваться ширина по умолчанию. Тип streamsize определяется компилятором как некоторая форма целоrо. После тoro, как вы установили минимальную ширину поля, проис ходит следующее. Если значение использует меньше символов, чем установленная ширина, то оставшаяся часть поля вывода будет запол нена текущим символомзаполнителем (по умолчанию пробелом). Если размер значения превышает минимальную ширину поля, поле будет расширено. Никакие значения не усекаются. При выводе значений с плавающей точкой в экспоненциаль ной форме вы можете задать число цифр, выводимых после деся ТИЧНОЙ точки, с помощью функции precision( ). Ее прототип BЫ rлядит так: streamsize precision(streamsize р); Здесь число цифр (точность) устанавливается равнымр, и функция возвращает старое значение. Число цифр по умолчанию равно 6. В He которых реализациях точность необходимо устанавливать перед каж дbIM выводом значения с плавающей точкой. Если точность не ycтa новлена, используется значение по умолчанию. По умолчанию, если поле нуждается в заполнении, оно заполняет ся пробелами. С помощью функции till( ) вы можете задать символза полнитель. Прототип этой функции: char fill( char ch); После вызова ffll() сЬ становится новым символомзаполнителем, а старое значение возвращается функцией. Ниже пр и веде на проrpамма, демонстрирующая использование этих трех функций: // Демонстрация функций width(), precision() и fill(). #include <iostream> 
474 Модуль 11 С++ и система BBoдaBЫBoдa using namespace std; int main ( ) { cout.setf(ios::showpos); Установим точность cout.setf(ios::scientific); cout « 123 « · · « 123.23 « .'n.; ! cout.precision(2); // две цифры после десятичной точки cout.width(10); // в поле шириной 10 символов  cout « 123 « " "; I cout. width (10) ; / / установим ширину 10 Установим ширину поля cout « 123.23 « "\n"; cout. f i 11 ( 1#' ); / / заполнитель #  Установим символ-заполнитель cout.width(10); // в поле шириной 10 символов cout « 123 « " "; cout.width(10); // установим ширину 10 cout « 123.23; return о; } Проrpамма выводит на экран следующее: +123 +1.232300е+ОО2 +123 +1.23е+002 ######+123 +1.23е+002 в некоторых реализациях ширину поля необходимо устанавливать перед каждым выводом значения с плавающей точкой. Именно по этому в приведенной проrpамме функция width( ) вызывается MHoro кратно. Функции width( ), precision( ) и fill( ) имеют переrpуженные формы, которые позволяют получать, но не изменять текущие установки. Эти формы приведены ниже: char fill( ); streamsize width( ); streamsize precision( ); Минутная тренировка 1. Для чеrо предназначен флаr boolalpha? 2. Для чеro предназначена функция setf( )? З. Какая функция устанавливает символзаполнитель? 
Использование манипуляторов BBoдaBЫBoдa 475 Если флаr boolalpha установлен, булевы значения вводятся и выводятся с использовани.. ем слов tпIе и false 2 set(f) устанавливает один или более флаrов формата 1 Символ ..заполнитель устанавливается функцией till( ) Цель _ Использование манипуляторов BBoдa вывода Система BBoдaBЫBoдa С++ предлаrает и друrой способ измене ния параметров формата потока. В этом способе используются спе циальные функции, называемые манипуляторами, которые можно включать в выражения BBoдaBЫBoдa. Стандартные манипуляторы перечислены в табл. 11  1. Для использования манипуляторов, при ни мающих арrументы, вы должны включить в проrpамму заrоловок < iomanip>. Таблица 11-1. Манипуляторы BBoдaBЫBoдa С++ Манипулятор НазнаЧ8НИ8 Ввод или вывод boolalpha Устанавливает флаr boolalpha ВвоД/вывод dec Устанавливает флаr d8C ВвоД/вывод endl Выводит символ новой строки и очищает поток Вывод ends Выводит null Вывод f.xed Устанавливает флаr flx8d Вывод flush Очищает поток Вывод hex Устанавливает флаr h8X Ввод/вывод Internal Устанавливает флаr int8mal Вывод left Устанавливает флаr 18ft Вывод noboolalpha Сбрасывает флаr boolalpha ВвоД/вывод noshowbase Сбрасывает флаr showbas8 Вывод noshowpo.nt Сбрасывает флаr showpoint Вывод noshowpos Сбрасывает флаr showpos Вывод nosk.pws Сбрасывает флаr skipws Ввод noun.tbuf Сбрасывает флаr unitbuf Вывод nouppercase Сбрасывает флаr upp8rcas8 Вывод oct Устанавливает флаr ос! Ввод/вывод resetloss (fmts f) Сбрасывает флаrи I заданные в f ВвоД/вывод rlght Устанавливает флаr right Вывод 
476 Модуль 11 С++ и система BBoдaBЫBoдa Окончание табл. 11  1 Манипулятор Назначение Ввод или вывод sClentlf.c Устанавливает флаr scientific ВЫВОД setbase( Int base) Устанавливает систему счисления base ВвоД/Вывод setf.II(.nt ch) Устанавливает символ..заполнитель ch Вывод setloss(fmts f) Устанавливает флаrи, заданные в f ВвоД/вывод setpreclslon (Int р) Устанавливает число цифр точности Вывод setw( Int w) Устанавливает ширину поля w Вывод showbase Устанавливает флаr showbas8 Вывод showpolnt Устанавливает флаr showpoint Вывод showpos Устанавливает флаr showpos Вывод sklpws Устанавливает флаr skipws Ввод unltbuf Устанавливает флаr unitbuf Вывод uppercase Устанавливает флаr upp8rcas8 Вывод ws Пропускает лидирующие пробельные СИМВОЛЫ Ввод Манипулятор используется как элемент большеrо выражения BBO дaBЫBoдa. Вот простая проrpамма, использующая манипуляторы для управления форматом CBoero вывода: // Демонстрация манипуляторов BBoдaBЫBoдa. #include <iostream> #include <iomanip> using namespace std; int main () { cout « setprecision(2) « 1000.243 < 1 < endl; I cout « setw(20) « "Hello there."; return о; } Использование манипуляторов. Вывод этой проrpаммы: lе+003 Hello there. Обратите внимание, как манипуляторы включаются в цепочку опе раций BBoдaBЫBoдa. Также заметьте, что коrда манипулятор не при.. нимает apryмeHToB (например, endl в проrpамме), за ним не указыва ются крyrлые скобки. Проrpамма, приведенная ниже, использует функцию setiost1ags( ) для установки флаrов scientific и showpos: 
Создание собственных манипуляторных функций 477 // Использование setiosflags(). #include <iostream> #include <iomanip> using namespace std; int main ( ) { cout « setiosflags (ios: : showpos) «  setiosflags(ios::scientific) « 123 « " " « 123.23; Использование 88tioaflags( ) return о; } Следующая проrpамма показывает использование ws для удаления лидирующих пробельных символов при вводе строки в s: // Удаление лидирующих пробельных символов. #include <iostream> using namespace std; int rnain ( ) { char s [ 8 о] ; cin » ws » s; Использование.. cout « s; return о; } Цель _ Создание собственных манипуляторных фун кций Вы можете создать свои собственные манипуляторные функции. эти функции имеют две разновидности: с параметрами и без них. Соз дание параметризованных манипуляторов требует знакомства с вопро сами, которые выходят за рамки этой книrи. С друrой стороны, созда ние манипуляторов без параметров осуществляется весьма просто и мы ero здесь опишем. 
478 Модуль 11. С++ и система BBoдaBЫBoдa Все манипуляторные функции вывода без параметров имеют такой шаблон: ostream &manipname(ostream &stream) { // ваш код размещается здесь return stream; } Здесь manip....name ..... это имя манипулятора. Важно понимать, что хотя манипулятор имеет в качестве единственноrо параметра указатель на поток, которым он манипулирует, при использовании манипулятора в выражении вывода никакие арrументы не указыва ются. В приведенной ниже проrpамме создается манипулятор с именем setup( ), который устанавливает выравнивание влево, ширину поля в 10 символов и знак доллара в качестве символазаполнителя: // Создание манипулятора вывода #include <iostream> #include <iomanip> using nаmеэрасе std; ostream &setup(ostream &stream)  { stream.setf(ios::left); stream« setw(10) « setfill(I$'); return stream; Собственный манипулятор вывода } int main () { cout « 10 « " " « setup « 10; return о; } Собственные манипуляторы полезны по двум причинам. Вопер вых, вам может понадобиться выполнить операцию BBoдaBЫBoдa на устройстве, с которым не работает ни один из предопределенных Ma нипуляторов..... например, плоттере. В этом случае создание собствен ных манипуляторов облеrчит вывод на это устройство. BoBTopых, бы вает, что вы повторяете MHoro раз одну и ту же последовательность операций. Вы можете собрать эти операции в один манипулятор, как это иллюстрирует следующая проrpамма. Все манипуляторы ввода без параметров имеют такой шаблон: 
Создание собственных манипуляторных функций 479 istream &manipname(istream &stream) { // ваш код размещается здесь return stream; } в качестве примера приведенная ниже проrpамма создает манипу лятор prompt( ). Он выводит сообщениезапрос и затем конфиrypирует ввод так, чтобы принимались шестнадцатеричные цифры: // Создание манипулятора ввода. #include <iostream> #include <iomanip> using namespace std; istream &prompt(istream &stream)  { Прикладной манипулятор ввода. cin » hex; cout « "Введите число в шестнадцатеричном формате: "; return stream; } int main () { int i; cin » prompt » ii coиt « i; return о; } Не забывайте, что существенным условием для манипуляторов яв ляется возврат локальной переменной, названной в приведенном при мере streaт. Если этоrо не сделать, созданный вами манипулятор нель зя будет использовать в цепочечных операциях ввода или вывода. Минутная тренировка 1. Для чеrо служит endl? 2. Для чеrо служит ws? 3. Используется ли манипулятор BBoдaBЫBoдa как часть больших Bыpa жений BBoдaBЫBoдa? 
480 Модуль 11. С++ и система BBoдaBЫBoдa 1 endl выводит символ новой строки 2 WS при водит К пропуску при вводе лидирующих пробельных символов. 3 манипулятор ввода.. вывода используется как часть больших выражений ввода.. вывода Файловый ВВОД-ВЫВОД Вы можете использовать систему BBoдaBывoдa с++ для выполне ния операций BBoдaBЫBoдa над файлами. Для этоro в проrpамму следу ет включить зaroловок <fstream>. В нем определяется несколько важ ных классов и значений. Цель .. Открытие и закрытие файла в с++ файл открывается путем связывания ero с потоком. Как вы уже знаете, существуют три типа потоков: ввода, вывода и BBoдa вывода. Для Toro, чтобы открыть поток ввода, вы должны объявить поток класса ifstream. Для Toro, чтобы открыть поток вывода, вы должны объявить поток класса ofstream. Поток, который будет BЫ полнять обе операции, и ввода, и вывода, должен быть объявлен как объект класса fstream. Например, этот фраrмент создает один поток ввода, один поток вывода и один поток, способный делать и то, и друrое: ifstream in; // ввод ofstream out; // вывод fstream both; // ввод и вывод Создав поток, вы можете связать ero с файлом с помощью функции ореп( ). Эта функция входит как член в каждый из трех потоковых классов. Прототипы трех вариантов этой функции выrлядят так: void ifstream: :open(const char *fileпame, ios::openmode mode == ios::in); void ofstream::open(const char *fileпaтe, ios: :openmode тode == ios: :out I ios: :trunc); void fstream:' ореп( const char *fi le пате, ios::openmode mode == ios::in I ios::out); Здесь fileпame обозначает имя файла; в Hero может входить описа ние пути. Значение тode задает режим работы открываемоrо файла. Этот apryмeHT должен иметь значение, определенное типом openmode, который представляет собой перечислимый тип, определенный Bios 
Открытие и закрытие файла 481 (посредством ero базовоrо класса iosbase). Возможные значения тode приведены ниже: ios: :арр ios: :ate ios:: binary ios::in ios: :out ios: :trunc Вы можете комбинировать два или несколько значений с помощью побитовоrо ИЛИ ( I ). Указание ios::app приводит к тому, что весь вывод записывается в конец файла. Это значение можно использовать только с файлами, дo пускающими запись в них. Если в объявлении присутствует ios::ate, то при открытии файла осуществляется поиск ero конца. Хотя наличие ios::ate активизирует начальный поиск конца файла, тем не менее опе рации BBoдaBЫBoдa Moryт осуществляться, начиная с любоrо места файла. Значение ios::in указывает, что файл открывается для ввода. Значе ние ios::out указывает, что файл открывается для вывода. Значение ios::binary открывает файл в двоичном режиме. По умол чанию все файлы открываются в текстовом режиме. В этом режиме MOryт иметь место различные преобразования символов, например, последовательности возврат каретки ..... перевод строки преобразовы ваться в символы новых строк. Однако коrда файл открывается в ДBO ичном режиме, никаких преобразований не производится. Следует понимать, что любой файл, содержит ли он форматированный текст или необработанные данные, может быть открыт и в двоичном, и в текстовом режиме. Единственным различием будет наличие или OT сутствие преобразования символов. Значение ios::trunc при водит к уничтожению исходноrо содержи Moro существующеrо файла с указанным именем, и к усечению файла до нулевой длины. Такая операция усечения автоматически выполня ется над любым существующим файлом с указанным именем при OT крытии потока вывода класса ofstream. Следующий фраrмент открывает текстовый файл для вывода: ofstream mystreami mystream. ореn ("test.') ; Поскольку параметр тode функции ореп( ) по умолчанию при ни мает значение, соответствующее типу открываемоrо потока, часто нет необходимости задавать ero явным образом, как это и сделано в последнем примере. (Некоторые компиляторы не зада ют для fstream::open( ) по умолчанию значение параметра тode in 
482 Модуль 11 С++ и система BBoдaBЫBoдa I out; в этом случае вам придется задать этот параметр в явной форме.) Если функция ореп( ) завершилась неуспешно, поток, если ero ис пользовать в булевом выражении, примет значение false. Вы можете использовать это обстоятельство для подтверждения успешноrо OT крытия файла с помощью предложения тaKoro рода: i f ( ! ту s t r еат) { cout « "Не Mory открыть файл. \n" ; // обработка ошибки } Как правило, перед тем, как пытаться обратиться к файлу, вы должны проверить результат выполнения функции ореп( ). Вы можете также проверить, открылся ли файл, с помощью Функ ции isopen( ), которая является членом классов fstream, ifstream и ofstream. Вот ее прототип: Ьооl isopen( ), Функция возвращает true, если поток связан с открытым файлом, и false в обратном случае. Следующий фраrмент проверяет, открыт ли в настоящий момент mystream: if(!mystream.isopen()) { cout « "Файл не открыт.\n"; // Хотя использование функции ореп( ) для открытия файла  вполне допустимая операция, вы, скорее Bcero, не будете прибеrать к ней, по скольку в классах ifstream, ofstream и fstream имеются конструкторы, которые автоматически открывают файл. Конструкторы имеют те же самые параметры и apryмeHTЫ по умолчанию, что и функция ореп( ). Поэтому самый распространенный способ открытия файла выrлядит так: ifstream mystream ("myfile"); / / открыть файл для ввода Если по какойто причине файл не открылся, значение связанной с файлом переменной потока будет иметь значение false. Для закрытия файла используйте функциючлен close( ). Например, чтобы закрыть файл, связанный с потоком, который назван mystream, вы используете такое предложение: mystream.close(); Функция close( ) не принимает параметров и ничеrо не возвращает. 
, t НJние и запись текстовых файлов 483 Цель _ Чтение и запись текстовых файлов Простейшим способом чтения из TeKcToBoro файла или записи в Hero является использование операторов « и ». Например, приведенная ниже проrрамма записывает целочисленную пере менную, число с плавающей точкой и строку в файл с именем test: // Запись в файл. #include <iostream> #include <fstream> using namespace std; int main () { ofstream out{"test");  if(!out) { cout « "Не MOry открыть файл. \n"; return 1; Создаем и открываем файл с име- нем "test" для Teкcтoвoro вывода } out« 10«""« 123.23« "\п"; . out « "Это короткий текстовый файл."; Вывод в файл out.close{);  Закрытие файла return о; } Приведенная ниже проrрамма читает целое число, число типа float, символ и строку из файла, созданноrо в предыдущей про rpaMMe: / / Прочитаем из файла. #include <iostream> #include <fstream> using namespace std; int main () { char ch; int i; 
484 Модуль 11 С++ и система ввода"вывода float f; char str[80]; i f s t r еат in ( .. t е s t n );  Открываем файл для TeкCToвoro ввода. if(lin) { cout « "Не Mory открыть файл. \n" ; return 1; } in » i; in » f; Читаем из файла in » сЬ; in » str; cout « i « .. .. « f « .. n « сЬ « 11 \n" ; cout « str; in.close():  Закрываем файл return О; } Не забывайте, что при использовании оператора> > для чтения TeK стовых файлов осуществляется определенное преобразование симво лов. Например, убираются пробельные символы. Если вы хотите пре дотвратить какое бы то ни было преобразование символов, вы должны открывать файл в режиме двоичноrо доступа. Не забывайте также, что при чтении строки с помощью оператора> > ввод останавливается, KO rда встречается первый пробельный символ. Минутная тренировка 1. Какой класс создает файл для ввода? 2. Какая функция открывает файл? 3. Можете ли вы читать и записывать файл с помощью « и»? 1 Чтобы ОТКРЫ1Ь файл для ввода, следует использовать ifstream 2 Чтобы открыть файл, используйте конструктор класса ми функцию ореп() 3 Да, вы можете использовать < < и > > Ш1Я ч rения и записи файла (просим у эксперта Вопрос: Как вы уже объясняли в Модуле 1, с++ является надмножеством с. Я знаю, что С определяет собственную систему BBoдaBЫBoдa. Доступна ли для проrpаммистов на С++ система BBoдaBЫBoдa с? Если да, то следует ли использовать ее в С++проrpаммах? 
Неформатированный и двоичный BBOДBЫBOД 485 Orвeт: Ответ на первый вопрос: да. Система ввода"вывода языка С дос.. тупна проrpаммистам на С++. Ответ на второй вопрос: определенно нет. Система ввода"вывода С не является 06ъектно..ориентированной. По этой причине почти всеrда оказывается, что система ввода..вывода С++ более совместима с С++..проrраммами. Однако, система ввода.. вы вода С все еще широко используется; она весьма проста и при водит К незначительным из.. держкам в проrpаммах. Поэтому для узко специализированных проrpамм система ввода"вывода С может оказаться полезной. Сведения о системе ввода"вывода С можно найти в моей книrе: С++: Тhe Coтp/ete Refereпce (Osborne/McGraw.. Hill). Цель _ Неформатироеанный ВЫВОД '" и ДВОИЧНЫИ ВВОД" Хотя читать и записывать форматированные текстовые фалы весьма просто, такой способ не всеrда оказывается самым эффек" тивным. Кроме Toro, вам может понадобиться записывать в файл не текст, анеформатированные (необработанные) данные. Здесь опи.. сываются функции, позволяющие вам выполнять TaKoro рода опе.. рации. Если вы собираетесь выполнять над файлом двоичные операции, удостоверьтесь, что вы открьти ero с помощью описателя режима ios::binary. Хотя функции обслуживания неформатированноrо файла будут работать с файлом, открытым в текстовом режиме, однако при этом будут происходить преобразования некоторых символов. Преоб.. разование символов противоречит самой идее двоичных файловых операций. В принципе имеются два способа записывать и читать неформати" рованные двоичные данные в файл или из файла. Во..первых, вы мо" жете записать байт с помощью Функции..члена put () и прочитать байт с помощью функции..члена get( ). Во"вторых, вы можете использовать блочные функции ввода"вывода: read() и write(). Ниже будут рассмот" рены оба способа. Использование get( ) и put( ) Функция get( ) имеет MHoro форм, но чаще Bcero используется ва.. риант, приведенный (вместе с аналоrичным вариантом put( » ниже: istream &get( char &ch); ostream &put(char ch); 
486 Модуль 11. С++ и система BBoдaBЫBoдa Функция get( ) читает один символ из связанноrо с файлом потока и помещает ero значение в ch. Функция возвращает ссьтку на поток. Эrо значение может быть нулем, если достиrнут конец файла. Функ ция put( ) записывает ch в поток и возвращает ссылку на поток. Приведенная ниже проrpамма (предполаrается, что вы дали ей имя PR) выведет на экран содержимое любоrо файла. Она исполь зует функцию get( ): / / Чтение из файла с помощью get () и отображение на экране. #include <iostream> #include <fstream> using namespace std; int main(int argc, char *argv[]) { char ch; i f (argc! =2) { cout « "Использование: PR <имяфайла>\nП; return 1; } ifstream in(argv[l], ios: :in I ios: :binary); if(!in) { cout « .. Не Mory открыть файл. \n n ; return 1; Читаем данные из файла, пока } не будет достиrнyr конец файла while(in) { // in будеТ false. коrда достиrнут eOf in. get ( ch) ; if(in) cout « ch; Открываем файл для двоичных операций  } in.close(); return о; } Коrда поток in достиrнет конца файла, ero значение станет false, и цикл while завершится. В действительности имеется более компактный способ описать цикл чтения и отображения файла: while(in.get(ch)) cout « ch; 
Неформатированный и двоичный BBOДBЫBOД 487 это вполне допустимая форма, так как get( ) возвращает поток in, а in станет равным false, коrда будет достиrнyr конец файла. Приведенная ниже проrpамма использует put( ) для записи строки в файл: / / Использование put () для записи в файл. #include <iostream> #include <fstream> using паmеэрасе std; int main () { char *р = "Привет всем!\п"; ofstream out (" test", ios:: out I ios:: binary) ; if ( ! out) { cout « "Не MOry открыть файл. \п"; return 1; } // Запись символов, пока не будет достиrнут завершающий ноль. while (*р) out. put (*р++);  I ЗапИСЬ строки в файn с помощью рШ() Не вы- полняются никакие преобразования символов out.close(); return о; } После выполнения этой проrpаммы файл test будет содержать cтpo ку "Привет всем!", за которой будет записан символ новой строки. Никакие преобразования символов не осуществляются. Чтение и запись блоков данных Для чтения и записи блоков двоичных данных используйте функ циичлены read( ) и write( ). Они имеют такие прототипы: istream &read(char *bиf, streamsize пит); ostream &write( const char * buf, streamsize пит); Функция read( ) читает пит байтов из потока, связанноrо с фай лом, и заносит их в буфер, на который указывает buf. Функция write( ) записывает пит байтов в поток из буфера, на который yкa зыаетT Ьи!. Как уже упоминалось, streamsize ..... это имя типа той или 
488 Модуль 11 С++ и система ввода"вывода иной формы целоrо числа, определенный в библиотеке С++. Пере менные этоrо типа MOryт содержать значение, соответствующее максимальному числу байтов, которое может быть передано в одной операции BBoдaBЫBoдa. Приведенная ниже проrpамма записывает, а затем читает массив целых чисел: / / Использование read () и write () . #include <iostream> #include <fstream> using namespace std; int main () { in t n [ 5] = {1, 2, З, 4, 5}; register int i; ofstream out("test", ios::out I ios::binary); if(!out) { сои t « .. Не Mory открыть файл. \n" ; return 1; } out. write ( (char *) &n, sizeof n);  Запись блока данных out.close(); for(i=O; i<5; i++) // очистим массив n[i] = о; ifstream in (" test", ios:: in I ios:: binary) ; if(!in) { cout « "Не Mory открыть файл. \n" ; return 1; } in. read ( (char *) &n, sizeof n);  Чтение блока данных for(i=O; i<5; i++) // показывает значения, про читанные из // файла cout « n[i] « .. "; in.close(); return о; } 
Больше о функциях BBoдaBЫBoдa 489 Заметьте, что приведение типа, выполняемое внyrpи вызовов функ ций read( ) и write( ), необходимо, если вы используете буфер, опреде ленный не как символьный массив. Если конец файла достиrается до тoro, как будет прочитано пит символов, функция read( ) попросту останавливается, а буфер будет содержать столько символов, сколько удалось прочитать. Узнать, сколько символов бьто прочитано, можно с помощью дрyrой Функ циичлена, gcount( ), имеющий такой прототип: streamsize gcount( ); Функция gcount( ) возврашает число символов, прочитанных в по следней операции ввода. Минутная тренировка 1. Какой описатель режима вы должны использовать, чтобы читать или записывать двоичные данные? 2. Для чеrо предназначена функция get( )? Для чеrо предназначена Функ ция put( )? 3. Какая функция читает блок данных? 1 Чтобы читать или записывать двоичные данные, вы должны использовать описатель ре.. жима ios::binary. 2. Функция get( ) читает символ Функция put( ) записывает символ. 3. Для чтения блока данных используйте read(). Цель _Больше о функциях ввода- BblBOAa Система BвoдaBЫBoдa С++ определяет и дрyrие относящиеся к BВO ДYBЫВOДY функuии, некоторые из которых MOryr вам приrодиться. Мы их здесь рассмотрим. Друrие варианты get( ) Функция get() имеет целый ряд переrpуженныx форм помимо той, которая бьта рассмотрена выше. Приведем прототипы трех наиболее употребительных вариантов: istream &get( char * buf, streamsize пит); istream &get(char *buf, streamsize пит, char deliт); int get( ); 
490 Модуль 11. С++ и система BBoдaBЫBoдa Первый вариант читает символы в массив, на который указывает Ьи!, до тех пор, пока не будет введено пит ..... 1 символов, или обнару жится символ новой строки, или будет достиrнут конец файла. Функ ция get( ) в таком варианте поместит в массив, на который указывает Ьи!, завершающий ноль. Если во входном потоке обнаруживается сим  вол новой строки, он не извлекается, а остается в потоке до следующей операции ввода. Второй вариант читает символы в массив, на который указыва ет Ьи!, до тех пор, пока не будет введено пит ..... 1 символов, или обнаружится разделительный символ, заданный параметром delim, или встретится конец файла. Функция get( ) в таком вари анте поместит в массив, на который указывает Ьи!, завершающий ноль. Если во входном потоке обнаруживается разделительный символ delim, он не извлекается, а остается в потоке до следую щей операции ввода. Третья переrpуженная форма get( ) возвращает из потока следую щий символ. Если достиrнут конец файла, функция возвращает EOF (символическая константа, обозначающее конец файла). Зна чение EOF определено в <iostream>. Одно из полезных применений функции get( ) ..... чтение строки, содержащей пробелы. Как вы знаете, если строка читается с помо щью », чтение прекращается на первом пробельном символе. В результате оператор > > оказывается бесполезным при чтении строк, содержащих пробелы. Однако вы можете решить эту задачу с помо щью варианта функции get(buj, пит), как это показано в следующей проrpамме: // Использование get() для чтения строки, содержащей пробелы. #include <iostream> #include <fstream> using namespace std; int main ( ) { char s tr [80] ; cout « "Введите свое имя: "; cin.get(str, 79);  Использование get( ) для чтения строки, содер- жащей пробельные символы. cout«str« '\n'; return о; Здесь используется разделительный символ по умолчанию, KOTO рый соответствует символу новой строки. В результате get( ) действует практически так же, как и стандартная функция gets( ). 
Больше о функциях ввода.. вы вода 491 getline( ) Есть еще одна функция, выполняющая ввод..... это getline( ). Она яв" ляется членом класса потока ввода; ее прототипы выrлядят так: istream &getline( char * buf, streamsize пит); istream &getline(char *buf, streamsize пит, char delim); Первый вариант читает символы в массив, на который указывает Ьи!, до тех пор, пока не будет введено пит ..... 1 символов, или обнару жен символ новой строки, или встретится конец файла. Функция getline( ) в таком варианте помещает в массив, на который указывает Ьи!, завершающий ноль. Если во входном потоке обнаруживается сим вол новой строки, он извлекается, но не помещается в буфер buf. Второй вариант читает символы в массив, на который указывает Ьи!, до тех пор, пока не будет введено пит ..... 1 символов, или обнару жится разделительный символ, заданный параметром deliт, или Bcтpe тится конец файла. Функция getline( ) в таком варианте помешает в массив, на который указывает Ьи!, завершающий ноль. Если во BXOД ном потоке обнаруживается разделительный символ, он извлекается, но не помещается в буфер buf. Леrко видеть, что два варианта getline( ) практически идентичны Ba риантам get(buf, пит) и get(bu/, пит, deliт) функции get( ). Обе Функ ции читают символы из потока ввода и помещают их в массив, на ко.. торый указывает Ьи!, до тех пор, пока не будет введено пит..... 1 симво лов или обнаружится разделительный символ. Разница между get( ) и getline( ) заключается в том, что getUne( ) читает и извлекает из потока ввода разделительный символ; get( ) не делает этоrо. Обнаружение символа EOF Для Toro, чтобы узнать, не достиrнyт ли конец файла, можно ис пользовать функцию..член eof( ) со следующим прототипом: bool eof( ); Функция возвращает true, если был достиrнyr конец файла, и false в противном случае. peek( ) и putback( ) Имеется возможность получить следующий символ в потоке BBO да без извлечения ero из потока. Для этоrо предусмотрена функция peek( ). Она имеет следующий прототип: 
492 Модуль 11. С++ и система ввода"вывода int peek( ); peek() возвращает следующий символ в потоке или EOI\ если достиr пут конец файла. Символ содержится в младшем байте возвращаемоrо значения. Можно, наоборот, вернуть в поток последний прочитанный из Hero символ. Для этоrо используется функция putback( ). Ее прототип BЫ rлядит так: istream &putback( char с); Здесь с ..... последний прочитанный символ. flush( ) Коrда выполняется вывод, данные не записываются немедленно на физическое устройство, связанное с потоком. Вместо этоrо дaH ные запоминаются во внутреннем буфере до ero заполнения. Толь ко тоrда содержимое буфера переписывается на диск. Однако вы можете принудительно сбросить содержимое буфера на диск, даже если он еще не заполнен, вызвав функцию flush( ). Ее прототип выrлядит так: ostream &flush( ); Вызов t1ush( ) может быть оправдан, если проrpамма будет эксплуа тироваться в неблаrоприятныx условиях (например, при частых Hapy шениях питания).  Замечание Закрытие файла или завершение nporpaMMbI также приводит к сбросу всех буферов на диск. Проект 11..1 Утилита сравнения файлов В этом проекте разрабатывается простая, но весьма полезная утилита сравнения. Она открывает оба сравниваемых файла, а за тем читает и сравнивает соответствующие наборы байтов. Если Ha ХОДИТСЯ расхождение, значит, файлы различаются. Если достиrнут 
Больше о ФУНКЦИЯХ BBoдaBЫBoдa 493 конец каждоrо файла, а расхождений не было обнаружено, значит, файлы совпадают. Шаr за шаrом 1. Создайте файл с именем CompFiles.cpp. 2. Начните с добавления в CompFiles.cpp этих строк: /* Проект 111 Разработка утилиты сравнения файлов. */ #include <iostream> #include <fstream> using namespace std; int main(int argc, char *argv[]) { register int i; int numread; unsigned char buf1[1024], buf2[1024]; i f (argc! =3) { cout « "Использование: compfiles <файл1> <файл2>\n"; return 1; } Обратите внимание на то, что имена сравниваемых файлов вводят ся в качестве параметров командной строки. 3. Добавьте код, который открывает файлы для двоичных операций ввода, как это показано ниже: ifstream fl(argv[l], ios::in I ios::blnary); if(!f1) { cout « "Не MOry открыть первый файл. \n" ; return 1; } ifstream f2(argv[2], ios::in I ios::binary); if(!f2) { cout « "Не MOry открыть второй файл. \п" ; return 1; } 
494 Модуль 11. С++ и система BBoдaBЫBoдa Файлы открываются для ДВОИЧНЫХ операций, чтобы предотвратить возможные преобразования символов. 4. Добавьте код, который фактически сравнивает файлы: cout« "Сравнение файлов...\п"; do { fl.read«char *) bufl, sizeof bufl); f2.read«char *) buf2, sizeof buf2); if(fl.gcount() 1= f2.gcount(» { cout « "Файлы имеют разную длину. \n ,. ; fl.close(); f2.close(); return о; } // сравнение содержимоrо буферов for(i=O; i<fl.gcount(); i++) i f (Ьи f 1 [ i ] ! = Ьи f 2 [i]) { cout « "Файлы различаются.\п"; fl.close(); f2.close(); return о; } } while(!fl.eof() && !f2.eof(»; cout « "Файлы совпадают.\п"; в этом фраrменте с помощью функции read( ) из каждоrо фай ла последовательно читаются порции данных размером в Bыдe ленные для них буферы. Затем выполняется сравнение содержи Moro буферов. Если содержимое буферов различается, файлы за крываются, на экран выводится сообщение "Файлы различаются." и проrрамма завершается. В противном случае операции чтения в буферы и сравнения их содержимоrо повторяются, пока не будет достиrнут конец одноrо (или обоих) файлов. Поскольку в конце файлов последние порции читаемых данных Moryт не заполнить буферы целиком, проrрамма использует функцию gcount( ) для определения точноrо числа символов, прочитанных в буферы. Если один из файлов короче друrоrо, то при достижении конца более KopoTKoro файла значения, возвращаемые gcount( ), будут различаться. В этом случае выводится сообщение "Файлы имеют разную длину.". Наконец, если файлы совпадают, то при дости жении конца одноrо файла будет достиrнут конец и друrоrо, что 
Больше о ФУНКЦИЯХ ввода-вывода 495 подтверждается вызовом функции eof( ) для каждоrо потока. Если файлы оказываются полностью совпадающими, выводится COOT ветствующее сообщение. 5. Завершите проrpамму закрытием обоих файлов: fl.close(); f2.close(); return о; } 6. Ниже приведен полный текст проrpаммы CompFiles.cpp: /* Проект 111 Разработка утилиты сравнения файлов. */ #include <iostream> #include <fstream> using namespace std; int main{int argc, char *argv[]) { register int i; int numread; unsigned char buf1[1024], buf2[1024]; if(argc!=3) { cout « "Использование: compfiles <файл1> <файл2>\п"; return 1; } ifstream fl(argv[l], ios::in I ios::binary); if(!f1) { cout « "Не Mory открыть первый файл. \n"; return 1; } ifstream f2(argv[2], ios::in I ios::binary); if(!f2) { cout « "Не MOry открыть второй файл. \n"; return 1; } 
496 Модуль 11. С++ и система ввода.. вы вода cout« "Сравнение файлов...\п"; do { fl.read«char *) bufl, sizeof bufl); f2.read«char *) buf2, sizeof buf2); if(fl.gcount() 1= f2.gcount(» { cout « "Файлы имеют разную длину. \п'. ; fl.close(); f2.close(); return о; } // сравнение содержимоrо буферов for(i=O; i<fl.gcount(); i++) i f (Ьи f 1 [ i ] ! = Ьи f 2 [ i]) { cout « "Файлы различаются.\п"; fl.close(); f2.close(); return о; } } while(1fl.eof() && !f2.eof(»; cout « "Файлы совпадают.\п"; fl.close(); f2.close(); return о; } 7. Чтобы испытать проrpамму, сначала скопируйте файл CompFiles.cpp в файл под именем temp.txt. Затем введите такую KO мандную строку: CompFiles CompFiles.cpp temp.txt Проrрамма должна сообщить вам, что файлы совпадают. Далее, сравните CompFiles.cpp с какимнибудь дрyrим файлом, например, с дрyrой проrpаммой из этоrо модуля. Вы увидите, что CompFiles.cpp co общает о несовпадении файлов. 8. Попробуйте самостоятельно усложнить CompFiles.cpp. Например, добавьте режим, в котором проrpамма перестанет различать строчные и прописные буквы. Друrое усовершенствование может заключаться в том, чтобы CompFiles.cpp выводила на экран позицию внутри файла, rде найдено расхождение. 
Произвольный доступ 497 Цель _Про ИЗ ВОЛЬНЫЙ ДОСТУП ДО СИХ пор чтение и запись файлов осуществлялись в последова тельном режиме. Однако вы можете обращаться к произвольным местам файла в случайном порядке. В системе BBoдaBЫBoдa С++ произвольный доступ к файлу осуществляется с помощью функций seekg( ) и seekp( ). ИХ наиболее употребительные формы выrлядят таким образом: istream &seekg( offtype offset, seekdir origiп); ostream &seekp(offtype offset, seekdir origiп); Здесь otf......type  это целочисленный тип, определенный в ios. Пере менные этоrо типа имеют длину, достаточную для размещения макси мальноro значения, которое может иметь ojfset. seekdir является пере числимым типом, который может принимать следующие значения: Значение los..beg 105 .cur 105' end Описание Начало файла Текущая позиция Конец файла Система BBoдaBЫBoдa С++ поддерживает два указателя, свя занных с файлом. Один является указателем ввода; он указывает на текущую позицию ввода, т. е. в каком месте файла будет BЫ полняться очередная операция ввода. Друrой является указателем вывода; он указывает на текущую позицию вывода, т. е. в каком месте файла будет выполняться очередная операция вывода. Каж дый раз, коrда выполняется операция ввода или вывода, COOTBeT ствующий операции указатель автоматически перемещается. Ис пользуя функции seekg( ) и seekp( ), мы можем перемещать эти указатели и тем самым обеспечивать обращение к произвольным местам файла. Функция seekg( ) перемещает указатель ввода связанноrо с потоком файла на ojfset байтов от места, указанноrо параметром origiп. Функция seekp( ) перемешает указатель вывода связанноrо с потоком файла на offset байтов от места, указанноrо параметром origiп. В принципе операции BBoдaBЫBoдa с произвольным доступом должны выполняться только над файлами, отIфытыIии для ДВОИЧНЫХ операций. Символьные преобразования, которые MOryт происходить при работе с текстовыми файлами, MOryт привести к тому, что запрос на установку определенной позиции в файле не будет соответствовать фактическому содержимому файла. 
498 Модуль 11. С++ и система BBoдaBЫBoдa Приведенная ниже проrpамма демонстрирует использование функ  ции seekp(). Проrpамма (предполaraется, что вы дали ей имя CНANGE) позволяет задать на командной строке имя файла, вслед за которым указывается номер KOHкpeTHoro байта файла, которой мы хотим изме нить. Проrpамма записывает символ Х в указанный байт файла. Заметь те, что файл должен быть открыт для операций чтения/записи. / / Демонстрация произвольноrо доступа. #include <iostream> #include <fstream> #include <cstdlib> using namespace std; int main (int argc, char * argv [ ] ) { i f (argc ! =3) { cout « "Использование: CНANGE <имя...файла> <номербайта>\nll; return 1; } f strearn out (argv [1], ios:: in I ios:: out I ios:: binary) ; i f ( ! ои t ) { cout « "Не MOry открыть файл. \n"; return 1; } out.seekp(atoi(argv[2]), ios::beg);  out. put ( I Х' ) ; Поиск указанноrо байта в файле. Эта операция out . close ( ) ; смещает указатель вывода return о; } Следующая проrpамма использует функцию seekg( ). Она выводит на экран содержимое файла, начиная с тoro места, которое вы указали на командной строке. Слово NAМE в строке cout « "Использование: NAМE <ИМЯ файла> <начальная позиция>\n"; следует заменить именем файла с проrpаммой. // Вывод файла начиная с заданноrо места. #include <iostream> #include <fstream> 
Произвольный доступ 499 #include <cstdlib> using namespace std; int main(int argc, char *argvt) { char ch i i f (argc! =3) { cout « "Использование: NAME <ИМЯ файла> <начальная позиция> \n " ; return 1; } ifstream in(argv[l], ios::in I ios::binary); if(!in) { cout« "Не MOry открыть файл.\n"; return 1; } in.seekg(atoi{argv[2]), ios::beg);  Это предложение пере- мещает указатель ввода while(in.get(ch» cout « Chi return О i } Вы можете определить текущую позицию к3.ЖДоrо из указателей в файле с помощью таких функций: postype tellg( ); postype tellp( ); Здесь pos....type ..... это целочисленный тип, определенный в ios. Пере.. менные этоrо типа имеют достаточную длину для размещения макси.. мальноrо значения, которое MOryт возвращать эти функции. Имеются переrpуженные варианты seekg( ) и seekp( ), которые пере-- мещают указатели в файле в позиции, полученные с помощью предва.. рительноrо вызова функций tellg( ) и tellp( ). Их прототипы: istream &seekg(postype positioп); ostream &seekp(pos.....type positioп); Минутная тренировка 1. Какая функция позволяет определить достижение конца файла? 
500 Модуль 11 С++ и система ввода"вывода 2. Для чеrо предназначена функция getline( )? 3. Какие функции позволяют устанавливать позиции для произвольноrо доступа? 1 Функция еоС( ) позволяет определить ДОСl ижение конца файла 2 getline() читает строку текста 3 для установки позиции для произволъноrо ДОC1)'l1а используйте функции seekg( ) и seekp( ) Цель _Определение состояния BBoAa-выоАаa Система BBoдaBЫBoдa С++ ПОдrlерживает информацию о результа тах каждой операции BBoдaBЫ80дa. БитыI состояния потока B80дaBЫ вода описаны в объекте типа iostate, КОТОРЫЙ представляет собой пере числимый тип, определенный в ios и включающий следующие члены: Имя .05: goodb.t .05 eofb.t .05 fallblt Описание Не установлены никакие биты обшибок 1 если достиrнут конец файла, О в противном случае 1 если (возможно) возникла нефатальная ошибка ввода-выво" да; О в противном случае 1 если возникла фатальная ошибка ввода-вывода, О в против.. ном случае .05 badb.t Вы можете получить информацию о состоянии BBoдaBЫBoдa двумя способами. Первый способ ..... вызвать функцию rdstate( ). Ее прототип iostate rdstate( ); Функция возвращает текущее состояние флаrов ошибок. Как вы можете доrадаться, взrлянув на приведенный выше перечень флаrов, если ошибок нет, rdstate( ) возвращает goodbit. В противном случае yc танавливается флаr ошибки. Друrой способ обнаружения ошибки BBoдaBЫBoдa заключается в использовании одной или нескольких из следующих Функцийчленов ios: Ьооl bad( ); Ьооl eof( ); Ьооl fail( ); Ьооl good( ); 
Определение состояния ввода"вывода 501 Функция eof( ) уже обсуждалась ранее. Функция bad( ) возвра щает true, если установлен бит badbit. Функция fail( ) возвращает true, если установлен бит failbit. Функция good( ) возвращает true, если ошибок не было. В противных случаях все эти функции воз вращают false. Если произошла ошибка, то вам может понадобиться перед продолжением проrраммы сбросить ее флаr. Для этоrо можно BOC пользоваться Функциейчленом ios clear( ), имеющей такой прото тип: void clear(iostate flags := ios: :goodbit); Если j1ags := ios::goodbit, как это имеет место по умолчанию, все флаrи ошибок сбрасываются. В противном случае в переменной j1ags следует указать интересующие вас флаrи. Перед тем, как вы продолжите изучение этой книrи, можно поре комендовать вам поэкспериментировать с использованием этих Функ ций обнаружения ошибок и добавить в предшествующие примеры бо... лее детальную обработку возможных ошибок. ../ Вопросы для самопроверки 1. Как называются четыре предопределенных потока? 2. Определяет ли С++ символьные потоки и для 8битовых, И для "широких" символов? 3. Приведите общую форму переrpуженноrо оператора вставки. 4. Каково назначение ios::scientific? 5. Каково назначение width( )? 6. Манипулятор BBoдaBЫBoдa используется внутри выражений BBO да... вывода. Справедливо ли это утверждение? 7. Покажите, как открыть файл для ввода текста. 8. Покажите, как открыть файл для вывода текста. 9. Каково назначение los::binary? 10. Коrда достиrается конец файла, переменная потока становится равной false. Справедливо ли это утверждение? 11. Покажите, как прочитать файл до конца, если он связан с потоком ввода по имени strm. 12. Напишите проrpамму, которая копирует файлы. Пользователь должен задавать имена входноrо и выодноrоo файлов в командной строке. Удостоверьтесь, что ваша проrpамма копирует как TeKCTO вые, так и двоичные файлы. 13. Напишите проrpамму, которая объединяет два текстовых фай ла. Пользователь должен задавать имена двух файлов в команд... 
502 Модуль 11. С++ и система ввода..вывода ной строке в том порядке, в котором они должны войти в BЫ ходной файл. Кроме Toro, пользователь должен задавать имя выходноrо файла. Таким образом, если проrрамма имеет имя merge, то командная строка, которая объединит файлы MyFile l.txt и MyFile2.txt в файл Target.txt, будет выrлядеть сле дующим образом: merge MyFilel.txt MyFile2.txt Target.txt 14. Покажите, как с помощью предложения seekg( ) найти 330й байт в потоке с именем MyStrm. 
Модуль 12 Исключения, шаблоны и друrие дополнительные темы Цели, достиrаемы в этом модуле 1 2. 1 Изучить основы обработки исключений 12.2 Понять, как создаются родовые функции 12.3 Понять, как создаются родовые классы 12.4 Познакомиться с операторами new и delete динамическоrо выделе.. ния памяти 12.5 Начать использовать пространства имен 12.6 Рассмотреть использование статических членов классов 12.7 Научиться идентифицировать типы во время выполнения проrраммы 12.8 Освоить дополнительные операторы приведения 
504 Модуль 12 Исключения, шаблоны и друrие дополнительные темы С тoro момента, как вы начали изучать эту книry, вы MHoro узнали и MHoroMY научились. Теперь, в заключительном модуле, вы познакоми тесь с некоторыми важными разделами С++, включая обработку ис ключений, шаблоны, динамическое вьщеление памяти и пространства имен. Вы узнаете также об идентификаторах времени выполнения и операторах приведения. Имейте в ВИДУ, что С++  это сложный про фессиональный язык проrpаммирования с большим количеством ca мых разнообразных средств, и в этом пособии для начинающих нельзя описать все ero дополнительные возможности, специальные приемы или нюансы проrраммирования. Однако, закончив изучение этоrо MO дуля, вы освоите базовые элементы языка и сможете приступить к раз работке реальных проrpамм. Цель _Обработка исключений Исключительной ситуацией, или исключением, называется ошибка, возникающая во время выполнения. С помощью подсистемы обработ ки исключений С++ вы можете систематическим и управляемым обра зом обрабатывать ошибки времени выполнения. Если в вашей проrpам ме используется обработка исключений, то при возникновении исклю чения в ней автоматически вызывается проrpамма ero обработки. Принципиальным достоинством обработки исключений является воз можность автоматическоrо включения в большую проrpамму значи  тельной части проrpаммноrо кода, предназначенноrо для обработки ошибок, который в противном случае пришлось бы вводить вручную. Основы обработки исключений Обработка исключения в С++ основана на трех ключевых сло вах: try, catcb и tbrow. Коротко rоворя, проrраммные предложения, в которых вы хотите отслеживать исключения, заключаются в блок попытки try. Если внутри блока try возникает исключение (т. е. ошибка), оно выбрасывается (с помощью tbrow). Исключение ло вится с помощью catch и обрабатывается. Ниже этот процесс будет описан с большими деталями. Код, в котором вы хотите контролировать исключения, должен входить в состав блока try. (Функция, вызываемая из блока try, тоже контролируется.) Исключения, выбрасываемые контролируемым KO дом, ловятся предложением catch, которое следует непосредственно за предложением try, выбросившем исключение. Общая форма KOHCтpYК ции try..catcb выrлядит таким образом: 
Обработка исключений 505 try { / / блок try } catch (тип] apZYMeHm) { / / блок catch } catch (тип2 apZYMenт) { / / блок catch } catch (типЗ apZYMenm) { / / блок catch } // ... catch (muпN apZYMenm) { / / блок catch } Блок try должен содержать 1)' часть вашей проrpаммы, в которой вы хотите отслеживать ошибки. Эта проrpаммная секция может быть очень короткой и содержать Bcero несколько предложений внутри од.. ной функции, или быть такой всеобъемлющей, как блок try, заклю.. чающий в себе весь код функции main( ) (что приведет к контролю ис.. ключений во всей вашей проrpамме). Выброшенное исключение ловится соответствующим предложени ем catcb, которое обрабатывает это исключение. К каждому блоку try может относиться несколько предложений catch. Какое из этих пред ложений catcb будет использоваться, определяется типом исключения. Если тип данных, указанный в предложении catcb, соответствует типу исключения, будет выполняться именно это предложение catcb (все остальные предложения catcb обходятся). Коrда исключение поймано, apZYMeпт блока catcb принимает ero значение. Пойманы MOryr быть любые типы данных, включая классы, которые вы создаете сами. Общая форма предложения throw выrлядит так: throw исключение Предложение tbrow выбрасывает исключение, указанное парамет.. ром исключепие. Если это исключение требуется поймать, тоrда tbrow должны быть выполнено либо внутри caмoro блока try, либо внутри любой функции, вызываемый из этоrо блока try (непосредственно или косвенным образом). Если вы выбрасываете исключение, для KOТOpOro не предусмотрено соответствующеrо предложения catcb, возникает аварийное заверше.. ние проrpаммы. Эrо значит, что проrpамма прекратит свое выполне.. ние не контролируемым образом. Таким образом, необходимо ловить все выбрасываемые исключения. 
506 Модуль 12 Исключения, шаблоны и друrие дополнительные темы Ниже приведен простой пример проrpаммы, в которой исполъзо ван механизм обработки исключений С++: // Простой пример обработки исключений. #include <iostream> using namespace std; int main () { cout « "Старт\n"; try { // начинается блок try  cout « "Внутри блока try\n"; throw 99; / / выбросим ошибку  cout « "Это выполняться не будет"; Начало блока ау Выбрасывание исключения } са t сЬ (i n t i) { / / пойма ем ошибку  Улавливание исключения cout « "Поймано исключение  значение равно: "; cout « i « "\n"; } cout « "Конец"; return о; } Эта проrpамма выведет следующее: Старт Внутри блока try Поймано исключение  значение равно: 99 Конец Рассмотрим текст этой проrpаммы. Как вы видите, в ней есть блок try, включающий в себя три предложения, а также предложение catch(int i), которое обрабатывает исключение типа int. Внyrpи блока try только два из трех предложений будут выполняться: первое предло жение cout и tbrow. После Toro, как исключение будет выброшено, управление передается на выражение catcb, а блок try завершается. Дрyrими словами, catch не вызывается. Вместо этоrо проrpаммное управление передается на предложение catch. (Для выполнения этоrо действия проrpаммный стек при необходимости автоматически ycтa навливается в исходное состояние.) Таким образом, предложение cout, следующее за tbrow, никоrда выполняться не будет. Обычно код внутри предложения catcb пытается исправить ошибку с помощью соответствующих действий. Если ошибка может 
Обработка исключений 507 быть ликвидирована, тоrда выполнение проrpаммы продолжится с тех предложений, которые стоят вслед за предложением catcb. В противном случае выполнение проrpаммы будет завершено KOHтpO лируемым образом. Как уже упоминалось выше, тип исключения должен cooтвeТCTBO ватъ типу, указанному в предложении catcb. Если, например, в преды дущей проrpамме вы измените тип в предложении catch на doubIe, тo rда исключение не будет поймано, и произойдет аварийное заверше ние проrpаммы. Такой пример приведен ниже: / / Эта проrрамма работать не будет. #include <iostream> using namespace std; int main () { cout « "CTapT\n"; try { // начало блока try cout « "Внутри блока try\n"; throw 99; // Выбросим ошибку cout « " Это выполняться не будет"; } catch (double i) { // не будет работать для исключения int cout « ,. Поймано исключение  значение равно: "; i cout « i « "\n"; } Это предложение не может поймать исключение int' cout « "Конец"; return о; } Вывод этой проrpаммы приведен ниже. Ero содержимое roворит о том, что целочисленное исключение не может быть поймано предло жением catch(doubIe 1). Учтите, что заключительное сообщение, rOBo рящее об аварийном завершении проrpаммы, может изменяться от компилятора к компилятору. СТдрТ Внутри блока try Abnormal program termination Исключение, выброшенное функцией, которая вызывается изнут ри блока try, может быть поймано этим блоком try. Например, приве денная ниже проrpамма вполне правильна: 
508 Модуль 12. Исключения, шаблоны и друrие дополнительные темы /* Выбрасывание исключения из функции, вызванной из блока try. */ #include <iostream> using namespace std; void Xtest(int test) { cout « "Внутри Xtest, пробное значение равно: 11 « test « " \n" i if(test) throw testi  Это исключение улавливается пред- ложением catch в main( ). } int main () { cout « "CTapT\n"i try { // начинается блок try cout « .. Внутри блока try\n" i Xtest(O); Xtest(l);  Xtest(2) i Поскольку Xtest( ) вызвана изнутри блока ау, ее код таюке контролируется на ошибку. } catch (int i) { // поймаем ошибку cout « "Поймано исключение  значение равно: "; cout« i« "\n"; } cout « "Конец"; return О i } Проrpамма ВЫВОДИТ на экран следующее: Старт Внутри блока try Внутри Xtest, пробное значение равно: О Внутри Xtest, пробное значение равно: 1 Поймано исключение  значение равно: 1 Конец Как подтверждается ВЫВОДОМ проrpаммы, исключение, выброшен ное В Xtest( ), было поймано обработчиком исключений В main( ). Блок try может БыTh локализован В функции. В этом случае при каждом входе в функшпо обрабO'Iчик ИСЮIlOЧений, связанный с этой функцией, приводится В исходное cocroяние. Посмотрим на следующую проrpамму. 
Обработка исключений 509 // Блок try может быть локализован в функции. #include <iostream> using namespace stdi // try/catch переустанавливается при каждом входе в функцию. void Xhandler(int test) { try{  if(test) throw testi ЭтОТ блок ау локален В Xharкller. } catch(int i) { cout « "Поймали! Исключение #: " « i « '\n'; } } int main ( ) { cout « "Старт\п"; Xhandler(l); Xhandler(2); Xhandler(O); Хhаndlеr(З)i cout « "Конец"; return о; } Проrpамма въшает на экран следующее: Старт Поймали! Исключение #: 1 Поймали! Исключение #: 2 Поймали! Исключение #: 3 Конец в этом при мере выбрасываются три исключения. После каждоrо исключения осуществляется возврат из функции. Коrда функция BЫ зывается в следующий раз, обработчик исключений приводится в ис ходное состояние. В принципе блок try переустанавливается каждый раз, коrда происходит вход в Hero. Таким образом, если блок try является ча стъю цикла, он будет переустанавливаться в каждом новом шаrе цикла. 
510 Модуль 12. Исключения, шаблоны и друrие дополнительные темы Минутная тренировка 1. Что называется исключением в С++? 2. На каких трех ключевых словах основывается обработки исключений? 3. Улавливание исключения основывается на ero типе. Правильно ли это? 1 Исключением называется ошибка времени выполнения 2 Обработка исключений основывается на ключевых словах try, catch и throw 3 Правильно, улавливание исключения основывается на ero типе Использование rруппы предложений catch Как уже отмечалось, с одним предложением try можно связать He сколько предложений catch. В действительности обычно так и делают. Однако эти предложения catcb должны ловить исключения разных ти пов. Например, приведенная ниже проrpамма ловит исключения типа int и указателя на символы: // Использование rруппы предложений catch. #include <iostream> using namespace std; // Можно ловить исключения разных типов. void Xhandler(int test) { try{ if(test) throw test; // выбрасываем int else throw "Значение равно нулю"; / / выбрасываем char * } са tch (in t i) {  Это предложение повит исключения int cout« "Поймали! Исключение #: " « i« '\n / ; } catch(char *str) {  cout « "Поймали строку: "; cout« str« '\n / ; Это предложение повит исключения char* } } int main () { cout « "Старт\п"; Xhandler(l); Xhandler(2); 
Обработка исключений 511 Xhandler(O) ; Хhаndlеr(З); cout « "Конец"; return о; } Проrрамма выводит следующее: Старт Поймали! Исключение #: 1 Поймали! Исключение #: 2 Поймали строку: Значение равно нулю Поймали! Исключение #: 3 Конец Как видите, каждое предложение catcb откликается только на ero собственный тип. В общем случае выражения catcb проверяются в том порядке, в кa ком они включены в проrpамму. Выполняется только предложение тoro же типа. Все остальные блоки catcb иrнорируются. Улавливание исключений базовоrо класса Существует важное правило использования rpуппы предложений catcb, имеющее отношение к производным классам. Предложение catcb для базовоrо класса будет также соответствовать любому классу, производному от этоrо. Поэтому, если вы хотите ловить исключения как типа базовоrо класса, так и типа производноrо класса, поместите производный класс в начале последовательности catcb. Если вы noc1Y пите наоборот, catcb базовоro класса будет ловить также исключения всех производных классов. Рассмотрим в качестве примера следylO щую проrpамму: // Улавливание производноrо класса. Проrрамма неверна! #include <iostream> using namespace std; class В { } ; class D: public В { } ; 
512 Модуль 12. Исключения, шаблоны и друrие дополнительные темы int main () { D derived; try { throw derived; } catch (В Ь) { 411 cout « "Пойман базовый класс.\n"; ЭтОТ СПИСОК catch написан в не- правильном порядке! ПроиЗ80Д- ные классы ДОЛЖНЫ ЛОВИТЬСЯ пе- ред базовым классом } catch (D d) {  cout « "Это не будет выполняться.\n"; } return о; } Изза TOrO, что у объекта derived базовым классом является класс В, он будет пойман первым предложением catch, а второе предложение catch никоrда выполняться не будет. Некоторые компиляторы отметят это предложение и выведут предупреждающее сообщение. Дрyrие MO ryт вывести сообщение об ошибке и прекратить компиляцию. В лю бом случае, чтобы проrpамма работала правильно, вы должны изме нить порядок предложений catch. Улавливание всех исключений в некоторых случаях вам может понадобиться обработчик исклю чений, который будет ловить все исключения, а не исключения одноrо определенноrо типа. Для построения тaKoro обработчика вы должны использовать следующую форму catch: catch(...) { / /обработка всех исключений } Здесь мноrоточие  символ улавливания всех исключений. Приведенная ниже проrpамма иллюстрирует форму catch(...). // В этом примере ловятся все исключения. #include <iostream> using namespace std; void Xhandler(int test) { 
Обработка исключений 513 try{ if(test==O) throw test; // выбросить int if(test==l) throw 'а / ; // выбросить char if(test==2) throw 123.23; // выбросить double } catch ( . . .) { / / ловим все исключения  cout « "Поймали! \n 11 ; ЛОВИТ Все исключения } } int main () { cout « "CTapT\n"i Xhandler(O)j Xhandler(l); Xhandler(2); cout « "Конец"; return о; } Проrpамма ВЫВОДИТ следующее: Старт Поймали! Поймали! Поймали! Конец Xhandler выбрасывает исключения трех типов: int, char и doubIe. Все они ловятся с помощью предложения catch(...). Весьма полезным приемом применения catcb(...) является ero ис пользование в качестве последнею catcb в rpуппе таких предложений. В этом случае оно предоставляет удобный вариант по умолчанию. Ис пользуя catcb(...) в качестве предложения по умолчанию, вы получаете способ улавливания всех исключений, которые вы не хотите обраба тыватъ явным образом. Кроме тою, улавливая все исключения, вы предотвращаете аварийное завершение проrpаммы в случае выбрасы вания необрабатыIаемоrоo исключения. Задание исключений, выбрасываемых функцией Вы можете задать тип исключений, которые MOryr быть выбро шены функцией, вне этой функции. Вы можете также запретить функции выбрасывать какиелибо исключения. Для реализации 
514 Модуль 12. Исключения, шаблоны и друrие дополнительные темы этих оrpаничений необходимо добавить предложение tbrow копре.. делению функции. Общая форма TaKoro определения выrлядит сле дующим образом: тuп..возврата uмяФункцuu(спuсок"арzументов) throw (спuсок..тuпов) { //... } При таком определении функции она может выбрасывать исключе ния только тех типов, которые содержатся в перечне cпUCOKтuпoв. Выбрасывание исключения любоrо дpyroro типа приведет к аварийно му завершению проrpаммы. Если вы хотите запретить функции вы.. брасывать какие бы то ни было исключения, укажите вместо списка типов пустой список.  Замечание к моменту написания этой книrи Vlsual С++ фактически не предохра.. нял функцию от выбрасывания исключений, не перечисленных в пред.. ложении throw. Однако такое поведение является нестандартным. Вы по..прежнему может включить в определение функции предложение throw, однако такое предложение будет носить чисто информационный характер. Приведенная ниже проrpамма показывает, как задавать типы ис ключений, которые MOryт быть выброшены функцией. // Оrраничение типов исключений, выбрасываемых функцией. #include <iostream> using namespace std; / / Эта функция может выбрасывать только int, char и double. void Xhandler(int test) throw(int, char, double) { if(test==O) throw testi // выбросить int if(test==l) throw 'а'; // выбросить char if(test==2) throw 123.23; // выбросить double } int main ( ) { Укажите исключения, которые мoryr выбрасываться функцией Xhandler cout « "CTapT\n"; 
Обработка исключений 515 try{ Xhandler(O); // попробуйте также передать функции // Xhandler() 1 и 2 } catch(int i) { cout « "Поймано int \n" ; } catch(char с) { cout « "Поймано char\n"; } catch(double d) { cout « "Поймано double\n" i } cout « .. end" ; return о; } В этой проrpамме функция Xhaвdler может выбрасывать только ис ключения типов int, char и doubIe. Если она попытается выбросить ис ключение какоrолибо дpyroro типа, произойдет аварийное завершение проrpаммы. Чтобы наблюдать такое поведение проrpаммы, удалите iDt из списка и запустите проrpамму заново (предварительно перекомпи лировав ее). Вы получите предупреждающее сообщение компилятора и ошибку при выполнении проrpаммы. (Как уже отмечалось, в настоящее время Visual С++ не оrpаничивает типы исключений, выбрасываемых функцией.) Важно отдавать себе отчет в том, что функция может быть оrpани чена в выбрасывании исключений только в тот блок try, который ее вызвал. Блок try внутри функции может выбрасывать исключения лю бых типов, при условии, что эти исключения будут пойманы внутри этой функции. Оrраничения относятся только к выбрасыванию ис ключений из функции наружу. Вторичное выбрасывание исключения Вы можете выбросить исключение изнутри обработчика исключе ния, вызвав throw само по себе, без указания типа исключения. В pe зультате текущее исключение будет передано внешнему предложению try /catch. Наиболее вероятным применением тaKoro вызова throw яв ляется ситуация, коrда несколько обработчиков исключений должны иметь доступ к одному исключению. Скажем, один обработчик ис ключения занимается одним аспектом этоrо исключения, а второй об работчик  дрyrим аспектом. Исключение может быть выброшено 
516 Модуль 12. Исключения, шаблоны и друrие дополнительные темы только изнутри блока catch (или из любой функции, вызванной изнyr ри этоrо блока). Коrда вы вторично выбрасываете исключение, оно не будет поймано тем же предложением catch. Оно перейдет к следующе му предложению catch. Приводимая ниже профамма иллюстрирует вторичное выбрасывание исключения. В ней вторично выбрасывается исключение char*. // Пример вторичноrо выбрасывания исключения. #include <iostream> using namespace std; void Xhandler () { try { throw "Привет"; // Выбрасываем char * } catch(char *) { // ловим char * cout « "Поймано char * внутри Xhandler\n"; throw; //вторичное выбрасывание char * / / из функции наружу } I Вторичное выбрасывание исключения } int main ( ) { cout « "Старт\n"; try{ Xhandler(); } catch (char *) { cout « "Поймано char * внутри main\n"; } cout « "Конец"; return о; } Проrpамма выводит следующее: Старт Поймано char * внутри XHandler Поймано char * внутри main Конец 
Обработка исключений 517 Минутная тренировка 1. "окажите, как поймать все исключения. 2. Как указать тип исключений, которые MOryr быть выброшены из Функ ции? 3. Каким образом можно вторично выбросить исключение? 1. для Toro чтобы поймать все исключения, используйте catch(...). 2. Для указания типов исключений, которые MOryr быть выброшены из функции, исполь.. зуйте преможение throw. 3. Для вторичноrо выбрасывания исключения вызовите throw без значения. Спросим у эксперта Вопрос: Получается, что у фунщии есть два спос06а сообщить 06 ошибке: выбросить исключение или BepHyrb код ошибки. В каких случаях лучше пользоваться тем или дрyrим подходом? Ответ: Вы правы, имеются два общих подхода к сообщению 06 ошибках: выбрасывание исключений и возврат кода ошибки. На сеrодня специалисты по языку предпочитают использовать исключения, а не коды ошибок. Ha пример, языки Java и С# плотно привязаны к исключениям, которые они используют Д1IЯ сообщений о большинстве распространенных ошибок, Ta ких, как ошибка открытия файла или арифметическое переполнение. По скольку С++ происходит от С, он использует для сообщений 06 ошибках и коды ошибок, и исключения. Так, мноrие ошибочные ситуации, возникаю щие при использовании библиотечных функций С++, приводят к возврату в проrpамму кода ошибки. Однако, в новых проrpаммах, которые вы будете создавать, вам следует использовать для сообщений 06 ошибках механизм исключений. Это путь, по которому идет современное проrpаммирование. ШаБЛОНЬI Шаблон является одним из наиболее сложных и мощных средств С++. Шаблонов не было в исходных спецификациях С++; они были добавлены лишь несколько лет назад и поддерживаются всеми COBpe менными компиляторами С++. Шаблоны помоraют вам достичь oд ной из самых трудноуловимых целей в npоrpаммировании: созданию повторно используемоro кода. Использование шаблонов позволяет создавать родовые (типовые, обобщенные) функции и классы. В родовой функции или классе тип данных, с которыми работает функция или класс, задается с помощью параметра. Таким образом, вы можете использовать одну функцию или класс для обработки данных различных типов вместо тoro, чтобы создавать специфические варианты кода для каждоro типа данных. Здесь мы коснемся и родовых функций, и родовых классов. 
518 Модуль 12. Исключения, шаблоны и друrие дополнительные темы Цель __ Родовые функции Родовая функция определяет обобщенный набор операций, KOTO рые будyr применены к различным типам данных. Тип данных, с KO торыми будет работать функция, передается ей через параметр. По средством родовой функции единую обобщенную процедуру можно приложитъ к широкому диапазону данных. Как вы, возможно, знаете, мноrие алrоритмы лоmчески остаются неизменными, независимо от Toro, данные KaKoro типа они обслуживают. Например, алrоритм упо рядочения Quicksort остается одним и тем же, будет ли он приложен к массиву целых чисел или к массиву чисел с плавающей точкой. Разли чаются в этом случае лишь типы обрабатыIаемыЪIx данных. Создавая родовую функцию, вы определяете существо алrоритма независимо от какихлибо данных. После Toro, как вы это сделаете, компилятор бу дет автоматически rенерироватъ правильный код для Toro типа дaH ных, который фактически используется при выполнении этой функ ции. По существу, создавая родовую функцию, вы создаете функцию, которая может автоматически переrpужать саму себя. Родовая фyнкuия создается с помощью ключевоrо слова template. Обычное значение этоrо слова (шаблон в переводе на русский язык) точ но отражает ero использование в С++. С помощью template создается шаблон или каркас, который описывает, что должна делать эта функция, оставляя для компИЛЯ'Юра задачу заполнение этоrо каркаса требуемыми деталями. Общая форма определения родовой функции выrлядит так: template <class тип> типвозврата имяфункции(список параметров) { / /тело Функции } Здесь тип  это условное имя обобщенноrо, родовоrо типа обраба TыBaeMыx данных. Это имя затем используется внyrpи определения функции для объявления типа данных, с которыми будет работать функция. Компилятор, создавая конкретный вариант ФУНКЦИИ, aBTO матически заменит тип на тип фактически используемых данных. Хотя использование ключевоrо слова class для задания родовоrо типа в объявлении шаблона стало традиционным, вы можете также исполь зовать ключевое слово typename. В приведенной ниже проrpамме создается родовая функция, KOTO рая обменивает значения двух переменных, для которых она вызыва ется. Поскольку алrоритм обмена двух значений независим от типа пе ременных, такая процедура удачно подходит в качестве кандидата на роль родовой функции. 
Родовыо (I)УtlКЦИИ ')1 С . 11 нример родовой функции. // Это родовая функция. template <class Х> void swapargs (Х &а I { Родовая функция. которая обменива- ет значения своих apryмeHToB Здесь Х  это родовой тип данных. Х &Ь)  #include <iostream> using nатеврасе std; х tempi temp = а; а = Ь; Ь = t еrnр i } int main () { int i=10, j=20; float х=10.1, у=2З.Зi char а=' х', Ь=' Z' ; cout « "Исходные i, j: " « i « I I « j « '\n'; CQut« "Исходныех, у:" «Х« l' «у« '\n'; сои t < < " и сходные а, Ь: " < < а < < ' I < < Ь < < I \ n' ; swapargs(i, j);//обменяем целые swapargs(x, у);//обменяем значения / / с плавающей точкой swapargs(a, Ь);//обменяем символы Компилятор автоматически создает варианты функции swaparga( ). которые ис- ПОЛЬЗУЮТ ТИПЫ данных сво- их apryмeнтOB. cout « "После обмена i, j : " « i « , , « j « I \n' ; cout « "После обмена Х, у: " « х « I , « у « I \n ' ; cout « "После обмена а, Ь: " « а « , , « Ь « I \n' ; return о; } Рассмотрим детально эту проrpамму. Строка template <class Х> void swapargs (Х &а I Х &Ь) сообщает компилятору, что, вопервых, создается шаблон и, BOBТO рых, что начинается родовое объявление. В этой строке Х  это услов ное имя родовоrо типа. Далее объявляется функция swapargs( ), которая использует Х как тип данных для обмениваемых значений. В main( ) функция swapargs( ) вызывается с арryментами трех различных типов: 
520 Модуль 12. Исключения, шаблоны и друrие дополнительные темы iot, Ooat и char. Поскольку swapargs( ) является РОДОВОЙ функцией, компилятор автоматически создает три варианта swapargs( ): один, KO торый будет обменивать целые числа, дрyrой для обмена чисел с пла вающей точкой и третий для символов. Таким образом, одна и та же родовая функция swapargs( ) может быть использована для обмена ap ryмeHToB любых типов данных. Приведем несколько важных терминов, имеющих отношение к шаб лонам. Вопервых, родовые функции (т. е. функции, объявления KOТO рых начинаются со предложения template) называют также шаблонными функциями. Оба этих термина будут использоваться в нашей книrе. Ко.. rда компилятор создает конкретный вариант этой функции, roворят, что создается специализация. Ее также называют nорожденной функцией. Про акт порождения функции rоворят, что создается экземпляр родовой функции. В целом можно сказать, что порожденная функция является специализированным экземпляром родовой функции. Функция с двумя родовыми типами в предложении template можно определить более одноrо родовоrо типа данных, если использовать список с разделяющими запятыIи.. Например, приведенная ниже проrрамма создает шаблонную функ ЦИЮ, использующую два родовых типа: #include <iostream> using namespace std; template <class Туреl, class Туре2> void myfunc(Тypel х, Туре2 у) { cout « х « I I « у « I \n ' ; } int main ( ) { myfunc(10, "hi"); myfunc(O.23,10L)i return о; } в этом примере условные имена типов 1)rpel и Jype2 заменяются компилятором на типы данных iot и char*, а также doubIe и loog соот" ветственно, коrда компилятор создает специфические экземпляры функции тyfпос( ) в maio( ). 
Родовые ФУНКЦИИ 521 Явная переrрузка родовых функций Хотя родовые функции сами переrpужают себя по мере необходи мости, вы также можете переrpужатъ их явным образом. Формально это называется явной специализацией. Если вы переrpужаете родовую функцию, тоrда эта переrpуженная функция замещает (скрывает) po довую функция относительно этоrо специфическоrо варианта. Pac смотрим, например, эту проrpзмму  модифицированный вариант уже известной вам проrpаммы обмена значений: // Специализация родовой функции. #include <iostream> using namespace stdi template <class Х> void swapargs(X &а, Х &Ь) { х tempi temp = а; а = Ь; Ь = tempi cout « "Внутри шаблонной функции swapargs.\n"i } // ЭТа функция замещает родовой вариант swapargs() для int. void swapargs(int &а, int &Ь)  {  Явная neperpузка lWaparga( ) int tempi temp = а; а = Ь; Ь = tempi cout « "Внутри специализации swapargs для int.\n"i } int main () { int i=10, j=20; float х=10.1, у=2З.Зi char а = ' х', Ь=' Z ' ; ЭДесь вызывается neperружен- ный явно вариант 8Waparga( ). cout « "Исходные i, j: .. « i« I 1« j « '\n'; cout«" Исходные х, у: "« х«' '« У« '\n'; cout « .. Исходные а, Ь: .. « а « ' , « Ь« ' \n I ; swapargs(i, j)i // вызывается явно переrруженная swapargs() 
522 Модуль 12 Исключения, шаблоны и друrие дополнительные темы swapargs(x, у) i // вызывается родовая swapargs () swapargs(a, Ь) i // вызывается родовая swapargs ( ) cout « 11 После обмена i, j : 11 « i « , , « j « '\n' i cout « "После обмена х, у: 11 « х « , I « у « '\n' i cout « 11 После обмена а, Ь: 11 « а « , , « Ь « ' \n ' i return О i } Проrрамма ВЫВОДИТ на экран следующее: Исходные i, j: 10 20 Исходные х, у: 10.1 23.3 Исходные а, Ь: х Z Внутри специализации swapargs для int. Внутри шаблонной функции swapargs. Внутри шаблонной функции swapargs. После обмена i, j: 20 10 После обмена х, у: 23.3 10.1 После обмена а, Ь: Z х Как показывают комментарии к строкам проrpаммы, при вызове swapargs(i, j) активизируется переrpуженный явным образом вариант родовой функции swapargs( ), потому что родовая функция замещена явной переrрузкой. Относительно недавно бьт предложен альтернативный вариант син таксиса для обозначения явно переrpуженной специализации функции. Этот более НОВЫЙ подход использует ключевое слово template. При ис пользовании синтаксиса HOBoro стиля для специализации, переrpужен ная функция swapargs( ) из последней проrpаммы будет выrлядеть таким образом: // Использование HOBoro синтаксиса для специализации. template void swapargs<int>(int &а, int &Ь) { int tempi temp = а; а = Ь; Ь = tempi cout « 11 Внутри специализации swapargs для int. \n 11 ; } Как видите, новый синтаксис использует конструкцию template для указания на специализацию. Тип данных, для KOToporo создается спе циализация, помещается внутри yrловых скобок вслед за именем 
Родовые классы 523 функции. Этот же синтаксис используется для специализации родовой функиии любоrо типа. Хотя на сеrодня у HOBoro стиля синтаксиса спе циализации нет ВИДИМЫХ преимуществ перед старЫМ, однако в буду щем, видимо, новый стиль окажется лучшим подходом. Явная специализация шаблона позволяет вам подrонять вариант родовой функции под конкретные условия  например, чтобы полу чить выиrpыш В производительности для HeKoToporo определенноrо типа данных. Однако в принципе, если вам нужны различающиеся Ba риантыI функции для различных типов данных, лучше использовать переrруженные функции, а не шаблоны. Цель 12.3. Родовые классы в дополнение к родовым функциям вы можете также определить родовый класс. В этом случае вы создаете класс, который определяет все алrоритмы, используемые этим классом; однако конкретный тип обрабатываемых данных будет задан в виде параметра при создании объектов этоrо класса. Родовые классы оказываются полезными в тех случаях, коrда класс использует лоrику, поддающуюся обобщению. Например, алrоритм, обслуживающий очередь целых чисел, точно так же будет работать с очередью символов, а механизм управления связанным списком aдpe сов рассьтки так же хорошо справится с управлением связанным спи  ском автомобильных деталей. Создаваемый вами родовой класс может выполнять определяемые вами операции, например, управление оче редью или связанным списком, для любоrо типа данных. Компилятор автоматически сrенерирует правильный тип объекта, исходя из типа, который вы задаете при создании объекта. Общая форма объявления родовоrо класса выrлядит следующим образом: template <class тип> class имякласса { / / тело класса } Здесь тип ..... это условное имя обобщенноrо, родовоrо типа, KOТO рый будет задан при создании экземпляра класса. При необходимости вы с помощью списка с разделяющими запятыми можете определить более одноrо родовоrо типа данных. После тoro, как вы создали родовой класс, вы создаете конкретный экземпляр этоrо класса посредством следующей общей формы: имякласса <тип> объект; 
524 Модуль 12. Исключения, шаблоны и друrие дополнительные темы Здесь тип ..... это имя типа данных, с которыми будет работать класс. Функциичлены родовоrо класса автоматически делаются родовыми. Вам не нужно использовать ключевое слово template для явноrо указа ния на их обобщенность. Вот пример родовоrо класса: // Простой родовой класс. #include <iostream> using namespace stdi template <class Т> class MyClass {  т Х, у; public: MyClass(T а, Т Ь) { х = а; у = Ь; Объявление POAOBOro класса. Здесь Т  ЭТО родовой тип. } т div() { return Х/У; } } i // Создание варианта MyClass для double. MyClass<double> d.....ob(10.0, 3.0 );  cout « ., деление чисел типа double: .. « d.....ob. di v () Создание КОНlфeтнorО экземпля- ра POAoвoro класса I int main ( ) { « "\n"i // Создание варианта MyClass для int. MyClass<int> iob(10, З) i cout « .. деление чисел типа int: " « i.....ob. di v () « .. \n 11 i return О i } Вот вывод этой проrраммы: Деление чисел типа double: 3.33ЗЗ3 Деление чисел типа int: 3 Как показывает этот вывод, объект doubIe выполнил деление с пла вающей точкой, а объект int ..... целочисленное деление. Коrда объявляется конкретный экземпляр класса MyClass, компи лятор автоматически rенерирует вариант функции div( ), а также пере менные х и у для хранения фактических данных. В этом примере объ являются два различных типа объектов. Первый, d.....ob, обрабатыIаетT данные типа doubIe. Это значит, что х и у являются значениями типа doubIe, и тип результата деления, как и возвращаемый функцией div( ) 
Родовые классы 525 тип  тоже doubIe. Второй объект, iob, обрабатывает данные типа int. Поэтому х, у и возвращаемое из div( ) значение имеют тип int. Обрати те особое внимание на эти объявления: MyClass<double> dob(10.0, 3.0); MyClass<int> iob(10, 3); Посмотрите, как требуемый тип данных передается внутри yrло вых скобок. Изменяя тип данных, указываемый при создании объек тов MyClass, мы можете изменять тип данных, обрабатываемых клас сом MyClass. Шаблонный класс может иметь более одноrо родовоrо типа дaн ных. Просто объявите все требуемые классом типы данных посредст вом списка с разделяющими запятыми внутри спецификации template. Например, следующий при мер создает класс, использующий два po довых типа данных: /* Этот пример использует два родовых типа данных в определении класса. */ #include <iostream> using namespace std; template <class Тl, class Т2> class MyClass { Тl i; Т2 j; public: MyClass(Tl а, Т2 Ь) { i = а; j = Ь; } void show() { cout « i « ' , « j « ' \n'; } } ; int main () { MyClass<int, double> оЫ(10, 0.23); MyClass<char, char *> оЬ2 ( , Х', "это проверка " ) j obl.shoW()j // вывести int, double ob2.show(); // вывести char, char * return о; } Проrpамма выводит следующее: 10 0.23 Х Это про верка 
526 Модуль 12 Исключения, шаблоны и друrие дополнительные темы Проrpамма объявляет два вида объектов. оы1 использует данные ти пов int и doubIe. оЬ2 использует символ и указатель на символы. В обо их случаяХ компилятор автоматически rенерирует подходящие данные и функции для обслуживания создаваемых объектов. Явные специализации класса Как и в случае шаблонных функций, вы можете создать специали зацию родовоrо класса. Для этоrо надо использовать конструкцию template, как это вы делали при создании явных специализаций шаб лонных функций. Вот пример: // Демонстрация специализации класса. #include <iostream> using namespace stdj template <class Т> class MyClass { Т Х; public: MyClass (Т а) { cout « "Внутри родовоrо класса MyClass\n" i Х = а; } Tgetx() {returnXj} } j // Явная специализация для int. template class MyClass<int> {  Это явная специализация класса MyClass int Х; public: MyClass(int а) { cout « "Внутри специализации MyClass<int> \n" j Х = а * а; } int getx () { return Х j } } j int main ( ) { MyClass<double> d(10.1)j cout « "double: " « d.getx() « "\n\n" j MyClass<int> i(5);  cout« "int: " « i.getx() « "\n"; Здесь используется явная спе- циализация класса Мyclass 
Родовые классы 527 return о; } Эта проrpамма выводит следующее: Внутри родовоrо класса MyClass double: 10.1 Внутри специализации MyClass<int> int: 25 В этой проrрамме обратите особое внимание на строку: template class MyClass<int> { Она rоворит компилятору, что создается явная целочисленная спе циализация класса MyClass. Тот же самый синтаксис используется при создании специализации любоro типа для родовоrо класса. Явная специализация класса расширяет возможности применения родовых классов, поскольку она позволяет вам без труда контролиро вать один или два особых случая, в то время как все остальные aBTOMa тически обрабатываются компилятором. Конечно, если оказывается, что вы вынуждены создавать слишком MHoro специализаций, тоrда вам, видимо, лучше вообще не связываться с шаблонным классом. Минутная тренировка 1. Какое ключевое слово используется для объявления родовой функции или класса? 2. Можно ли явно переrpузить родовую функцию? 3. Выполняется ли автоматическое объявление родовыми функцийчле нов родовоro класса? 1 для объявления родовой функции Wlи класса используется ключевое слово template 2 Да, родовую функцию можно переrpузить явным образом 3 Да, в родовом классе все ero функции..члены автоматически делаются родовыми Проект 12..1 Создание pOAoBoro класса очереди В проекте 82 вы создали класс Queue, поддерживающий очередь символов. В этом проекте вы преобразуете Queue в родовой класс, KO торый сможет управлять данными любых типов. Queue является удач ным кандидатом на преобразование в родовой класс, потому что ero 
528 Модуль 12. Исключения, шаблоны и друrие дополнительные темы лоrика не зависит от типа данных, с которыми он работает. Тот же Me ханизм, что обслуживает очередь, скажем, целых чисел, rодится и для очереди значений с плавающей точкой, и даже для объектов ваших собственных классов. как только вы создали родовой класс Qиeиe, вы сможете использовать ero во всех случаях, коrда вам понадобится оче редь какихлибо объектов. Шаr за marOM 1. Начните с копирования класса Queue из Проекта 82 в файл с именем GenericQ.cpp. 2. Измените объявление Queue на объявление шаблона, как показа но ниже: template <class ОТуре> class Queue { Здесь родовой тип данных назван Q1YPe. 3. Замените тип данных массива q на тип Q1YPe: QType q[maxQSlze]; // массив для хранения очереди Поскольку q теперь принадлежит родовому типу, эту переменную можно будет использовать с любым типом данных, объявленным объ ектом Queue. 4. Замените тип данных параметра функции put( ) на Q1YPe, как по казано ниже: // Поместим данные в очередь. void put(QТype data) { if(putloc == size) { cout « "  Очередь полна. \n" ; return; } putloc++; q[putloc] = data; } 5. Замените тип возврата функции get( ) на Q1YPe, как по казан о ниже: // Извлечем данные из очереди. QТype get () { if(getloc == putloc) { cout « "  Очередь пуста. \n"; return о; 
Родовые классы 529 } getloc++; return q[getloc]; } 6. Ниже приведена полная проrpамма родовоrо класса Queue BMe сте с функцией main( ), демонстрирующей ero использование: /* Проект 121 Шаблонный класс очереди. */ #include <iostream> using namespace std; const int maxQsize = 100; // Далее создается родовой класс очереди. template <class QТype> class Queue { QТype q[maxQsize]; // массив для хранения очереди int size; // максимальное число элементов, // которые MorYT находиться в очереди int putloc, getloc; // индексы "положить" и "взять" public: // Сконструируем очередь конкретной длины. Queue (int len) { // Размер очереди должен быть меньше mах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } // Поместим данное в очередь. void put(QТype data) { if(putloc == size) { cout « "  Очередь полна. \n"; return; } putlOC++i q[putloc] = data; } 
530 Модуль 12 Исключения, шаблоны и друrие дополнительные темы / / Извлечем данное из очереди. QТype get () { if(getloc = putloc) { cout « "  Очередь пуста. \n" ; return о; } getloc++; return q[getloc]; } } ; // Демонстрация родовоrо класса аиеие. int main () { Queue<int> iQa (10), iQb(10); / / создадим две очереди / / целых чисел iQa.put(l); iQa.put(2); iQа.рut(З); iQb.put(10); iQb.put(20); iQЬ.рut(ЗО); cout « "Содержимое очереди целых чисел iQa: "; for(int i=O; i < 3; i++) cout « iQa.get() « " "; cout « endl; cout « "Содержимое очереди целых чисел iQb: "; for(int i=O; i < з; i++) cout « iQb.get() « " "; cout « endl; Queue<double> dQa(10), dQb(lO); // создадим две очереди //чисел с плавающей точкой dQa.put(l.Ol); dQa.put(2.02); dQа.рut(З.ОЗ); dQb.put(10.01); dQb.put(20.02); dQЬ.рut(ЗО.ОЗ); 
Динамическое выделение памяти 531 cout« "Содержимое очереди чисел с плавающей точкой dQa: "; for(int i=O; i < 3; i++) cout « dQa.get() « " "; cout « endl; cout « "Содержимое очереди чисел с плавающей точкой dQb: 11; for(int i=O; i < 3; i++) cout « dQb.get() « " "; cout « endl; return о; } Вот вывод этой проrpаммы: Содержимое очереди целых чисел iQa: 1 2 3 Содержимое очереди целых чисел iQb: 10 20 30 Содержимое очереди чисел с плавающей точкой dQa: 1.01 2. 02 3. ОЗ Содержимое очереди чисел с плавающей точкой dQb: 10.01 20.02 ЗО. 03 7. Как ВИДНО из этоrо примера, родовые функции и классы являlOТ ся мощным средством оптимизации процесса разработки проrpамм, потому что они позволяют вам определять общую форму объекта, KO торую затем можно использовать применительно к любому типу дaH ных. Вы избавляетесь от утомительной процедуры создания отдельных реализаций вашеro алrоритма для каждоrо типа данных, с которыми будет использоваться этот алrоритм. Компилятор автоматически соз дает для вас конкретный вариант класса. Цель _Динамическое выделение памяти Имеются два основных способа, с помощью которых С++проrpам мы MOryт сохранять информацию В основной памяти компьютера. Пер вый способ заключается в использовании переменных. Память, Bыдe ляемая под переменные, фиксируется во время компиляции и не может быть изменена в процессе выполнения проrpаммы. Второй способ xpa нения информации использует систему динамическоzо выделения па.мя ти С++. При таком способе память под данные вьшеляется по мере He обходим ости из объема свободной памяти, которая располaraется меж ду вашей проrpаммой (вместе с ее областью хранения постоянных данных) и стеком. Эта область памяти называется кучей. (На рис. 121 показано как в принципе выrлядят С++проrpаммы в памяти.) 
532 Модуль 12 Исключения, шаблоны и друrие дополнительные темы Большие адреса памяти Меньшие адреса Стек ! f Куча rлобалъиые данные проrpаммиый код Рис. 12 1. Приниипиальная схема распределения памяти Ш1Я С + +..проrpаммы Динамическое выделение памяти осуществляется во время выпол нения проrpаммы. Таким образом, динамическое выделение дает воз можностъ вашей проrpамме создавать переменные в процессе выпол нения проrpаммы. Проrpамма может создать столько переменных, сколько ей требуется в зависимости от ситуации. Динамическое вьше ление памяти часто используется для поддержки таких структур дaн ных, как связанные списки, двоичные деревья и разреженные масси вы. Разумеется, вы можете использовать динамическое вьшеление na мяти во всех случаях, коrда это будет иметь смысл. Динамическое выделение памяти с той или иной целью является важнейшей частью практически всех реальных проrpамм. Память для динамическоrо выделения берется из кучи. Нетрудно доrадаться, что в определенных крайних ситуациях эта память может исчерпаться. Таким образом, хотя динамическое вьшеление памяти обеспечивает значительное повышение rибкости, оно имеет свои or раничения. С++ предоставляет два оператора для динамическоrо выделения памяти: new и delete. Оператор new вьшеляет память и возвращает yкa затель на ее начало. Оператор delete освобождает память, предвари тельно выделенную посредством оператора new Общая форма этих операторов приведена ниже: pvar == new тип; delete р  var; 
Динамическое выделение памяти 5ЗЗ Здесь pvar представляет собой переменнуюуказатель, принимаю шую адрес памяти, объем которой достаточен для хранения элемента данных типа тип. Поскольку куча имеет оrpаниченный объем, она может исчерпать ся. Если в системе не хватит наличной памяти мя выполнения всех запросов на ее выделение, оператор new не выделит память и выбросит исключение bad....alloc. это исключение определено в зarоловке <new>. Вашей проrpамме следует обрабатывать это исключение с целью при нятия соответствующих действий в случае отказа в въшелении памяти. Если исключение bad....alloc вашей проrpаммой не обрабатывается. оно приведет к аварийному завершению проrpаммы. Действия new в случае нехватки памяти, описанные выше, опреде лены стандартным с++. Однако некоторые старые компиляторы pea лизуют new несколько иначе. Коrда С++ только появился, new в слу чае отказа возвращал нулевой указатель. Позже алrоритм ero выполне ния был изменен, и теперь new при невозможности вьшеления памяти выбрасывает исключение, как это описано в предыдущих строках. Если вы используете старый компилятор, сверьтесь с документацией к нему, чтобы узнать, как в точности он реализует ne Поскольку стандартный с++ определяет, что при отказе new reHe рирует исключение, именно такой алroритм предполarается в про rpaMMHblx примерах этой книrи. Если ваш компилятор по"иному об.. рабатывает отказ выделения памяти, вам придется внести в проrpам.. мы соответствующие изменения. Ниже приведена проrpамма, которая выделяет память для хранения переменной типа int: // Демонстрация new и delete. #include <iostream> #include <new> using namespace std; int main () { int *р; Выделение памяти ПОД Int. try { I р = new int; // выделение места в памяти под int  } catch (badalloc ха) { cout « "Отказ в выделении памяти\п";  return 1; I } Ловим ошибку выделения памяти. *р = 100; cout « "По адресу" « р « " "; 
534 Модуль 12. Исключения, шаблоны и друrие дополнительные темы cout « "находится значение" « *р « "\n"; delete р;  Освобождение выделенной памяти. return о; } эта проrpамма присваивает указателю р адрес области памяти из кучи, достаточной для хранения целоrо числа. Затем она записывает в эту память число 100 и выводит содержимое этой памяти на экран. После этоrо выделенная память освобождается. Оператор delete должен использоваться только с указателем пра вильноrо типа и имеющим значение, которое было получено при BЫ делении памяти оператором new Использование с delete указателя лю боrо дрyrоrо типа не определено в языке и почти наверняка породит серьезные проблемы, возможно, фатальный отказ системы. Спросим у эксперта Вопрос: Мне встречались проrpаммы на с++, которые использовали для динамическоrо выделения памяти функции malloc( ) и free( ). Что это за функции? OrвeT: Язык С не поддерживает операторы new и delete. для динамиче cKoro выделения памяти С использует функции malloc( ) и free( ). шаПос( ) выделяет память, а free( ) ее освобождает. с++ также ПОДZ1ерживает эти функции, так что вы можете иноrда увидеть malloc( ) и free( ) в C++npo rpaMMe, особенно, если Д1IЯ нее за основу бьт взят старый код на С. Одна.. ко использовать malloc( ) и free( ) в ваших nporpaMMax не следует. Операто.. ры new и delete не только предоставляют более удобный способ управления динамическим выделением памяти, но они также предотвращают некото" рые виды ошибок, весьма типичных при использовании шаОос( ) и free( ). Еще одно предупреждение: хотя и нет установленных правил запрета на использование malloc( ) и free( ), совместное с new и delete в одной nporpaM ме, однако мешать их не следует. Нет полной rарантии, что они взаимно совместимы. Инициализация выделенной памяти Вы можете инициализировать вьшеленную память какимлибо из вестным значением, указав значениеинициализатор после имени типа в предложении new Вот общая форма ne если этот оператор ис пользуется вместе с инициализатором: pvar == new тип (иницuалuзатор); 
Динамическое выделение памяти 535 Конечно, тип инициализатора должен быть совместим с типом тип данною, для KOTOPOro вьшеляется память. Приведенная ниже проrpамма вьщеляет память под целочисленную переменную и инициализирует ее значением 87: // Инициализация памяти. #include <iostream> #include <new> using namespace stdi int main () { int *р: Инициализация выделенной памяти .J try { р = new int (87) i // инициализируем значением 81 } catch (badalloc ха) { cout « "ошибка выделения\n"; return 1; } cout « "По адресу" « р « " "; cout « "находится значение" « *р « "\n"; delete р i return О i } Выделение памяти под массивы Вы можете выделить память под массив с ПОМОЩЬЮ ne используя следующую общую форму: pvar == new типMaccивa [размер]; Здесь размер определяет число элементов в массиве. Для освобождения памяти, выделенной под массив, следует ис пользовать такую форму delete: delete [ ] р  var; Здесь квадраmые скобки [ ] сообщают оператору delete, что необхо димо освободить память, занимаемую массивом. 
536 Модуль 12 Исключения, шаблоны и друrие дополнительные темы в качестве примера приведенная ниже проrpамма вьшеляет память под 10элементный массив целых чисел: // Выделение памяти под массив. #include <iostream> #include <new> using namespace std; int main () { int *р, i; Выделение памяти ПОД массив int try { J р = new int [10]; // выделим память под 10 чисел int } catch (badalloc ха) { cout « 11 Ошибка выделения\n 11 ; return 1; } for(i=O; i<10; i++ ) p[i] = i; for(i=O; i<10; i++) cout « p[i] « 11 11; delete [] р; / / освободим память  Освобождение памяти массива return о; } Обратите внимание на предложение delete. Как только что бьто упомянyro, если освобождается память, выделенная под массив с по мощью ne то оператору delete необходимо сообщить, что освобожда ется массив, для чеrо и используются квадратные скобки [ ]. (Как вы увидите в следующем подразделе, это особенно важно в тех случаях, коrда память вьшеляется под массив объектов.) Одно оrpаничение при выделении памяти под массивы: им нельзя присвоитъ начальное значение. Дрyrими словами, выделяя память под массив, вы не можете указать инициализатор. Выделение памяти под объекты Используя оператор ne вы можете динамически вьшелять память под объекты. В этом случае создается объект и возвращается указатель 
Динамическое ВЫДеление памяти 537 на Hero. Динамически созданный объект ведет себя подобно любому дpyrOMY объекту. Коrда он создается, вызывается ero конструктор (если у объекта есть конструктор). Korдa память объекта освобождает ся, вызывается ero деструктор. Приведенная ниже профамма создает класс с именем Rectangle, который инкапсулирует ширину и высоту прямоyroлъника. Этот объ ект уничтожается перед завершением профаммы. // Выделение памяти под объект. #include <iostream> #include <new> using namespace std; class Rectangle { int width; int height; public: Rectangle (int w, int h) { width = Wi height = hi cout « "Конструируем прямоуrольник размером" « width « " на " « height « ".\n"i } Rectangle () { cout « "Уничтожаем прямоуrольник размером " « width « " на " « height « ". \n 11 i } in t area () { return width * heighti } } ; int main () { Rectangle *р; try { р = new Rectangle(10, 8);  } catch (badalloc ха) { cout « "ошибка выделеНИЯ\n"i return 1; Выделение памяти ПОД объект класса Rectangle При этом вы- ЗblВ8вТСЯ конструктор Rectangle } cout« "Площадь равна 11 «p>area()i 
538 Модуль 12 Исключения, шаблоны и друrие дополнительные темы cout « "\n" i delete р;  Освобождение памяти, занятой объектом. При этом вызывается деструктор Rectangle return о; } Вывод проrpаммы выrлядит таким образом: Конструируем прямоуrольник размером 10 на 8. Площадь равна 80 Уничтожаем прямоуrольник размером 10 на 8. Обратите внимание на то, что в строке создания объекта значения apryмeHТOB для конструктора указаны в скобках после типа, как это делается при любоrо рода инициализации. Также заметьте, что, по скольку р содержит указатель на объект, для вызова функции area( ) используется операторстрелка (а не операторточка). Вы можете вьшелить память под массив объектов, однако здесь есть скрытая ловушка. Поскольку при выделении памяти под массив с по мощью new нельзя указать инициализатор, вы должны быть уверены, что если в классе определены конструкторы, среди них должен быть один без параметров. Если тaKoro конструктора нет. компилятор не найдет нужноro конструктора, коrда вы попытаетесь выделить память под массив объектов, и проrрамма компилироватъся не будет. В приведенном ниже варианте предыдущей проrpаммы в класс вклю чен KOHcтpyкrop без параметров, чтобы можно было выделить память под массив объектов Rectangle. Кроме тoro, к составу класса добавлена функция set( ), которая устанавливает размеры каждоro прямоyrольника. // Выделение памяти под массив объектов. #include <iostream> #include <new> using namespace std; class Rectangle { int width; int heighti public: Re с t а n 9 1 е () {  Добавим конструктор без параметров. width = height = о; cout « "Конструируем npямоуrольник размером" « width « " на .. « height « ". \n" ; } Rectangle (int w, int Ь) { width = Wi 
Динамическое выделение памяти 539 height = Ь: cout « "Конструируем прямоуrольник размером" « width « " на " « height « ". \n" ; } Rectangle() { cout « "Уничтожаем npямоуrольник размером .. « width « " на " « height « 11. \п" ; } void set (int w, int Ь) {  width = W; height = h: Добавим функцию иt( ) } in t area () { return width * height; } } ; int main () { Rectangle *р; try { Р = new Rectangle [3]; } catch (badalloc ха) { cout « "ошибка выделения\n"; return 1; } cout « "\n"; р [ О] . set (З, 4); р [ 1] . set ( 1 О, 8); р [2] . set ( 5, 6); for(int i=Oi i « 3; i++) cout « "ПЛощадь равна " « р [i] . area () « endl; cout « "\n ll i delete [] р;  в этом месте для каждоrо объекта в массиве вызывается деструктор. return О: } 
540 Модуль 12 Исключения, шаблоны и друrие дополнительные темы Ниже приведен вывод этой профаммы: Конструируем прямоуrольник размером О на О. Конструируем прямоуrольник размером О на О. Конструируем прямоуrольник размером О на о. Площадь равна 12 Площадь равна 80 Площадь равна 3 О Уничтожаем прямоуrольник размером 5 на 6. Уничтожаем прямоуrольник размером 10 на 8. Уничтожаем прямоуrольник размером 3 на 4. Поскольку указатель р освобождается с помощью delete [ ], деструк  тор вызывается для каждоrо объекта массива, как это и видно ИЗ BЫBO да проrpаммы. Обратите внимание также на вызов функции set( ). Ин дексированный указатель обозначает имя элемента массива, и поэто му при обращении к членам Rectangle используется операторточка. Минутная тренировка 1. Какой оператор выделяет память? Какой оператор освобождает память? 2. Что происходит, если запрос на вьшеление памяти не может быть BЫ полнен? 3. Можно ли инициализировать память при ее выделении? 1 Выделяет память оператор new Освобождает память оператор delete 2 Если запрос не выделение памяти не может быть выполнен, выбрасывается исключение bad....alloc 3 Да. память можно инициализировать при ее выделении Цель _Пространства имен Пространства имен уже были кратко описаны в Модуле 1. Здесь мы займемся ими более детально. Назначение пространства имен заклю чается в локализации имен идентификаторов с целью избежать KOH фликта имен. В среде проrpаммирования на С++ произошло букваль но обвальное возрастание числа переменных, функций и имен клас сов. Перед изобретением пространств имен все эти имена боролись за места в rлобальном пространстве имен, в результате чеrо часто возни кали конфликтыI. Например, если ваша проrpамма определила Функ цию с именем toupper( ), она моrла (в зависимости от cBoero списка па 
Пространства имен 541 раметров) заместить стандартную библиотечную функuию toupper( ), потому что оба имени сохранялись бы в rлобальном пространстве имен. Проблемы конфликта имен усyryблялись, если для одной и той же проrpаммы использовались две или больше библиотек сторонних про изводителей. В этому случае было возможно  даже весьма вероятно  что имена, определенные в одной библиотеке, стали бы конфликто ватъ С именами, определенными в дрyrой. К особенно большим He приятностям приводили конфликтыI имен классов. Если, например, ваша проrpамма определяла класс с именем Stack, а класс с таким же именем бът уже определен в библиотеке, используемой вашей про rpаммой, неминуемо возникал конфликт. Ответом на все эти проблемы стало со:шание ключевоrо слова namespace. Пространство имен, локализуя видимость объявленных в нем имен, позволяет одному и тому же имени использоваться в разных контекстах без возбуждения конфликта. Возможно, максимальную выrоду от использования пространств имен получила стандарmая библиотека с++. Перед введением в оборот понятия пространства имен все библиотека с++ выла определена в rлобальном пространстве имен (которое, конечно, бьто единственным пространством). BBeдe ние namespace позволило определить для библиотеки с++ собствен ное пространство имен, названное std, что существенно уменьшило вероятность конфликтов имен. Вы можете также создать собственное пространство имен для локализации видимости любых имен, которые, на ваш взrляд, MOryr привести к конфликтам. это становится особен но важным, если вы создаете библиотеки классов или функций. ОСНОВЫ использования пространств имен Ключевое слово namespace позволяет вам разделить на части rло бальное пространство имен путем создания декларативноrо района. По существу namespace определяет область видимости. Общая форма объявления пространства имен выrлядит таким образом: namespace имя { / / объявления } Все, что определяется внутри предложения namespace, находится в области видимости этоrо пространства имен. Вот пример создания пространства имен. В нем локализуются име на, используемые для реализации простоrо класса счетчика для обрат.. Horo счета. В пространстве имен определен класс counter, который pea лизует такой счетчик, а также переменные upperbound и lowerbound, KO торые содержат верхнюю и нижнюю rpаницы, применимые ко всем счетчикам. 
542 Модуль 12 Исключения, шаблоны и друrие дополнительные темы // Демонстрация пространства имен namespace CounterNameSpace {  int upperbound; int lowerbound; class counter { int count; public; counter (int п) { if(n <= upperbound) count = п; else count = upperbound; Создание пространства имен с именем CounterNameSpaC8 } void reset (int п) { if(n <= upperbound) count = п; } int run() { if(count> lowerbound) return count; else return lowerbound; } } ; } В этой проrpамме upperbound, lowerbound, и класс counter являют ся частью области видимости, определенной пространством имен CounterNameSpace. Внутри пространства имен обращение к объявленным в нем идеН1И фикаторам осуществляется непосредственно, без квалификации их име нем пространства имен. Например, внутри CounterNameSpace функция run( ) может обращаться непосредственно к переменной lowerbound в предложении if(count> lowerbound) return count; Однако, поскольку namespace определяет область видимости, для обращения к объектам, объявленном в пространстве имен, извне этоrо пространства, вы должны использовать оператор разрешения видимо сти ::. Например, для присваивания значения 10 переменной upperbound из кода, не входящеrо в CounterNameSpace, вы должны ис пользовать такое предложение: CounterNameSpace::upperbound = 10; Или, для объявления объекта типа counter вне CounterNameSpace, придется использовать предложение вроде этоro: 
Пространства имен 543 CounterNameSpace::counter оЬ; в общем случае для обращения к членам пространства имен вне этоrо пространства предваряйте имя члена именем пространства имен с оператором разрешения видимости. Ниже приведена проrpамма, демонстрирующая использование про странства имен CounterNameSpace: // Демонстрация пространства имен. #include <iostream> using namespace std; namespace CounterNameSpace { int upperbound; int lowerbound; class counter { int counti public: counter (int n) { if(n <= upperbound) count = n; else count = upperbound; } void reset (int n) { if(n <= upperbound) count = n; } in t run () { if(count > lowerbound) return count; else return lowerboundi } } ; } int main () { CounterNameSpace::upperbound = 100; CounterNameSpace::lowerbound = о; Полностью квanифициро- ванное обращение к чле- нам CounterNameSp8C8 Обратите внимание на ис- пользование оператора разpeweния видимости. CounterNameSpace: :counter оЫ(10); int i; do { 
544 Модуль 12 Исключения, шаблоны и друrие дополнительные темы i = о Ы . run () ; cout « i « " "; } while(i > CounterNameSpace::lowerbound)j cout « endl; CounterNameSpace::counter оЬ2(20); do { i = оЬ2. run ( ) ; cout « i « " "; } while(i > CounterNameSpace::lowerbound); cout « endl; ob2.reset(100) ; CounterNameSpace::lowerbound = 90; do { i = оЬ2. run ( ) ; cout « i « " "; } while(i > CounterNameSpace::lowerbound); return о; } Обратите внимание на то, что объявление объекта counter и ссылки на переменные upperbound и lowerbound квалифицированы указанием CounterNameSpace. Однако после Toro, как объект типа counter объяв лен, нет необходимости в дальнейшем квалифицировать ero caMoro или какиелибо ero члены. Таким образом, obl.run( ) может быть BЫ звана непосредственно; пространство имен уже разрешено. Допустимо использовать более одноrо объявления одноrо и Toro же пространства имен. В этом случае пространства имен складываются. Это дает возможность расщепить пространство имен по нескольким файлам или даже использовать части пространства имен в одном фай ле. Например, во фраrменте namespace NS { int i; } // namespace NS { in t j; } пространство имен NS расщеплено на две части, но содержимое обеих частей входят в одно и то же пространство имен NS. 
Пространства имен 545 Последнее замечание: пространства имен MOryт вкладываться дpyr в дрyrа. Дрyrими словами, одно пространство имен может быть объяв лено внyrpи дрyrоrо. Предложение using Если ваша проrpамма часто ссылается на члены пространства имен, то требование указывать имя пространства имен вместе с опера тором разрешения видимости каждый раз, коrда вы 06ращаетесь к ero членам, становится весьма утомительным. Предложение using было придумано для облеrчения этой процедуры. Предложение using имеет две общие формы: using namespace имя; using имя:: член; В первом предложении имя обозначает имя пространства имен, к которому мы хотим получить доступ. Все члены, определенные внутри указанноrо пространства имен, становятся видимы (т. е. они становятся частью текущеrо пространства имен) и MorYT ис пользоваться без дополнительной квалификации. Во втором предложении сделан видимым только один член пространства имен. Например, при наличии описанноrо выше пространства имен CounterNameSpace следующие предложения вполне npa вильны: using CounterNameSpace::lowerbound; // только lowerbound видим lowerbound = 10; / / ОК потому что lowerbound видим using namespace CounterNameSpace; // все члены видимы upperbound = 100; / / ОК потому что все члены видимы Приведенная ниже проrpамма иллюстрирует использование клю чевоro слова using в примере из предыдущеrо подраздела: / / Демонстрация using. #include <iostream> using namespace std; namespace CounterNameSpace { int upperbound; int lowerbound; class counter { int count; 
546 Модуль 12 Исключения, шаблоны и друrие дополнительные темы public: counter(int n) { if(n <= upperbound) count = nj else count = upperbound; } void reset(int п) { if(n <= upperbound) count = n; } in t run () { if(count > lowerbound) return count; else return lowerbound; } } ; } int main () { 1/ используем только upperbound из CounterNameSpace us ing Coun terNameSpace: : upperbound; 4 Использование KOHкpeтHoro члена CounterNameSpace 11 теперь для установки upperbound квалификация не требуется upperbound = 100; // квалификация все еще нужна для lowerbound и др. CounterNameSpace::lowerbound = о; CounterNameSpace::counter оЫ(10); int i; do { i == оы. run ( ) ; cout « i « " "; } while(i > CounterNameSpace::lowerbound); cout « endl; // Теперь используем все пространство CounterNameSpace us ing namespace CounterNameSpace;  Использование Bcero пространства имен CounterNameSpace counter оЬ2 (2 О) ; do { i = оЬ2. run ( ) ; cout « i « " "; } while(i > lowerbound); cout « endl; 
Пространства имен 547 ob2.reset(100); lowerbound = 90; do { i = оЬ2. run ( ) ; cout « i « " "; } while(i > lowerbound); return о; } Проrpамма иллюстрирует один важнЫЙ момент: использование oд Horo пространства имен не замещает дpyroro. Коrда вы подключаете пространство имен к области видимости, оно просто добавляет свои имена к любым дрyrим пространствам имен, которые уже действуют. Таким образом, к концу проrpаммы к rлобальному пространству имен оказались добавлены std и CounterNameSpace. Безымянные пространства имен Имеется специальный тип пространства имен, называемый безbI.МЯН ным (или aHOHUМHbI.М) пространством имен, который позволяет вам соз давать идентификаторы, уникальные для данноrо файла. Такое про странство имен имеет следующую общую форму: namespace { / / объявления } Безымянные пространства имен позволяют вам установить уни кальные идентификаторы, которые будут известны только в области видимости одноrо файла. Дрyrими словами, в файле, который coдep жит безымянное пространство имен, члены этоro пространства MOryr использоваться непосредственно, без квалификации. Однако вне фай ла эти идентификаторы неизвестны. Как уже упоминал ось ранее, одним из способов оrpаничитъ область видимости rлобальноrо имени является объявление ero как static. Хотя использование статических rлобальных объявления разрешено в с++, лучшим способом достичь той же цели является использование безы мянноrо пространства имен. Пространство имен std Стандартный С++ определяет всю библиотеку в ее собственном пространстве имен с именем std. Именно по этой причине большинст во проrpамм этой книrи включали такое предложение: 
548 Модуль 12 Исключения, шаблоны и друrие дополнительные темы using namespace std; В результате все пространство имен std подключалось к текущему пространству имен, что давало вам возможность обращаться к именам функций и классов, определенных в библиотеке, без квалификации их каждый раз посредством std::. Конечно, вы можете при желании квалифицировать каждое имя библиотеки префиксом std::. Например, можно явным образом квали фицировать cout таким образом: std: : cout « 11 Явно квалифицируем cout посредством std: : .11; Вам может оказаться нежелательным включение стандартной биб лиотеки С++ в rлобальное пространство имен, если ваша проrpамма мало использует эту библиотеку, или если ее использование может по родить конфликты имен. Однако, если ваша проrpамма содержит COT ни ссылок на библиотечные имена, включение std в текущее про странство имен будет значительно более простой операцией, чем квa лификация каждоrо имени индивидуально. Минутная тренировка 1. Что такое пространство имен? С помощью кaKoro ключевоrо слова можно создать пространство имен? 2. Складываются ли пространства имен? 3. Для чеrо предназначено ключевое слово using? 1 Пространсmо имен это декларативное пространство, которое определяет область ви" димости Оно создается с помощью ключевоrо слова namespace 2 Да, пространства имен складываются 3 using включает члены пространства имен в текущую область видимости Цель _Статические члены классов Из Модуля 7 вы узнали о ключевом слове static, которое там использо валось ШIЯ модификации 06ЪЯRЛеНИЙ локальных и rл06альных перемен ных. Помимо тaкoro применения, ОIШсатель static может быть применен к членам класса. И переменныIчленыы, и функциичлены MOryт быть объ явленыI с ОIШсателем static. Ниже будyr рассмотрены и те, и дрyrnе. Статические переменные..члены Если вы предваряете объявление переменнойчлена словом static, вы сообщаете компилятору, что может существовать только 
Статические члены классов 549 одна копия этой переменной, и что все объекты данноrо класса будут использовать эту единственную копию. В отличие от обыч ных данныхчленов, для каждоrо объекта класса не создается ин дивидуальной копии статической переменнойчлена. Сколько бы объектов класса вы не создали, будет существовать только один экземпляр статическоrо данноrо..члена. В результате все объекты класса используют одну и ту же переменную. Коrда создается первый объект, все статические переменные инициализируются нулем. Если вы объявляете данное..член вида static внутри класса, вы не определяете ero. Вы обязаны выполнить rлобальное определе ние этой переменной rдето в друrом месте, вне класса. При этом статическая переменная объявляется еще раз с использованием оператора разрешения видимости, чтобы указать, к какому классу она относится. Такое объявление выделяет под статическую пере менную память. Вот пример использование члена static: // Использование статической переменной класса. #include <iostream> using namespace stdi class ShareVar { static int num;  public: void setnum(int i) { nит = ii }; void shownum () { cout « num « 11 11 i } } i Объявление статическorо данноrо-члена. Ero будyr использовать все экземпляры клаcra ShareVar int ShareVar:: num; / / определим num  Определение статическоrо данноrо-члена int main () { ShareVar а, Ь; a.shownum() i // выводит О b.shownum() i // выводит О a.setnum(10)i // устанавливает num = 10 a.shownum()i // выводит 10 b.shownum(); // также выводит 10 return о; } 
550 Модуль 12 Исключения, шаблоны и друrие дополнительные темы Вот вывод этой проrpаммы: о о 10 10 Обратите внимание на то, что статическая целочисленная пере менная пот объявлена внутри класса Sharer и определена как rлобальная переменная. Как уже отмечалось выше, такая процеду ра необходима, потому что объявление пот внутри SbareVar не BЫ деляет для этой переменной память. с++ инициализирует пот HY лем, поскольку никакой друrой инициализации нет. Именно по этому оба первых вызова shownum( ) выводят О. Далее объект а присваивает пот значение 10. После этоrо оба объекта, и а, и Ь, используют sbownum( ) для вывода значения пот. Поскольку име ется только один экземпляр пот, используемый и а, и Ь, оба вызо ва shownum( ) выводят 10. Если статическая переменная описана в секции pubIic, к ней можно обращаться с указанием имени класса, но без ссътки на конкретный объект. К ней также можно обратиться посредством объекта. Вот при мер этоrо: // Обращение к статической переменной с указанием имени класса. #include <iostream> using namespace std; class Test { public: static int пит; void shownum () { cout « nит « endl; } } ; int Test::nurn; // определяем nит int main () { Test а, Ь; // Установим nит, указав имя ее класса. Test: :nит = 100 i  Обращение к num посреД" ством имени класса Test. a.shownum()i // выводит 100 Ь . shownum ( ); / / выводит 1 О О // Установим пит, указав имя объекта. а . пит = 2 О О ;  а. shownиm () i / / выводит 200 Обращение к num посред СТ80М имени объекта 8. 
Статические члены классов 551 Ь. shownum ( ) i / / ВЫВОДИТ 200 return о; } Обратите внимание, как значение переменной пот устанавливается с использованием имени ее класса: Test: :nит = 100 i Эта переменная доступна также посредством объекта: а . n ит = 2 О О i Оба способа допустимы. Статические функции..члены Функциючлен также можно объявить статической, хотя это ис.. пользуется довольно редко. Функция..член, объявленная как static, может обращаться только к друrим статическим членам cBoero класса. (Разумеется, статическая Функциячлен имеет доступ к He статическим rлобальным данным и функциям.) Статическая функ.. ция..член не имеет указателя this. Виртуальными статические функции..члены быть не Moryт. Кроме Toro, их нельзя объявлять как const или volatile. Статическая функциячлен может быть активизирована объектом ее класса, или ее можно вызвать независимо от какоrо..либо объекта, используя имя класса и оператор разрешения видимости. Рассмотрим для примера приведенную ниже проrpамму. В ней определена стати.. ческая переменная с именем count, которая хранит число объектов, су.. ществующих в каждый момент времени: // Демонстрация статической функциичлена. #include <iostream> using namespace std; class Test { static int counti public: Test () { count++i cout « "Конструируем объект" « 
552 Модуль 12. Исключения, шаблоны и друrие дополнительные темы count « endl; } ...,Test () { cout « "Уничтожаем объект " « count « endl; counti } Статическая функция-член  static int numObjects() { return counti } } ; int Test: :counti i n t та i n () { Test а, Ь, с; cout « "Теперь имеются " « Test::numObjects() « " в наличии.\n\n"; Test *р = new Test(); cout « "После создания объекта Test " « "имеются" « Test::numObjects() « " в наличии.\n\n"; delete р; cout « "После удаления объекта" « " имеются " « a.numObjects() « " в наличии. \ n \ n" ; return о; } Вывод профаммы выrлядит следующим образом: Конструируем объект 1 Конструируем объект 2 Конструируем объект 3 Теперь имеются 3 в наличии. Конструируем объект 4 После создания объекта Test имеются 4 в наличии. 
Динамическая идентификация типов (Rnl) 553 Уничтожаем объект 4 После удаления объекта имеются 3 в наличии. Уничтожаем объект 3 Уничтожаем объект 2 Уничтожаем объект 1 Обратите внимание, как в npофамме вызывается статическая Функ циячлен numObjects( ): Tes t : : [ltlffiUbj ects ( ) в третьем вызове она активизируется посредством обычноrо син таксиса с указанием имени объекта и оператораточки. Цель 12.7. Динамическая идентифика- ция типов (RТТI) Вы, возможно, до сих пор не сталк.ивались с динамической иденти фикацией типов (т. е. с определением типа во время выполнения про rpаммы), потому что это понятие oTcyrcTByeT в неполиморфных язы ках, таких как С или традиционный BASIC. Внеполиморфных языахх нет необходимости в динамической идентификации типа, потому что тип каждоrо объекта всеrда известен во время компиляции (т. е. коrда проrpамма пишется). Однако, в полиморфных языках, к которым OT носится И С++, MOryr быть ситуации, в которых тип объекта неизвес тен во время компиляции, потому что природа объекта полностью оп ределяется лишь во время выполнения npофаммы. Как вы знаете, С++ реализует полиморфизм блаrодаря использованию иерархии классов, виртуальных функций и указателей на базовый класс. Указа тель базовоrо класса может быть использован для указания как на объ екты базовоrо класса, так и на любые 06ъекты, производные от этОlО 6азов020 класса. Таким образом, не всеrда возможно знать заранее, на объект кзкоrо типа будет указывать базовый указатель в каждый дaH ных момент. Определение типа должно быть пере несено на время BЫ полнения, для чеrо и используется механизм динамической иденти фикации типов (Runtime Туре Identification, R1ТI). для получения типа объекта предусмотрен оператор typeid. Для ис пользования typeid необходимо подключить заroловок <typeinfo>. Чаще вcero используется такая форма этоrо оператора: typeid(06beKm) 
554 Модуль 12 Исключения, шаблоны и друrие Дополнительные темы Здесь объект является тем объектом, у KOToporo требуется опреде лить тип. Этот объект может быть любоrо типа, включая встроенные типы и типы создаваемых вами классов. typid возвращает ссылку на объект типа type.....info, который описывает тип объекта объект. Класс type.....info определяет следующие открытые члены: bool operator == == (const typeinfo &оЬ); Ьооl operator !== (const typeinfo &оЬ); bool before (const typeinfo &оЬ); const char *name( ); Переrpуженные операторы == == и !== предоставляют возможность сравнения типов. Функция before( ) возвращает true, если вызываю щий ее объект расположен (в порядке сравнения) до объекта, исполь зуемоrо в качестве параметра. (Эта функция в основном предназначе на для BHyrpeHHero использования. Возвращаемое ею значение не имеет никакоrо отношения к наследованию или иерархии классов.) Функция пате( ) возвращает указатель на имя типа. Вот простой пример использования typeid: // Простой пример использования typeid. #include <iostream> #include <typeinfo> using namespace stdi class MyClass { / / ... } i int main ( ) { int i I j; float f; MyClass оЬ; typeid позволяет получить тип объек- та во время выполнения nporpaMMbl cout « "Тип i : .. « typeid(i) .nаmе(); cout « endl i cout « 11 Тип f: .. « typeid(f) .name()i cout « endl; cout « .. Тип оЬ: 11 «typeid(ob) .паmе(); cout « 11 \n\n"; if(typeid(i) == typeid(j» cout « "Типы i и j совпадают\n"; i f ( t уре i d ( i) ! = t ур е i d ( f) ) 
Динамическая идентификация типов (RЛI) 555 cout « "Типы i и f различны\n"; return о; } Проrрамма выводит следующее: Тип i: i n t Тип f: float Тип оЬ: MyClass Типы i и j совпадают Типы i и f различны Пожалуй, наиболее важным является такое использование typeid, коrда этот оператор применяется к указателю на базовый полиморф ный класс (т. е. класс, включающий в себя по меньшей мере одну вир туальную функцию). в этом случае typeid автоматически возвращает тип объекта, на который фактически указывает указатель; это может быть объект базовоrо класса или объект класса, производноrо от базо Boro. (Вспомним, что указатель базовоro класса может указывать как на объекты базовоrо класса, так и на объекты любоro класса, произ водноrо от этоrо базовоrо.) Таким образом, используя typid, вы можете определить во время выполнения тип объекта, на который указывает указатель баЗОБоrо класса. Приведенная ниже проrpамма иллюстриру ет эту возможность: // Пример использования typeid в иерархии полиморфных классов. #include <iostream> #include <typeinfo> using nатеэрасе std; class Base { virtual void f() {}; // сделаем Base полиморфным // } ; class Derivedl: public Base { // } i class Derived2: public Base { // } ; 
556 Модуль 12. Исключения, шаблоны и друrие дополнительные темы int main () { Base *р, baseob; Deri vedl оЫ; Derived2 оЬ2; р = &baseob; cout « "р указывает на объект типа "; cout « typeid(*p} .nаmе() « endl; р = &оЫ; cout « "р указывает на объект типа "; cout « typeid(*p} .nаmе(} « endl; р = &оЬ2; cout « "р указывает на объект типа "; cout « typeid{*p} .nаmе{) « endl; return о; } Вывод проrpаммы выrлядит так: р указывает на объект типа Ваве р указывает на объект типа Derivedl р указывает на объект типа Derived2 Как показывает этот вывод, если оператор typeid применен к указа телю базовоrо полиморфноrо класса, то тип объекта, на который YKa зывает этот указатель, будет определен динамически, во время выпол нения проrpаммы. Если typeid применен к базовому указателю неполиморфной ие рархии классов, то во всех случаях будет получен базовый тип yкa зателя. Дрyrими словами, для неполиморфной иерархии не выпол няется идентификация типа объекта, на который фактически yкa зывает указатель. В качестве эксперимента закомментируйте объявление виртуальной функции f( ) в Base и посмотрите на pe зультат. Вы увидите, что типы всех объектов будут Base, потому что это и есть тип указателя. Поскольку typeid обычно применяется к указателю со снятой ссыл.. кой (т. е. к указателю, перед которым указан знак *), предусмотрено специальное исключение для обработки СИ1Уации, коrда снимается ссылка с нулевоrо указателя. В этом случае typeid выбрасывает исклю чение bad.....typeid. Ссылки на объекты полиморфной иерархии классов действуют аналоrично указателям. Если оператор typeid применен к ссьтке на 
Операторы приведения типа 557 объект полиморфноrо класса, он вернет тип объекта, на который фак тически указывает эта ссылка, т. е., возможно, объекта производноrо класса. Чаще вcero вам придется использовать такую возможность в ситуациях, коrда объекты передаются в функции по ссътке. Имеется и вторая форма typeid, которая в качестве арryментз ис пользует имя типа: tуреid(uмятuпа) Например, следующее предложение вполне правильно: coul « typeld (int) . паше () ; Основное использование такой формы typeid  получить объект type..Jnfo, который описывает конкретный тип, чтобы ero можно было использовать в предложении сравнения типов. Минутная тренировка 1. Чем замечательна статическая переменная член? 2. Для чеrо предназначен оператор typeld? 3. КaKoro типа объект возвращает typeld? 1 Статическая переменнаячлен существует в ОДНОМ зкзеМlUlЯре и доступна всем объек" там этоrо класса. 2 Оператор typeid определяет тип объекта во время выполнения проrpаммы. 3 Оператор typeid В01врашает объект типа type ..Jnfo , Цель _Операторы прведения типа в С++ определены пять операторов приведения типа. Первый  это оператор традиционноrо стиля; он уже был описан в этой книrе. Этот оператор был включен в С++ с caMoro начала разработки языка. Оставшиеся четыIеe оператора были добавлены лишь несколько лет назад; это: dynamiccast, const....cast, reinterpret.....cast и static.....cast. Эrи операторы предоставляют дополнительный контроль над способом приведения типа. каждыIй из них кратко описан ниже. dynamic.....cast Это, возможно, самый важный из дополнительных операторов приведения. Оператор dynamic.....cast выполняет приведение типа во 
558 Модуль 12. Исключения, шаблоны и друrие дополнительные темы время выполнения проrpаммы с контролем ero допустимости. Если в тот момент, коrда выполняется dynamiccast, приведение недопусти мо, оно не производится. Общая форма dynamiccast выrлядит сле.. дующим образом: dynamiccast <типмишени> (выражение) Здесь типмишени задает тип, к которому выполняется приведе.. ние, а выражение  это выражение, которое приводится к новому типу. Тип мишени должен быть указателем или ссылкой, а приво димое выражение тоже должно давать в результате указатель или ссылку. Таким образом, dynamiccast может использоваться только для приведения одноrо типа указателя к друrому или одноrо типа ссьтки к дрyrому. Оператор dynamiccast используется для приведений полиморф ных типов. Пусть, например, даны два полиморфных класса В и D, причем D является производным от В. dynamiccast всеrда может привести указатель D* к указателю В*, так как базовый указатель всеrда может указывать на производный объект. Однако dynamiccast может привести указатель В* к указателю D*, только если указываемый объект действительно является объектом D. В общем случае dynamiccast успешно выполнится, если приводимый указатель (или ссылка) указывает (или ссылается) либо на объект типа мишени, либо на объект, производный от типа мишени. В противном случае приведение не состоится. В случае ошибки при ведения MOryт быть два варианта. Если приведение выполняется над указателями, возвращается нулевой указатель. Если приведе ние осуществляется над ссылками, выбрасывается исключение badcast. Ниже приведен простой пример. Предположим, что Base явля ется полиморфным классом, а Derived  это класс, производиый от Base. Base *Ьр, Ь.....оЬ; Derived *ар, а.....оЬ; Ьр = &а.....оЬ; // базовый указатель указывает на объект Derived ар = dynamic.....cast<Derived *> (Ьр); // приведение к / / производному указателю ОК if(dp) cout « "Приведение ОК"; Здесь приведение базовоrо указателя Ьр к производному указа телю dp возможно, так как Ьр фактически указывает на объект класса Derived. В результате этот фраrмент выводит на экран co общение "Приведение ОК". Однако в следующем фраrменте при ведение не выполняется, так как Ьр указывает на объект класса 
Операторы приведения типа 559 Base, а приведение базовоrо объекта к производному объекту He допустимо: Ьр = &bob; // базовый указатель указывает на объект Base dp = dynamiccast<Derived *> (Ьр); // error if(!dp) cout« "ошибка приведеНИЯ"i Поскольку приведение не состоялось, этот фрarмент выведет "Ошиб ка приведения" . const cast Оператор CODstcast используется для Toro, чтобы явным обра зом заместить const или volatile (или и то, и дpyroe) в операции приведения. Тип мишени должен быть тем же самым, что и тип источника, за исключением отсутствия const или volatile. Чаще Bce ro constcast используется для снятия атрибута const. Общая форма constcast такова: constcast <тип> (выражение) Здесь тип задает тип мишени приведения, а выражение  это выражение, которое приводится к новому типу. Следует подчерк нуть, что использование CODstcast для снятия атрибута const явля ется потенциально опасным мероприятием. Применяйте ero с oc торожностью. Еще одно замечание: только CODst...cast может снять атрибут cODst. Операторы dynamiccast, reinterpret...cast и static...cast не MOryr этоro сделать. static cast Оператор static...cast выполняет неполиморфное приведение. Ero можно использовать для любоrо стандартноrо преобразования типов. В этом случае не выполняется проверка типа во время выполнения проrpаммы. Таким образом, оператор static...cast просто является заме ной исходноrо оператора приведения. Общая форма этоrо оператора такова: staticcast <тип> (выражение) Здесь тип задает тип мишени приведения, а выражение  это Bыpa жение, которое приводится к новому типу. 
560 Модуль 12. Исключения, шаблоны и друrие дополнительные темы reinterpret..... cast Оператор reinterpretcast преобразует один тип в принципиально дрyrой тип. Например, он может преобразовать указатель в целое или целое в указатель. Этот оператор можно также использовать для приведения внутренне несовместимых типов указателей. Ero общая форма: reinterpretcast <тип> (выражение) Здесь тип задает тип мишени приведения, а выражение  это Bыpa жение, которое приводится к новому типу. Что дальше? Целью этой книrи было познакомить вас с базовыми элемента ми языка. Это средства и приемы С++, используемые в каждо дневном проrраммировании. Со знаниями, которыми вы теперь обладаете, вы можете приступить к написанию реальных проrрамм профессиональноrо качества. Однако, С++ является весьма боrа тым языком, и В нем содержится MHoro дополнительных средств, к обладанию которыми вам следует стремиться. К этим средствам относятся: · стандартная библиотека шаблонов (Standard Template Library, STL); · явные конструкторы; · функции преобразования; · функциичлены с описателем const и ключевое слово mutabIe; · ключевое слово asm; · переrpузкз оператора индексирования массивов [ ], оператора вызова функции ( ) и операторов динамическоrо выделения па мяти new и delete. Из перечисленноrо, возможно, самым важным средством является стандартная библиотека шаблонов. Это библиотека шаблонных клас сов, предоставляющая rотовые решения для большоrо количества ти пичных задач орrанизации и хранения данных. Например, STL опре деляет такие родовые структуры, как очереди, стеки и списки, KOTO рые часто используются в проrpаммах. Вам также следует изучить библиотеку функций С++. Она coдep жит боrатыIй набор подпроrpамм, которые существенно упростят ваш труд по разработке собственных проrpамм. Для желающих продолжить изучение С++ я рекомендую свою книry С++: The Coтplete Refereпce, Osborne/McGrawHill, Berkeley, California В ней рассматриваются темы, перечисленные в приведен 
Операторы приведения типа 561 ном выше списке, а также MHoroe, MHoroe дpyroe. Теперь вы обла даете достаточными знаниями, чтобы эффективно использовать этот достаточно rлубокий путеводитель по С++. "" Вопросы для самопроверки 1. Объясните, как try, catch и throw совместно поддерживают обработ ку исключений. 2. Как следует орrанизовать список catcb, чтобы ловились исключе ния как базовоrо, так и производных классов? 3. Покажите, как задать, что из функции с именем Сопс( ), KOTO рая возвращает void, может быть выброшено исключение MyExcpt. 4. Определите исключение для родовоrо класса Queue, paCCMoтpeH HOro в Проекте 121. Пусть Queue выбрасывает это исключение, коrда происходит переполнение или потеря значимости. Проде монстрируйте использование этоrо исключения. 5. Что такое родовая функция, и с помощью KaKoro ключевоrо слова она создается? 6. Создайте родовой вариант функций quicksort( ) и qs( ), описанных в Проекте 51. Продемонстрируйте их использование. 7. Используя класс Sample, приведенный ниже, создайте очередь из трех объектов Sample с помощью родовоrо класса Queue, paCCMOT peHHoro в Проекте 121: class Sample { int id; public: Sample() { id = о; } Sample (int х) { id = Х; } void show () { cout « id « endl; } } i 8. Разработайте вариант вашеrо ответа на п. 7, в котором для объ ектов Sample, помещаемых в очередь, память выделяется дина мически. 9. Покажите, как объявить пространство имен под названием RobotMotion. 10. Какое пространство имен содержит стандартную библиотеку С++? 11. Может ли статическая Функциячлен обратиться к нестатическим данным класса? 12. Какой оператор позволяет получить тип объекта во время выпол нения проrpаммы? 
562 Модуль 12 Исключения, шаблоны и друrие дополнительные темы 13. Какой оператор приведения типов следует использовать, чтобы оп ределить допустимость полиморфноrо приведения во время выпол нения? 14. Для чеrо предназначен оператор constcast? 15. Попытайтесь самостоятельно поместить класс Queue из Проек та 12I в ero собственное пространство имен под названием QueueCode, и в ero собственный файл с именем Queue.cpp. За тем преобразуйте функцию main( ) так, чтобы она использовала предложение using для подключения QueueCode к области види мости. 16. Продолжите изучение С++. Этот язык является на сеrодня самым мощным компьютерным языком. Ero освоение сделает вас членом элитной лиrи проrpаммистов. 
Приложение А Ответы на Вопросы для самопроверки 
564 Приложение Д Модуль 1. ОСНОВЫ С++ 1. С++ находится в центре cOBpeMeHHoro мира проrpаммирования, потому что он произошел от С и является родителем языков Java и С#. Это четыре важнейших языка проrpаммирования. 2. Справедливо. Компилятор С++ создает код, который может быть непосредственно выполнен компьютером. 3. Тремя основными принципами объектноориентированноrо проrpаммирования являются инкапсуляция, полиморфизм и насле дование. 4. Выполнение С++проrpаммы начинается с функции main( ). 5. 3аrоловок содержит информацию, используемую проrрам мой. 6. <iostream> представляет собой заrоловок, поддерживающий BBOД вывод. Указанное предложение включает зarоловок <iostream> в про rpaммy. 7. Пространство имен  это декларативная область, в которую MOryr быть помещены различные проrpаммные элементы. Элементы, объяв ленные в одном пространстве имен, отделены от элементов, объявлен ных в дpyroM пространстве. 8. Переменная  это поименованная ячейка памяти. Содержи мое переменной может быть изменено по ходу выполнения про rраммы. 9. Неправильны имена d и е. Имена переменных не MOryт начи натъся с цифры или совпадать с ключевыми словами С++. 10. Однострочный комментарий начинается с символов / / и закан чивается в конце строки. Мноrострочный комментарий начинается с символов /* и заканчивается символами * /. 11. Общая форма предложения if: if (условие) предложение; Общая форма предложения for: fоr(инициализация; условие; приращение); 12. Проrpаммный блок начинается символом { и заканчивается сим волом }. 13. // Таблица соответствия веса на Земле и Луне. #include <iostream> using namespace std; 
Ответы на Вопросы для самопроверки 565 i n t та i n () { double earthweight; // вес на Земле double moonweight: // вес на Луне int counter: counter = о: for(earthweight = 1.0; earthweight <= 100.0: earthweight++) { moonweight = earthweight * 0.17; cout « earthweight « " земных фунтов эквивалентно " « moonweight « " лунным фунтам. \n" ; counter++; if(counter == 25) { cout « 11 \n" : counter = о: } } return о: } 14. // Преобразование юпитерианских лет в земные. #include <iostream> using namespace std: in t та i n () { double eears; // rоды на Земле double jears; / / rоды на Юпитере cout « "Введите число лет на Юпитере: ": cin » j.,..years: e.,..years = jears * 12.0: cout « "Эквивалентное число лет на Земле: " « e.,..years: return о; } 15. Korдa вызывается функция, проrpаммное управление предается на эту функцию. 
566 Приложение Д 16. // Среднее абсолютных значений 5 чисел. #include <iostream> #include <cstdlib> using namespace std; int main () { int i; double avg, val; avg = о. о ; for(i=O; i<5; i++) { cout « "Введите значение: "; cin » val; avg = avg + abs (val) ; } avg = avg / 5; cout « "Среднее абсолютных значений: " « avg; return о; } Модуль 2. Знакомимся с данныи,, типами и операторами 1. В С++ имеются следующие типы целых: Int unslgned Int slgned Int short Int unslgned short Int slgned short Int long Int unslgned long Int slgned long Int Тип char также может быть использован в качестве целочисленноrо типа. 2. 12.2 имеет тип doubIe. 3. Переменная Ьооl может принимать значения true или false. 4. Длинному целому соответствует тип loog iot, или просто loog. 
Ответы на Вопросы для самопроверки 567 5. Символу табуляции соответствует последовательность \t. Звуко вой сиrнал создается последовательностью \Ь. 6. Справедливо. Строка окружается двойными кавычками. 7. Шестнадцатеричные цифры: О, 1,2,3,4,5,6,7,8,9, А, В, С, О, Е, F. 8. Для инициализации переменной используйте следующую общую форму: тип переменная == значение; 9. Оператор % выполняет деление по модулю. Он возвращает ocтa ток от целочисленноrо деления. Этот оператор недопустимо использо вать со значениями с плавающей точкой. 10. Если оператор инкремента предшествует своему операнду, С++ выполнит соответствующую операцию до получения значения операн да, которое он будет использовать в оставшейся части выражения. Если оператор следует за своим операндом, то С++ получит значение опе ранда до ero инкремента. 11. А, С и Е. 12. х +== 12; 13. Приведение типа представляет собой явное пре06разование типа. 14. Ниже показан один из возможных способов получения простых чисел в диапазоне от 1 до 100. Имеются и дрyrие способы. // Найдем простые числа в диапазоне от 1 до 100. #include <iostream> using nаmеврасе std; int main () { int i, j; bool isprime; for(i=li i < 100; i++) { isprime = true; // посмотрим, делится ли это число без остатка for(j=2; j <= i/2; j++) // если да, оно не простое if«i%j) == О) isprime = false; if(isprime) cout « i « 11 простое. \n 11 ; } return о; } 
568 Приложение Д Модуль з. Предложения управления проrраммой 1. // Посчитаем точки. #include <iostream> using namespace stdi int main () { char сЬ i int periods = о; cout « "Введите $ для завершеНИЯ.\n"i do { cin » сЬ; if(ch == '.') periods++i } while(ch 1= '$'); cout « "Точки: " « periods « "\n"; return о; } 2. Может. Если последовательность предложений ветви case не за вершается предложением break, тоrда выполнение проrpаммных строк продолжится в следующую ветвь case. Предложение break предохраня ет от этоrо. 3. if(условие) предложение; else if(условие) предложение; else if(условие) предложение; else предложение; 4. Последнее else относится к внешнему if, которое является бли жайшим if на том же уровне, что и else. 
Ответы на Вопросы для самопроверки 569 5. for(int i = 1000; i >= о; i = 2) // ... 6. Нет. Соrласно стандарту с++ ANSIjISO, переменная i неизвест на вне цикла for, в котором она объявлена. (Заметьте, что некоторые компиляторы MOryт обрабатывать эту ситуацию поиному.) 7. break приводит к завершению блИЖЗЙшеrо к нему цикла или пред ложения switcb. 8. После выполнения break на экран будет выведено "после while". 9. о 1 2 3 4 5 6 7 8 9 10. /* Использование цикла для rенерации проrрессии 1 2 4 8 16, ... */ #include <iostream> using namespace std; i  t та in () { for(int i = 1; i < 100; i += i) cout « i « " "; cout« "\n"; return о; } 11. // Изменение реrистра букв. #include <iostream> using namespace std; i n t та i n () { char ch; 
570 Приложение Д int changes = о; cout « "Введите точку для заверmения.\п"; do { cin » ch; if(ch >= 'а' && ch <= 'z') t ch = (char) 32; changes++; cout « ch; } else if(ch >= 'А' && сЬ <= 'Z') { сЬ + = (с har ) 32; changes++; cout « ch; } } while(ch 1= '.'); cout « "\пИзменение реrистра: " « changes « "\п"; return о; } 12. Предложением безусловноrо перехода в с++ является goto. Модуль 4. Массивы, строки и уазатели 1. short int hightemps[31]; 2. нуля 3. // Найдем одинаковые числа #include <iostream> using namespace std; int main ( ) { int nums [] = {1, 1, 2, 3, 4, 2, 5, 4, 7, 7}; 
Ответы на Вопросы для самопроверки 571 for(int i=Oi i < 10; i++) for(int j=i+1; j < 10; j++) if(nums[i] == nums[j]) cout « "Одинаковые: " « nums[i] « "\n"; return о; } 4. Строка, завершающаяся нулем  это массив символов, который заканчивается байтом с нулевым значением. 5. // Иrнорирование реrистра букв при сравнении строк. #include <iostream> #include <cctype> using namespace stdi int main () { char strl [80] i char str2 [80] i char *р1, *р2; cout « "Введите первую строку: .. i cin » str1i cout « .Введите вторую строку: "; cin » str2 i р1 = str1 i р2 = str2i / / цикл, пока р1 и р2 указывают на ненулевые символы while(*p1 && *р2) { if(tolower{*pl) 1= tolower(*p2» breaki else { рl++; р2++; } } /* строки совпадают, если и рl, и р2 указывают на завершающие нули. */ if(!*pl && !*р2) cout « "строки совпадают за исключением возможноrо " « "несовпадения реrистра букв.\n"; else 
572 Приложение Д cout « "Строки различаются \n"; return о; } 6. При использовании strcat( ) массивприемник должен быть дoc таточно велик, чтобы вместить содержимое обеих строк. 7. В MHoroMepHoM массиве каждый индекс указывается в собствен ной паре квадратных скобок. 8. i n t n ums [] = { 5 , 6 6, 8 8 } ; 9. Объявление массива неопределенной длины дает уверенность в том, что инициализированный массив всеrда будет достаточно велик для размещения в нем всех указанных инициализаторов. 10. Указатель представляет собой объект, содержащий адрес памя ти. Операторами указателей являются & и.. 11. Да, указатель можно индексировать так, как это делается с Mac сивом. Да, к массиву можно обратиться посредством указателя. 12. // Подсчет числа прописных букв. #include <iostream> #include <cstring> #include <cctype> using namespace std; int main () { char str [80] ; int i; int count; strcpy(str, "This 1s А Test"); count = о; for(i=Oi str[i]; i++) if(isupper(str[i])) count++i cout« str« " содержит" « count« " прописных букв."; return о; } 13. Если один указатель указывает на дрyrой указатель, то исполь зуется термин вложенная косвенность. 14. По соrлашению нулевое значение указателя обозначает, что этот указатель не используется. 
Ответы на Вопросы для самопроверки 573 Модуль 5. Введение в функции 1. Общая форма определения функции такова: типвозврата имяФункции (cпиCOKпapaMeтpoв) { / / тело функции } 2. #include <iostream> #include <cmath> using namespace stdi double hypot(double а, double Ь); in t та in () { cout « "rипотенуза прямоуrольноrо треуrольника 3 на 4: "; cout « hypot (З . О, 4. О) « 11 \n 11 ; return о; } double hypot(double а, double Ь) { return sqrt «а*а) + (Ь*Ь» i } 3. Да. функция может вернуть указатель. Нет, функция не может вернуть массив. 4. // Собственный вариант функции strlen(). #include <iostream> using namespace std; int mystrlen(char *str); int main ( ) { cout « "Длина строки Привет вам! равна: 11; cout « mystrlen ("Привет вам!") ; return о; 
574 Приложение Д } // Собствен»ый вариант функции strlen(). int mystrlen(char *str) { int i; for(i=O; str[i]; i++) ; // найдем конец строки return i; } 5. Нет, значение локальной переменной теряется, коrда происходит выход из функции. (Или, в более общем виде, значение локальной пе ременной теряется, коrда происходит выход из ее блока.) 6. Основное преимущество rлобальных переменных заключается в том, что они доступны всем остальным функциям в проrpамме, и что они сушествуют в течение Bcero времени жизни проrpаммы. ИХ OCHOB ной недостаток заключается в том, что они потребляют память все время, пока проrpамма выполняется. Использование rлобальной пе ременной, коrда можно обойтись локальной, делает функцию менее универсальной, а использование большоrо числа rлобальных перемен ных может привести к неожиданным побочным эффектам. 7. #include <iostream> using namespace std; int seriesnum = о; int byThrees () ; void reset ( ) ; in t та in () { for(int i=O; i < 10; i++) cout « bYThrees () « 11 11; сои t « 11 \ n " ; reset(); for(int i=O; i < 10; i++) сои t « byThrees () « 11 11; cout « "\n"; return о; 
Ответы на Вопросы для самопроверки 575 } int byThrees ( ) { int t; t = seriesnumi seriesnum += зi return t; } void reset ( ) { seriesnuт -= о; } 8. #include <iostream> #include <cstring> using namespace std; int main{int argc, char *argv[]) { if{argc != 2) { cout « "Требуется пароль ! \n" ; return о; } i f ( ! s trcmp ( 11 mypassword 11, argv [ 1] ) ) cout « It Доступ разрешен. \n" ; else cout « "Доступ запрещен. \n"; return о; } 9. Справедливо. Прототип предотвращает вызов функции с непра вилъным числом apryмeHTOB. 10. #include <iostrearn> using namespace std; void printnurn (int n) ; 
576 Приложение Д int main ( ) { printnum (10) ; return о; } vo id printnum(int n) { if(n> 1) printnиm(nl); coиt « n « " "; } Модуль 6. Подробнее о функциях 1. ApryмeHT может быть передан в подпроrpамму по значению и по ссьтке. 2. Ссьтка является неявным указателем. Чтобы создать параметр ссьтку, надо предварить имя параметра знаком &. 3. f (ch, & i ) ; 4. #include <iostream> #include <cmath> using namespace std; void roиnd (doиble &nиm) ; int main ( ) { doиble i = 100.4; coиt « i « " окруrленное равно " . , roиnd(i) ; coиt « i « t8\n"; i = 10.9; coиt « i « l' окруrленное равно " . , roиnd(i) ; coиt « i « "\n"; return о; } 
Ответы на Вопросы для самопроверки void round(double &nит) { double frac; double val; / / разделим пит на целую и дробную части frac = modf(num, &val); if(frac < 0.5} пит = val; else nит = val+l.0; } 5. #include <iostream> using namespace std; // Обменять aprYMeHTbl и вернуть меньший. int & min.....swap (int &х, int &У); int main ( ) { int i, j, min; i = 10; j = 20; cout « "Исходные значения i и cout « i « I , « j « I \n' ; . J: 11; min = minswap(j, i}; cout « "Новые значения i и j: "; cout « i « I I « j « I \n I ; cout « "Меньшее значение: " «min « "\n"; return о; } // Обменять aprYMeHTbl и вернуть меньший. int &min.....swap(int &х, int &у) { int temp; // для обмена значений aprYMeHToB используем ссылки temp = х; х = у; у = temp; 
578 Приложение Д // вернем ссылку на меньший aprYMeHT if(x < у) return х; else return у; } 6. Функция не должна возвращать ссылку на локальную перемен нyIO, потому что коrда происходит выход из функции, локальная пере менная выходит из области видимости (т. е. перестает существовать). 7. Переrpуженные функции должны различаться типом или числом своих параметров (или и тем, и дрyrим). 8. /* Проект 61  модифицирован для раздела NВопросы для самопроверки" Создание переrруженных функций println(), которые выводят данные различных типов. Этот вариант включает параметр отступа. */ #include <iostream> using namespace std; // Эти функции выводят символ новой строки. void println(bool Ь, int ident=O) ; void println(int i, int ident=O); void println(long i, int ident=O); void println(char сЬ, int ident=O) ; void println(char *str, int ident=O); void println(double d, int ident=O); // Эти функции не выводят символ новой строки. void print(bool Ь, int ident=O); void print(int i, int ident=O); void print(long i, int ident=O); void print(char сЬ, int ident=O); void print(char *str, int ident=O); void print(double d, int ident=O); int main () { println(true, 10); println(10,5); рrintln("Это просто проверка "); println( 'х'); println(99L, 10); 
Ответы на Вопросы для самопроверки 579 println(123.23, 10); print("BoT несколько значений: "); print(false); print (88, 3); print(100000L, 3); print(100.01); println(" Выполнено!"); return о; } // Несколько функций println(). void println(bool Ь, int ident) { if(ident) for(int i=O; i < ident; i++) cout « ' '; if(b) cout « "true\n"; else cout « "false\n"; } void println(int i, int ident) { if(ident) for(int i=O; i < ident; i++) cout« ' '; cout « i « "\n 11 ; } void println(long i, int ident) { if(ident) for(int i=Oi i < ident; i++) cout « ' '; cout « i « "\n"; } void println(char сЬ, int ident) { if(ident) for(int i=O; i < ident; i++) cout « 1 1; cout « сЬ « "\n h ; } void println(char *str, int ident) 
580 Приложение Д { if(ident) for(int i=Oi i < identi i++) cout« ' 1; cout « str « "\n"; } void println(double d, int ident) { if(ident) for(int i=Oi i < identi i++) cout« 1 '; cout« d« "\n"; } // Несколько ФУНКЦИЙ print(). void print(bool Ь, int ident) { if(ident) for(int i=Oi i < ident; i++) cout« ' '; if(b) cout « "true"i else cout « "false"i } void print (int i, int ident) { if(ident) for(int i=Oi i < identi i++) cout « 1 , . , cout « ii } void print (long i, int ident) { if (ident) for(int i=Oi i < ident; i++) cout « 1 1 . , cout « i; } void print(char сЬ, int ident) { if(ident) for(int i=O; i < ident; i++) cout« 1 1; cout « сЬ; 
Ответы на Вопросы для самопроверки 581 } void print(char *str, int ident) { if(ident) for(int i=O; i < ident; i++) cout« 1 1; cout « str; } void print(double d, int ident) { if(ident) for(int i=Oi i < ident; i++) cout « 1 1; cout « d; } 9. myfunc('x'); myfunc('x', 19); myfunc('x', 19,35); 10. Переrpузка функций может привести к неоднозначности, если компилятор не может определить, какой вариант функции надо BЫ звать. Это может случиться, коrда выполняется автомаrnческое преоб разование типов, а также коrда используются apryмeHты по умолчанию. Модуль 7. Подробнее о типах даиныx и операторах 1. static int test = 100; 2. Справедливо. Описатель volatile сообщает компилятору, что пере менная может быть изменена какимилибо событиями вне проrpаммы. 3. Чтобы в мноrофайловом проекте один файл узнал о rлобальных переменных, объявленных в дрyrом файле, следует использовать опи сатель extern. 4. Наиболее важным свойством статической локальной переменной является ТО, что она сохраняет свое значение между вызовами функции. 5. // Использование статической переменной / / для подсчета числа вызовов функции. 
582 Приложение Д #include <iostream> using namespace std; int counter(); int main () { int result; for(int i=O; i<10; i++) result = counter(); cout « "Функция вызывалась 11 « resul t « 11 раз. 11 « 11 \n 11 ; return о; } int counter () { static count = о; count++; return count; } 6. Максимальный выиrpыш в производительности получится при объявлении реrистровой переменной х, затем у, затем z. Так будет по тому, что обращение к х осуществляется наиболее часто (поскольку она входит в цикл), реже осуществляется обращение к у, а z использу ется только коrда цикл инициализируется. 7. & является побитовым оператором, который воздействует на ин дивидуальные БитыI внутри значения. && является лоrическим опера тором, который воздействует на значения tmejfalse. 8. Указанное предложение умножает текущее значение х на 10 и присваивает результат той же переменной х. Предложение равносиль но следующему: х = х * 10; 9. // Использование циклических сдвиrов для шифрования сообщения. #include <iostream> #include <cstring> 
Ответы на Вопросы для самопроверки 583 using namespace std; unsigned char rrotate(unsigned char val, int n); unsigned char lrotate(unsigned char val, int n); void showbinary (unsigned int и) i int main ( ) { char msg[] = "This is а test."i char *key = "xanadu"; int klen = strlen (key) ; int rotnum; cout « "Исходное сообщение: 11 « msg « "\n"; // Шифруем сообщение циклическими сдвиrами влево. for(int i = О ; i < strlen(msg); i++) { /* Сдвиrаем влево каждую букву на число битов, определенное с помощью ключа. */ rotnum = key[i%klen] % 8; msg[i] = lrotate(msg[i], rotnum); } cout « "Зашифрованное сообщение: " «msg« "\n"; // Расшифровываем сообщение циклическими сдвиrами вправо. for(int i = О i i < strlen(msg)i i++) { /* Сдвиrаем вправо каждую букву на число битов, определенное с помощью ключа. */ rotnum = key[i%klen] % 8; msg[i] = rrotate(msg[i], rotnum); } cout « "Расшифрованное сообщение: " «msg« "\n"; return о; } // Циклический сдвиr байта влево на n позиций. unsigned char lrotate(unsigned char val, int n) { unsigned int ti t = val; for(int i=O; i < n; i++) { 
584 Приложение Д t = t « 1; /* Если бит выпадает, это будет бит 8 целочисленной переменной t. Если это так, поместим этот бит с правой стороны. */ if(t & 256) t = t I 1; // поместим 1 с правой стороны } return t; // вернем младшие 8 бит. } // Циклический сдвиr байта вправо на n позиций. unsigned char rrotate(unsigned char val, int n) { unsigned int t; t = val i // Прежде Bcero, сдвинем значение на 8 бит влево. t = t « 8; for(int i=O; i < n; i++) { t = t » 1; / * Если бит выпадает, это будет бит 7 целочисленной переменной t. Если это так, поместим этот бит с левой стороны. */ if(t & 128) t = t I 32768; / / поместим 1 с правой стороны } /* Теперь сдвинем результат назад в младшие 8 бит переменной t. */ t = t » 8; return t; } // Выведем биты байта. void show.....binary (unsigned int и) { int t; for(t=128; t>O; t = t/2) if(u & t) cout « "1 "; else cout « "О "; cout « "\n" i } 
Ответы на Вопросы для самопроверки 585 Модуль 8. Классы и объекты 1. Класс представляет собой лоrическую конструкцию, которая оп ределяет форму объекта. Объект является экземпляром класса. Таким образом, объект физически существует в памяти. 2. Для определения класса используется ключевое слово class. 3. Каждый объект содержит собственную копию переменнычле нов класса. 4. class Test { int count; int тах; / / ... } 5. Имя конструктора совпадает с именем класса. Имя деструктора также совпадает с именем класса, однако предваряется символом ....,. 6. Ниже приведены три способа создания объекта, при которых пе ременная i инициализируется числом 10: Sample оЬ (10) ; Sample оЬ = 10; Sample оЬ = Sample(10)i 7. Если функциячлен объявлена внутри объявления класса, она" если возможно, делается встроенной. 8. // Создание класса Triangle. #include <iostream> #include <cmath> using namespace std; class Triangle { double height; double base; public: Triangle(double Ь, double Ь) { height = Ь; base = Ь; } double hypot() { return sqrt(height*height + base*base)i } 
586 Приложение Д double area () { return base * height / 2.0; } } ; int main () { Triangle tl(З.О, 4.0); Triangle t2(4.5, 6.75); cout « "rипотенуза треуrольника tl: " « tl . hypot () « "\ n" ; cout « "Площадь треуrольника tl: " « tl.area() « "\n"; cout « "rипотенуза треуrольника t2: " « t2 .hypot () « "\n"; cout « "Площадь треуrольника t2: " « t2.area() « "\n"; return о; } 9. /* Расширенный Проект 81 Добавление к классу Help идентификатора пользователя. */ #include <iostream> using namespace std; // Класс, инкапсулирующий справочную систему. class Help { int userID; public: Help(int id) { userID = id; } Help() { cout « "Справочник завершается для #" « userID « ". \n"; } int getID () { return userID; } void helpon(char what); void showmenu ( ) ; bool isvalid(char сЬ); } ; 
Ответы на Вопросы для самопроверки 587 // ВЫВОД справочной информации. void Help: :helpon(char what) { switch(what) { case '1': cout « "Предложение if:\n\n"; cout « "if(условие) предложение;\п"; cout « "else предложение;\п"; break; case '2': cout « 11 Предложение swi tch: \п \п" ; cout « "switсh(выражение) {\п"; cout « " case константа:\п"; cout « 11 последовательность предложений\п 11 ; cout « " break; \n"; cout« " // .. .\п"; cout« "}\п"; break; case I 3 ' : cout « "ЦИКЛ for: \п \п" ; cout « 11 for (инициализация; условие; приращение) "; cout « " предложение;\п"; break; case I 4 ' : cout « "ЦИКЛ while:\n\n"; cout « "whilе(условие) предложение;\п"; break; case '5': cout« "ЦИКЛ dowhile:\n\n"; cout « "do {\п"; cout «" предложение; \п"; cout « "} whi le (условие); \п" ; break; case I 6 ' : cout « "Предложение break:\n\n"; cout « "break; \п" ; break; case '7': cout « "Предложение continue:\n\n"; cout « "continue;\n"; break; case I 8' : cout « "Предложение goto:\n\n"; cout « "goto метка;\п"; break; } cout « "\n" i 
588 Приложение Д } // Вывод на экран меню справочной системы. void Help::showmenu() { cout « "Справка по: \n" ; cout « 11 1 . if\n"; cout « " 2. swi tch \n" ; cout « " 3 . for\n"; cout « " 4 . while\n" ; cout « " 5 . dowhile\n" ; cout « " 6 . break\n" ; cout « " 7 . continue\n"; cout « " 8 . goto \n" i cout « "Выберите один из пунктов (q для завершения) : " . , } // Возвращает true, если выбор допустим. bool Help: :isvalid(char сЬ) { i f (сЬ < ' l' I I сЬ > ' 8' && сЬ ! = ' q' ) return false; else return true; } int main () { char choice; Help ЫроЬ(27); // создание экземпляра класса Help. cout « "ID пользователя: " « hlpob.getID() « " . \п " ; // Используем объект Help для вывода информации. f or ( ; ;) { ао { hlpob.showmenu(); cin » choice; } while(!hlpob.isvalid(choice»; if(choice == 'q') break; cout« "\n"; hlpob.helpon(choice)i } return о; } 
Ответы на Вопросы для самопроверки 589 Модуль 9. Подробнее о классах 1. Конструктор копий создает копию объекта. Он вызывается, KO rда один объект инициализирует дрyrой. Вот ero общая форма: имякласса (const имякласса &объект) { //тело конструктора } 2. Коrда объект возвращается функцией, создается временный объ ект для использования ero в качестве возвращаемоrо значения. После тoro, как значение возвращено, этот объект уничтожается дeCТPYКTO ром объекта. 3. in t s ит ( ) { return this.i + this.j; } 4. Структура..... это класс, в КОТОРОМ все члены по умолчанию объяв ляются открытыми. Объединение ..... это класс, в котором все дaнHыe члены располаrаются в одной и той же памяти. Члены объединения таюке объявляются открытыми по умолчанию. 5. *this относится к объекту, для Koтoporo вызвана эта функция. 6. Дружественная функция..... это Функцияне член класса, которой предоставлен дос1УП к закрытым членам класса, по отношению к KO торому она является дрyrом. 7. тип имякласса: :operator#(тun oпepaHд2) { / / левый операнд предается посредством "this" } 8. для Toro чтобы моrли выполняться операции между классовым и встроенным типами, вы должны использовать две дружественные опе раторные функции, одну с классовым типом в качестве первоrо пара метра, а дрyryю со встроенным типом в качестве первоrо параметра. 9. Нет, оператор? не может быть переrpужен. Нет, вы не можете изменить относительный приоритет оператора 10. / / Определим, является ли данное множество подмножеством друrоrо. bool Set: : oppr'dtor < (Set оЬ2) { 
590 Приложение Д if (len > ob2.1en) return fa1se; / / в оЫ больше элементов for(int i=O; i < 1еn; i++) if (оЬ2. find(members [i]) == 1) return fa1se; return true; } / / Определим, является ли данное множество надмножеством дpyroro. 0001 Set: : operator > (Set оЬ2) { if (len < ob2.1en) return fa1se; / / в оЫ меньше элементов for(int i=O; i < ob2.1en; i++) if (find(ob2 .members [i]) == 1) return false; return true; } 11. // Установим пересечение. Set Set: :operator &(Set оЬ2) { Set newset; // Добавим элементы, общие для обоих множеств. for{int i=O; i < len; i++) if(ob2.find(members[i]) 1= 1) // добавим, если элемент //есть в обоих множествах newset = newset + members[i]; return newset; // return set } Модуль 1 о. Наследование, виртуальные функции и полиморфизм 1. Класс, который наследуется, называется базовым классом. Класс, который наследует, называется ПРОИЗВОllНым классом. 2. Базовый класс не имеет доступа к членам производных классов, потому что базовый класс ничеrо не знает о производных классах. Про изводный класс имеет доступ к незакрытым членам базовоrо класса (или классов). 
Ответы на Вопросы для самопроверки 591 3. / / Класс Kpyra. class Circle : public TwoDShape { public: Circle(double r) : TwoDShape(r) { } // зададим радиус аоиЫе area () { return getWidth () * getWidth () * 3.1416; } } ; 4. Для предотвращения доступа производноrо класса к члену базо Boro класса объявите этот член закрытым в базовом классе. 5. Вот общая форма конструктора, который вызывает конструктор базовоrо класса: проuзводный"ласс( ) : базовый"ласс ( ) { / /... 6. Конструкторы вызываются в порядке наследования. Поэroму, KO rда создается объект Gamma, конструкторы вызываются в таком поряд ке: Alpba, ВеЬ, Gamma 7. К защищенному (protected) члену базовоrо класса может обра титъся ero собственный класс и производные классы. 8. Коrда виртуальная функция вызывается посредством указателя базовоrо класса, то тип объекта, на который фактически указывает этот указатель, определяет, какой вариант функции будет вызван. 9. Чистая виртуальная функция  это функция, у КОТОРОЙ в ее базо вом классе нет тела. Поэтому чистая виртуальная функция должна быть переопределена в производных классах. Абстрактным называется класс, коroрый содержит по крайней мере одну чистую виртуальную функцию. 10. Нет, абстрактный класс нельзя исполъзоватьдля со3llЗНИЯ объекта. 11. Чистая виртуальная функция представляет собой родовое опре деление, которому должны следовать все реализации этой функции. Во фразе "один интерфейс, множество методов" чистая виртуальная функция представляет интерфейс, а ее индивидуальные реализации представляют методы. Модуль 11. С++ и система BBoa -ВЬlвоАа 1. Предопределенными потоками являются cin, cout, cerr и clog. 2. Да, С++ определяет символьные потоки и для 8битовых, И для "широких" символов. 
592 Приложение Д 3. Общая форма переrpуженноrо оператора вставки выrлядит сле дующим образом: ostream &operator«ostream &stream, типкласса obj) { // Здесь размещается специфический код класса return stream; // возврат stream } 4. ios::scientific определяет вывод числовых значений в экспоненци альной форме. 5. Функция width( ) устанавливает ширину поля вывода. 6. Справедливо. Манипyляroр BвoдaBывoдa используется внyrpи BЫ ражений BвoдaBывoдa. 7. Вот один из способов открытия файла для ввода текста: ifstream in("test"); if(lin) { cout « "Не Mory открыть файл. \n" ; return 1; } 8. Вот один из способов открытия файла для вывода текста: ofstream out("test"); f ( ! out) { cout « "Не Mory открыть файл.\n"; return 1; } 9. ios::binary указывает, что файл открывается для двоичноrо, а не TeKcToBoro BBoдaBЫBoдa. 10. Справедливо. Коrда достиrается конец файла, переменная по тока становится равной false. 11. while (strm.get (ch) / / ... 12. Эта задача имеет множество решений. Вот только одно из них: // Копирование файла. #include <iostream> #include <fstream> using namespace std; iht main(int argc, char *argv[]) { char сЬ; i f (argc! =3) { cout « "Использование: сору <источник> <приемник>\n"; 
Ответы на Вопросы для самопроверки 593 return 1; } ifstream src(argv[1], ios::in I ios::binary}; if(!src) { cout « "Не MOry открыть файлисточник.\n"; return 1; } ofstream targ(argv[2], ios: :out I ios: :binary); i f ( ! targ) { cout « "Не Mory открыть файлприемник. \n"; return 1; } do { src.get(ch); if(!src.eof(» targ.put(ch); } while(!src.eof(»; src.close(); targ.close(}; return о; } 13. Эта задача имеет множество решений. Вот одно из простых: // Слияние файлов. #include <iostream> #include <fstream> using namespace std; int main(int argc, char *argv[]) { char сЬ; i f (argc ! = 4) { cout « .Использование: merge <источник1> <источник2> <приемник>\n"; return 1; } ifstream srcl(argv[1], ios::in I ios::binary); if(!src1) { cout « "Не Mory открыть 1й файлисточник. \n"; 
594 Приложение Д return 1; } ifstream src2(argv[2], ios: :in I ios: :binary); i f ( ! src2) { cout « "Не Mory открыть 2й файлисточник. \n"; return 1; } ofstream tаrg(аrgv[З], ios: :out I ios: :binary); i f ( ! targ) { cout « "Не Mory открыть файлприемник. \n"; return 1; } // Копируем первый файлисточник. do { srcl.get(ch); if(!src1.eof(» targ.put(ch); } while(!src1.eof(»; // Копируем второй файлисточник. do { src2 . get (ch) ; if(!src2.eof(» targ.put(ch); } while(!src2.eof(»; srcl.close(); src2.close(); targ.close(); return о; } 14. МуStrm.sееkg(ЗОО, ios::beg); Модуль 12. Исключения, шаблоны и друrие допол- нительныe TeMbl 1. Обработка исключений в С++ основана на трех ключевых сло вах: try, catcb и tbrow. В самых общих чертах обработка исключений op rанизуется следующим образом. Проrpаммные предложения, которые 
Ответы на Вопросы для самопроверки 595 вы хотите контролировать на предмет исключений, содержатся в бло ке try. Если внутри блока try возникает исключение (т. е. ошибка), BЫ брасывается исключение (с помощью tbrow). Исключение ловится с помощью catch и обрабатывается. 2. Если вы хотите ловить исключения как базовоrо, так и производ ных классов, в списке catch сначала должны идти производные клас сы, а за ними  базовый. 3.void func{ ) throw{MyExcpt) 4. Вот один из способов добавления исключения к классу Queue. Эrо только одно из мноrих возможных решений: /* Добавление исключения в Проект 121 Шаблонный класс очереди. */ #include <iostream> #include <cstring> using namespace stdi // Это исключение, выбрасываемое Queue в случае ошибки. class QExcpt { public: char msg [ 8 О] ; } ; const int maxQsize = 100; // Далее создается родовой класс очереди. template <class ОТуре> class Queue { QType q[maxQsize]i // массив для хранения очереди int size; // максимальное число элементов, // которые MorYT находиться в очереди int putloc, getloci // индексы "положить" и "взять" QExcpt Qerr; // добавим поле исключения public: // Сконструируем очередь конкретной длины. Queue{int len) { // Размер очереди должен быть меньше тах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } 
596 Приложение Д // Поместим данное в очередь. void put(QType data) { if(putloc == size) { strcpy(Qerr.msg, "Очередь полна.\п"); throw Qerr; } putloc++; q[putloc] = data; } // Извлечем данное из очереди. аТуре get () { if(getloc == putloc) { strcpy(Qerr.msg, "Очередь пуста.\п"); throw Qerr; } getloc++; return q[getloc]; } } ; // Демонстрация родовоrо класса аиеие. int main () { // заметьте, что iQa имеет только 2 элемента Queue<int> iQa(2), iQb(lO); try { iQa.put(l); iQa.put(2); iQа.рut(З); // здесь будет переполнение! iQb.put(lO); iQb.put(20); iQЬ.рut(ЗО); cout « "Содержимое целочисленной очереди iQa: "; for(int i=O; i < з; i++) // здесь будет антипереполнение! cout « iQa.get() « " "; cout « endl; cout « " Содержимое целочисленной очереди iQb: "; for(int i=O; i < з; i++) cout « iQb.get() « " "; cout « endl; 
Ответы на Вопросы для самопроверки 597 Queue<double> dQa (10), dQb (10) i / / создадим две очереди / / double dQa.put(l.Ol); dQa.put(2.02); dQа.рut(З.ОЗ)i dQb.put(lO.Ol); dQb.put(20.02); dQЬ.рut(ЗО.О3); cout « "Содержимое очереди double dQa: "; for(int i=O; i < з; i++) cout « dQa.get() « " "; cout « endl; cout « "Содержимое очереди double dQb: "; for(int i=O; i < з; i++) cout « dQb.get() « " "; cout « endl; } catch(QExcpt ехс) { cout « eXc.msgi } return о; } 5. Родовая функция определяет обобщенную форму подпроrpаммы без указания типов данных, с которыми она работает. Родовая Функ ция создается с помощью ключевоrо слова template. 6. Вот один из способов пр е образован ия quicksort( ) и qs( ) в poдo вые функции: // Родовая функция Quicksort. #include <iostream> #include <cstring> using namespace std; // Зададим вызов фактической функции упорядочения. template <class Х> void quicksort(X *items, int len) { qs(items, О, lenl); } // Родовой вариант Quicksort. template <class Х> void qs(X *items, int left, int right) 
598 Приложение Д { int i, j; Х х, у; i = left; j = right; х = items[( left+right) /2]; do { while«items[i] < х) && (i < right») i++; wh i 1 е ( (х < i t ems [ j ]) && (j > 1 е f t» j   ; if(i <= j) { у = items[i]; items[i] = items[j]; i tems [j] = у; i++; j; } } while(i <= j); if(left < j) qs(items, left, j); if(i < right) qs(items, i, right); } i n t та i n () { / / упорядочение символов char str[] = "jfmckldoelazlkper"; int i; cout « "Исходный массив: " « str « "\п"; quicksort(str, strlen(str»; cout « "Упорядоченный массив: " « str « "\п"; / / упорядочение целых in t nums [] = { 4, 3, 7, 5, 9, 8, 1, 3, 5, 4 }; cout « "Исходный массив: "; for(int i=O; i < 10; i++) cout « nums[i] « " "; cout « endl; quicksort(numS,10); cout « "Упорядоченный массив: "; for(int i=O; i < 10; i++) cout « nums[i] « " "; cout « endl; 
Ответы на Вопросы для самопроверки 599 return о; } 7. ВОТ ОДИН из способов поместить в Queue объекты Sample: /* Использование Проекта 121 для хранения объектов Sample. Шаблонный класс очереди. */ #include <iostream> using namespace std; class Sample { int id; public: Sample () { id = о; } Sample (int х) { id = х; } void show () { cout « id « endl; } } ; const int maxQsize = 100; // Далее создается родовой класс очереди. template <class QТype> class Queue { QType q[maxQsize]; // массив для хранения очереди int size; // максимальное число элементов, // которые MorYT находиться в очереди int putloc, getloc; // индексы "положить" и "взять" public: // Сконструируем очередь конкретной длины. Queue(int len) { // Размер очереди должен быть меньше mах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } // Поместим данное в очередь. void put(QТype data) { if(putloc == size) { cout « "  Очередь полна. \n" ; return; } 
600 Приложение Д putloc++; q[putloc] = data; } // Извлечем данное из очереди. ОТуре get () { if(getloc == putloc) { cout « "  Очередь пуста. \п" j return о; } getloc++; return q[getloc]; } } ; // Демонстрация родовоrо класса аиеие. int main ( ) { Queue<Sample> sampQ(3); Sample 01 (1), 02 (2) , 03 (3) ; sampQ.put(ol); sampQ.put(02)i sampQ. put (03) ; cout « "Содержимое sampQ:\n"; for(int i=O; i < 3; i++) sampQ.get() .show(); cout « endl; return о; } 8. Динамическое въшеление памяти для объектов Sample: /* Использование Проекта 121 для хранения объектов Sample. Динамически выделяем память под объекты Sample. Шаблонный класс очереди. */ #include <iostream> using namespace stdj class Sample { int id; 
Ответы на Вопросы для самопроверки 601 public: Sample() { id = о; } Sample (int х) { id = Х; } void show() { cout « id « endl; } } i const int maxQsize = 100; // Далее создается родовой класс очереди. template <class аТуре> class аиеие { аТуре q[maxQsize); // массив для хранения очереди int size; // максимальное число элементов, // которые MorYT находиться в очереди int putloc, getloc; // индексы "положить" и "взять" public: // Сконструируем очередь конкретной длины. Queue(int len) { // Размер очереди должен быть меньше тах и положителен. if(len > maxQsize) len = maxQsize; else if(len <= О) len = 1; size = len; putloc = getloc = о; } // Поместим данное в очередь. void put(QType data) { if(putloc == size) { cout « "  Очередь полна. \n" ; return; } putloc++; q[putloc] = datai } / / Извлечем данное из очереди. аТуре get () { if(getloc == putloc) { cout « "  Очередь nyста. \n" ; return о; } getloc++; return q[getloc]; } 
602 Приложение Д } ; // Демонстрация родовоrо класса аиеие. int main () { Queue<Sample> sаmрQ(З); Sample *рl, *р2, *рЗ; pl = new Sample(l); р2 = new Sample(2); рЗ = new Sаmрlе(З); sampQ. put (*рl) ; sampQ. put (*р2) ; sampQ. put (*рЗ) ; cout « "Содержимое sampQ: \n" ; for(int i=O; i < 3; i++) sampQ.get() .show(); сои t « endl; delete(pl); delete(p2); delete(p3); return о; } 9. Для объявления пространства имен под названием RobotMotion следует использовать такую конструкцию: namespace RobotMotion { // } 10. Стандартная библиотека С++ содержится в пространстве имен std. 11. Нет, статическая функциячлен не имеет доступа к нестатиче ским данным класса. 12. Для получения типа объекта во время выполнения проrpаммы следует использовать оператор typeid. 13. Для определения допустимости полиморфноrо приведения во время выполнения следует использовать dynamiccast. 14. constcast отменяет в операции приведения const и volatile. 
Приложение В Препроцессор 
604 Приложение В П реnроцессор является частью компятора, которая выполняет различ ные манипуляции с текстом вашеи проrpаммы еще до Toro, как нач нется фактическая трансляция вашеrо исходноrо кода в объектный код. Вы можете передавать препроцессору команды манипуляции с текстом. Эти команды носят название директив nреnроцессора, и хотя они формально не являются частью языка С++, они расширяют воз можности среды проrpаммирования. Препроцессор является в какойто степени пережитком, OCTaB шимся от С; дЛЯ С++ он не так важен, как дЛЯ С. к тому же, HeKOТO рые средства препроцессора бьти замещены более новыми и лучшими элементами языка С++. Однако поскольку мноrие проrpаммисты все еще используют препроцессор, и поскольку он все еще является ча стью среды языка С++, мы кратко обсудим ero в этом приложении. Преnpоцессор С++ содержит следующие директивы: #deflne #error #Include #If #else #ellf #endlf #Ifdef #Ifndef #undef #llne #pragma Мы видим, что все директивы препроцессора начинаются со знака #. Рассмотрим каждую из них в отдельности. #define Директива #de6ne используется для определения иденrnфикатора, а также символьной последовательности, кoroрой будет заменяться этот идентификатор каждый раз, коrда он будет встречаться в тексте проrpам мы. Идентификатор носит название имени макроса, а процесс замены Ha зывается макроподстановкой. Общая форма этой директивы такова: #define имямакроса символьная"'nоследовательность Обратите внимание на отсyrствие точки с запятой в конце этоrо предложения. Между идентификатором и началом символьной после довательности может быть любое число пробелов, но после тoro, как символьная последовательность началась, она может закончиться только переходом на следующую строку. Если, например, вы хотите использовать слово "UP" для обозначе ния числа 1, а слово "DOWN" дЛЯ обозначения числа О, вам надо объ явить с помощью директивы #define два таких макроса: #define UP 1 #define DOWN О 
Препроцессор 605 Эти предложения заставят компилятор подставлять 1 или О всякий раз, коrда в вашем исходном файле встретятся имена UP или DOWN. Например, следующая строка выведет на экран 1 О 2: сои t « UP « I I « DOWN « I I « UP + UP i Важно отдавать себе отчет в том, что макроподстановка представ ляет собой просто замену идентификатора соответствующей символь ной строкой. Так, если вы захотите определить стандартное сообще ние, вы можете написать чтонибудь вроде этоro: #define GETFILE "Введите имя файла" // ... cout « GETFILEi С++, встретив имя GETFILE, подставит вместо Hero строку "BBe дите имя файла". Для компилятора предложение cout будет фактиче ски выrлядеть так: cout « "Введите имя файла"; Если имя макроса встречается внутри текстовой строки в кавычках, то макроподстановка не npоизводится. Например, #define GETFILE "Введите имя файла" // ... cout « "GETFILE представляет собой имя макроса \n" ; не выведет Введите имя файла представляет собой имя макроса в действительности на экран будет выведено следующее: GETFILE представляет собой имя макроса Если символьная строка не умещается на одной строке текста, вы можете перенести продолжение на следующую строку, поместив знак обратной косой чертыI в конце строки, как это показано ниже: #de f ine LONG....STRING "это очень I очень длинная строка, \ которая используется здесь в качестве примера- Проrpaммисты на С++ обычно исполъзyIOr для имен макросов про шlсныIe буквы. Эro соrлашение помоraeт ЧИIaIOщему проrpам:му сразу по нять, rде будут вьmOJПlЯТЬCЯ макроподС1аНОВКИ. Также имеет смысл раз местить все диреК1ИВы #define в начале файла или даже вьшелить их в or дельный "включаеМЫЙ" файл, а не разбрасывать их по тексту проrpаммы. 
606 Приложение В Последнее замечание: с++ предоставляет лучший способ для опре... деления констант, чем использование директивы #detine. Однако мно", rие проrpаммисты пришли к с++ от с, rде для определения констант используется именно #de6ne. Так что вы будете часто сталкиваться с этой директивой и в проrpаммах на с++. Макросы, подобные функциям Директива #de6ne имеет еще одно свойство: имя макроса может со... провожцаться арryментами. Каждый раз, коrда в тексте проrpаммы встречается имя макроса, связанные с ним apryмeHTЫ заменяются фак", тическими арryментами, указанными в макровызове. В результате соз... дается макрос, подобный функции. Вот пример TaKoro макроса: // Использование макроса, подобноrо функции. #include <iostream> using namespace std; #define MIN(a,b) «(а)«Ь)) ? а Ь) int main () { int х, у; х = 10; У = 20; cout « "Меньшее значение: tI « MIN (х, у); return о; } Коrда эта проrpамма компилируется, в нее подставляется выраже... ние, определенное макросом MIN(a,b), но только в качестве операн... дов этоrо выражения будут использованы х и у. Таким образом, пред'" ложение cout после подстановки будет выrлядеть таким образом: cout « " Меньшее значение: " « «х) < (у)) ? х : у); в сущности, макрос, подобный функции, представляет собой спо... соб определить встроенную (inline) функцию, код которой не вызыва... ется, а расширяется внyrpи проrpаммы. Излишние, на первый взrляд, крyrлые скобки, окружающие опреде... ление макроса MIN, в действительнOCIИ необходимы, пoroму что они обеспечивaюr правильное вычисление подставляемоro выражения с уче... том относительных приоритетов операторов. Фактически все макроопре... 
Препроцессор 607 деления с арryмента.ми должны содержать в дополнительные крyrлые скобки. В противном случае вы можете получить весьма удивительные результаThI. Например, рассмотрим короткую проrpамму, которая с по мощью макроса определяет, является ли значение четным или нечетным: // Эта nporpaNNa даст неверный ответ. #include <iostream> using namespace std; #define EVEN(a) а%2==О ? 1 О int main () { if(EVEN(9+1» cout« "чеТНО"i else cout « "нечетно"; return о; } эта проrpамма даст неверный ответ, что будет понятно, если pac смотреть процедуру макрорасширения. При компиляции выражение EVEN (9+ 1) будет расширено в 9+1%2==0 ? 1 : О Как вы, возможно, помните, оператор деления по модулю % имеет более высокий относительный npиоритет, чем оператор плюс. Это значит, что операция % будет выполнена не над 9+1, а над 1, а ее ре... зультат будет прибавлен к 9, что (конечно) не равно О. Для тoro чтобы избавиться от неправильной замены, необходимо заключить в крyrлые скобки параметр а в макроопределении EVEN, как это показано в ис правленном варианте проrpаммы: / / Теперь эта проrрамма будет работать правильно. #include <iostream> using namespace std; #define EVEN(a) (а)%2==0 ? 1 О int main () { if(EVEN(9+1» cout« "четно"; else cout « "нечетно": return о; } 
608 Приложение В Теперь выражение 9+ 1 вычисляется до операции деления по MOДY лю. Вообще полезно всеrда заключать параметры макросов в крyrлые скобки, чтобы избежать неприятностей вроде только что описанной. Использование макросов вместо настоящих функций имеет одно преимущество: поскольку макрорасширение включается прямо в текст проrpаммы, не возникает никаких издержек, связанных с вызовами функций, поэтому скорость проrpаммы возрастает. Однако за это воз растание скорости приходится платить увеличением размеров про фаммы, связанным с дублированием проrpаммных строк. Хотя макросы можно часто увидеть в проrpаммах на с++, появле ние описателя inline, выполняющеrо ту же задачу, но лучше и надеж нее, сделало макросы, подобные функциям, абсолютно излишним средством. (Вспомним, что inline заставляет компилятор включать текст функции прямо в проrpаммный код вместо орrанизации проце дуры ее вызова.) К тому же встроенные функции не требуют дополни тельных скобок, обязательных для большинства макросов. Однако макросы, подобные функциям, несомненно будут еще долrо исполь зоваться в С++проrpаммах, потому что мноrие проrpаммисты, начи навшие с языка с, продолжают пользоваться ими по привычке. #error Директива #error, встретившись в тексте проrpаммы, заставляет компилятор прекратить компиляцию. Эта директива используется в основном в целях отладки. Общая ее форма такова: #error сообщенuеобошuбке Обратите внимание на отсyrcтвие кавычек BOкpyr строки с сообщени ем. Коrда компилятор встречает Э1У дирекrnву, он выводит сообщение об ошибке и дрyryю информацию и прекращает компиляцию проrpзммы. какая именно информация будет выведена, зависит от реализации Ba шей среды проrpаммирования. (Вы можете поэкспериментировать со своим компилятором и посмотреть, что будет выведено.) #include Директива препроцессора #include требует, чтобы компилятор под ключил к тексту проrраммы, содержащей директиву #include, либо стандартный заrоловок, либо дрyrой файл с исходным текстом. Имя стандартноrо заrоловка заключается в уrловые скобки, как это мы дe лали во всех проrpаммах этой книrи. Например, #include <fstream> 
Препроцессор 609 подключает стандартный заroловок для файловых операций BBoдaBЫ'" вода. При подключении дpyroro исходноrо файла ero имя должно заклю чаться в двойные кавычки или в yrловые скобки. Например, обе при веденных ниже строки #include <sample.h> #include "sample.h" требуют от компилятора чтения и компиляции файла sample.h. При подключении файла с помощью директивы #include окружаю... щие имя файла знаки  кавычки или yrловые скобки  определяют способ поиска указанноrо файла. Если имя файла заключено в yrло вые скобки, компилятор сначала ищет ero в одном или нескольких каталоrзх, определенных заранее в среде проrpаммирования. Если имя файла заключено в кавычки, компилятор ищет ero в дрyrом кa талоrе, тоже определенном в среде проrpаммирования. Обычно этим каталоrом назначается текущий рабочий каталоr. Если файл в этом каталоrе не найден, дальнейший поиск производится так же, как если бы имя файла было заключено в yrловые скобки. Поскольку пути поиска зависят от реализации среды проrpаммирования, вам придется свериться с руководством пользователя для вашеrо компи", лятора. Директивы условной КОМПИЛЯЦИИ Среди директив препроцессора имеются несколько директив, KOТO рые позволяют вам выборочно компилировать определенные части Ba шеrо проrpаммноro кода. Этот процесс, называемый условной компи... ляцuей, широко используется проrpаммистскими фирмами, разраба тывающими и поддерживающими различные специализированные варианты одной проrpаммы. #if, #eI5e, #elif и #endif Обшая идея, воплощенная в директиве #If, заключается в том, что если константное выражение, следующее за тс, истинно, тоrда код между ним и директивой #endif компилируется; в противном случае этот код опускается. Директива #епdifиспользуется для тoro, чтобы OT метить конец блока #if. 
610 Приложение В Общая форма #if такова: #if "онстантноевыражение последовательность предложений #endif Если константное выражение справедливо, последовательность предложений будет компилироваться; в противном случае эти строки пропустятся. Например: // Простой пример использования директивы #if. #include <iostream> using namespace std; #define МАХ 100 int main () { #if МАХ>10 cout < "Требуется дополнительная память.\n"; #endif / / ... return о; } Эта проrpамма выведет на экран сообщение о нехватке памяти, по тому что, как это определено в проrpамме, МАХ больше 10. Этот при мер иллюстрирует важный момент. Выражение, следующее за #if, вы числяется во время "омпшяции. Поэтому оно может содержать только константы или предварительно определенные идентификаторы. Пере... меННые здесь использовать нельзя. Директива #else действует практически так же, как и предложение else, являющееся элементом языка С++: она определяет альтернативу, если директива #if дает отрицательный результат. Предыдущий при мер можно расширить, включив в Hefo директиву #else, как это пока зано ниже: // Простой пример использования директив #if/#else. #include <iostream> using namespace std; #define МАХ 6 int main () 
Препроцессор 611 { #if МАХ>10 cout« "Требуется дополнительная паМЯТЬ.\n")i #else cout « "Наличной памяти достаточно.\n"; #endif // ... return о; } в этой профамме константа МАХ определена как 6, т. е. меньше 10, поэтому часть кода после #if не компилируется, но зато компили руется альтернативный блок, стоящий после #else. В результате на эк ран выводится сообщение "Наличной памяти достаточно". Заметьте, что директива #endif завершает как блок #if, так и (в дан... ном случае) всю конструкцию #if/#else. Как в том, так и в дрyroм слу... чае использование #endif необходимо, причем с каждой директивой #if может быть связана только одна директива #endif. Директива #еlif обозначает "else if' и используется для образования цепочки ifеlseifпри наличии нескольких вариантов компиляции. Если выражение истинно, тоrда компилируется именно этот проrpаммный блок, а остальные выражения #eUf не анализируются и не компилиру ются. В противном случае npoверяется следующее условие в цепочке. Обшая форма все конструкции выrлядит таким образом: #if выражение последовательность предложений #elif выражение 1 последовательность предложений #elif выражение2 последовательность предложений #elif выражение3 последовательность предложений / /... #elif выражениеN последовательность предложений #endif Приведенный ниже фраrмент использует значение сомрпЕО BY дЛЯ тooo чтобы определить, кто компилировал данную проrpамму: #define JOHN О #define ВОВ 1 #de f ine ТОМ 2 #define COMPILEDBY JOHN 
612 Приложение В #if COMPILEDBY == JOHN char who [] = .. Джон" ; #elif COMPILEDBY == ВОВ char who [] = "Боб"; #else char who [] = "Том"; #endif Директивы #if и #elif MOryт быть вложенными. В этом случае #endif, #else или #eBf связываются с ближайшим #if или #elif. Так, приведен ный ниже фраrмент вполне правилен: #if COMPILEDBY == ВОВ #if DEBUG == FULL int port = 198; #elif DEBUG == PARTIAL int port = 200; #endif #else cout « "Боб должен компилировать с отладочным выводом. \n"; #endif #ifdef и #ifndef Дрyrой способ условной компиляции использует директивы #lfdef и #ifndef, которые означают "if defined" (если определено) и "if not defined" (если неопределено) соответственно и относятся к именам макросов. Общая форма директивы #ifdef: #ifdef имямаJ(,роса последовательность предложений #endif Если макрос с указанным именем бьт предварительно определен в предложении #detine, последовательность предложений между #ifdef и #endif компилируется. Общая форма директивы #ifndef: #ifndef имямаJ(,роса последовательность предложений #endif Если макрос с указанным именем не бьт предварительно опреде лен в предложении #define, то блок предложений компилируется. Обе 
Препроцессор 613 директивы, и #ifdef и #ifndef, MOryт использовать предложения #else и #elif. Вы также можете вкладывать директивы #ifdef и #ifndef тем же об... разом, что и директивы #if. #undef Директива #undеfиспользуется для тoro чтобы orменить указанное ра... нее определение имени макроса. Общая форма эroй диреК'IИВЫ такова: #undef uмя"'макроса Рассмотрим такой пример: #define TIMEOUT 100 #define WAIT О // ... #undef TIMEOUT #undef WAIT Здесь TIMEOUТ и WAIT определены до тех пор, пока в тексте про... фаммы не встретятся предложения #Undef. Основное назначение пред... ложений #Undef  оrpаничить область действия имени макроса теми частями проrpаммы, rде оно используется. Использование defined Помимо директивы #ifdef, имеется еще один способ узнать, опреде... лено ли имя макроса. Вы можете использовать директиву #if в сочета... нии с оператором времени компиляции defined. Например, для тoro, чтобы узнать, определен ли макрос МYFILE, вы можете использовать любую из этих команд препроцессора: #if defined MYFILE и #ifdef MYFILE Вы также можете предварить ключевое слово defined знаком !, что... бы обратить условие. Например, следующий фраrмент будет компили... роваться, только если DEBUG определено: #if !defined DEBUG cout « "Окончательная версия! \n"; #endif 
614 Приложение В #Iine Директива #line используется, чтобы изменить содержимое предо пределенных макросов  LINE  и  FILE . Макрос  LINE  содержит номер компилируемой строки, а макрос  FILE  содержит имя компилируемоrо файла. Основная форма директивы #Iine такова: #line число "имяфайла" Здесь число представляет собой любое положительное целое, а He обязательный параметр uм.яфайла обозначает любой допустимый идентификатор. Указанно число становится номером текущей строки исходноrо кода, а указанное имя файла становится именем исходноrо файла. Директива #'ine используется rлавным образом в целях отладки и в специальных приложениях. #pragma Директива #pragma поразному определяется в конкретных реализа циях сред проrpаммирования. Она позволяет предавать компилятору различные инструкции, состав которых определил разработчик данноro компилятора. Общая форма директивы #pragma такова: #pragma имя Здесь имя определяет требуемое действие. Если имя не распознается компилятором, тоrда директива #pragma попросту иrнорируется; такая ситуация не является ошибкой. Чтобы узнать, какие действия можно затребовать с помощью дирек тивы #pragma, обратитесь к документации по вашему компилятору. Вы можете найти вариантыI, которые вам приrодятся при разработке про фамм. Типичные инструкции, задаваемые директивой #pragma, опреде ляют состав выводимых компилятором предупреждений, вид reнери pyeMoro кода и имя используемой библиотеки. Операторы препроцессора # и ## с++ поддерживает два оператора препроцессора: # и ##. Эти опе раторы используются в сочетании с директивой #define. Оператор # 
Препроцессор 615 образует пару кавычек BOкpyr следующеrо за ним apryмeнтa. PaCCMOT рим такую проrpамму: #include <iostream> using namespace std; #define rnkstr(s) # s int main () { cout« rnkstr(МEe нравится С++); return о; } Препроцессор С++ преобразует строку cout « mkstr(MHe нравится с++); в такую: cout « "Мне нравится с++"; Оператор ## используется ШIЯ слияния двух символических обо значений. Вот пример: #include <iostrearn> using namespace std; #define concat(a, Ь) а ## Ь int main () { int ху = 10; cout « concat (х, у); return о; } Препроцессор пре06разует строку cout« concat(x, у); в такую: cout « ху; 
616 Приложение В Если эти операторы показались вам странными, не смущайтесь: в большинстве проrpамм они не нужны и не используются. Операторы # и ## предназначены rлавным образом для обработки препроцессо ром некоторых специальных макросов. Предопределенные макросы В с++ определены шесть встроенных имен макросов: LINE  FILE  OATE   TIME  STDC   cplusplus Макросы  LINE  и  FILE  уже упоминались в подразделе, описывающем директиву #Iine. Они содержат текуШИЙ номер строки и имя файла компилируемой проrpаммы. Макрос  OATE  содержит строку в форме .месяц/день/zод, KOТO рая представляет собой дату трансляции ИСХОдноrо текста в объектный код. Макрос  TIME  содержит время, коrда nporpaMMa была OТКOM пилирована. Время представлено в виде строки в формате час:мuну та: секунда. Смысл макроса  STDC  зависит от реализации компилятора. Обычно если макрос  STDC  определен, компиляroр будет BOC принимать только стандартный код С/С++, Не содержащий никаких нестандартных расширений. Компилятор, совместимый со стандартным С++ ANSIjISQ должен определять  cplusplus как значение, содержащее по крайней мере 6 цифр. Компилятор, не соответствующий стандарту ANSIjISO, должен использовать значение с пятью или меньшим числом цифр. 
Приложение С Работа со старым компилятором с++ 
618 Приложение С Е сли вы используете современный компилятор, у вас не будет проблем с компиляцией проrpаммных примеров этой книrи. В этом случае вам не нужна информация, приводимая в настоящем приложении. Просто вводите проrpаммы в точности в том виде, в каком они приведены в книrе. Как уже отмечалось, проrраммы этой книrи полностью COOT ветствуют стандарту ANSIjISO на язык с++ и будут правильно KOM пилироваться почти любым современным компилятором С++, вклю чая Мiсrоsоft Visua1 с++ или Borland с++ Builder. Если, однако, вы используете компилятор, разработанный несколь ко лет назад, то при компиляции некоторых проrpамм вы, возможно, будете получать сообщения об ошибках, потому чro ваш компилятор не распознает некоторые новейшие средства с++. Не расстраиваЙ1'еСЬ  для тoro, чтобы проrpаммные примеры работали с вашим старым KOM пилятором, вам придется внести в них лишь незначительные измене ния. Чаше вcero различия между старым и новым стилем кода оrpани чиваются двумя моментами: новым стилем заrоловков и предложением namespace. Оrmшем эти различия подробнее. Как уже объяснялось в Модуле 1, предложение #include включает в вашу проrpамму заrоловок. В ранних версиях С++ все заrоловки пред ставляли собой файлы с расширением .Ь. Например, в проrрамме, Ha писанной в старом стиле, для включения зarоловка iostream вы ис пользуете такое предложение: #include <iostream.h> В результате файл iostream.h включался в текст вашей проrpаммы. Таким образом, в проrpамме cтaporo стиля для включения заroловка вы указывали имя файла с этим заrоловком. Файл этот имел расшире ние .Ь. Теперь так не делают. Современный с++ использует дрyrой вид заrоловков, появившийся после разработки стандартноrо с++ ANSIjISO. Зaroловки HOBoro стиля не используют имен файлов. Вы просто указываете в проrpамме стан... дартный идентификатор, который может быть отображен компьютером на файл, но это совсем не обязательно. Заrоловки HOBoro стиля пред ставляют собой абстракцию, которая просro rарантирует, что cooтвeTCТ вующая информация, требуемая ваше проrpамме, будет в нее включена. Поскольку заrоловки HOBoro стиля не являются именами файлов, они не имеют расширения .Ь. Вы должны указать только имя зarолов ка в yrловых скобках. Вот, например, два заrоловка HOBoro стиля, под держиваемые стандартным С++: <iostream> <fstream> Для преобразования этих заroловков в формат cTaporo стиля просто добавьте к их именам расширение .Ь. 
Работа со старым компилятором С++ 619 Содержимое добавляемоrо к проrpамме заrоловка входит в про странство имен std. как уже rоворилось, пространство имен  это про сто декларативный район. Ero цель заключается в локализации имен идентификаторов, что позволяет избежать конфликтов имен. Старые версии С++ помещали имена библиотечных функций и проч. в rло бальное пространство имен, а не пространство имен std, как это дела ют современные компиляторы. Поэтому при работе со старым компи лятором нет необходимости в предложении using namespace std; Собственно rоворя, старый компилятор даже не будет восприни мать предложение using. Два простых изменения Если ваш компилятор не поддерживает пространства имен и заrо ловки HOBoro стиля, то при компиляции первых строк примеров этой книrи он будет вьшавать одно или несколько сообщений об ошибках. Если это происходит, вам достаточно внести в проrpаммы два простых изменения: использовать заrоловки cтaporo стиля и удалить предложе ние namespace. Например, строки #include <iostream> using namespace std; следует заменить одной строкой #include <iostream.h> это изменение преобразует современную проrpамму в проrpамму, написанную в старом стиле. Поскольку заrоловок cTaporo стиля счи ТbIBaeT все свое содержимое в rлобальное ПрОС'lpанство имен, не воз никает необходимости в предложении namespace. После внесения yкa занных изменений проrрамма будет успешно компилироваться CTa рым компилятором. В некоторых случаях вам придется внести еще одно изменение. С++ наследует некоторые заrоловки от С. Язык С не поддерживает заrоловки HOBoro стиля. Для Toro, чтобы обеспечить обратную co вместимость, стандартный С++ все еще поддерживает заrоловочные файлы Сстиля. Однако С++ определяет также заrоловки HOBoro стиля, которые вы можете использовать вместо заrоловочных фай лов С. в версиях С++ стандартных заrоловков С просто прибавля ется префикс с к имени файла и опускается расширение .Ь. Так. за rоловок HOBoro стиля С++ для заrоловочноrо файла math.h будет 
620 Приложение С <cmath>. Для файла string.h это будет заrоловок <cstring>. Хотя в настоящее время разрешается включать в проrрамму заrоловочные файлы Сстиля, однако стандартный с++ не рекомендует такой подход. Поэтому в этой книrе во всех предложениях #include ис пользуются зarоловки с++ HOBoro стиля. Если ваш компилятор не поддерживает форму HOBoro стиля для заrоловков с, просто замени те их заrоловочными файлами cTaporo стиля. 
Предметный указатель + 78  78 * 78 /78 % 78 ++ 78  78 < 44, 82 < 44, 82 > 44, 82 >== 44, 82 =- 44, 82 !-= 44, 82 ; 29, 48 : : 318, 397, 542  33, 170   170 [ 1 140, 145, 176 # 614 ## 614 #define, директива препроцессора 604, 614 #elif, директива препроцессора 611 #else, директива препроцессора 61 О #endif, директива препроцессора 609 #error, директива препроцессора 608 #1С, директива препроцессора 61 О #1fdef, директива препроцессора 612, 613 #ifndef, директива препроцессора 612 #mclude, директива препроцессора 608, 618 #Нnе, директива препроцессора 614 #pragma, директива препроцессора 614 #undef, директива препроцессора 613 & (оператор указателя) 165, 238, 241, 290 ( ) 92, 190 * 33, 241 · (оператор указателя) 165, 239, 241 , (запятая) 305 .397 . (операторточка) 314, 319 . * 397 .срр, расширение имени файла 25 .Ь, расширение имени файла 618, 619 / 33 /* * / 28 //29 ? 303, 383, 397   cplusplus, макрос 616  oATE...., макрос 616  FILE, макрос 614, 616  LINE ...., макрос 614, 616 ....  STDC  , макрос 616   TIME.... ...., макрос 616 { } 29, 30, 47, 48, 189  327 + 33, 170 + + 170 « оператор вывода 29,462 <cctype>, зarоловок 156 <clim.its>, заrоловок 66 <cmath>, заrоловок 68 <cstdio>, заrоловок 152 <cstdl1b>, заrоловок 54, 99, 219 <iomamp>, заrоловок 475 <10stream.h>, заrоловочный файл 458 <10stream>, заrоловок 458, 462 <new>, заroловок 533 <typeinfo>, заrоловок 553 == 32, 87  > 348 » 35, 36 > >: оператор сдвиra вправо 290 «: оператор сдвиrа влево 290 оператор ввода 35, 462, 484 А abs( ) 54 adjustfield, флаrи формата 470 ANSI/ISO, стандартный С + 18 argc, параметр 216 argv, параметр 216 ASCII, набор символов 65, 120 asm 560 
622 Предметный указатель auto 205, 276 в в (язык проrpаммирования) 15 bad( ) 501 badalloc, исключение 533 badtypeld, исключение 556 baslCios, класс 461 basic10stream, поток 461 basicistream, класс 461 baslcostream, поток 461 basic  streambuf, класс 461 BCPL (язык проrpаммирования) 15 before( ) 554 bool, тип данноrо 68, 82 преобразование в Hero и из нею 90 break, предложение 105, 128 с С# 14, 19 С++ 14 и С# 14, 19 и Java 14, 19 и Windows 25,27 и объектноориентированное про.. rpаммирование 16, 17, 20 история имени 81 история развития 14 прописные и строчные буквы 56 case 105 catch 504, 516 rpуппа предложений 510 catch(...) 512 cerr, поток 460 char, тип данноrо и модификаторы типа 65 как "маленькое" целое 65 cin, поток 35, 36, 151, 460 class, ключевое слово 312, 518 clear( ) 501 clog, поток 460 close( ) 482 const, описатель 272, 559 constcast 559 contlnue, предложение 130 cout, поток 29, 33, 36, 460 D dec, флаr формата 469 default, предложение 105 dеfшеd, оператор времени компиляции 613 delete, оператор 532 double, тип данноrо 38, 67 издержки 40 dowhile, цикл 122 и contlnue 130 dYnarn.1ccast 557 Е else 98, 101 enum, ключевое слово 285 EOF 490 eof( ) 491, 501 Еsспоследовательности 74 extern, описатель класса памяти 276 F F, спецификатор 73 fшl( ) 501 false 68, 82 fill( ) 473 Па( ) 471 float, тип данноrо 38, 67 издержки 40 floatfield, флаrи формата 470 flush( ) 492 fmtfla, перечислимый тип 468 for, ЦИКЛ 112, 122 и сопtшuе 130 бесконечный 117 объявление переменной внyrpи 118, 127 FORTRAN (язык проrpaммирования) 16 free( ) 534 fnend, ключевое слово 367 fstream, класс 480 
Предметный указатель 623 G gcount( ) 489 get( ) 486, 489 gеtlше( ) 491 gets( ) 152 good( ) 501 goto, предложение 136, 200 н hex, флаr формата 469 1 if, преШIожение 98 объявление переменной внyrpи 127 if..еlsе..if...цепочка 102, 109 ifstream, класс 480 lпlше, ключевое слово 335, 336 int правило по умолчанию int 230 тип данною 29, 32, 38 char как "маленькое" целое 65 в 16.. и 32..разрядных средах 66 и модификаторы типа 63 interna1, флаr формата 469 ios 481, 485, 497, 500 ios, класс 468, 500 флаrи формата 469 10S  base, класс 461 iostate 500 is  open( ) 482 isalpha( ) 157 isdtglt( ) 157 islower( ) 157, 174 ispunct( ) 157 isspace( ) 157 isupper( ) 157, 174 J Java и с++ 14, 19 L L, спецификатор 73, 74 left, флаr формата 469 long, модификатор типа 61, 64 м main( ) 29, 189, 216 apryмeHТЫ кома1ШНОЙ строки 216 прототип 190, 216 ma1loc( ) 534 Мiсrоsоft Foundation Classes (MFC) 447 mutable, ключевое слово 275, 560 N name( ) 554 namespace 541, 618 преШIожение 619 NET Framework Windows Forms, биб.. лиотека классов 447 new, оператор 532 выбрасывание исключения 533 о oct, флar формата 469 offtype 497 ofstream, класс 480 open( ) 480 ostream, класс 463 overload, ключевое слово 56 р Pascal (язык проrpаммирования) 15 peek( ) 492 РОО 373 pos  type 499 pow( ) 92, 93 precision( ) 473 private, описатель доступа 417, 418, 421 protected, описатель доступа 414, 417, 419, 421 public, описатель доступа 313, 413,417, 421 put( ) 486 putback( ) 492 
624 Предметный указатель Q qsort( ) 227 QUlcksort 227 R rand( ) 99 rdstate( ) 500 read( ) 485, 487 register, описатель класса памяти 282 relnterpret  cast 560 retum, предложение 193 nght, флаr формата 469 R1ТI 553 s SClentific, флаr формата 469 seekg( ) 497 seekp( ) 497 setf( ) 470 short, модификатор типа 61, 64 showbase, флаr формата 469 Showpolnt, флаr формата 469 showpos, флаr формата 469 slgned, модификатор типа 61, 63, 65 slZeof, оператор времени компиляции 307 skipws, флаr формата 469 sqrt( ) 67, 113 Standard Template Llbrary (STL) 18 static, описатель класса памяти 278 static  cast 559 std, пространство имен 541, 547, 619 STL (Standard Template Llbrary) 18, 560 strcat ( ) 154 strcmp( ) 154 strcpy( ) 153 streamslZe 473, 487 string, класс 150 strlen( ) 154 struct, ключевое слово 372 sWltch, предложение 104, 130 вложенное 108 объявление переменной внутри 127 т tellg( ) 499 tellp( ) 499 template, ключевое слово 518, 522, 524 синтаксис для специализации 522 специализация родовою класса 526 tlus, указатель 378 и дружественныe опера торные функ.. ции 390 и операторные функции..члены 383, 385 throw 504, 505, 514 общая форма 505 tolower( ) 157 toupper( ) 156, 157 true 68, 82 true и false в с++ 68 try 504 try..catch, общая форма 504 typelnfo, класс 554 typedef 288 typeld, оператор 553 typename, ключевое слово 518 u U, спецификатор 73 umtbuf, флаr формата 469 UNIX 15 unsetf( ) 471 unstgned, модификатор типа 61, 63, 64, 65 uppercase, флаr формата 469 USlng, предложение 545, 619 v vlrtual, ключевое слово 441 vOld, тип данноrо 69 volatile, описатель 274, 559 w wchar  t, тип данноrо 66 литерал 74 
Предметный указатель 625 wcin, wcout, wcerr, wclog, потоки 460 wlule, ЦИКЛ 120 и сопНпие 130 width{ ) 473 write( ) 485, 487 х XOR лоrическая операция 84 побитовый оператор 290, 293 А ApryмeHTЫ командной строки 216 передача 234 с инициализацией по умолчанию 261 инеоднозначность 268 список 53 функции 53 инициализация по умолчанию 261 Б Базовые классы 410 наследование множественное 436, 439 обшая форма наследования 413 ссылки 441 указатели 440, 443, 555 управление доступом 417 Байт.. код 20 Библиотека классов 55, 447, 452, 541 Библиотечные функции 55 и заrоловки 223 Блок проrpаммный 46, 199 Быстрое упорядочение 150 в Ввод.. вывод С и с++ 485 с++ 458 библиотеки 458 потоки 459 форматированный 468 Виртуальные функции 441 и наследование 444 и полиморфизм 410, 441, 443, 446 иерархически ий характер 445 чистые 452 Возврат CCЬVIOK 243 Восьмеричные литералы 73 Вызов по значению 234 по CCЬVIKe 234 использование параметра..ссЬVI" ки 238 использование указателя 236 Выражения 89 преобразования типов 90 условные 100 д Деструкторы 327 и наследование 437 и параметры 331 Динамическая иnентификация nmов 553 Динамическое вьщеление памяти 531 та11ос( ) и free( ) 534 new и delete 532 инициализация 534 под массивы 535 под массивы объектов 538 под объекты 536 Директива #include 28 Директивы препроцессора 604 #define 604 #elif 611 #else 610 #endlf 609 #епоr 608 #if 609 #lfdef 612 #lfndef 612 #lnclude 608 #lше 614 #рrзgта 614 #undef 613 Дополнение до единицы () 290 Дополнительный код 62, 63 Дрyr класса 367 
626 Предметный указатель Дружественные функции 367 и переrpузка операторов 390 переrpузка операторов ввода"вьтода 465 з Завершающий ноль 151 использование 156 Зarоловки 28, 222, 618 Заrоловок <lostream> 28 Заrоловочные файлы 618 Запятая, оператор 114, 304 и И лоrический оператор (&&) 82 побитовый оператор (&) 290 Идентификаторы, правило составле.. ния 56 Идентификация типов, динамическая 553 Иерархическая классификация 23 ИЛИ лоrический оператор (11) 82 побитовый оператор ( 1 ) 290, 292 Инициализация и объекты 364 переменной 77 Инкапсуляция 21, 200, 313, 321 Исключающее ИЛИ, побитовый опе.. ратор (Л) 290 Исключения 504 вторичное выбрасывание 515 и коды ошибок 517 Исключительная ситуация 504 Исходный код 25 к Класс 312 Классы 22 абстрактные 455 вложенные 334 доступ к членам 313 и объединения 375 и структуры 374 объявление 312 объявление, общая форма 420 памяти, описатели 275 полиморфные 441 потоковые 460 члены 312 Ключевые слова, таблица 56 Комментарии 27, 29 Компилятор, с++ 25 старый 618 Компиляция 25 условная 609 Консольный ввод 35 Консольный вывод 29 Константы 72 строковые 177 Конструктор 327 копий 358, 361, 364 по умолчанию 358 копий и возврат из функций 363 копий и инициализация 364 копий и присваивание 364 копий, общая форма 364 Конструкторы и наследование 422, 437 и передача объектов функциям 357 параметрические 329 переrpузка 352 Косвенность 166 вложенная 184, 185 Куча 531, 532, 533 л Литералы 72 с плавающей точкой 72 строки 74 суффиксы ШIЯ числовых значений 73 целочисленные 72 м Макроподстановка 604 Макросы 604 подобные функциям 606, 608 предопределенные 616 
Предметный указатель 627 Манипуляторные функции 477 Манипуляторы BBoдaBЫBoдa 468, 475 Маскипи (Mascittl, Rtck) 81 Массивы двумерные 145 динамическое вьшеление памяти 535 индексация 141, 173 инициализация 157 использование new и de1ete 535 MHorOMepHble 146, 147, 149 неопределенной мины 161 объектов, динамическое вьщеление памяти 538 одномерные 140 определение 140 передача в ФУНКltии 208, 210 проверка rpаниц 144 строк 162 указателей 181 упорядочение 147, 149 Математические функции, стандартная библиотека 114 Метка 136 Метод 22 Модификаторы типа 61 н Наследование 21, 23, 410 и конструкторы 422 и мноrоуровенвая иерархия 433 конструкторы и деструкторы 438 мноrоуровневая иерархия 438 общая форма класса 413 управление доступом 417 НЕ лоrический оператор (!) 82 побитовый оператор (....) 290, 294 Независимые ССЬVIочные переменные 246 НеОДН01начность 266 о Обработка исключений 504 улавливание всех исключений 512 Объединения 374 безымянные 377 и классы 375 оrpаничения 376, 377 Объект 313 передача по ссылке 360 создание 314 Объектноориентированное проrpам.. мирование 16, 17,20,21,23, 312 Объектный код 25 Объекты 22, 312 возврат из функций 361 динамическое вьшеление памяти 536 инициализация 327, 329, 364 массивы 344 передача по CCЬUlKe 358 передача функциям 355 присваивание 353 ссылки 350 указатели 348 Объявление переменных 34, 35 упреждающее 370 Оператор ввода (») переrpузка 466 вставки «<) 462 вывода «<) 29, 37,462 переrpузка 465 декремента ( ) 46, 79 и указатели 170 переrpузка 385, 395 деления по модулю (%) 79 извлечения (») 462 инкремента (+ +) 46, 79 переrpузка 385, 395 и указатели 170 постфикс и префикс 386 присваивания (==) 33 проверки на равенство (===---) 44 разрешения видимости (: :) 318, 397, 542 тернарный (?) 303, 383 Операторзапятая (,) 304 Операторные функциичлены 381 Операторстрелка (» 348 Операторточка (.) 314, 319, 368, 443 Операторы 
628 Предметный указатель арифметические 33, 78 BBoдaBЫBoдa 29 переrpузка 462 как приведения типов 91 лоrические 83 отношения 43, 172 переrpузка 379 и дружественные функции 390 оrpаничения 390 оrpаничения 397 с использованием функцийчле нов 381 побитовые 289 приведения типа 557 сдвиrа 295 указателей 165, 167 циклическоrо сдвиra 298 Операции присваивания 87 и указатели 168 и функции 243 составные 88 Отступы, практика использования 49 Очередь 339 Ошибки, обработка 30 п Параметры 188, 191, 205 индексация 212 указатель 209, 210, 236 Параметры"ссЬUlКИ 238, 239, 241 Переrpузка операторов 379 бинарных 383, 390, 392 декремента 385 инкремента 385 инкремента и декремента, пост.. фиксных и префиксных 395 отношения 395 присваивания 384 унарных 383, 385, 390, 394 функций 247 и автоматическое преобразование типов 252 инеоднозначность 266 Передача по значению 234 по CCЬUlKe 234 и объекты 358 объекта 360 Переменные 31 rлобальные 199, 205 и extern 276 недостатки 208 статические 280 динамическая инициализация 77 инициализация 77 локальные 199, 207 объявление 202 статические 278 объявление 76 объявление и определение 277 присваивание значения 32 реrистровые 282 сокрытие имен 204 экземпляра 22, 312, 316 Переменные"члены 339 Переносимость 19, 20 Перечисление 284 Перечислимый тип 284 Побитовые операторы 289 Повышение типа 90 Полиморфизм 21, 234, 247, 249, 250, 349, 410, 441, 553 времени выполнения 446 и виртуальные функции 41 о, 441, 443, 446 и время выполнения 443 Потоки (BBOДBЫBOД) 459 Правила видимости 199 Предложение if 43 вложенное 101 и булева переменная 69 return 30, 193 usшg 28, 545 пустое 117 ПреШIожения выбора 98 и точка с запятой 29, 48 переходов 98 управления проrpаммой 43, 98 циклов 98 
Предметный указатель 629 Предупреждающие сообщения, обра.. ботка 31 Преобразование типа в выражениях 90 в операциях присваивания 88 инеоднозначность 266 и переrpуженные функции 252 Препроцессор 604 директивы 604 операторы 614 Приведение типа 90 и указатели 167 операторы 557 Приориты ornосителъныIe операторов 308 Присваивание и указатель tms 384 краткое 307 множественное 306 объектов 353 по умолчанию 353 составное 306 Пробельные символы 152 Проrpаммный блок 46 Производные классы 410 вызов конструктора базовою клас.. са 424 наследование множественное 436 общая форма наследования 413 определение 410 Прописные и строчные буквы в С++ 56 Пространства имен 282, 540 анонимные 547 безымянные 547 Пространство имен std 28 Прототипы 190, 221 Процессор данных 339 Псевдокод 20 р Рекурсия 223 Ритчи (Ritchie, Dennis) 15 Ричардс (RIchards, Martin) 15 Родовые классы 523 с несколькими родовыми данными 523, 525 явные специализации 526 Родовые функции 518 и переrpузка 523 с несколькими родовыми nmами 520 явная переrpузка 521 с Символ новой строки (\п) 37,40 Символы ASCII 65, 120 литералы 72, 75 преобразование при вводе"выводе 459 расширенные 120 Символьные константы с обратным слешем 74 Скобки О 81, 92, 93, 95 Сокрытие имен 204, 207 Спецификация компоновки, еюеrn 277 Ccьmкa 234 возврат из функции 243 вперед 370 на базовый класс 556 упреждающая 370 Ссьтки 238, 239, 241 и объекты 358 Ссьточные переменные оrpаничения 247 Стандартная библиотека С++ 55, 541 Стандартная библиотека шаблонов (STL) 18, 560 Стандартный С++ 18 Статические переменные 278 Статические переменные...члены 548 Статические функции..члены 551 Статические члены классов 548 Стек 339, 531, 532 Степанов (Stepanov, A1exander) 18 Страуструп (Stroustrup, BJame) 17, 81 Строки библиотечные функции обработки 153 завершающий ноль 150 как массивы символов 140, 150 константы 177 литералы 74 массивы 162 нулевые 151 
630 Предметный указатель определение 30 передача в функцию 213 преобразование в числа 219 символьные константы 150 Структурное проrpаммирование 15, 21 Структуры 372 и классы 372, 374 и наследование 417, 419 т Таблица строк 177, 182 Текущая позиция 459 Тернарный оператор 383 Типы данных 60 базовые 60 диапазоны 61, 66 классы 312, 313 модификаторы 61 числовые 40 Томпсон (Thompson, Ken) 15 у Указатели арифметика 170, 173, 174, 440 базовоrо класса 443 базовый тип 164, 167 в файле 497 возврат из функции 214 вызов по ссылке 236 и массивы 172, 173, 175, 177, 179, 181, 183 индексация 175 массивы указателей 181 на базовый класс 440, 555, 556 и виртуальность 441 на объекты 348 на указатель 184 нулевые 183 ошибки 185 передача в функции 209 приведение типа 167 сравнение 172 стиль объявлений 242 Указатель ввода 497 вывода 497 определение 164 Упорядочение 147, 149 QUlcksort 227 быстрое 147, 227 метод Шелла 147 пузырьковое 147 Управляющие последовательности 74 Управляющие предложения 43 ф Файл 25, 480 определение С++ 459 Файловый ввод"вывод 480 неформатированный и двоичный 485 определение состояния 500 открытие и закрытие 480 преобразование символов 484 произвольный доступ 497 указатели 497 чтение и запись текстовых файлов 483 Флar знака 63 Флаrи формата (ввод"вывод) 469 Функции тат( ) 189 vOld 188, 190 apryмeHТЫ 191 возврат 192 объектов 361 указателя 214 возвращаемые значения 195 встроенные 334 и макросы, подобные функциям 608 выбрасывание исключений оrpаничения 513 определение 514 вызов 190 доступа 337, 415 дружественные 367 и указатель this 379 и операции присваивания 53, 243 и спецификация компоновки 277 использование в выражениях 197 
Предметный указатель 631 как проrpаммные блоки 200 манипуляции символами 157 общая форма 188 операторные, общая форма 380 определение 22, 52, 188 переrpуженные и родовые 523 переrpУ1ка 247 передача массива 210 объектов 355 строк 213 указателя 209 порожденныe 520 правила видимости 200 правило по умолчанию int 230 прототип 190, 220 рекурсивные 223 специализация 520 тип возвращаемою значения 188, 195 шаблонные 520 Функции ..tШены 317 и указатель tbls 378 статические 551 Функция operator, общая форма 380 ц llиклическое определение 223 Ilиклы do..whlle 122 for45, 112, 113, 115, 117, 119 wlule 120, 122 бесконечные 117 вложенные 135 выбор формы 122 и break 129 и continue 130 и goto 137 ч Члены класса 22, 312 доступ 313, 337, 367,413 статические 548 ш Шаблон 517 Шестнадцатеричные литералы 73 я Языки В 15 BCPL 15 С# 14 с++ 14 FORTRAN 16 J ava 14 Pascal 15 неполиморфные 553 полиморфные 553 
оrЛАВЛЕНИЕ От переводчика Предисловие .......................................6 8 МОДУЛЬ 1. Основы С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 13 Краткая история С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 14 Язык С: Заря современной эры проrраммирования . . . . . . . . . . . . . .. 14 Потребность в С++ ...................................... 15 С++ родился . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 17 Эволюция С++ ..................................................... 17 Как С++ соотносится с языками Java и С# . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 18 Объектноориентированное проrраммирование . . . . . . . . . . . . . . . . . . . . . . .. 20 Инкапсуляция . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Полиморфизм . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Наследование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Первая простая nporpaMMa .......................................... 24 Ввод nporpaMMbl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Компиляция nporpaMMbl .................................. 25 Запуск nporpaMMbl ...................................... 26 Первый проrраммный пример строка за строкой. . . . . . . . . . . . . . . . . 27 Обработка синтаксических ошибок ................................... 30 Вторая простая nporpaMMa .......................................... 31 Использование операторов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 33 Ввод с клавиатуры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 35 Некоторые дополнительные возможности вывода ...................... 37 Еще один тип данных. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 38 Проект 1  1: Преобразование футов в метры. . . . . . . . . . . . . . . . . . . . . . . . . . .. 40 Два управляющих предложения ...................................... 43 Предложение if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Цикл for .......................... . . . . . . . . . . . . . . . . . . . . 45 Использование nporpaMMHblx блоков . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 46 Знак точки с запятой и позиционирование ............................. 48 Практика использования отступов ........................... 49 Проект 1 2: Создание таблицы преобразования футов в метры ........... 50 Знакомимся с функциями ........................................... 52 Библиотеки С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 55 Ключевые слова С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 55 
Оrлавление 633 Идентификаторы ................................................... 56 Вопросы для самопроверки ................................. . 57 МОДУЛЬ 2. Знакомимся с данными, типами и операторами .. 59 Почему так важны типы данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 60 Типы данных С++ .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 60 Целые числа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Символы ............................................. 65 Типы данных с плавающей точкой . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Булев тип данных ....................................... 68 Тип void .............................................. 69 Проект 2 1: Разrовор с Марсом. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 70 Литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 72 Шестнадцатеричные и восьмеричные литералы ................. 73 Строковые литералы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 Символьные Еsспоследовательности. . . . . . . . . . . . . . . . . . . . . . . . . 74 Подробнее о переменных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 76 Инициализация переменной ............................... 76 Динамическая инициализация . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Операторы ........................................................ 78 Арифметические операторы ......................................... 78 Инкремент и декремент . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 Операторы отношения (сравнения) и лоrические ....................... 81 Проект 22: Конструированиелоrической операции исключающее ИЛИ. . . . . . . 84 Оператор присваивания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 87 Составные присваивания . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Преобразование типов в операциях присваивания ............... 88 Вы ражения ........................................................ 89 Преобразование типа в выражениях . . . . . . . . . . . . . . . . . . . . . . . . . . 90 При ведение типа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Пробелы и скобки .................................................. 92 Проект 23: Вычисление реryлярных платежей по ссуде ................. 92 Вопросы для самопроверки .................................. 96 МОДУЛЬ. з. Предложения управления nporpaMMoA ......... 97 ПреДJ10жение if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 98 Условные выражения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 100 Вложенные предложения if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1 01 Цепочка ifelseif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 Предложение switch ............................................... 104 Вложенные предложения switch . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1 08 Проект 3 1 : Начинаем строить справочную систему С++ .. . . . . . . . . . . . . .. 109 Цикл f or .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 11 2 Некоторые варианты цикла for . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 114 
634 Опущенные секции ..................................... 115 Бесконечный цикл for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 117 Цикл с отсутствующим телом . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 117 Объявление переменных управления циклом внутри цикла for ...... 118 Цикл while ........................................................ 120 Цикл dowhile ..................................................... 122 Проект 32: Усовершенствование справочной системы С++ . . . . . . . . . . . .. 124 Использование break для выхода из цикла ............................ 128 Использование continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 130 Проект 33: Завершаем разработку справочной системы С++ ........... 131 Вложенные циклы ................................................. 135 Использование предложения goto ................................... 136 Вопросы для самопроверки ................................. 1 37 МОДУЛЬ 4. Массивы, строки и указатели ................ 139 Одномерные массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 140 rраницы не проверяются! ................................ 144 Двумерные массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 145 MHoroMepHble массивы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 146 Проект 4 1: Упорядочение массива .................................. 147 Стро ки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 1 50 Основы техники строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 150 Ввод строки с клавиатуры ................................ 151 Некоторые библиотечные функции обработки строк ................... 153 strcpy( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 153 strcat() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 154 strcmp() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 154 strle n () . . . . . . . . . . . . . . . . . . . . .  . . . . . . . . . . . . . . . . . . . . . . .. 1 54 Пример обработки строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 155 Использование завершающеrо нуля . . . . . . . . . . . . . . . . . . . . . . . .. 156 Инициализациямассивов .......................................... 157 Инициализация массивов неопределенной длины . . . . . . . . . . . . . .. 160 Массивы строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 162 Указатели ........................................................ 164 Что такое указатели? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 164 Операторы указателей ............................................. 165 Базовый тип указателя имеет большое значение . . . . . . . . . . . . . . .. 167 Операции присваивания посредством указателя . . . . . . . . . . . . . . .. 168 Выражения с указателями ................................ 169 Арифметика указателей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 170 Сравнение указателей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 172 Указатели и массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 172 Индексация указателя . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 175 Строковые константы .............................................. 177 
Оrлавление 635 Проект 42: Переворачивание строки ................................ 178 Массивы указателей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 181 Соrлашение о нулевом указателе. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 183 Указатель на указатель . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 184 Вопросы для самопроверки ................................. 186 МОДУЛЬ 5. Введение в ФУНКЦИИ. . . . . . . . . . . . . . . . . . . . . . .. 187 Основы функций .................................................. 188 Общая форма определения функции. . . . . . . . . . . . . . . . . . . . . . . .. 188 Создание функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 189 Использование apryMeHToB ............................... 190 Использование предложения return ......................... 192 Возвращаемые значения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 195 Использование функций в выражениях . . . . . . . . . . . . . . . . . . . . . .. 197 Правила видимости. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 199 Локальная область видимости ............................. 199 r лобальная область видимости ............................ 205 Передача в функции указателей и массивов . . . . . . . . . . . . . . . . . . . . . . . . . .. 208 Передача указателя . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Передача массива . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 О Передача строк . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Возврат указателей . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 214 Функция main() ................................................... 216 argc и argv: apryMeHTbl функции main() ............................... 216 Передача числовых apryMeHToB командной строки . . . . . . . . . . . . . . . 219 Прототипы функций ............................................... 220 Заrоловки содержат прототипы ............................ 222 Рекурсия ......................................................... 223 Проект 5 1: Быстрое упорядочение .................................. 227 Вопросы для самопроверки ................................. 231 МОДУЛЬ 8. Подробнее о ФУНКЦИЯХ . . . . . . . . . . . . . . . . . . . . .. 233 Два подхода к передаче apryMeHToB ................................. 234 Как С++ передает apryMeHTbl .............................. 234 Использование указателя для создания вызова по ссылке ......... 236 Параметрыссылки ................................................ 238 Возврат ссылок . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 Независимые ссылочные переменные ....................... 246 Несколько оrраничений при использовании ссылочных пере менных ... 247 Переrрузка функций ............................................... 247 Автоматическое преобразование типов и переrрузка . . . . . . . . . . . . . 252 Проект 61: Создание переrруженных функций для вывода на экран. . . . .. 255 ApryMeHTbl функций с инициализацией по умолчанию .................. 261 ApryMeHTbl с инициализацией по умолчанию или переrрузка? . . . . . . . 263 Правильное использование apryмeнтoB с инициализацией по умолчанию. . 265 
636 Оrлавление Переrрузка ФУНКЦИЙ инеоднозначность . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 266 Вопросы для самопроверки ................................. 269 МОДУЛЬ 7. Подробнее о типах данных и операторах ....... 271 Описатели const и volatile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 272 const ............................................... 272 volatile .............................................. 274 Описатели классов памяти ......................................... 275 auto ................................................ 276 extern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 Статические переменные . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Реrистровые переменные ................................ 282 Перечислимые типы ............................................... 284 typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 288 Побитовые операторы ............................................. 289 Операторы И, ИЛИ, исключающее ИЛИ и НЕ ................... 290 Операторы сдвиrа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 Проект 7  1: Создание функций циклическоrо побитовоrо сдвиrа . . . . . . . .. 298 Оператор? ....................................................... 303 Операторзапятая . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 304 Множественное присваивание ...................................... 306 Составное присваивание . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 306 Использование оператора sizeof .................................... 307 Обзор относительных приоритетов .................................. 308 Вопросы для самопроверки ................................. 309 МОДУЛЬ 8. Классы и объекты ......................... 311 Основы классов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 312 Общая форма класса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312 Определение класса и создание оъектов . . . . . . . . . . . . . . . . . . . . . . 313 Добавление в класс функцийчленов . . . . . . . . . . . . . . . . . . . . . . . . . 317 Проект 8 1: Создание класса справочника ............................ 321 Конструкторы и деструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 326 Параметрические конструкторы . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Добавление конструктора в класс Vehicle ..................... 331 Друrой способ инициализации . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333 Встроенные ФУНКЦИИ .............................................. 334 Создание встроенных ФУНКЦИЙ внутри класса ....... . . . . . . . . . . . 336 Проект 82: Создание класса очереди . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 339 Массивы объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 344 Инициализация массивов объектов ......................... 345 Указатели на объекты .............................................. 347 Ссылки на объекты ................................................ 350 Вопросы для самопроверки ................................. 350 
Оrлавление 637 МОДУЛЬ 9. Подробнее о классах . . . . . . . . . . . . . . . . . . . . . .. 351 Переrрузкаконструкторов......... ... .... ... ... ............... .....352 Присваивание объектов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 353 Передача объектов функциям . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 355 Конструкторы, деструкторы и передача объектов. . . . . . . . . . . . . . . . 357 Передача объектов по ссылке . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 Потенциальные проблемы при передаче объектов . . . . . . . . . . . . . . . 360 Возврат объектов ................................................. 361 Создание и использование конструктора копий ....................... 363 Дружественные функции ........................................... 367 Структуры и объединения .......................................... 372 Структуры . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Объединения ......................................... 374 Ключевое слово this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 378 Переrрузка операторов ............................................ 379 Переrрузка операторов с использованием функцийчленов . . . . . . . . . . . .. 381 Друrие вопросы ....................................... 384 Использование функцийчленов для переrрузки унарных операторов . . . 385 Операторные функциине члены. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 390 Использование дружественной функции для переrрузки YHapHoro оператора . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Советы и оrраничения при переrрузке операторов . . . . . . . . . . . . . . . 396 Проект 9 1 : Создание класса, определяющеrо множество .............. 397 Вопросы для самопроверки ................................. 407 МОДУЛЬ 10. Наследование, виртуальные функции и полиморфизм .......................... 409 Основы наследования . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 41 О Доступ к членам и наследование . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3 Управление доступом к базовому классу ............................. 417 Использование защищенных членов ................................. 419 Конструкторы и наследование ...................................... 422 Вызов конструктора базовоrо класса ........................ 424 Проект 1 o 1: Расширение класса Vehicle . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 429 Создание мноrоуровневой иерархии классов ......................... 433 Наследование от нескольких базовых классов . . . . . . . . . . . . . . . . . . . . . . . .. 436 Kor да выполняются функции конструктора и деструктора . . . . . . . . . . . . . .. 437 Указатели на производные классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 439 Ссылки на производные типы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441 Виртуальные функции и полиморфизм ............................... 441 Основы виртуальных функций . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441 Виртуальные функции наследуются ......................... 444 Зачем нужны виртуальные функции? . . . . . . . . . . . . . . . . . . . . . . . . . 446 Приложение виртуальных функций. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 447 
638 Оrлавление Чистые виртуальные функции и абстрактные классы ................... 451 Вопросы для самопроверки ................................. 455 МОДУЛЬ 11. С++ и система ввода-вывода. . . . . . . . . . . . . . .. 457 Старая и новая системы BBoдaBЫBoдa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 458 Потоки С++ ............................... . . . . . . . . . . . . . . . . . . . . . . .. 459 Предопределенные потоки С++. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460 Потоковые классы С++ ....................... . . . . . . . . . . . . . . . . . . . . .. 460 Переrрузка операторов BBoдaBЫBoдa ............................... 462 Создание операторных функций вывода . . . . . . . . . . . . . . . . . . . . . . 462 Использование дружественных функций для переrрузки операторов вывода . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464 Переrрузка операторов ввода ............................. 466 Форматированный BBOДBЫBOД . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 468 Форматирование с помощью функцийчленов ios ............... 468 Использование манипуляторов BBoдaBЫBoдa . . . . . . . . . . . . . . . . . . 475 Создание собственных манипуляторных функций ............... 477 Файловый BBOДBЫBOД ............................................. 480 Открытие и закрытие файла . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480 Чтение и запись текстовых файлов . . . . . . . . . . . . . . . . . . . . . . . . . . 483 Неформатированный и двоичный BBOДBЫBOД . . . . . . . . . . . . . . . . . . 485 Чтение и запись блоков данных ............................ 487 Больше о функция BBoдaBЫBoдa .................................... 489 друrие варианты get( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489 getli пе() .... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 Обнаружение символа EOF ............................... 491 peek( ) и putback( ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 fIUSh() ............................................... 492 Проект 11  1: Утилита сравнения файлов. . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 492 Произвольный доступ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 497 Определение состояния BBoдaBЫBoдa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 500 Вопросы для самопроверки ................................. 501 МОДУЛЬ 12. Исключения, шабпоны и друrие допопнитепьные темы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 503 Обработка исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 504 Основы обработки исключений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504 Использование rpYnnbl предложений catch .................... 51 О Улавливание всех исключений ............................. 512 Задание исключений, выбрасываемых функцией . . . . . . . . . . . . . . . . 513 Вторичное выбрасывание исключения ....................... 515 Шаблоны . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 517 Родовые функции ...................................... 518 Функция с двумя родовыми типами. . . . . . . . . . . . . . . . . . . . . . . . . . 520 Явная переrрузка родовых функций ......................... 521 
Оrлавление 639 Родовые классы ....................................... 523 Явные специализации класса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526 Проект 12..1: Создание родовоrо класса очереди ...................... 527 Динамическое выделение памяти ................................... 531 Инициализация выделенной памяти . . . . . . . . . . . . . . . . . . . . . . . . . 534 Выделение памяти под массивы . . . . . . . . . . . . . . . . . . . . . . . . . . . . 535 Выделение памяти под объекты . . . . . . . . . . . . . . . . . . . . . . . . . . . . 536 Пространства имен . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 540 Основы использования пространств имен . . . . . . . . . . . . . . . . . . . . . 541 Предложение using . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545 Безымянные пространства имен. . . . . . . . . . . . . . . . . . . . . . . . . . . . 547 Пространство имен std .................................. 547 Статические члены классов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 548 Статические переменные..члены . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548 Статические функциичлены .............................. 551 Динамическая идентификация типов (RЛI) ........................... 553 Операторы приведения типа . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 557 dynamiccast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557 const cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559 static  cast . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559 reinterpret cast ........................................ 560 Что дальше? ...................................................... 560 Вопросы для самопроверки ................................. 561 ПрипО)l(ение А. Ответы на Вопросы для самопроверки ...... 563 ПрипО)l(ение В. Препроцессор . . . . . . . . . . . . . . . . . . . . . . . . .. 603 #define . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 604 Макросы, подобные функциям . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606 # error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608 #include .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608 Директивы условной компиляции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 609 #if. #else, #elif и #endif . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 609 #ifdef и #ifndef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 612 #un def .............................................. 61 3 Использование defined .................................. 613 #Iine .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 614 #pragma ......................................................... 614 Операторы препроцессора # и ## . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 614 Предопределенные макросы. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. 616 ПрипО)l(ение с. Работа со старым компилятором с++ . . . . . .. 617 Два простых изменения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 619 Предметный указатепь ............................... 621 
С++ дnя начинаlOЩИХ 
Учебное издание Серия «Шаr за шаrом» mилдт rерберт с++ для начинающих Перевод К. Фu'Н.оzе'Н.ова Редактор К. Фи'Н.оzе'Н.ов Верстка В. Верхоаи'Н.а Дизайн и оформление О. Буд'Ко Директор издательства В. Товорухи'Н. rлавный редактор Л. Захарова ПОЛЬЗОВАТЕЛЬ  1 N L Е G А L U S Е Подписано в печать 18.02.2010. Формат 70хl00 1/16 rарнитура Newton. Печать офсетная. Уел. печ. л. 51,85 Тираж 500 экз. Заказ N2 А.. 1203. ЭКОМ Паблишерз 117342, Россия, Москва, УЛ. Бyrлерова, д.17а, оф. 105 www.ecom.ru Е ..mзil: zakaz@ecom.ru