/
Author: Каширин И.Ю. Новичков В.С.
Tags: программирование языки программирования учебное пособие компьютерные технологии язык программирования c++
ISBN: 978-5-9912-0259-6
Year: 2012
Text
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И.Ю.Каширин,В.С .Новичков
ОтСкС++
Допущено УМО по университетскому политехническому
образованию в качестве учебного пособия
для студентов высших учебных заведений,
обучающихся по специальности
«Программное обеспечение вычислительной
техники и автоматизированных систем»
Москва
Горячая линия - Телеком
2012
2-е издание, стереотипное
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
ББК 32.973
УДК 681.33
К31
Каширин И. Ю., Новичков В. С.
К31 От С к С++: Учебное пособие для вузов. – 2 -е изд., сте-
реотип. – М.: Горячая линия – Телеком, 2012. – 334 с.: ил.
ISBN 978-5-9912-0259-6.
Учебное пособие содержит необходимые теоретические
сведения и набор упр а жне ний и задач различной степени
сложности, позволяющих приобрести навыки практического
программирования на алгоритмических языках С и С++ (Си
и Си++) и проконтролировать усвоение материала. Практиче-
ские задания для программирования на С++ имеют «сквозную »
структуру – распределены по мере изложения разделов. Мате-
риал книги успешно апробирован авторами в высших техниче-
ских учебных заведениях.
Для студентов высших и средних учебных заведений, может
быть использована начинающими программистами при изуче-
нии алгоритмических языков С и С++.
ББК 32.973
Адрес издательства в Интернет WWW.TECHBOOK.RU
Учебное издание
Каширин Игорь Юрьевич, Новичков Валентин Семенович
От С к С++: Учебное пособие
2-е издание, стереотипное
Редактор П. В. Румянцев
Художник В. Г . Ситников
Подготовка оригинал-макета И. М . Чумаковой
.
Подписано к печати 25.03.2012 . Формат 60×88 1/16. Ус л. печ. л. 21 .
Тираж 200 экз . (1-й завод 100 экз.)
ISBN 978-5 -9912-0259-6
© И. Ю . Каширин,
В. С. Новичков, 2005, 2012
© Оформление издательства «Горячая линия – Телеком», 2012
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
3
ПРЕДИСЛОВИЕ
Среди современных алгоритмических языков языки С и С++ занима-
ют, пожалуй, первое место по распространенности и разнообразию вер-
сий. Они относятся к семейству универсальных языков программирова-
ния, т. е . ориентированных на весьма широкий круг задач, которые могут
решаться при помощи ЭВМ. Кроме того, авторы этой книги признают
лидерство языков С и С++ среди известных универсальных языков как
наиболее концептуально целостных. Дело в том, что разработка любого
из инструментальных программных средств, к которым относятся и язы-
ки программирования, основана на строгом теоретическом базисе.
Теория разработки алгоритмических языков учитывает отлаживае-
мость программ (как быстрый поиск ошибок), гибкость языка при внесе-
нии текущих изменений в программу, возможности дальнейшего разви-
тия самого языка и его средств программистом и т. д . В этом отношении
язык С довольно полно отвечает основным требованиям теории, являясь
последовательным преемником оригинальных решений, воплощенных
ранее в цепочке поколений языков Ассемблера, Фортрана, Алгола. Взяв
из них самое лучшее, язык С приобрел множество новых свойств, сде-
лавших его одним из первых универсальных функциональных языков.
Язык программирования С разработан сотрудниками фирмы Bell
Labs Деннисом Ритчи и Кеном Томпсоном в 1972 г. во время их совмест-
ной работы над операционной системой UNIX на ЭВМ РDР-11. Однако
его популярность быстро переросла рамки конкретной ЭВМ, конкрет-
ной операционной системы и конкретных задач системного програм-
мирования. В настоящее время ни одна инструментальная операцион-
ная система не может считаться полной, если в ее состав не входит
компилятор языка С.
В некотором смысле язык С является самым универсальным языком,
так как кроме набора средств, присущих современным языкам програм-
мирования высокого уровня (структурность, модульность, определяемые
типы данных), в него включены средства для программирования на уров-
не Ассемблера (указатели, побитовые операции, операции сдвига). Боль-
шой набор операторов и операций позволяет писать компактные и эф-
фективные программы.
Однако такие мощные средства требуют от программиста осторож-
ности, аккуратности и хорошего знания языка со всеми его преимущест-
вами и недостатками.
В настоящей книге рассматриваются реализации С и С++, разрабо-
танные фирмой Borland.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
4
Язык С – структурированный, модульный, компилируемый, универ-
сальный язык, традиционно используемый для системного программиро-
вания. Он является переносимым языком, так как прикладные програм-
мы, написанные на нем, могут быть легко перенесены с одного компью-
тера на другой, даже если они имеют различные операционные системы.
Язык С может использоваться практически для любых задач.
Строгое следование авторов языка С функциональной концепции по-
зволило изящно достроить язык и перевести его в объектно-ориенти-
рованную версию – С++, практически не меняя ни старой синтаксиче-
ской, ни семантической основы.
Быстрое развитие языка программирования С++ с появлением но-
вых версий, использующих идеи CASE-технологии, свидетельствует
о том, что идеология С не только современна, но и будет иметь боль-
шое будущее.
Настоящая книга состоит из двух основных частей, описывающих
соответственно программирование на языках C и C++. Для чтения книги
практически не нужно иметь навыков программирования на каких-либо
более простых алгоритмических языках.
В то же время читателю, уже знакомому с языком С, может быть ре-
комендовано начинать чтение с более поздних глав, посвященных про-
граммированию на языке С++.
Практические упражнения, приведенные в конце каждой главы, име-
ют различную степень сложности и значительно облегчат понимание ма-
териала при их выполнении.
Книга может быть использована как для самостоятельного изучения,
так и для чтения курса лекций с лабораторным практикумом в высших
учебных заведениях.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
5
ГЛАВА 1. ПРОГРАММИРОВАНИЕ
ЛИНЕЙНЫХ АЛГОРИТМОВ
1.1. Этапы решения задач на ЭВМ
Решение любой задачи с использованием ЭВМ состоит из нескольких
взаимосвязанных этапов, среди которых можно выделить следующие:
1) техническое задание (постановка задачи);
2) формализация (математическая постановка задачи);
3) выбор (или разработка) метода решения;
4) разработка алгоритма (алгоритмизация);
5) выбор языка программирования;
6) определение структуры данных;
7) оптимизация алгоритма;
8) подготовка отладки;
9) тесты и методы «ручной» проверки (без использования ЭВМ);
10) запись программы на конкретном языке программирования;
11) тестирование и отладка;
12) выполнение программы и обработка результатов;
13) документирование.
Последовательное выполнение перечисленных этапов составляет
полный цикл разработки, отладки и выполнения программы. Приведен-
ное разделение является условным. С развитием современных техноло-
гий программирования порядок и содержание этапов может меняться.
Рассмотрим более подробно некоторые наиболее общие и необходи-
мые этапы.
Постановка задачи. При постановке задачи первостепенное внима-
ние должно быть уделено выяснению конечной цели и выработке общего
подхода к исследуемой проблеме: выяснению, существует ли решение
поставленной задачи и единственно ли оно; изучению общих свойств
рассматриваемого физического явления или объекта; анализу возможно-
стей конкретной ЭВМ и данной системы программирования. На этом
этапе требуется глубокое понимание существа поставленной задачи.
Правильно сформулировать задачу иногда не менее сложно, чем ее ре-
шить.
Формализация. Формализация, как правило, сводится к построению
математической модели рассматриваемого явления, когда в результате
анализа существа задачи определяются объем и специфика исходных
данных, вводится система условных обозначений, устанавливается
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
6
принадлежность решаемой задачи к одному из известных классов задач
и выбирается соответствующий математический аппарат. При этом нуж-
но уметь сформулировать на языке математики конкретные задачи физи-
ки, механики, экономики, технологии и т. д. Для успешного преодоления
этого этапа требуются не только солидные сведения из соответствующей
предметной области, но и хорошее знание вычислительной математики,
т. е . тех методов, которые могут быть использованы при решении задачи
на машине.
Выбор метода решения. После того как определена математическая
формулировка задачи, надо выбрать метод ее решения. Вообще говоря,
применение любого метода приводит к построению ряда формул
и к формулировке правил, определяющих связи между этими формулами.
Все это разбивается на отдельные действия так, чтобы вычислительный
процесс мог быть выполнен машиной. При выборе метода надо учиты-
вать, во-первых, сложность формул и соотношений, связанных с тем или
иным численным методом, во-вторых, необходимую точность вычисле-
ний и характеристики самого метода. На выбор метода решения большое
влияние оказывают вкусы и знания самого пользователя.
Этот этап – важнейший в процессе решения задачи. С ним связаны
многочисленные неудачи, являющиеся результатом легкомысленного
подхода к ошибкам вычислений. При решении задачи на ЭВМ необходи-
мо помнить, что любой получаемый результат является приближенным!
Если известен алгоритм точного решения, то, кроме случайных ошибок
(сбоев в работе ЭВМ), возможны ошибки, связанные с ограниченной
точностью представления чисел в ЭВМ. При вычислениях, заключаю-
щихся в нахождении результата с заданной степенью точности, возникает
дополнительная погрешность, которую, если возможно, оценивают на
данном этапе (до выхода непосредственно на ЭВМ). Эта погрешность
определяется выбранным численным методом решения задачи.
Разработка алгоритма. Данный этап является первым этапом про-
граммирования и заключается в разложении вычислительного процесса
на возможные составные части, установлении порядка их следования,
описании содержания каждой такой части в той или иной форме и после-
дующей проверке, которая должна показать, обеспечивается ли реализа-
ция выбранного метода. В большинстве случаев не удается сразу полу-
чить удовлетворительный результат, поэтому составление алгоритма
проводится методом «проб и ошибок» и для получения окончательного
варианта требуется несколько шагов коррекции и анализа.
Как правило, в процессе разработки алгоритм проходит несколько
этапов детализации. Первоначально составляется укрупненная схема ал-
горитма, в которой отражаются наиболее важные и существенные связи
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
7
между исследуемыми процессами (или частями процесса). На последую-
щих этапах раскрываются (детализируются) выделенные на предыдущих
этапах части вычислительного процесса, имеющие некоторое самостоя-
тельное значение. Кроме того, на каждом этапе детализации выполняется
многократная проверка и исправление (отработка) схемы алгоритма. По-
добный подход позволяет избежать возможных ошибочных решений.
Ориентируясь на крупноблочную структуру алгоритма, можно быст-
рее и проще разработать несколько различных его вариантов, провести
их анализ, оценку и выбрать наилучший (оптимальный).
Эффект поэтапной детализации алгоритма во многом зависит от того,
как осуществляется его структуризация: расчленение алгоритмического
процесса на составные части, что должно определяться не произволом
пользователя (программиста), а внутренней логикой самого процесса.
Каждый элемент крупноблочной схемы алгоритма должен быть макси-
мально самостоятельным и логически завершенным в такой степени,
чтобы дальнейшую его детализацию можно было выполнять независимо
от детализации остальных элементов. Это упрощает процесс проектиро-
вания алгоритма и позволяет осуществлять его разработку по частям од-
новременно нескольким людям.
В процессе разработки алгоритма могут использоваться различные
способы его описания, отличающиеся по простоте, наглядности, ком-
пактности, степени формализации, ориентации на машинную реализацию
и другим показателям. В практике программирования наибольшее рас-
пространение получили:
1) словесная запись алгоритмов;
2) схемы алгоритмов;
3) псевдокод (формальные алгоритмические языки);
4) структурограммы (диаграммы Насси–Шнейдермана).
Разработка алгоритмов является в значительной степени творческим,
эвристическим процессом, как правило, требует большой эрудиции, изо-
бретательности, нестандартных и нетрадиционных подходов к решению
задачи.
Составление программы. Представление алгоритма в форме, допус-
кающей ввод в машину и последующий перевод на машинный язык, от-
носится к этапу составления программы (программированию), т. е. разра-
ботанный алгоритм задачи необходимо изложить на языке, который бу-
дет понятен ЭВМ.
Отладка программы. Составление программы представляет собой
трудоемкий процесс, требующий от исполнителя напряженного внима-
ния. Практика показывает, что в вычислениях следует избегать поспеш-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
8
ности и придерживаться золотого правила: «лучше меньше, да лучше».
Но на предыдущих этапах столько возможностей допустить ошибку, что,
как бы мы тщательно ни действовали, первоначально составленная про-
грамма обычно содержит ошибки и машина или не может дать ответа,
или приводит неправильное решение.
Отладка начинается с того, что аккуратно записанная программа про-
веряется непосредственно лицом, осуществившим подготовку и про-
граммирование задачи. Выясняется правильность написания программы,
выявляются смысловые и синтаксические ошибки и т. п . Затем програм-
ма вводится в память ЭВМ и ошибки, оставшиеся незамеченными, выяв-
ляются уже непосредственно с помощью машины.
Опытный пользователь ЭВМ знает, что необходим действенный кон-
троль над процессом вычислений, позволяющий своевременно обнару-
живать и предотвращать ошибки. Для этого используются различного
рода интуитивные соображения, правдоподобные рассуждения и кон-
трольные формулы. Начинающий пользователь часто считает отладку
излишней, а получение контрольных точек – неприятной дополнительной
работой. Однако очень скоро он убеждается, что поиск пропущенной
ошибки требует значительно большего времени, чем время, затраченное
на контроль.
Гарантией правильности решения может служить, например:
а) проверка выполнения условий задачи (например, для алгебраиче-
ского уравнения найденные корни подставляются в исходное уравнение
и проверяются расхождения левой и правой частей);
б) качественный анализ задачи;
в) пересчет (по возможности другим методом).
Для некоторых сложных по структуре программ процесс отладки
может потребовать значительно больше машинного времени и усилий
специалистов, чем собственно решение на ЭВМ, так как плохо спланиро-
ванные процессы алгоритмизации, программирования и отладки приво-
дят к ошибкам, которые могут быть обнаружены лишь после многократ-
ных проверок.
Вычисления и обработка результатов. Только после того как по-
явится полная уверенность, что программа обеспечивает получение пра-
вильных результатов, можно приступать непосредственно к расчетам по
программе.
После завершения расчетов наступает этап использования результа-
тов вычислений в практической деятельности или, как говорят, этап вне-
дрения результатов. Интерпретация результатов вычислений снова отно-
сится к той предметной области знаний, откуда возникла задача.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
9
Прежде чем приступать к составлению непосредственно программ на
языке, рассмотрим вкратце первый этап программирования.
1.2. Разработка алгоритма решения задачи
1.2.1. Понятие алгоритма
Термин «алгоритм» произошел от имени арабского математика аль-
Хорезми (IX в.), который описал общие правила (названные позднее ал-
горитмами) выполнения основных арифметических действий в десятич-
ной системе счисления. Понятие алгоритма используется сегодня не
только в математике, но и во многих областях человеческой деятельно-
сти; например, говорят об алгоритме управления производственным про-
цессом, алгоритме управления полетом ракеты, алгоритме пользования
бытовым прибором. Причем интуитивно под алгоритмом понимают не-
которую систему правил, обладающих определенными свойствами.
Изучая понятие алгоритма, мы будем предполагать, что его исполни-
телем является автоматическое устройство, а именно ЭВМ. Это наклады-
вает на запись алгоритма целый ряд обязательных требований. Сформу-
лируем эти требования в виде перечня свойств, которыми должны обла-
дать алгоритмы, предназначенные для исполнения на ЭВМ.
1. Первым свойством алгоритма является дискретный, т. е. пошаго-
вый, характер определяемого им процесса. Возникающая в результате та-
кого разбиения запись алгоритма представляет собой упорядоченную по-
следовательность отдельных предписаний (правил, директив, команд),
образующих прерывную (или дискретную) структуру алгоритма – только
выполнив требования одного предписания, можно приступить к испол-
нению следующего.
2. Исполнитель может выполнить алгоритм, если он ему понятен,
т. е . записан на понятном ему языке и содержит предписания, которые
исполнитель может выполнить. Набор действий, которые могут быть вы-
полнены исполнителем, называется системой команд исполнителя. Алго-
ритм не должен содержать описание действий, не входящих в систему
команд исполнителя.
3. Алгоритм, предназначенный для исполнения некоторым техниче-
ским устройством, не должен содержать предписаний, приводящих к не-
однозначным действиям. Алгоритм рассчитан на чисто механическое ис-
полнение, и если применять его повторно к одним и тем же исходным
данным, то всегда должен получиться один и тот же результат; если при
этом каждый раз сравнивать результаты, полученные после соответст-
вующих шагов алгоритмического процесса, то они тоже должны быть
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
10
одинаковыми. Это свойство определенности и однозначности, или де-
терминированности, алгоритмов позволяет использовать в качестве ис-
полнителя специальные машины-автоматы.
4. Основополагающим свойством алгоритма является его массовость
или применимость к некоторому классу объектов, возможность получе-
ния результата при различных исходных данных в некоторой области до-
пустимых значений. Например, исходными данными в алгоритмах аль-
Хорезми могут быть любые пары десятичных чисел. Конечно, его способ
не всегда самый рациональный по сравнению с известными приемами
быстрого счета. Но смысл массовости алгоритма состоит как раз в том,
что он одинаково пригоден для всех случаев, требует лишь механическо-
го выполнения цепочки простых действий и при этом исполнителю нет
нужды в затратах творческой энергии.
5. Цель выполнения алгоритма – получение определенного результа-
та посредством выполнения указанных преобразований над исходными
данными. В алгоритмах аль-Хорезми исходными данными являются два
десятичных числа, результатом – также некоторое десятичное число.
Причем при точном исполнении всех предписаний алгоритмический
процесс должен заканчиваться за конечное число шагов. Это обязательное
требование к алгоритмам.
В математике известны вычислительные процедуры алгоритмическо-
го характера, не обладающие свойством конечности. Например, процеду-
ра вычисления числа π. Однако если мы введем условие завершения вида
«закончить после получения n десятичных знаков числа π», то получим
алгоритм вычисления n десятичных знаков числа π. На этом принципе
построены многие вычислительные алгоритмы.
6. Эффективный алгоритм должен быть выполнен не просто за
конечное время, а за разумное конечное время. Время выполнения
алгоритма – очень важный параметр, однако понятие эффективности
алгоритма чаще трактуется шире и включает такие аспекты, как слож-
ность, необходимые ресурсы, информационно-программное обеспечение.
Эффективность алгоритма часто определяет возможность его практиче-
ской реализации.
Перечисленные свойства алгоритма, по существу, являются нефор-
мальным его определением. Объединяя их в одно целое, мы можем
сформулировать это определение следующим образом. Алгоритм – это
полное и точное описание на некотором языке конечной последователь-
ности действий, которые должен выполнить исполнитель, чтобы за ко-
нечное время перейти от (варьируемых) исходных данных к искомому
результату.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
11
1.2.2. Алгоритмизация
Алгоритмизация – процесс разработки и описания алгоритма реше-
ния какой-либо задачи.
Пусть мы имеем некоторую математическую задачу, которая может
быть решена одним из известных математических методов. Как присту-
пить к процессу построения алгоритма решения такой задачи?
Поскольку речь идет о разработке алгоритма для ЭВМ, то нужно сна-
чала проанализировать возможность его машинной реализации, оценить
ресурсы и возможности конкретной ЭВМ, имеющейся в вашем распоря-
жении (в том числе допустимую точность вычислений, объем запоми-
нающих устройств, быстродействие, информационно-программное обес-
печение).
Собственно непосредственная разработка алгоритма начинается с
осознания существа поставленной задачи, с анализа того, что нам извест-
но, что следует получить в качестве результата, в какой форме нужно
представить исходные данные и результаты вычислений. Следующая сту-
пень – разработка общей идеи алгоритмического процесса и анализа этой
идеи. После этого можно приступить к более детальной разработке уже
задуманного конкретного алгоритма. И вот этот процесс разработки кон-
кретного алгоритма, в соответствии с определением самого понятия «ал-
горитм», заключается в последовательном выполнении следующих пунк-
тов:
1. Разложение всего вычислительного процесса на отдельные шаги –
составные части алгоритма, что определяется внутренней логикой самого
процесса и системой команд исполнителя.
2. Установление взаимосвязей между отдельными шагами алгоритма
и порядка их следования, приводящего от известных исходных данных
к искомому результату.
3. Полное и точное описание содержания каждого шага алгоритма на
языке выбранной алгоритмической системы.
4. Проверка составленного алгоритма на предмет того, действительно
ли он реализует выбранный метод и приводит к искомому результату.
В результате проверки могут быть обнаружены ошибки и неточно-
сти, что вызывает необходимость доработки и коррекции алгоритма, т. е.
возвращение к одному из предыдущих пунктов. Во многих случаях раз-
работка алгоритма включает в себя многократно повторяющуюся проце-
дуру его анализа и коррекции.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
12
Процедура анализа и коррекции алгоритма производится не только
с целью устранения ошибок, но и с целью улучшения, т. е . оптимизации
алгоритма. При определенном методе решения задачи оптимизация про-
водится с целью сокращения алгоритмических действий и упрощения, по
возможности, самих этих действий. При этом алгоритм должен оставать-
ся «эквивалентным» исходному. Будем называть два алгоритма эквива-
лентными если:
1) множество допустимых исходных данных одного из них является
множеством допустимых исходных данных и другого; из применимости
одного алгоритма к каким-либо исходным данным следует применимость
и другого алгоритма к этим данным;
2) применение этих алгоритмов к одним и тем же исходным данным
дает одинаковые результаты.
1.2.3. Схемы алгоритмов
Характер языка, используемого для записи алгоритмов, определяется
тем, для какого исполнителя предназначен алгоритм. Возможности ис-
полнителя алгоритмов определяют уровень применяемых языковых
средств, т. е. степень детализации и формализации предписаний в алго-
ритмической записи. Если алгоритм предназначен для исполнителя-
человека, то его запись может быть не полностью формализована и дета-
лизирована, но должна оставаться понятной и корректной. Для записи
таких алгоритмов может применяться естественный язык. Для записи ал-
горитмов, предназначенных для исполнителей-автоматов, необходимы
строгая формализация средств записи и определенная детализация алго-
ритмических предписаний. В таких случаях применяют специальные
формализованные языки.
Поскольку одним из пользователей языка описания алгоритмов так
или иначе остается человек, то, говоря об уровне языка, имеют в виду
также и уровень его доступности для человека.
Схема алгоритма – это графический способ его представления с эле-
ментами словесной записи. Каждое предписание алгоритма изображается
с помощью плоской геометрической фигуры – блока. Отсюда название –
блок-схема . Переходы от предписания к предписанию изображаются
линиями связи – линиями потоков информации, а направления перехо-
дов – стрелками. Различным по типу выполняемых действий блокам
соответствуют различные геометрические фигуры. Приняты определен-
ные стандарты графических изображений блоков (табл. 1.1).
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
13
Таблица 1.1
Наименование
символа
Обозначение и размеры
Функция
Процесс (вы-
числительный
блок)
b
a
Выполнение операции или группы
операций, в результате которых
изменяются значение, форма
представления или расположение
данных
Решение (логи-
ческий блок)
Выбор направления выполнения
алгоритма или программы в зави-
симости от некоторых условий
Модификация
(заголовок цик-
ла)
0,25a
Выполнение операций, меняющих
команды или группы команд
Пуск-останов
(начало-конец)
0
,
5
a
Начало, конец, прерывание про-
цесса обработки данных или вы-
полнения программы
Предопреде-
ленный процесс
(подпрограмма)
0,15a
Использование ранее созданных
или отдельно описанных алгорит-
мов и программ
Соединитель
0,5a
Указание связи между прерван-
ными линиями потока, связываю-
щими символами
Межстранич-
ный соедини-
тель
0,5a
0
,
2
a
0
,
6
a
Указание связи между разъеди-
ненными частями схем алгорит-
мов и программ, расположенных
на разных листах
Ввод-вывод
0,25a
a
b
Преобразование данных в форму,
пригодную для обработки (ввод)
или отображения результатов об-
работки (вывод)
Рассмотрим общие правила построения схем алгоритмов.
1. Для конкретизации содержания блока и уточнения выполняемого
действия в блоке помещаются краткие пояснения – словесные записи с
элементами общепринятой математической символики (рис. 1 .1, а).
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
14
A:=B+2
C:=A3
8
9
A:=B+2
C:=A3
8
а
Вво д
A,B
2
MAX
(A, B)
A
A
Вывод
Z
7
8
в
A>B
A:=B
A,B
Да
Нет
B2
B4
C3
б
D:=A+C
12
E3
D2
Лист10
10
D2
A>B
Да
Нет
E3
Лист12
г
Рис. 1 .1 . Элементы схем алгоритмов
2. Основное направление потока информации в схемах может не от-
мечаться стрелками. Основное направление – сверху вниз и слева напра-
во. Если очередность выполнения блоков не соответствует этому направ-
лению, то возможно применение стрелок (рис. 1 .1, б).
3. По отношению к блоку линии могут быть входящими и выходя-
щими. Количество входящих линий принципиально не ограничено. Ко-
личество выходящих линий регламентировано и зависит от типа блока.
Например, логический блок должен иметь не менее двух выходящих ли-
ний, каждая из которых соответствует одному из возможных направле-
ний вычислений. Блок модификации должен иметь две выходящие ли-
нии, одна соответствует повторению цикла, вторая – его окончанию.
4. Допускается разрывать линии потока информации, размещая на
обоих концах разрыва специальный символ «соединителя» (рис. 1.1, в, г).
В пределах одной страницы используется символ обычного «соедините-
ля», во внутреннем поле которого помещается маркировка разрыва либо
отдельной буквой, либо буквенно-цифровой координатой блока, к кото-
рому подходит линия потока.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
15
Если схема располагается на нескольких листах, переход линий потока с
одного листа на другой обозначается с помощью символов «межстраничного
соединителя». При этом на листе с блоком-источником соединитель содер-
жит номер листа и координаты блока-приемника, а на листе с блоком-
приемником – номер листа и координаты блока-источника.
5. Нумерация блоков осуществляется либо в левом верхнем углу бло-
ка в разрыве его контура, либо рядом слева от блока (см. рис. 1 .1, а).
Принцип нумерации может быть различным, наиболее простой – сквоз-
ная нумерация. Блоки начала и конца не нумеруются.
6. Для блоков приняты следующие размеры: а=10, 15, 20 мм; в=1,5а.
Если необходимо увеличить размер блока, то допускается увеличение на
число, кратное пяти. Необходимо выдерживать минимальное расстояние
3 мм между параллельными линиями потоков и 5 мм между остальными
символами.
С помощью блок-схем можно изображать самые различные алгорит-
мы. На рис. 1 .2 приведен пример схемы алгоритма линейной структуры
для вычисления коэффициентов приведенного квадратного уравнения р и
q по значению его корней x1 и x2.
Начало
Ввод x1, x2
P := -(x1+x2)
Q := x1*x2
Вывод P, Q
Конец
1
2
3
Рис. 1.2. Схема линейного алгоритма
1.3. Построение простейших программ
В этом разделе рассматриваются общая структура программы,
а также основные элементы языка Turbo С, позволяющие строить про-
граммы линейной структуры, такие, как описания данных различных ти-
пов, арифметические выражения, операторы присваивания и ввода-
вывода.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
16
1.3.1. Структура программы
Любая программа на языке С состоит из одной и более функций, одна
из которых должна иметь имя main, и именно ей передается управление
из операционной системы. Функция – это коллективное имя для некото-
рой группы описаний и операторов, заключенных в фигурные скобки { }
и являющихся телом функции. В общем виде программа на языке С име-
ет следующую структуру:
#<Директивы препроцессора>
main( )
{
<
тело функции main>
}
function1( )
{
<
тело функции function1>
}
function2( )
{
<
тело функции function2>
}
...
Структура каждой, в том числе и главной, функции будет ясна из
приведенного ниже примера программы, обеспечивающей сложение двух
целых чисел a и b.
/*Простейшая программа*/
#include <stdio.h>
#define STARS рrintf("****************************\n")
main( )
{
int a,b,sum;
STARS;
printf("Введите два числа :");
scanf("%d%d",&a,&b);
sum=a+b;
рrintf("Сумма =%d\n",sum);
STARS;
}
В результате ее выполнения на экране дисплея появится следующая
информация:
*****************************
Введите два числа: 5 78
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
17
Сумма = 83
*****************************
где подчеркнутые цифры вводятся пользователем.
В начале рассмотренной программы присутствуют директивы про-
цессора:
#include<stdio.h> – включает в программу информацию из стандарт-
ного файла об операторах ввода-вывода;
#define STARS рrintf(«*****************************\n») – заменяет
строку STARS на обращение к функции рrintf( ); .
Далее в программе следует оператор описания целых переменных int
a,b,sum; операторы вывода рrintf( ) и ввода scanf( ) и оператор присваива-
ния sum = a+b.
Символы %d обозначают формат ввода-вывода целого числа, а \n –
переход на новую строку.
В программу могут быть включены комментарии, обрамляемые сим-
волами /* (начало комментария) и */ (конец комментария). Пробелы, сим-
волы табуляции и перехода на новую строку в программе игнорируются.
Таким образом, можно выбрать любую наглядную форму представления
текста программы (операторы можно начинать с любой позиции, можно
пропускать строки и т. п.) . При этом каждое описание и каждый оператор
должны обязательно заканчиваться точкой с запятой.
Рассмотрим далее те элементы, из которых строится программа.
1.3.2. Идентификаторы
Идентификаторы используются для обозначения имен переменных,
функций и типов данных. Идентификаторы состоят из прописных и
строчных букв латинского алфавита, цифр и символа подчеркивания. На-
чинаться они могут только с буквы или с символа подчеркивания. Длина
идентификатора не ограничена, но только первые 32 символа являются
значащими. Например:
X tN Yellow Code_4
Прописные и строчные буквы рассматриваются как различные бук-
вы, поэтому, например, идентификаторы index, Index, INDEX – это раз-
ные идентификаторы.
В качестве идентификаторов не должны использоваться ключевые
слова, зарезервированные в Turbo С:
asm
extern
return
_cs
_CH
auto
far
short
_ds
_CL
break
float
signed
_es
_CX
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
18
case
for
sizeof
_ss
_DH
cdecl
goto
static
_AH
_DL
char
huge
struct
_AL
_DX
c
o
n
s
t
i
f
s
w
i
t
c
h
_
A
X
_
B
Р
continue
int
tyрedef
_BH
_DI
default
interrupt
union
_BL
_SI
do
long
unsigned
_BX
_SР
double
near
void
else
рascal
violate
enum
register
while
1.3.3. Константы
Язык С поддерживает целые, длинные, с плавающей точкой, сим-
вольные и строковые константы.
Среди целых констант можно выделить десятичные, восьмеричные
и шестнадцатеричные.
Десятичная целая константа – это последовательность цифр от 0
до 9, не начинающаяся с нуля:
26
11 1991
32760
Восьмеричные константы состоят из цифр от 0 до 7 и начинаются с
нуля: 0347 (десятичное представление этого числа будет равно 3·8
2
+
+4·8+7=231).
Шестнадцатеричные константы строятся из цифр 0–9 и букв a, b, c,
d,e,fилиA,B,C,D,E,Fдляпредставленияцифр10,11,12,13,14,15и
начинаются символами 0x или 0X:
0X1B2F = 1·163
+11·162+2·16+15=6959
Длинная целая константа определяется буквами l или L, стоящими
после константы:
361327L
076l 0xabl
Константа с плавающей точкой состоит из целой части, десятичной
точки, дробной части, символа экспоненты e или E и экспоненты в виде
целой константы (возможно со знаком). При этом могут отсутствовать
целая или дробная часть, десятичная точка или символ e (E) и экспонен-
та. Например:
592.
3.14159 –2 .3Е9
.356Е3
307е-5
Символьная константа состоит из одного символа кода ASCII, заклю-
ченного в апострофы: 'q', '2', '.' и т. п . Специальные управляющие симво-
лы представляются в таком виде:
'\a' – звуковой сигнал BEL;
'\B' – забой BS;
'\f' – перевод формата FF;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
19
'\n' – перевод строки LF;
'\r' – возврат каретки CR;
'\t' – горизонтальная табуляция HT;
'\v' – вертикальная табуляция VT;
'\\' – обратный слеш;
'\'' – апостроф;
'\''' – кавычка;
'\?' – вопросительный знак;
'\0' – нулевой символ NULL.
Кроме этого, любой символ может быть представлен последователь-
ностью трех восьмеричных цифр '\DDD' или трех шестнадцатеричных
цифр '\xHHH'. Например, вместо символа '4' можно записать его код '\044'
или '\x34'.
Ниже приводится основная таблица литер кода ASCII (табл. 1 .2).
Таблица 1.2
DEC/ 0
16 3248648096112128144160176192208224240
/HEX00102030405060708090A0B0C0D0E0F0
00NULDLE
0@Р
рАРа░└╨рЕ
11SONDC1!1AQaqБСб▒┴╤сЕ
22STXDC2«2BRbrBТ
в
▓┬╥тЄ
33ETXDC3#3CScsГУ
г
│├╙уЄ
44EOTDC4$4DTdtДФд┤─╘фЇ
55ENQ
%5EUeuЕХ
е
╡┼╒хЇ
66ACKSYN&6FVfvЖЦж╢╞╓цЎ
77BELETB‘7GWg
w
ЗЧз╖╟╫чЎ
88BSCAN(8HXhxИШи╕╚╪ш
°
99HTEM)9IYiyЙЩй╣╔┘щ̇
10ALFSUB*
:
JZjzКЪ
к
║╩┌ъ?
11BVTESC+;K[k{ЛЫл╗╦█ы
_
12CFFFS,
<L
\l|МЬм╝╠▄ьNo
13DCR
?
-
=
M]m}НЭн╜
═
▌э¤
14ESORS.
>N^
n
~
ОЮо╛╬▐ю
_
15F SI
/?O_
oDELП
Яп┐╧▀я
Дополнительная таблица (включающая и русские буквы) определяет-
ся соглашением для конкретного компьютера или драйвера – программы,
генерирующей изображения различных символов.
Строковая константа состоит из последовательности символов кода
ASCII, заключенной в кавычки («как, например, эта»). Она располагается
обязательно на одной строке. Для продолжения символьной последова-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
20
тельности на новой строке необходимо использовать символ новой стро-
ки \n.
В языке С разрешается использовать многостроковые элементы
в строковых константах, которые могут потребоваться для конкатенации
(соединения) строк. Так, например, программа
#include <stdio.h>
main( )
{
char *р = " Это пример того, как С"
" будет автоматически\n выполнять конкатенацию"
" ваших очень длинных строк,\n делая наглядным"
" общий вид программы.";
puts(р);
}
выдаст следующий результат:
Это пример того, как С будет автоматически выполнять конкатенацию
ваших очень длинных строк, делая наглядным общий вид программы.
1.3.4. Арифметические операции
В языке С существуют арифметические операции сложения +, вычи-
тания –, умножения *, деления / и деления по модулю %.
Следует подчеркнуть особенность операции деления. Эта операция
дает целый результат, если оба операнда целые. Например, выражение
9/5 даст результат, равный единице. Чтобы получить действительный ре-
зультат, необходимо иметь хотя бы один действительный операнд. Так,
9./5 будет равно 1.8.
Выражение a%b дает остаток от целочисленного деления a на b. При
выполнении, например, операции 9%5 получаем 4.
В языке существуют также две нетрадиционные операции – операция
увеличения (инкремента) «++» и операция уменьшения (декремента) «--»
на единицу значения операнда. Операции «++» и «--» имеют префиксную
(++n или --n) и постфиксную (n++ или n--) формы записи. В первом слу-
чае значение операнда n сначала изменяется, а затем используется для
дальнейших вычислений, во втором же случае n сначала используется,
а затем изменяется. Так, например, запись
sum=a+b++;
означает «сложить a и b, присвоить результат sum и увеличить b на еди-
ницу», а
sum=a+++b;
«увеличить b на единицу, сложить a и b и присвоить результат sum».
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
21
Арифметические операции, как и операции других типов, выполня-
ются в порядке увеличения их приоритета в соответствии с табл. 1 .3 .
Таблица 1.3 .
Приори-
тет
Операция
Название
Порядок
выполнения
()
Скобки, вызов функции
Слева направо
[]
Квадратные скобки, выделение элемента
массива
Слева направо
.
Выделение элемента структуры или объе-
динения
Слева направо
1
–>
Выделение элемента структуры (объеди-
нения), адресуемой (го) указателем
Слева направо
!
Логическое отрицание
Справа налево
~
Побитовое отрицание
Справа налево
-
Изменение знака
Справа налево
++
Увеличение на ед иницу
Справа налево
--
Уменьшение на единицу
Справа налево
&
Определение адреса
Справа налево
*
Обращение по адресу (содержимое адре-
са), разыменование
Справа налево
(тип)
Преобразование типа
Справа налево
2
sizeof
Определение размера в байтах
Справа налево
*
Умножение
Слева направо
/
Деление
Слева направо
3
%
Остаток от деления
Слева направо
+
Сложение
Слева направо
4
-
Вычитание
Слева направо
<<
Сдвиг влево
Слева направо
5
>>
Сдвиг вправо
Слева направо
<
Меньше
Слева направо
<= (!=>) Меньше или равно
Слева направо
>
Больше
Слева направо
6
>= (!<)
Больше или равно
Слева направо
==
Равно
Слева направо
7
!=
Не равно
Слева направо
8
&
Поразрядное И
Слева направо
9
^
Исключающее ИЛИ
Слева направо
10
|
Поразрядное ИЛИ
Слева направо
11
&&
Логическое И
Слева направо
12
||
Логическое ИЛИ
Слева направо
13
?:
Условная операция
Справа налево
=
Присваивание
Справа налево
*=
Умножение и присваивание
Справа налево
/=
Деление и присваивание
Справа налево
%=
Остаток и присваивание
Справа налево
+=
Сложение и присваивание
Справа налево
14
-=
Вычитание и присваивание
Справа налево
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
22
Приори-
тет
Операция
Название
Порядок
выполнения
<<=
Сдвиг влево и присваивание
Справа налево
>>=
Сдвиг вправо и присваивание
Справа налево
&=
Поразрядное И и присваивание
Справа налево
^=
Исключающее ИЛИ и присваивание
Справа налево
|=
Поразрядное ИЛИ и присваивание
Справа налево
15
,
Операция запятая
Слева направо
Для четырех операций: логического И (&&), логического ИЛИ (||),
условной (?:), запятой (,) – гарантируется, что левый операнд будет обра-
батываться первым. Для остальных операций порядок обработки операн-
дов может быть различным на разных компиляторах.
1.3.5. Математические функции
Язык С имеет широкий набор библиотечных функций. Они обеспечи-
вают ввод-вывод низкого и высокого уровня, работу со строками и фай-
лами, распределение памяти, управление процессами, преобразование
данных, математические вычисления и многое другое. Многие из них бу-
дут рассмотрены в последующих главах. Здесь же мы остановимся толь-
ко на математических функциях, список которых приведен в табл. 1 .4 . Те
функции, которые возвращают значения, отличные от целого (int), долж-
ны быть описаны с указанием их типа в вызывающей программе. В таб-
лице указано также имя включаемого файла, содержащего эту функцию.
Таблица 1.4
Тип
Функция
Обозначе-
ние
функции функции
аргумента
Файл
описания
abs(x)
int
int
<stdlib.h>
cabs(x)
double
struct
<math.h>
fabs(x)
double double
<math.h>
Абсолютное значение
labs(x)
long
long
<stdlib.h>
Арккосинус
acos(x)
double double
<math.h>
Арксинус
asin(x)
double double
<math.h>
Арктангенс
atan(x)
double double
<math.h>
Арктангенс2 от y/x
atan2(y,x) double double
<math.h>
Косинус
cos(x)
double double
<math.h>
Синус
sin(x)
double double
<math.h>
Тангенс
tan(x)
double double
<math.h>
Синус гиперболический
sinh(x)
double double
<math.h>
Тангенс гиперболический
tanh(x)
double double
<math.h>
Округление до большего целого
ceil(x)
double double
<math.h>
Округление до меньшего целого
floor(x)
double double
<math.h>
Экспоненциальная функция e
x
exр(x)
double double
<math.h>
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
23
Тип
Функция
Обозначе-
ние
функции функции
аргумента
Файл
описания
Экспоненциальная функция x
2n
ldexр(x,n) double double x int n <math.h>
Степенная функция x
y
рow(x,y) double double
<math.h>
Степенная функция 10
n
рow10(n) double int
<math.h>
Логарифм натуральный
log(x)
double double
<math.h>
Логарифм десятичный
log10(x)
double double
<math.h>
Корень квадратный
sqrt(x)
double double
<math.h>
Остаток от деления x на y
fmod(x,y) double double
<math.h>
Генератор случайных чисел в диапа-
зоне от 0 до 32767
rand( )
int
<stdlib.h>
Инициализатор случайных чисел, на-
чиная с числа n
srand(n)
int
<stdlib.h>
1.3.6. Операция присваивания
Наиболее общей операцией является операция присваивания, на ос-
нове которой строится оператор присваивания, имеющий следующий
синтаксис:
V1=V2= ...= Vn=E;
где V1, V2... – переменные; E – выражение. При его выполнении вначале
вычисляется значение выражения E, а затем это значение присваивается
переменным левой части в порядке справа налево.
Например, оператор
sum=a =b;
присвоит переменной a значение b, а переменной sum – значение a.
Если слева и справа от операции присваивания стоит одна и та же пе-
ременная, то запись операции присваивания можно сократить. Так, вме-
стоs=s+iможнозаписатьs+=i,авместоa=a/bсоответственноa/=b.
Полный список комбинированных операций присваивания имеет сле-
дующий вид:
+=
−=
*=
/=
%= >>= <<= &=
^=
|=
Приведенная ниже программа иллюстрирует применение различных
операций.
#include <stdio.h>
main( )
{
inta=0,b=0,c=0,d=0,n=2,m=10;
a=++n;
n--;
b=n++;
c= --n;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
24
d=n--;
/*Демонстрация префиксных (++n, --n) и постфиксных (n++, n--) операций
увеличения и уменьшения */
рrintf("a=%d b=%d c=%d d=%d n=%d\n",a,b,c,d,n);
a+=2;
b–= 2;
c*= 2;
d%=2;
m/=2;
/*Демонстрация операций присваивания и арифметических операций*/
рrintf("a=%d b=%d c=%d d=%d m=%d\n",a,b,c,d,m);
}
Результаты ее работы имеют следующий вид:
a=3 b=2 c=2 d=2 n=1
a=5 b=0 c=4 d=0 m=5
Операция присваивания в языке С трактуется так же, как и другие
операции, т. е. она присваивает определенное значение, которое здесь же
вновь может использоваться для дальнейших вычислений. Так, с помо-
щью выражения
(r = sqrt(x*x + y*y)) < R
не только можно определить расстояние r от центра до точки с координа-
тами (x,y), но и проверить, находится ли эта точка в круге радиусом R.
Операция присваивания по сравнению с другими операциями имеет
более низкий приоритет, поэтому в выражениях при необходимости она
должна заключаться в скобки.
1.3.7. Функции ввода и вывода
Язык С имеет большой набор различных функций для организации
эффективного ввода и вывода разных типов данных. В этом подразделе
рассмотрим только две функции – printf и scanf, предназначенные для
реализации форматированного вывода и ввода данных. Функция printf
имеет следующий синтаксис:
printf("<управляющая строка> "[,<список аргументов>]);
Список аргументов представляет собой последовательность констант,
переменных или выражений, значения которых выводятся на экран дис-
плея в соответствии с форматом управляющей строки.
Управляющая строка содержит объекты трех типов: обычные симво-
лы, которые текстуально выводятся на экран дисплея, спецификации
преобразования, каждая из которых вызывает вывод на экран значения
очередного аргумента из последующего списка, и управляющие сим-
вольные константы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
25
В общем виде спецификация преобразования имеет следующую форму:
%[<выравнивание>][<ширина>][<дополнительные признаки>]<символ
преобразования>
Каждая спецификация преобразования начинается с символа % и за-
канчивается символом преобразования. Между ними могут записываться:
знак минуса, указывающий, что преобразованный параметр дол-
жен быть выровнен влево в своем поле, при его отсутствии данное
при выводе прижимается к правой границе поля;
строка цифр, задающая минимальный размер поля;
точка, отделяющая размер поля от последующей строки цифр;
строка цифр, задающая максимальное число символов, которое
нужно вывести в строке, или же число цифр, выводимое после де-
сятичной точки в значениях типа float или double;
символ l, указывающий, что соответствующий аргумент имеет тип
long.
Символ преобразования может быть следующим:
d – аргумент преобразуется в десятичное представление;
o – аргумент преобразуется в восьмеричное представление;
x – аргумент преобразуется в шестнадцатеричное представление;
c – значением аргумента является символ;
s – значением аргумента является строка символов;
g – один из форматов f или e;
e – значением аргумента является величина типа float или double
в форме с плавающей точкой;
f – значением аргумента является величина типа float или double
в форме с фиксированной точкой;
u – значением аргумента является целое беззнаковое число;
р – значением аргумента является указатель (адрес).
Таким образом, управляющая строка определяет количество и тип
аргументов. Среди управляющих символьных констант наиболее часто
используются следующие:
\a – кратковременная подача звукового сигнала;
\n – переход на новую строку;
\t – горизонтальная табуляция;
\b – возврат курсора назад на один шаг;
\r – возврат каретки.
Например, в результате вызова функции
рrintf("\t i=%ld; \n j=%d, a=%6.2f .\n", i , j, a);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
26
при условии, что i = 123456, j = 127, a = 86,531, будет выведена информа-
ция в виде:
i=123456;
j=127, a= 86.53 .
Функция scanf описывается так же, как и функция рrintf:
scanf("<управляющая строка>", <список аргументов>);
Аргументы scanf должны быть указателями на соответствующие зна-
чения (для этого перед именем переменной, не являющейся указателем,
записывается символ взятия адреса &). Управляющая строка содержит
спецификации преобразования и используется для установления количе-
ства и типа аргументов. В нее могут включаться:
пробелы, символы табуляции и перехода на новую строку (все они
игнорируются при вводе);
спецификации преобразования, состоящие из знака %; возможно,
символа запрещения (*), возможно, числа, задающего максималь-
ный размер поля, и самого символа преобразования. Например:
scanf("%d %f %c %s", &i, &a, &ch, r);
Здесь r представляет собой строку символов, которая сама является ука-
зателем, поэтому перед ней знак амперсанта & не ставится.
Исходные данные во входном потоке записываются через один или
несколько пробелов, символы табуляции или новой строки. Если же
входные данные разделяются запятыми, то и в управляющей строке спе-
цификации преобразования должны быть разделены запятыми.
Примеры использования функций рrintf и scanf будут даны позже при
программировании конкретных задач.
1.3.8. Основные типы данных
Все данные в программе должны быть описаны с помощью операто-
ров объявления типа данных, имеющих следующий вид:
<
имя типа> <список переменных>;
Типы данных задаются ключевыми словами int (целый), long (длин-
ный), short (короткий), unsigned (беззнаковый), char (символьный), float
(действительный одинарной точности), double (действительный двойной
точности), pointer (указатель), enum (перечислимый). Например, оператор
int i, j; описывает переменные i и j как целые, а оператор float t; определя-
ет переменную t как действительную с одинарной точностью. Допусти-
мые в языке типы данных, их размеры в битах и диапазон представления
приведены в табл. 1 .5 .
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
27
Таблица 1.5
Тип
Размер, бит
Диапазон
unsigned char
8
0– 255
char
8
-1 28 –127
enum
16
-3 2768–32767
unsigned short
16
0–65535
short
16
-3 2768–32767
unsigned int
16
0–65535
int
16
-3 2768–32767
unsigned long
32
0–4294967295
long
32
-2 14 7 48 3648–2147483647
float
32
3.4 ·10
-38
–3.4·E
+38
double
64
1.7·10
– 308
– 1 .7·10
+308
long double
80
1.7 ·10
–4932
–1 .7 ·10
+4932
рointer
16
32
(указатели near, _ c s,_ ds,_ es, _ss)
(указатели far, huge)
Язык С поддерживает стандартные механизмы по автоматическому
преобразованию одного типа данных в другой. Если в выражении при-
сутствуют операнды различных типов, то они преобразуются в некото-
рый общий тип, при этом к каждому арифметическому операнду приме-
няется следующая последовательность правил:
char и short преобразуются в int;
float преобразуется в double;
если один из операндов двойной точности, то другие приводятся к
двойной точности и результат будет типа double;
если один из операндов типа long, то другие преобразуются в long
и результат будет long;
если один из операндов unsigned, то другие преобразуются в тип
unsigned и результат будет иметь тип unsigned;
в противном случае операнды должны быть типа int и результат
будет типа int.
Преобразование типов происходит и при выполнении операции при-
сваивания: значение правой части преобразуется в тип левой, при этом
если преобразование идет от длинного типа к более короткому, то стар-
шие разряды теряются.
1.4. Стиль записи программ на языке С
Программа должна быть правильной и эффективной. Это определя-
ется стилем программирования и записи программы. Чтобы легко было
понимать программы и вносить в них необходимые изменения, они
должны быть написаны просто и ясно.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
28
Для написания наглядных и легко читаемых программ необходимо
выполнять следующие рекомендации:
1. Стандартизация стиля программирования. Если существует бо-
лее одного способа реализации программы, то необходимо остановиться
на каком-то одном способе (лучше всего общепринятом) и всегда его
придерживаться. Это позволит избежать ошибок и путаницы и будет по-
нятно другим.
2. Размещение текста программы. Не следует операторы програм-
мы писать сплошным текстом. Программу необходимо размещать струк-
турированно:
конструкции языка (описания, операторы, блоки) более глубоких
уровней вложенности сдвигать от начала строки вправо;
конструкции языка одинаковых уровней располагать друг под дру-
гом;
каждое описание и каждый оператор писать с новой строки;
продолжение описаний и операторов на новые строки сдвигать
вправо относительно начала;
избегать слишком длинных строк.
3. Комментарии. Комментарии – это пояснительные тексты в скоб-
ках комментариев /* */. Они могут вставляться в любое место программы,
где допустимо размещения пробела. Выбор комментариев полностью за-
висит от программиста. Однако не следует ими слишком увлекаться. Ре-
комендуется любую программу сопровождать вводными комментариями,
поясняющими назначение программы и сообщающие некоторые сведе-
ния о программисте.
4. Выбор имен. Имена надо выбирать так, чтобы они наилучшим об-
разом соответствовали тем величинам, которые они представляют.
5. Упорядочение списков. Во всех списках (например, в описаниях)
числа следует располагать в порядке роста их значений, а имена – по ал-
фавиту.
6. Программирование «сверху вниз». Вначале строится общая схе-
ма алгоритма, где описываются этапы решения задачи в общем виде. За-
тем уточняются и детализируются отдельные ее блоки.
7. Эхопечать исходных данных. Контрольный вывод исходных дан-
ных (эхопечать) целесообразно производить в начале программы сразу
же после оператора ввода этих данных.
1.5. Пример составления линейной программы
Пусть требуется составить программу вычисления общей поверхно-
сти и объема круглого конуса по заданным радиусу основания R и длине
образующей L. При вычислениях использовать равенства:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
29
2
SRR
L
;
=π +π
2
1
VR
H
,
3
=π
где H – высота конуса, определяемая по формуле
22
HLR
.
=−
Программа на языке С в общем случае должна содержать вводные
комментарии, директивы препроцессора, заголовок, описания перемен-
ных, операторы. Определение исходных данных может быть осуществ-
лено с помощью операторов присваивания либо с помощью специальных
операторов ввода. Вычисления по формулам реализуются в порядке оп-
ределения числовых значений переменных H, S и V соответственно, по-
сле чего значения S и V выводятся на экран.
Для удобства пользования
введем комментарии, которые
позволяют записать нужную
учетную и поясняющую инфор-
мацию (фамилию программиста,
назначение переменных и цель
написания программы).
При вычислениях будет ис-
пользована константа π, значе-
ние которой связано с констан-
той, имеющей имя РI, директи-
вой препроцессора define.
В качестве имен переменных
будем употреблять переменные,
обозначения которых макси-
мально совпадают с именами и
обозначениями переменных са-
мой задачи.
Общий вид схемы алгоритма
для рассматриваемого примера
показан на рис. 1 .3, а ниже – про-
грамма на языке С.
Начало
Ввод
R,L
Вывод
S,V
Конец
RL
R
S
π
π
+
=
2
H
R
V
2
3
1
π
=
.
2
2
R
L
H
−
=
Рис. 1 .3. Алгоритм и программа
вычисления площади и объема конуса
/*Цель: вычисление площади и объема конуса
*/
/*Описание параметров и переменных:
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
30
/* R – радиус; L – длина образующей;
*/
/* H – высота; S – площадь поверхности;
*/
/* V – объем.
*
/
/*Требуемые подпрограммы: нет
*
/
/*Метод: вычисление по формулам
*
/
/*Программист: Иванов И.И .
*
/
/*Дата написания: 25 октября 2003 г.
*/
#include <stdio.h>
#define Рi 3.1415926
main( )
{
double sqrt( ), H, S, V;
float R, L;
char Line[ ]= "\n Программа вычисления площади и объема.\n";
рrintf("%s Введите радиус R и длину образующей L: ", Line);
scanf("%f%f",&R,&L);
рrintf("Конус с радиусом R = %f и образующей L = %f\n", R, L);
H=sqrt(L*L–R*R);
S=Рi*R*R+Рi*R*L;
V=Рi*R*R*H/3;
рrintf("имеет объем V = %.4f, площадь S = %.4f и высоту H = %.4f\n",
V,S,H);
}
Исходные данные для просчета контрольного варианта выбираем та-
ким образом, чтобы вычисления были достаточно простые и в то же вре-
мя обеспечивали бы полные вычисления по всем формулам.
Примем R=3 см, L=5 см. Тогда получим: H=4 см, S=75,36 см
2
,
V=37,68 см
3
.
Результаты просчета контрольного варианта на ЭВМ дают следую-
щие значения:
Программа вычисления площади и объема.
Введите радиус R и длину образующей L:3 5.
Конус с радиусом R = 3.000000 и образующей L = 5.00000
имеет объем V = 37.6799, площадь S = 75.3982 и высоту H = 4.0000 .
Откуда видно, что они хорошо совпадают с результатами ручного
просчета.
Вопросы для самоконтроля
1. Какова структура программы на языке С?
2. Каковы правила записи идентификаторов на языке?
3. Какие типы констант вам известны?
4. Каковы особенности выполнения арифметических операций?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
31
5. Какие библиотечные математические функции используются
в языке С?
6. В чем особенность операции присваивания?
7. Каким образом осуществляется ввод данных в программу?
8. Что такое форматированный вывод данных?
9. Какие типы данных используются в С-программах?
10. По каким правилам происходит преобразование типов данных
в арифметических выражениях?
11. Какими свойствами должен обладать алгоритм?
12. В чем заключается процесс алгоритмизации?
13. Каковы правила построения схем алгоритмов?
14. Каковы основные принципы структурного подхода к разработке
и оформлению алгоритмов?
Упражнения
Составить программу вычисления функции.
1.z=
2
5sin x
x+e
;y=
33
6, 35 10 (lnz sin x)k
zp
⋅++
+
приx=1,2;k=2.
2.
54
2
2
1-ax+
axbcosxbx;y=
lnx
ψ
ψ=−
+⋅
+
+ψ
приa= –0,75;b=51;x= 4.
π
3.
2
2
4x 0, 75sin x
z
; s=2,5x -10 x
5x 1,385
+
=
+
при x = 1,52.
4.
x=abc+tga;
32
2
3
5
za
xb
xc
x
ds
i
n
x
=+
+
+
+
приa=0,75;b=
3
0,8 10 ;−
⋅
c= -7;d=
2
510−
⋅
.
5.
3
22
x1y
a
xy
1
24
−−
=
++
; b= x(arctgz +
(x 3)
e
−
+
)
приx=0,3;y=2;z=
2
0, 03 10−
⋅
.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
32
6.
3
y1
2
2
y-x
3e
(y-
x
)
a
; b=1+y-x+
+
23
1xy tgz
−
+
=
+−
приx=
1-
2
7,510 ; y=24,610
−
⋅⋅
; z= 0,1.
7.z=
2
2
4x 0,75sin x
; s=2,5x 10 x
5x 1,38s 1
+
−
+−
при x = 1,52.
8.
2x
l
g
x
1x
Al
n
t
g
;
=
1
0
cos x
2
+
=+
+
β
β
приx=
1
22, 4 10−
⋅
.
9.a=y+
2
2
2
3
xz
ay
; b=1+tg
2
x
y
x
y
3
=+
+
+
приx=0,8;y=–2;z=
3
20, 5 10−
⋅
.
10.
64
3
22
xz
xc
o
s
(
x
z
)
;zxb
lg(x z)
+++
α=
=
+
+
приx=2;b=
2
248, 677 10−
⋅
.
11.r=
32
2rx
xz
2
2
0,9sin y z
re
;t
e1
c
o
s
rs
i
n
x
−
−
+
=
++
приx=0,8;z=3;y=
6
0,4 10−
⋅
.
12.W =
k
2
x
sin( )2;x=y+0,5678
xx
π
+π+
приy=
3
99, 81 10−
⋅
;k=5,2.
13.р =
22
2
ab
(ab) acos(ab)
; a=x+y;b=x+y
ab
++
⋅
++
+
приx=0,04;y=
2
4, 93 10−
⋅
.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 1. Программирование линейных алгоритмов
33
14.a=
3
2
y
ln(y x)(x
); b=a
a;
x
z
4
+−
+
приx= –3;y=
52
3,4 10 ; z=1,2 10
−
⋅⋅
.
15.
2
sin z
22x
y
2
2
lny x xcosy
ze
z;
t
xc
o
s
y
xe
xz
−
−+
=+
π
=
++
+
приx=
32
2,6 10 ; y=8,5 10
−−
π+
⋅
⋅
.
16.
22
2
22
tsinr
z2c
o
s
rt
;y
=
zl
n
(r t)
rt
=π
−
⋅
+
−
приr=0,5;t=
2
2,6 10
⋅
.
17.
53
2
xa
x2
;s
i
n
(
x
)
2lg5
+
α=
β=
α
+
приx=0,8;a=5.
18.W =
sin
;
tg
α+ αβ
αβ+β
33
5,1 10−
α=β+ ⋅
при β = 0,513.
19.
3
23
33
2
4
at
gx
zq
z;
y
=
ax
ax
++
=
+
+
приa=
3
0,3 10
−⋅;x=7,35;q=3.
20.
3
32
2
23
tg
W;
v
W
W
12
sin
sin
α+γ+απ
=+
=
α
+
γ
γ+γ
при
1
0, 25;
3,8 10−
α=
γ=−
⋅
.
21.
4
5
2
xyz3,3x y
d
;sdsinx
10 lg4
−+
==
+
+
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
34
приx=
3
560 10−
−⋅ ;y=2;z=0,08.
22.
24
r
sin
r;
z
r
s
i
n
(
r
)
e
cos 2 ctg
−
β+
π
==
+
β
+γ
приγ =0,06;β =4.
23.
22
22
sin
G;
h
G
2cos
β+ βξ
=
+απ
=
α +β+ξ
+β
ξ
+
ξ
при
3
0,751; 151,35 10 ;
3.
−
β=
ξ =⋅α
=
24.t =
3
23
2
2
aax
c
o
s
as
i
na
;ztcosa
xal
na
x
+−
+π
=⋅
+
приa=0,03;x=1,02.
25.q =
53
q
xq
y
23
xs
i
n
y
;s
e
15,2 10 sin y
+
=
+
⋅+
приx=0,51;y= 3
π
.
26.z=
22
sin x
xy
2
xy
e
;vxz
xy
−
+
+π
=
приx=6;y=
2
864 10−
⋅
.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
35
ГЛАВА 2. ПРОГРАММИРОВАНИЕ
РАЗВЕТВЛЯЮЩИХСЯ АЛГОРИТМОВ
2.1. Понятие разветвляющегося алгоритма
Вычислительные процессы, в которых в зависимости от тех или иных
условий должны выполняться различные ветви алгоритма вычислений,
называются разветвляющимися. Для построения алгоритмов, реализую-
щих такие вычислительные процессы, необходимы специальные коман-
ды (управляющие структуры), позволяющие управлять ходом выполне-
ния алгоритма, а именно осуществить выбор одного из нескольких все-
возможных действий. Основной такой конструкцией является структура
простого ветвления, реализующая принятие двоичного, или дихотомиче-
ского, решения. Рассмотрим пример алгоритма с ветвлениями.
Пример 2.1. Вычислить значение функции, график которой изобра-
жен на рис. 2 .1 . Область определения функции разбивается на 4 участка:
x, если x0
;
0,
если 0x3
;
y
x3
,если 3x5
;
2,
если x5
.
−≤
<≤
=
−
<≤
>
⎧
⎪⎨
⎪⎩
y
x
03
5
2
π/4
Рис. 2 .1 . График функции
Для построения схемы алгоритма решения данной задачи используем
вложенную конструкцию команд ветвления (рис. 2 .2). Проверяем усло-
вия последовательно. Первым проверим условие x≤0. Следующее усло-
вие – 0<x≤3 будет проверяться только в том случае, если первое не со-
блюдается и x>0. Следовательно, часть второго условия 0<x можно не
проверять: если дело дошло до проверки этого условия, то заведомо 0<x.
Аналогично исключается проверка 3<x из условия 3<x≤5, а также про-
верка последнего условия – x>5.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
36
Начало
Ввод
x
x<=0
y= -x
x<=3
y=0
x<=5
y=x-3
y=2
Выв.
x,y
Конец
1
2
3
4
5
6
78
9
Да
Нет
Да
Нет
Да
Нет
Рис. 2 .2 . Алгоритм вычисления значения функции
2.2. Операции логического типа
Для получения логического значения («истинно», кодируемое циф-
ровой 1, или «ложно», кодируемое цифрой 0) используются операции
отношения, логические и побитовые операции.
В языке С используется обычный набор операций отношений: <
(меньше), <= (меньше или равно), > (больше), >= (больше или равно), ==
(равно) и != (не равно).
Логическое выражение в виде отношения принимает значение 1 (ис-
тинно), если оно удовлетворяется для входящих в него операндов, и 0
(ложно) в противном случае, поэтому выражение 5>2 имеет значение 1,
a7<=0 –0.
К логическим операциям относятся логическое И или логическое ум-
ножение (&&), логическое ИЛИ или логическое сложение (||), исклю-
чающее ИЛИ (^) и логическое отрицание (!). Если операнд операции от-
рицания равен нулю, то результат операции будет равен единице; если же
значение операнда отлично от нуля, результат операции будет равен ну-
лю. Операция логического умножения дает значение «истинно», если оба
операнда истинны, т. е . отличны от нуля; в противном случае результат
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
37
операции будет равен нулю, т. е. ложен. Логическое сложение вырабаты-
вает значение 1 (истинно), если хотя бы один из операндов истинен (от-
личен от нуля) и 0 (ложно) в противном случае. Операция исключающего
ИЛИ дает истинное значение, равное единице, если операнды имеют
противоположные значения (истинно и ложно), и 0, когда оба операнда
одновременно ложны или истинны. По сравнению с операциями отноше-
ния они имеют меньший приоритет. В качестве операндов могут исполь-
зоваться данные любого типа. При этом истинным считается значение,
отличное от нуля, а нулевое значение – ложным. Так, например, выраже-
ние 5&&3 дает значение, равное единице. Выполнение выражения такого
типа прекращается, как только становится ясно, будет ли результат иметь
значение «истина» или «ложь».
2.3. Условный оператор
Условный оператор позволяет выбрать и выполнить один из двух
входящих в него операторов в зависимости от значения некоторого вы-
ражения. Он имеет следующий вид:
if(<выражение>)
<
оператор 1>;
[else
<
оператор 2>;]
Здесь if и else – зарезервированные слова языка, означающие соответст-
венно «если» и «иначе»; квадратные скобки означают, что конструкция
else <оператор 2> может отсутствовать; <выражение> является любым
выражением, которое приводится или может быть приведено к целочис-
ленному значению.
Порядок выполнения условного оператора поясняется рис. 2 .3 . Если
<Выражение> принимает значение «истина», т. е. отлично от нуля, то
выполняется <Оператор 1>; если же оно принимает значение «ложь»,
т. е . равно нулю, то выполняется <Оператор 2>.
В любом случае далее выполняется оператор, стоящий в программе
непосредственно за условным. Например, оператор, вычисляющий
y = |x|, будет иметь следующий вид:
if(x >= 0)
y=x;
else
y= –x;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
38
Выражение
Оператор1
Истина
Оператор2
Ложь
Рис. 2 .3 . Схема полного условного оператора
Условный оператор может не иметь альтернативной конструкции
else, тогда он называется сокращенным условным оператором. Если про-
веряемое выражение принимает значение «ложь», сразу выполняется
оператор, следующий за условным (рис. 2 .4). Например, оператор
if (x<0)
x:= -x;
обеспечивает инвертирование значения переменной x, если оно отрица-
тельно, и оставляет его без изменения в противном случае. Рассмотрим
более подробно составные части условного оператора.
Выражение
Оператор1
Ложь
Истина
Рис. 2 .4. Схема сокращенного условного оператора
В качестве внутренних операторов оператора if может использовать-
ся любой оператор, в том числе и условный. То есть оператор if может
иметь вложенную конструкцию. В этом случае часть else связывается с
ближайшим предыдущим if в том же блоке, не имеющем части else.
Если необходимо выполнить одновременно несколько операторов, то
используют составной оператор или блок, представляющий собой группу
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
39
операторов, заключенную в фигурные скобки { }, в начале которого мо-
гут следовать описания. Точка с запятой после закрывающей фигурной
скобки } не ставится. Например:
if((ch = qetchar( ))=='a')
y=1;
else if (ch==‘b’)
{
y=2
;
ch=5;
}
else if(ch=='c')
y=3
;
e
l
s
e
рrintf("Ошибка ввода \n");
Здесь функция qetchar( ) обеспечивает ввод символа с клавиатуры,
значение которого присваивается переменной ch. Так как операция
присваивания имеет меньший приоритет, ее необходимо заключить
в круглые скобки.
Примером задачи, приводящей к сложному разветвлению, может
служить следующее задание. Даны две переменные x и y. Если x=y, то
вывести на печать значения этих переменных без изменения. Если x>y,
значения переменных уменьшить в 2 раза. Если же x<y, то значения пе-
ременных увеличить на 10. Схема алгоритма для данного примера пока-
зана на рис. 2.5.
В ветви if внешнего разветвления содержится еще один условный
оператор. Конструкция else во внешнем разветвлении отсутствует. Ветвь
else в данном примере (так же как и всегда) относится к ближайшему if,
не имеющему else. Ниже приведена структурированная программа, реа-
лизующая данный алгоритм. В результате структурирования соответст-
вующие элементы условного оператора if...else записаны друг под дру-
гом. Операторы, ограниченные операторными скобками { }, записаны
также друг под другом и смещены вправо относительно их.
/* **************************** */
/* Цель: изменение переменных.
*/
/* Переменные: х, у
*
/
/* Дата: 05.09 .03
*/
/* Программист: Кротов П.В .
*/
/* **************************** */
#include <stdio.h>
main( )
{
float x, y;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
40
рrintf("Введите х и у \n");
scanf("%f%f",&x,&y);
рrintf("x=%f, y=%f \n", x, y);
if (x<>y)
if (x< y)
{
x+=10;
y+=10;
}
else
{
x
/
=
2
;
y
/
=
2
;
}
рrintf("x=%f, y=%f \n", x, y);
}
Начало
Ввод
x,y
x<>y
x>y
x=x+10
y=y+10
x=x/2
y=y/2
Вывод
x,y
Конец
Нет
Да
Нет
Да
Внешнее
разветвление
Внутреннее
разветвление
Рис. 2 .5 . Схема алгоритма сложного разветвления
2.4. Операция условия
Операция условия «?:» применяется для записи условного выра-
жения:
(E1)?E2:E3
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
41
где E1, E2, E3 – выражения. Если выражение E1 истинно (отлично от ну-
ля), то значением всего условного выражения будет выражение E2. Если
же E1 ложно (равно нулю), то за значение условного выражения прини-
мается величина, вычисляемая в выражении E3.
Условное выражение позволяет более компактно представлять раз-
ветвляющийся процесс. Так, например, алгоритм вычисления модуля пе-
ременной x можно записать в виде следующего оператора:
y=(x<0)?–x:x;
2.5. Оператор-переключатель
Если в программе необходимо выбрать один из нескольких много-
численных вариантов, то вместо вложенной конструкции if более целесо-
образно применять оператор-переключатель switch, имеющий следую-
щий синтаксис:
switch(<выражение>)
{
case<константа1>:<операторы1>;
case<константа2>:<операторы2>;
-–
-
case <константа n>:<операторы n>;
[default:<операторы>;]
}
Здесь для выполнения выбирается тот вариант (группа операторов), кон-
станта которого совпадает со значением выражения. Как выражение, так
и метки (константы) должны иметь значения целого или символьного ти-
па.
После выполнения выбранной группы операторов будут выполнять-
ся все оставшиеся операторы до тех пор, пока не произойдет новый пере-
ход. Если в конце выбранного варианта поместить оператор break; (раз-
рыв), то управление будет сразу передано в конец оператора-переключа-
теля. Когда некоторому значению выражения не соответствует никакая
метка, управление передается операторам с меткой default (прочие). Ва-
риант default не обязательно должен быть последним и вообще может от-
сутствовать. В последнем случае, если значение выражения не соответст-
вует ни одной из констант, управление передается оператору, следующе-
му за оператором switch.
В качестве примера можно рассмотреть программу, выполняющую
функции простейшего арифмометра, алгоритм которой представлен на
рис. 2.6.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
42
Начало
Ввод
a,znak,b
znak
y=a*b
y=a+b
y=a+b
y=a/b
Вывод
y
Конец
Ошибка
+
-
*
/
default
Рис. 2 .6 . Алгоритм моделирования калькулятора
Ниже приводится текст программы.
/* ************************************* */
/*Цель: моделирование калькулятора
*
/
/*Описание параметров и переменных:
*/
/* a, b – операнды; znak – символ знака операции */
/* y – результат; flag – признак правильности
*/
/* введенного знака
*
/
/*Требуемые подпрограммы: нет
*
/
/*Метод: выбор формулы
*
/
/*Программист: Светлов П.В .
*
/
/*Дата написания: 28 октября 2003 г.
*/
/* ************************************* */
#include <stdio.h>
main()
{
inta,b,y;
char Znak;
рrintf("Введите a, знак операции и b\n\n");
scanf("%d %c %d", &a, &Znak, &b);
flag = 1;
switch(Znak)
{
case'+':y=a+b;break;
case'–':y=a –b;break;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
43
case'*':y=a *b;break;
case'/':y=a/b;break;
default : рrintf("Недопустимый знак операции \n");
flag = 0;
}
/* Реализация в переключателе switch требуемой операции */
if (flag)
/*Вывод на экран дисплея результата выполнения операций*/
рrinft("%d %c %d = %d\n", a, Znak, b, y);
}
Ниже приведены результаты ее выполнения. При этом исходные
данные, вводимые пользователем, подчеркнуты, а результаты, выводи-
мые программой, – нет.
Введите a, знак операции и b.
-17
–
-56
-17
–
-56 =39
2.6. Пример составления разветвляющейся
программы
Пусть даны три неравных числа a, b, c. Составить программу вычис-
ления значения y, равного квадрату большего из них.
Ниже представлен текст программы, а на рис. 2 .7
–
детализирован-
ная схема алгоритма.
/**************************** */
/* Цель: вычисление квадрата
*
/
/* максимального из трех чисел
*/
/* Переменные:
*
/
/*
a, b, c – исходные числа
*
/
/*
z – результат
*
/
/* Требуемые подпрограммы: нет
*
/
/* Метод: вычисление по формулам
*/
/* Программист: Федоров А.В .
*
/
/* Дата написания: 4 октября 2003 г.
*/
/**************************** */
#include <stdio.h>
main()
{
float a, b, c;
рrintf("Введите три числа a, b, c\n");
scanf("%f %f %f", &a, &b, &c);
рrintf("Исходные данные: \");
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
44
рrintf("a=%6.2f, b=%6.2f, c=%6.2f\n",
a, b, c);
if a>b
y=a
else
y=b;
if y<c
y=c;
y=y*y;
рrintf("Квадрат максимального: \n");
рrintf("y=%8.2f\n",z);
}
Просчет контрольного варианта. При
a = 3,83; b = 1,53; c = 4,5 максимальным
значением является 4,5; следовательно,
y=4,5
2
= 20,25.
Результат выполнения программы
на ЭВМ:
Введите три числа a, b, c:
3.83 1.53 4.5
Исходные данные:
a= 3.83, b= 1.53, c= 4.50
Квадрат максимального:
y= 20.25
Введите три числа a, b, c:
3.83 15.3 4.5
Исходные данные:
a= 3.83, b= 15.30, c= 4.50
Квадрат максимального:
z= 234.09
Введите три числа a, b, c:
5 1.53 4.5
Исходные данные:
a= 5.00, b= 1.53, c= 4.50
Квадрат максимального:
z= 25.00
Начало
Ввод
a,b ,c
a>b
y=a
y=b
y<c
y=c
y=y*y
Вывод
y
Конец
Нет
Да
Да
Нет
Рис. 2 .7. Детализированная
схема алгоритма
Упражнения
При выполнении программы на ЭВМ исходные данные выбрать та-
ким образом, чтобы получить результаты по каждой из ветвей програм-
мы.
1. Составить программу вычисления значения функции
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
45
)
(
)
(
2
3
2
2
2
x3
x
при x0
;
yx3 0
,
5x
при 0x1
;
xx 3 In( x) приx1
.
⎧ −−π
−
<
⎪⎪=+
−
π
+
≤
<
⎨
⎪
⎪ ++π
+
≥
⎩
2. Даны три числа a, b, c. Если хотя бы одно из них равно нулю с по-
грешностью ε = 0,01, то вычислить сумму этих чисел, в противном слу-
чае – их произведение.
3. Составить программу вычисления значения функции
3
sin 2
при k5
;
yc
o
s
при k2
0
;
tg sin
при k10или k 15.
2
x
x
xx
+=
⎧⎪==
⎨
⎪+
=
=
⎩
В остальных случаях значение y не определено.
4. Составить программу вы-
числения значения функции, за-
данной графиком при произволь-
ном значении x.
R=1
x
0
y
45°
5. Даны отрезки A, B, C. Составить программу
1) для определения возможности построения из этих отрезков тре-
угольника и выяснения, будет ли построенный треугольник рав-
носторонним;
2) для печати соответствующего сообщения.
6. Определить, принадлежит
ли точка M(x, y) к заштрихован-
ной области.
y
x
0
-1
1
7. Составить программу вычисления значения функции
()
2
2
sinx tgx
при
x;
3,5cos x
2
2
y
cos x/3
при
x.
2
sinx tgx
⎧+
ππ
⎪
−<<
⎪=⎨
⎪
π
<<π
⎪+
⎩
В остальных случаях значение y равно нулю.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
46
8. Даны три числа a, b, c. Составить программу нахождения значения
минимального отклонения каждого из них от их среднего арифметиче-
ского. Данные выбрать произвольно.
9. Даны три целых положительных числа a, b, c. Найти остаток k от
деления на 3 величины М:
2
ab
M;
c
+
=
вычислить значение функции
()
()
MC
2
e
при k=1;
yl
n
a
/
b
при k=0;
ab c при k=2.
+
⎧
⎪⎪= ⎨
⎪++
⎪⎩
10. Составить программу, печатающую одно из сообщений:
Корни действительные и равные,
Корни действительные и различные,
Корни мнимые.
в зависимости от вида корней квадратного уравнения
2
axbxc0
+ +=.
Принять a, b, c 0
≠.
11. Определить, принадлежит
ли точка А(x, y) к заштрихованной
области.
y
x
0
-1
1
-1
R
12. Составить программу вы-
числения значения функции, за-
данной графиком, при произволь-
ном значении x.
45°
45°
x
y
1
π
2
−
π
2
0
y=cosx
13. Составить программу вычисления значения функции:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
47
-ax
1-e sin(ax+b) при x>р;
-ax
V= 1-e (ax+b) при-р x р;
-ax -bx
1-(e +e ) приx<-р.
⎧
⎪⎪
≤≤
⎨
⎪
⎪⎩
где
22
xa
bs
i
n
bm
b
=−
+.
14. Даны три числа A, B, C. Если все числа положительны, вычислить
Z=A+B+C; если все отрицательны − Z=(A+B)*C; в противном случае
Z=ABC.
15. Составить программу преобразования вещественного вектора X
(x1, x2, x3) по правилу: если x1<x2<x3, то всем компонентам присвоить
значение наибольшей из них х3, если x1>x2>x3, то вектор оставить без
изменения, в противном случае все компоненты заменить их квадратами.
16. Составить программу для вычисления функции
2
sin1,25 tgx x
ac
o
s
2tg x
+
+
=
,
где {
щ-р
x=
щ+р
при π/2<ω<3π/4
В остальных случаях принять а=0. Для просчета значение ω выбрать
произвольно.
17. Составить
программу
вычисления значения функции,
заданной графиком. Значение х
взять произвольно.
x
y
1
12
0
R
x
2
18. Даны три числа a, b, c. Составить программу вычисления экспо-
ненты числа, значение которого ближе всего к значению функции
tg(p*b) + cosa
y
ln(c + 2)
=
.
19. Определить принадлеж-
ность точки B(x,y) к заштрихо-
ванной области.
x
y
12
03
1
x2
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
48
20. Даны произвольные отрезки a, b, c и d. Составить программу оп-
ределения возможности построения из них параллелограмма и печати со-
ответствующего сообщения.
2.7. Побитовые операции
В языке С предусмотрены также операции с битами (разрядами) –
побитовые операции, которые нельзя применять к переменным типа float
или double. К таким операциям относятся: поразрядное И (&), поразряд-
ное ИЛИ (|), поразрядное Исключающее ИЛИ (^), поразрядная инверсия
(~), сдвиг влево (<<), сдвиг вправо (>>). С помощью таких операций
пользователь легко может обращаться к двоичным «образам» значений и
манипулировать ими.
Пусть, например, a = 11, b = 6, что в двоичном представлении дает
соответственно 1011 и 0110; тогда:
a&b будет равно 2 (1011&0110 = 0010);
a|bравно15(1011|0110=1111);
a ^ b равно 13 (1011^0110 = 1101);
~a будет равно 4 (~1011 = 0100).
При выполнении операций сдвига двоичное представление операнда,
стоящего слева от знака << или знака >>, сдвигается на количество раз-
рядов, определяемых правым операндом, после чего результат преобра-
зуется в десятичное представление, если это обусловлено соответствую-
щим типом:
a<<2 даст значение 12 (1011<<2 = 1100);
b>>1 равно 3 (0110>>1 = 0011).
Работа с разрядами, обычно свойственная программистам, исполь-
зующим язык Ассемблера, нужна, например, тогда, когда проверяются
разряды регистров или маскируются некоторые из разрядов при приеме
или передаче информации.
Так, для определения значения n-го разряда какого-либо регистра не-
обходимо его значение умножить побитово на соответствующую степень
двойки. Например, a&8 даст значение 1011&1000, равное 1000. Если по-
лученное значение отлично от нуля, то из этого следует, что в анализи-
руемом разряде содержится единица. В противном случае можно считать,
что данный разряд содержит значение 0.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
49
Для занесения единицы в разряд какого-либо данного его следует
сложить побитово с соответствующей степенью двойки. Так, например,
операция b|8 приведет к результату 1110 (0110 | 1000 = 1110).
Если же необходимо обнулить значение какого-либо разряда, следует
произвести логическое умножение данного на маску, содержащую нуль в
соответствующем разряде и единицы во всех остальных разрядах. На-
пример, для обнуления первого разряда регистра, содержащего значение
переменной a (крайний разряд справа считается нулевым), необходимо
выполнить операцию a&13: 1011&1101 = 1001).
Рассмотрим пример программы, использующей побитовые операции.
Пусть необходимо составить программу, производящую отображение
на экране дисплея вводимого символа и одновременное преобразование
строчных латинских букв в прописные.
Для решения этой задачи из кода двоичного представления строчной
буквы необходимо удалить единицу пятого разряда (младший – нулевой),
поскольку их коды отличаются на величину 32 или 40 в восьмеричном и
20 в шестнадцатеричном представлении. Тогда программа будет иметь
следующий вид:
#include <stdio.h>
#include <conio.h>
#define bit_5 0x20
main()
{
char ch=getch( );
if ((ch >= 0x61) && (ch <= 0x7A))
ch &= ~ bit_5;
рrintf("%c", ch);
}
Вопросы для самоконтроля
1. Какие операции отношения вам известны?
2. Какие логические операции вам известны?
3. Что такое побитовые операции?
4. Каковы формы условного оператора?
5. Что такое блок и для каких целей он используется?
6. Приведите примеры вложенных условных операторов.
7. Каким образом строится условное выражение?
8. Какую структуру имеет оператор-переключатель?
9. Каков порядок выполнения оператора-переключателя?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
50
10. С какой целью применяется оператор разрыва?
11. Что происходит, если значение выражения не совпадает ни с од-
ной из меток оператора-переключателя?
Дополнительные упражнения
Для выполнения данных упражнений необходимо знать системы
счисления, используемые в вычислительной технике, представление дан-
ных в памяти ЭВМ, структуру и функционирование ЭВМ.
1. Определить, содержит ли двоичное представление целого 2-байто-
вого числа x единицу в k-м разряде. Если да, то вывести на печать значе-
ние старшего байта числа x, в противном случае – младшего.
2. Представить значение введенного числа в обратном коде и вывести
его двоичное представление на печать.
3. Сравнить содержимое регистра счетчика и регистра данных, обо-
значаемых соответственно псевдопеременными _CX и _DX. Результат
представить в виде двоичного кода.
4. Представить значение введенного числа в дополнительном коде и
вывести его двоичное представление на печать.
5. Проверить, находится ли в единице 7-й или 15-й разряд регистра
сегмента данных, обозначаемого псевдопеременной _DS.
6. По двоичному коду операции двухадресной команды определить
ее мнемоническое обозначение, применяемое в Ассемблере.
7.ВычислитьS=A+B+C+D,еслихотябыодноизчиселA,B,C,D
равно нулю, и Р=ABCD в противном случае, где A, B, C, D – числа, упа-
кованные в четырех разрядах каждого регистра указателя стека, имеюще-
го имя _SP.
8. В зависимости от кода, содержащегося в младшем полубайте ак-
кумулятора, имя младшего байта которого _AL, распечатать содержимое
сегментных адресных регистров: сегмента кода _CS, сегмента данных
_ DS, сегмента стека _SS, сегмента данных _ES и специальных регистров:
указателя стека _SР, указателя базы _BР, индекса источника _SI, индекса
получателя _DI .
9. Вычислить разность между старшим (_BH) и младшим (_BL) бай-
тами регистра базы _BX. Если 6-й разряд разности окажется равным еди-
нице, то присвоить значение 1 регистру данных, обозначаемому псевдо-
переменной _DX .
10. Составить программу преобразования восьмеричной цифры в ее
двоичный эквивалент.
11. Определить сумму байтов регистров базы и счетчика и записать в
регистр данных единицу, если 6-й разряд суммы будет равен единице.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 2. Программирование разветвляющихся алгоритмов
51
При этом используются следующие псевдопеременные: _BX
–
регистр
базы, _BH и _BL
–
его старший и младший байты, _CX – регистр счетчи-
ка, _CH и _CL – его старший и младший байты, _DX
–
регистр данных,
_DHи_DL
–
его старший и младший байты.
12. Вывести на печать двоичное представление введенного символа.
13. Ввести целое число x. Если его второй двоичный разряд равен
единице, то записать в регистр данных, обозначенный псевдопеременной
_ DX, число 2.
14. В зависимости от кода, содержащегося в младшем полубайте
старшего байта аккумулятора, имеющего имя AH, распечатать содержи-
мое регистров общего назначения (см. упражнение 8).
15. Определить количество единиц в четырех старших разрядах реги-
стра данных, обозначенного псевдопеременной _DX. Если это количест-
во четно, то вывести на печать содержимое регистра данных, в против-
ном случае – содержимое аккумулятора _AX.
16. Составить программу, обеспечивающую эхопечать введенного
символа с одновременным преобразованием прописных латинских букв в
строчные.
17. Преобразовать прописные латинские буквы, вводимые с клавиа-
туры, в графические символы и вывести их на печать.
18. Составить программу, обеспечивающую эхопечать введенного
символа и вывод мнемонического обозначения непечатаемого символа
(см. табл. 1 .3).
19. Обеспечить эхопечать введенного символа и вывод при этом
восьмеричного кода спецсимвола.
20. Определить, к какому типу относится введенный символ: буква,
цифра, спецсимвол, непечатаемый символ.
21. Определить, какому спецсимволу соответствует код, полученный
инверсией четных разрядов кода введенного символа.
22. Определить, какому символу будет соответствовать код, полу-
ченный путем обмена местами младшего и старшего полубайтов кода
введенного символа.
23. Определить, какому символу будет соответствовать код, полу-
ченный за счет инверсии 5-го разряда кода введенного символа.
24. Определить, какому символу будет соответствовать код, полу-
ченный за счет поразрядного логического сложения старших и логиче-
ского умножения младших полубайтов кодов двух введенных символов.
25. Определить, какому символу будет соответствовать код, полу-
ченный за счет инверсии разрядов младшего полубайта кода введенного
символа.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
52
ГЛАВА 3. ПРОГРАММИРОВАНИЕ
ЦИКЛИЧЕСКИХ АЛГОРИТМОВ
3.1. Понятие циклического алгоритма
3.1.1. Определение цикла
Вычислительные процессы с многократным повторением однотип-
ных вычислений/действий для различных значений входящих вели-
чин/данных называются циклическими, повторяющиеся участки вычисле-
ний – циклами, изменяющиеся в цикле величины – переменными цикла.
Для организации циклов в алгоритмах необходимо предусмотреть сле-
дующие этапы (рис. 3 .1):
Подготовка
цикла
Условие
продолжения
цикла
Тело цикла
(ТЦ)
Изменение
переменных
цикла
а
Нет
Да
Подготовка
цикла
Тело цикла
(ТЦ)
Изменение
переменных
цикла
б
Нет
Да
Условие
продолжения
цикла
Рис. 3 .1 . Общие схемы циклического алгоритма
подготовку цикла – задание начальных значений переменным цик-
ла перед первым его выполнением (инициализация переменных
цикла;
тело цикла (ТЦ) – действия, повторяемые в цикле для различных
значений переменных цикла;
модификацию/изменение значений переменных цикла перед каж-
дым новым его повторением или параметров, влияющих на усло-
вие окончания цикла;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
53
управление циклом – проверку условия продолжения/окончания
цикла и переход на повторение цикла или его окончание.
В зависимости от того, где осуществляется проверка условия про-
должения или окончания цикла, последний относят к виду:
цикла с предусловием, когда цикл начинается с проверки условия
продолжения цикла (см. рис. 3 .1, а);
цикла с постусловием, когда условие проверяется после выполне-
ния тела цикла (см. рис. 3 .1, б).
Наиболее наглядным примером циклического вычислительного про-
цесса является задача табулирования функции: вычисления значений
функции y=f(x) для значений аргумента x, изменяющихся от начального
значения x0 до конечного xn с постоянным шагом hx (рис. 3 .2).
x=x0
x<=x
n
ТЦx
x=x+hx
а
Да
Нет
x=x0
ТЦx
x=x+hx
б
Да
Нет
x<=x
n
y=f(x)
Вывод
x,y
Рис. 3 .2 . Общие схемы алгоритма табулирования функции
Здесь многократно повторяемые действия (тело цикла) – это вычис-
ление значения функции f(x) для очередного значения аргумента x и вы-
вод полученного результата на печать; переменная цикла – переменная x.
Цикл выполняется для значений x, равных x0, x0+hx, x0+2hx, ..., xn. Алго-
ритмы табулирования функции с пред- и постусловием дают, вообще го-
воря, одинаковый результат. Но тело цикла с предусловием в определен-
ной ситуации может не выполниться ни разу, а тело цикла с постуслови-
ем обязательно выполняется хотя бы один раз. Рассмотрим случай, когда
нижняя граница x0 переменной x превышает верхнюю xn. В этой ситуа-
ции цикл не должен выполняться ни разу. Поэтому в задаче табулирова-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
54
ния функции лучше использовать цикл с предусловием, цикл же с посту-
словием может дать неверный результат.
3.1.2. Структурограммы
Для представления алгоритмов кроме схем алгоритмов, рассмотрен-
ных ранее, могут использоваться также структурограммы. В них для изо-
бражения различных этапов алгоритмов допускается применение сле-
дующих блоков:
1. Блока обработки (
вычисле-
ний). Каждый символ структурограм-
мы является блоком обработки. Каж-
дый прямоугольник внутри любого
символа представляет собой также блок
обработки.
2. Блока следования. Этот символ
объединяет ряд следующих друг за
другом процессов обработки.
3. Блока решения. Этот символ
применяется для обозначения струк-
туры типа разветвления. Условие
располагается в верхнем треугольни-
ке, варианты решения – по сторонам
треугольника, а процессы обработки
обозначаются прямоугольниками. Если
блок решения является сокращенным
(отсутствует одна из ветвей), то струк-
турограмма видоизменяется соответст-
вующим образом.
4. Блока варианта. Этот символ
представляет собой расширение блока
решения. Те варианты выхода из этого
блока, которые можно сформулировать
точно, размещаются слева. Остальные
объединяются в один, называемый вы-
ходом по несоблюдению условий и рас-
положенный справа. Если нужно пере-
числить все возможные случаи, правую
часть можно оставить незаполненной
или совсем опустить.
Вычислить
y = Sinx/(1+ax+bx
2
)
y =a+Sinx
z=x
2
/2+tgx
х≤0
Да
Нет
б=х
2
б=х
1/2
Да
A>B
Вывод X, Y
1
Курс ?
2
По-
след-
3
ний
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
55
5. Блока цикла с предусловием.
Этот символ обозначает циклическую
конструкцию с проверкой условия в
начале цикла. Условие продолжения
цикла размещается в верхней полосе,
сливающейся с левой полосой, указы-
вающей границу цикла. Данная струк-
тура может быть использована также
для обозначения цикла с параметром.
При этом вверху указывают закон из-
менения параметра цикла.
6. Блока цикла с постусловием.
Этот символ аналогичен блоку цикла с
предусловием, но условие располагает-
ся внизу.
Поках≤хn
Тело цикла
х=х0 (hx) xn
Тело цикла
До х>xn
Тело цикла
Каждый блок имеет форму прямоугольника и может быть вписан в
любой внутренний прямоугольник любого другого блока. Блоки допол-
няются элементами словесной записи с помощью предложений на есте-
ственном языке или с использованием математических обозначений.
Приведем пример целесообразного использования цикла с постусло-
вием.
Пример 3.1. Составим алгоритм вычисления суммы всех целых чи-
сел, вводимых с терминала до тех пор, пока не будет введен нуль.
Накопление суммы S будем осуще-
ствлять в цикле путем прибавления
очередного введенного числа k к сумме
всех предыдущих: S=S+k. Перед нача-
лом цикла значение переменной S об-
нулим: S=0. Проверка условия оконча-
ния цикла возможна лишь после ввода
хотя бы одного числа, поэтому лучше
использовать цикл с постусловием. Ал-
горитм вычисления искомой суммы
представлен на рис. 3 .3 .
S=0
Ввод k
S=S+k
До k==0
Вывод S
Рис. 3.3. Алгоритм вычисления
суммы вводимых чисел
3.1.3. Циклы с известным числом повторений
Помимо циклов с пред- и постусловием принято различать циклы с
заранее неизвестным и заданным числом повторений. Примером цикла
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
56
первого типа может служить последний алгоритм вычисления суммы.
Примером цикла второго типа – алгоритм табулирования функции, где
число повторений цикла Nx определяется как
Nx= [(xn – x0)/hx]+1,
где [Z] означает целую часть Z.
В циклах с известным числом повторений всегда можно определить
переменную, связанную с числом повторений цикла, значение которой
изменяется по заданному закону: от начального до конечного с постоян-
ным шагом. Такая переменная используется для управления циклом: в
условии окончания цикла осуществляется сравнение текущего значения
переменной с заданным порогом. Эту переменную именуют параметром
цикла, а сам цикл – циклом с параметром.
Для схемного представления цикла с параметром используют специ-
альную управляющую структуру с блоком модификации (рис. 3 .4), где
указывают закон изменения параметра цикла. Например, в задаче табу-
лирования функции y=f(x) параметром цикла является переменная x, за-
кон изменения которой можно представить в виде x=x0(hx)xn. Схема цик-
ла с параметром для табулирования функции одной переменной приведе-
на на рис. 3.4.
1
x=x0(hx)xn
2
ТЦx
3
i
i+1
Рис. 3 .4. Схема цикла с параметром
На схеме вход 1 в блок i – первоначальный вход в цикл, вход 2 – очеред-
ное повторение цикла, выход 3 – окончание цикла. Блок модификации вклю-
чает в себя подготовку цикла (x=x0), изменение параметра цикла при его
очередном повторении (x:=x+hx), управление циклом – проверку условия
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
57
его продолжения (x<xn) и переход на продолжение или окончание цикла.
Проверка условия x<xn проводится перед каждым, в том числе первым, вы-
полнением цикла, как в цикле с предусловием. И если начальное значение
параметра цикла больше конечного, то цикл не выполняется ни разу.
Для записи цикла с параметром в языках программирования сущест-
вует специальный оператор – оператор цикла с параметром.
Пример 3.2 . Составим алгоритм табулирования сложной функции
2
sin x, если xa
;
z
y,
где z
cos x, если axb
;
ln(2 x)
tgx, если xb
,
x
≤
⎧
+
⎪
==
<
<
⎨
+
⎪
≥
⎩
для x, изменяющегося от x0 до xn с шагом hx, используя структуру цикла с
параметром. Алгоритм представлен на рис. 3.5 . Тело цикла в этом алго-
ритме представляет собой композицию двух вложенных структур ветвле-
ния и структуры следования.
Начало
Ввод
a,b,x0
,h
x
,x
n
x=x0(hx)xn
x<=a
z=sinx
x<b
z=cosx
z=tgx
y = (z2+x)/
ln(2+x2)
Вывод
x,y
Конец
Да
Нет
Да
Нет
Рис. 3 .5 . Алгоритм табулирования сложной функции
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
58
Пример 3.3. Вычислить сте-
пень Y=a
n
действительного числа a
с натуральным показателем n. Вос-
пользоваться для вычислений сле-
дующей формулой: a
n
=a*a*a*...*a –
n раз. Компактно произведение
может быть записано в виде
∏=
=
n
i
n
a
a
1
.
Для вычисления указанного
произведения Y организуем цикл с
параметром i, в котором будем на-
капливать искомое произведение
по следующему правилу: до начала
цикла положим Y=1, а в цикле бу-
дем домножать n раз накопленное
ранее произведение на a, т. е .
Y:=Y*a. Алгоритм представлен на
рис. 3.6.
Пример 3.4 . Вычислим сумму
квадратов всех целых чисел из
заданного интервала [m,n] по
формуле
.
2
∑=
=
n
m
i
i
S
Для вычисления указанной
суммы организуем цикл с парамет-
ром, в котором будем n раз вычис-
лять значение очередного слагае-
мого y:=i
2
и накапливать искомую
сумму S:=S+y. До начала цикла по-
ложим S:=0. Алгоритм приведен на
рис. 3.7.
Начало
Ввод
a,n
Y=1
i=1 ,n
Y =Y*a
Вывод
Y
Конец
Рис. 3 .6. Алгоритм вычисления
конечного произведения
Начало
Ввод
m,n
S=0
i=m,n
y =i*i
S =S+y
Вывод
S
Конец
Рис. 3 .7. Алгоритм вычисления
конечной суммы
3.1.4. Итерационные циклы
Среди циклов с неизвестным числом повторений большое место за-
нимают циклы, в которых в процессе повторения тела цикла образуется
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
59
последовательность значений a1, a2, ..., an,, сходящаяся к некоторому пре-
делу a:
n
n
lima a.
→∞
=
Каждое новое значение an в такой последовательности является более
точным приближением к искомому результату a. Циклы, реализующие
такую последовательность приближений/итераций, называют итераци-
онными.
В итерационных циклах условие окончания цикла основывается на
свойстве безграничного приближения значений an к искомому пределу с
увеличением n. Итерационный цикл заканчивают, если для некоторого
значения n выполняется условие
nn
1
aa,
−
−
≤ε
где ε – допустимая погрешность вычислений. При этом результат ото-
ждествляют со значением an, т. е. считают, что an=a.
Пример 3.5. Составим алгоритм вычисления yx
=
с заданной по-
грешностью ε, используя следующее рекуррентное соотношение:
yn = yn-1
- (x/yn-1
- yn-1)/2; y1 = x/2.
Условием окончания данного итерационного цикла будет условие
nn
1
yy−
−≤
ε
. Для его проверки необходимо иметь два приближения: те-
кущее yn и предыдущее yn-1
.
Алгоритм можно упростить, если ввести
вспомогательную переменную d = yn-yn-1 = (x/yn-1
- yn-1)/2 и использовать ее
в условии окончания цикла: ⏐d⏐≤ε. Для проверки такого условия доста-
точно иметь лишь одно приближение, которое обозначим через y.
Алгоритм вычисления квад-
ратного корня представлен на рис.
3.8 . Для организации итерационно-
го процесса использована структу-
ра цикла с постусловием.
Ввод x, ε
y=x/2
d=(x/y-y)/2
y=y+d
До|d|<=ε
Вывод x, y
Рис. 3 .8. Алгоритм вычисления
квадратного корня
3.1.5. Вложенные циклы
В практике алгоритмизации достаточно часто встречаются задачи,
при решении которых необходимо проектировать алгоритмы с несколь-
кими циклами. Если в теле цикла содержится один или несколько других
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
60
циклов, то такие циклы называют вложенными. Цикл, содержащий в себе
другой цикл, называют внешним. Цикл, содержащийся в теле другого
цикла, называют внутренним.
Выполняются вложенные циклы следующим образом. Сначала при
фиксированных начальных значениях переменных внешнего цикла пол-
ностью выполнится внутренний цикл и его переменные «пробегут» все
свои значения. Затем переменные внешнего цикла примут следующие
значения и, если условие окончания внешнего цикла не будет достигнуто,
снова полностью выполнится внутренний цикл и его переменные опять
«пробегут» все свои значения, и так до тех пор, пока не выполнится ус-
ловие окончания внешнего цикла.
Пример 3.6 . Составим алгоритм табулирования сложной функции
двух переменных
asinx bcosy, если x(
a
,
b)и y(a
,
b);
zs
i
n
xc
o
s
y
,
если x(
a
,
b)и y(a
,
b);
1 в остальных случаях
+∈
∈
⎧
⎪=+
∉∉
⎨
⎪⎩
для x=x0(hx)xn и y=y0(hy)yn.
Вычисление значений функции z для всех различных пар (x,y) необ-
ходимо организовать следующим образом. Сначала при фиксированном
значении одного из аргументов, например при x=x0, вычислить значения
z для всех заданных y: y0+hy, y0+2hy, ..., yn. Затем, изменив значение x на
x0+hx, вновь перейти к полному циклу изменения переменной y. Данные
действия повторить для всех заданных x: x0, x0+hx, x0+2hx, ..., xn. Для за-
писи такого алгоритма необходима структура вложенных циклов со сле-
дующими параметрами: для внешнего цикла – с параметром x, для внут-
реннего цикла – с параметром y. В данной задаче внешний и внутренний
циклы можно поменять местами, при этом изменится только очередность
изменения аргументов.
Общее количество значений z равно Nz=NxNy, где
Nx= [(xn - x0)/hx] +1; Ny = [(yn - y0)/hy] +1.
Здесь скобки [ ] обозначают целую часть числа.
Алгоритм табулирования функции, выполненный по технологии нис-
ходящего проектирования, представлен на рис. 3 .9 .
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
61
Ввод x0, h
x
,x
n
,y0,hy,y
n
,
a,b
x = x0(hx)xn
y = y0(hy)yn
Вычисление и
вывод z
x∈(a,b) и y∈(a,b)
Да
Нет
x∉(a,b) и y∉(a,b)
z=a*sinx+
b*cosy
Да
Нет
z=sinx+
cosy
z=1
Вывод x, y, z
Рис. 3 .9. Алгоритм табулирования функции двух переменных
3.2. Программирование циклических
алгоритмов с известным числом повторений
3.2.1. Оператор цикла с параметром
Для реализации циклического процесса с известным числом повто-
рений целесообразно использовать оператор цикла с параметром. Цикл с
параметром является одним из основных видов циклов, которые имеются
во всех универсальных языках программирования, включая С. Однако
версия цикла, используемая в С, обладает большей гибкостью. Оператор
имеет следующий вид:
for(<выражение1>;<выражение2>;<выражение3>)
<
оператор>;
В круглых скобках оператора for содержатся 3 выражения, разделен-
ные точкой с запятой.
Первое из них служит обычно для инициализации параметра цикла и
вычисляется только один раз перед началом выполнения цикла.
Второе выражение определяет условие продолжения цикла. Оно вы-
полняется перед каждым шагом цикла. Когда выражение становится
ложным, цикл завершается. Если же оно истинно, то выполняется опера-
тор тела цикла, который может быть любым простым или составным
оператором.
Третье выражение вычисляется в конце каждого выполнения тела цикла.
Обычно оно применяется для коррекции значения параметра цикла.
Ниже приведена программа, использующая оператор for.
#include <stdio.h>
#define MASC '\001'
main( )
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
62
{
int a, i;
рrintf("Введите символ\n");
0,x 0;
yx
,
0x1
;
1,x 1
≤
⎧⎪=<
<
⎨
≥
⎪⎩
a = getchar( );
for(i=0;i<=7;i++)
{
if(a & MASC)
рrintf("единица в %d разряде\n", i);
a>>=1;
}
}
Программа позволяет найти позиции единиц в коде ASCII соответст-
вующего символа a, вводимого с клавиатуры.
В операторе for можно опустить одно, два или даже все выражения,
однако точки с запятой (;) должны оставаться на месте. Если опустить
<выражение2>, то это будет равносильно тому, что значение этого выра-
жения всегда будет иметь значение «истина» и цикл никогда не завер-
шится, если в теле цикла нет прерывания.
Рассмотрим несколько типовых примеров использования оператора
цикла с параметром.
3.2.2. Табулирование функции
При табулировании функции
производится вычисление таблицы
ее значений для аргумента x, изме-
няющегося от начального значения
x0 до конечного xn с постоянным
шагом hx.
Для переменной x в начале заго-
ловка цикла в первом выражении
зададим ее начальное значение x0, а
в третьем выражении будем произ-
водить ее модификацию (измене-
ние). В результате получаем схему
алгоритма циклической структуры
с заголовком (рис. 3 .10), для кото-
рой запишем программу табулиро-
вания функции при x=x0(hx)xn
в следующем виде:
Начало
Ввод
x0,x
n
,h
x
x=x0(hx)xn
Вычисление
значений
y=f(x)
Вывод
x,y
Конец
Рис. 3 .10. Схема алгоритма
табулирования функции
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
63
/********************************** */
/*Цель: табулирование функции y=F(x) с */
/*
помощью оператора цикла с
*/
/*
параметром
*
/
/*Переменные:
*
/
/* x – переменная цикла;
*
/
/* x0, xn – начальное и конечное значения; */
/* hx – шаг изменения;
*/
/*Дата написания: 07.09 .03 г.
*
/
/*Программист: Федоров Ф.Ф .
*
/
/********************************** */
#include <stdio.h>
main( )
{
float hx, x, x0, xn, y;
/*Ввод и эхопечать исходных данных*/
рrintf("x0=");
scanf("%f",&x0);
рrintf("hx=");
scanf("%f",&hx);
рrintf("xn=");
scanf("%f",&xn);
рrintf("X0=%f, HX=%f, XN=%f\n", x0, hx, xn);
/*Табулирование функции*/
for(x=x0; x<=xn; x+=hx)
{/*начало цикла*/
if(x<=0)
y
=
0
;
elseif(x<1)
y=x;
else
y
:
=
1
;
рrintf("X=%6.2f, Y=%6.2f\n", x, y);
}/*конец цикла*/
}
3.2.3. Вычисление конечных сумм и произведений
Составим программу вычисления значений функции
n
10
n1
15
n0
x1x
, если x2
;
2n
z
sinx cosx
x
1,
если x2
.
2si
nx
n2
=
=
⎧+ ⎛⎞
≤
⎪
⎜⎟
⎪
⎝⎠
=⎨
+
⎛⎞
⎪
+
>
⎜⎟
⎪++
⎝⎠
⎩
∑
∏
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
64
В зависимости от значения переменной x реализуется вычисление
суммы или произведения (рис. 3 .11). Вычисление суммы целесообразно
реализовать с помощью оператора цикла с параметром n. В теле цикла
необходимо вычислить значение очередного слагаемого un=(x/n)
n
при те-
кущем n и осуществить накопление суммы по формуле Sn=Sn-1+un. По-
добные операции требуется выполнить для n=1(1)10. Так как нет необхо-
димости запоминать значения всех слагаемых u1,u2, ..., u10 и конечных
сумм S1,S2, ..., S10, то в качестве Sn и un можно использовать скалярные
переменные S и u. При этом накопление суммы можно реализовать с по-
мощью операции S=S+u. Перед выполнением цикла значение переменной
S должно быть нулевым (S=0).
u=(x/n)
n
S=S+u
u=1+x/(n+2)
Р=Р ⋅u
Ввод x, ks, kр
x≤2
Да
Нет
S=0
Р=1
n=1(1)ks
n=1(1)kр
S
x
z
⋅
+
=
2
1
P
x
x
x
z
⋅
+
+
=
sin
2
cos
sin
Вывод x, z
Рис. 3 .11. Структурограмма алгоритма вычисления суммы и произведения
Вычисление произведения организуем с помощью аналогичной цик-
лической структуры с параметром. В данном случае необходимо вычис-
лять сомножитель u=1+x/(n+2) и произведение по формуле р=р∗u. Перед
выполнением цикла переменной р должно быть присвоено значение
1 (р=1).
Для обеспечения большей универсальности алгоритма обозначим
предел суммирования через ks, а предел произведения через kр и обеспе-
чим их ввод в программу в качестве исходных данных. Запишем про-
грамму в следующем виде:
/********************************************** */
/*Цель: вычисление сложной функции (конечная
*/
/*
сумма и произведение)
*/
/*Переменные:
*
/
/*
z – значение функции; x – аргумент функции;
*/
/*
S – сумма; Р – произведение;
*/
/*
n – переменная суммирования и произведения;
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
65
/*
u – слагаемое (сомножитель)
;
*
/
/*
KS – число слагаемых; KР – число сомножителей */
/*Дата написания: 07.09 .03 г.
*
/
/*Программист: Сергеев С.С.
*
/
/********************************************** */
#include <stdio.h>
#include <math.h>
main( )
{
floatР,S,T,u,x,z;
int n, KР, KS;
рrintf("X=");
scanf("%f",&x);
рrintf("KS=");
scanf("%d",&KS);
рrintf("KР=");
scanf("%d",&KР);
рrintf("X=%6.2f, KS=%d, KР=%d\n", x, KР, KS);
if(x<=2)
{
S=0;
for(n=1; n<=KS; n++)
{
u
=
рow(x/n, n);
S=S+u;
}
z=S*(x+1)/2;
}
else
{
Р=1;
for(n=0;n<=KР;n++)
Р=Р*(1+x/(n+2));
T=sin(x);
z=(T+cos(x))*Р/(2+T)
}
рrintf("X=%6.2f', Z=%f\n", x, z);
}
Вопросы для самоконтроля
1. Какова общая структура цикла с параметром?
2. В чем особенность оператора цикла с параметром?
3. Каким образом оформить тело цикла с параметром для нескольких
операторов?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
66
4. Приведите общую структуру оператора цикла с параметром.
5. Каковы основные правила организации цикла с параметром?
6. Для каких целей служат выражения в спецификации оператора
цикла с параметром?
7. Какие значения может принимать шаг изменения параметра цикла
в операторе for?
8. В чем отличие алгоритма вычисления суммы от алгоритма вычис-
ления произведения?
9. Приведите пример вычисления конечной суммы.
10. Являются ли обязательными выражения в спецификации операто-
ра цикла с параметром?
Упражнения
1. Вычислить
2sin x, если x/
2
;
zA
s
i
n
xB
,
если
/2x/2;
cos x, если x/
2
,
≤π
⎧
⎪=+−
π
<
<
π
⎨
⎪
≥π
⎩
гдеx= -2(0,2)2;A= -5;B=12.
2. Вычислить значение функции y = f(x) по указанному графику для
значения аргумента x= x0(hx)xn,
0
-1
1
Y
X
гдеx0= -2;hx=0,5;xn=2.
3. Вычислить значение функции одной переменной
22
2
sin x
cos x
z
(x 2)(x 5)
x4
=+
−
−
−
в интервале -3 ≤ x ≤ 6 с шагом hx = 0,5. Точки разрыва исключить.
4. Вычислить сумму
10
12
23
n1
n1
Snn
.
==
=+
∑∑
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
67
5. Вычислить сумму
4n1
10
n
n
n0
x
S
при x1
(
0
,
1
)
2
4n1
+
=
==
+
∑
.
6. Вычислить произведение
10
i
i
2
i1
2x1
P
при x2
,
1
(
0
,
1
)
3
.
(2i) 1
=
+
==
+
∏
7. Вычислить значение интеграла
b
2
a
1tgx
Jd
x
1tgx
+
=
+
∫
по формуле прямоугольников
n
ii
i1
Jh f(x)
,где h(ba)/n
;xai
h;
=
≈=
−
=
+
∑
f(xi) – подынтегральная функция. Принять a = 0, b = π/4, n = 30.
8. Вычислить сумму
20
n
n1
0, 5, если n1
2и x3
,
5;
S(
a
1
)
l
n
x
;
a
7, 5 в остальных случаях.
=
≥≥
⎧
=+
=
⎨
⎩
∑
Для контрольного просчета принять x = 1,75.
9. Вычислить значение интеграла
b
3
4
a
x
Jd
x
x1
=
+
∫
по формуле трапеций
n1
k
k1
f(a) f(b)
Jh
f(x),где h(ba)/n
;
2
−
=
+
⎡⎤
≈+
=
−
⎢⎥
⎣⎦
∑
xk = a + i⋅h; f(xk) – подынтегральная функция.
Принятьa=1,b =4,n =40.
10. Вычислить произведение
3
10
4
22
i1
k2
i(i a)
k
P
при a2
.
ka
ia
==
+
=+
=
+
+
∏∏
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
68
11. Вычислить
n
m1
10, если k четно;
(l k)!, l
1, если k нечетное;
причемn! 12 n
m.
=
⎧
λ=
⋅
=⎨
⎩
=⋅
=
∏
...
Для контрольного просчета принять x = 7,5; a = 1,7.
12. Вычислить
10
k
k1
5
mm
m1
k
xsin , если xa
;
4
W
(a x), еслиxa
.
=
=
π
⎧
≥
⎪⎪= ⎨
⎪−
<
⎪⎩
∑
∏
Для контрольного просчета принять k = 5.
13. Определить количество заданных точек (x, y), попавших в ука-
занную область, включая ее границы, где x = x0 + ih;
y=y0+ih;
x0 =-1,5; y0 =0,5;
h=0,1;i=1(1)10.
X
Y
2
2
-2
14. Определить, сколько четных целых чисел лежит в интервале (a,b),
гдеa<sinx
2
;b=x
4
;x=3.
15. Определить максимальное целое число n, удовлетворяющее усло-
вию 3n
2
–
730n < 5.
16. Вычислить первые 20 членов последовательности чисел Фибо-
наччи: u1 = 1; u2 = 2; un = un-1 + un-2, а также значение золотого сечения
n
n
n1
u
V.
u−
=
17. Вычислить
asinx, если x0
;
alnx, еслиx0
,
>
⎧⎪
α=⎨
≤
⎪⎩
для x = -3(0,5)3; a = 1,35. Причем точку 0, т. е. x = 0, исключить.
5
3
1,5
sin x
Jd
x
cx
=
+
∫
18. Вычислить значение интеграла по формуле трапеций при n = 30
(см. вариант 9), где c = 2,1.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
69
19. Вычислить значение интеграла
1
x
2
0
1e
Jd
x
xc
−
=
−
∫
по формуле трапеций при n = 20 (см. вариант 9), где c = 1,5.
20. Вычислить
33
33
(a c)sinx c
Z
(a c)(a 4)
+
+
=
−−
для x = 0,5(0,2)1,7. Точки разрыва исключить.
3.3. Конструирование программ циклической
структуры с неизвестным числом повторений
3.3.1. Оператор цикла с предусловием
Часто встречаются задачи, когда число повторений в цикле неизвест-
но, а задано только некоторое условие его продолжения или окончания.
Для программирования таких алгоритмов в языке С существует два типа
операторов: оператор цикла с предусловием и оператор цикла с посту-
словием.
Оператор цикла с предусловием, или цикл while, является наиболее
общим по сравнению с другими конструкциями. В принципе для про-
граммирования достаточно иметь только цикл while, другие же цикличе-
ские конструкции служат лишь для удобства написания программ.
Оператор цикла с предусловием имеет следующий формат:
while(<выражение>)
<
оператор>;
В процессе выполнения цикла на каждом его шаге вычисляется зна-
чение выражения. Если оно истинно (отлично от нуля), то выполняется
внутренний оператор и выражение вычисляется вновь. Как только оно
становится ложным (равным нулю), цикл завершается. Если условие
продолжения цикла ложно с самого начала, то внутренний оператор не
выполняется ни разу. Условие продолжения цикла вычисляется и анали-
зируется перед каждым шагом выполнения цикла, отсюда и термин «пре-
дусловие».
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
70
В качестве внутреннего оператора можно использовать любой
оператор языка или группу операторов, заключенную в фигурные
скобки. Символ точки с запятой после закрывающей скобки при этом
не ставится.
Рассмотрим для примера программу, считывающую с клавиатуры
предложение и определяющую его длину:
#include <conio.h>
#include <stdio.h>
main( )
{
char ch;
intlen=0;
puts("\nНаберите предложение, затем нажмите <Ввод>:\n");
while ((ch = getch( )) != '\r')
{
рutch(ch);
len++;
}
printf("\n Ваше предложение имеет"
"
длину %d символов.\n", len);
}
В этой программе для отображения вводимых символов на экран
дисплея используется функция putch, так как функция getch в отличие от
функции getchar не обеспечивает режим «эхо» для вводимых с ее помо-
щью символов. Описание функции getch содержится в файле <conio.h>.
Следует отметить, что в выражении оператора while используется
операция присваивания. Это позволяет программе читать и одновремен-
но сравнивать считанные символы с символом конца строки '\r', соответ-
ствующим клавише «Ввод».
Еще один пример. Пусть необходимо вычислить среднее арифме-
тическое последовательности чисел, отличных от нуля, произвольной
длины.
Каждый раз, когда вводится число N, счетчик количества чисел i уве-
личивается на единицу. Конец ввода определяется по значению числа N,
равного нулю.
Ниже приведена схема алгоритма (рис. 3 .12) и текст программы:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
71
/*Цель: вычисление среднего ариф- */
/*
метического произвольного */
/*
количества чисел
*
/
/*Переменные:
*
/
/*
N – вводимые числа;
*/
/*
i – счетчик количества чисел;*/
/*
S – сумма чисел.
*/
/*Дата написания: 07.09 .2003 г.
*/
/*Программист: Иванов И.И.
*/
#include <stdio.h>
main( )
{
float S;
int i, N;
S=i =0;
printf("Пожалуйста, введите спи-
сок"
" заканчивающийся нулем\n");
scanf("%d",&N);
while(N != 0)
{
S+=N;
i++;
printf("следующее число\n");
scanf("%d",&N);
}
printf("Среднее %d чисел: %f\n", i,
S/i);
}
Начало
S=0
i=0
Ввод N
N!=0
Ввод N
S=S+N
i=i+1
Вывод
S/i
Конец
Нет
Да
Рис. 3 .12 . Схема алгоритма
вычисления среднего
арифметического числа
3.3.2. Оператор цикла с постусловием
Синтаксис оператора цикла с постусловием имеет следующий вид:
do
<
оператор>;
while(<выражение>);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
72
Действия, определяемые оператором, выполняются до тех пор, пока
выражение не станет ложным или равным нулю. Его основное отличие от
оператора while состоит в том, что внутренний оператор цикла do...while
всегда выполняется хотя бы один раз, так как проверка условия продол-
жения осуществляется после выполнения тела цикла.
Ниже приведен пример программы, использующей рассмотренный
тип оператора цикла.
#include <conio.h>
#include <stdio.h>
main( )
{
float a, b, ratio;
do
{
рrintf("Введите два числа :");
scanf("%f %f",&a, &b);
if (b == 0.0)
рrintf("\n Внимание! Деление на нуль !\n");
e
l
s
e
{
ratio=a/b;
printf("\n Результат деления двух чисел : %f \n", ratio);
}
printf("Нажмите \'q\' для выхода или любую клавишу для"
"
продолжения \n");
}
while (getch( ) != 'q');
}
Результаты ее работы имеют следующий вид:
Введите два числа: 3 4
Результат деления двух чисел: 0.75000
Нажмите 'q' для выхода или любую клавишу для продолжения
Введите два числа: –7 45.89
Результат деления двух чисел: –0 .152539
Нажмите 'q' для выхода или любую клавишу для продолжения
Введите два числа: 1.07 0
Внимание! Деление на нуль!
Нажмите 'q' для выхода или любую клавишу для продолжения
Эта программа вычисляет результат деления одного числа на другое.
Оба числа вводятся по запросу программы с клавиатуры. Если вводится
символ 'q', то выражение в операторе цикла do – while в конце программы
примет значение «ложь» и цикл (а значит, и программа) завершится. Если
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
73
будет введен какой-либо другой символ, отличный от 'q', то выражение
будет иметь значение «истина» и цикл повторяется.
Существует программа, алгоритм которой приведен на рис. 3 .13, оп-
ределяющая, является ли число простым. С помощью операции % (деле-
ние по модулю) проводится проверка всех целых чисел от 2 до N. Если
такой множитель находится, цикл завершается значением i, равным это-
му множителю. Если число является простым, цикл завершается при зна-
чении i, равном N.
Начало
Ввод N
i:=1
i:=i+1
Остаток
N%i!=0
i=N
Вывод
N простое
Вывод
N делится
наi
Конец
Нет
Да
Да
Нет
Рис. 3 .13. Схема алгоритма определения простого числа
/*Цель: определить, является ли число простым
*
/
/*Переменные: N – исследуемое число; i – возможный делитель.
*/
/*Дата написания: 02.11 .03 г.
*/
/*Программист: Сидоров С.С.
*
/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
74
#include <stdio.h>
main( )
{
int i, N;
printf("Введите число:");
scanf("%d",&N);
printf("%d – ", N);
i=1;
do
i++;
while(N%i !=0);
if(i==N)
рrintf("простое число!\n");
e
l
s
e
printf("делится на %d \n", i);
}
Еще одна программа, приведенная ниже, позволяет определить все
делители некоторого целого положительного числа, вводимого с клавиа-
туры. При этом предусматривается защита от неправильного ввода и
обеспечивается повторный ввод исследуемого числа.
/*Цель: нахождение делителей целого положительного числа
*/
/*
(кроме 1 и самого числа)
*
/
/*Переменные: X – исходное число; Half – половина введенного
*/
/*
числа; Divider – делитель ; i – счетчик.
*/
/*Дата написания: 07.09 .03 г.
*
/
/*Программист: Сидоров С.С.
*
/
#include <stdio.h>
main( )
{
int Divider, Half, i, X;
do
{
printf("\nВведите целое положительное число:");
scanf("%d",&X);
if(X<0)
printf("Неправильный ввод\n");
}
while(X<0);
Half=X/2;
Divider = 1;
i =0;
while(++Divider <= Half)
if(X % Divider == 0)
printf("%d-й делитель равен: %d\n", ++i, Divider);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
75
if(i == 0)
printf("Делителей нет\n");
рrintf("Конец решения\n");
}
При программировании циклов с предусловием или постусловием
необходимо соблюдать следующие рекомендации:
а) перед каждым выполнением цикла условие окончания или про-
должения цикла должно быть определено, т. е . иметь конкретное значе-
ние;
б) тело цикла должно содержать хотя бы один оператор, влияющий
на условие окончания или продолжения, иначе цикл будет продолжаться
бесконечно;
в) условие окончания цикла должно быть в конце концов удовлетво-
рено;
г) условие вычисляется при каждом выполнении цикла и поэтому
должно быть по возможности наиболее простым.
3.3.3. Операция «запятая»
Операция «запятая» увеличивает гибкость использования оператора
цикла for, позволяя включать в его спецификацию несколько инициали-
зирующих или корректирующих выражений. Операция «запятая» объе-
диняет два выражения в одно и гарантирует их вычисление слева напра-
во. Например, в операторе
for(uр = 1, down = 9; uр <= 10; uр++, down– –)
рrintf("uр %2d – растет, down %2d – уменьшается \n", uр, down);
первое и последнее выражения состоят из двух выражений. Эти выраже-
ния могут быть сколь угодно сложными. Применение операции «запятая»
возможно и во втором логическом выражении, но за значение всего вы-
ражения будет принято только значение последнего из объединяемых
выражений.
В теле цикла могут использоваться также операторы завершения
break и продолжения continue. Первый из них обеспечивает выход из цик-
ла, а второй вызывает прекращение очередного и начало следующего ша-
га цикла.
3.3.4. Пример циклической программы
Пусть необходимо составить программу выдачи кода введенного
символа в десятичном, восьмеричном и шестнадцатеричном представле-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
76
нии или же полной таблицы кодов ASCII. Ее текст будет иметь следую-
щий вид:
#include <stdio.h>
#include <conio.h>
#include <string.h>
unsigned char MinCode = 32, MaxCode = 255;
void рrintchar (unsigned char ch)
{
if(ch==0)
ch =getch( ), рrintf(" 0:%3u 0:%3o 0:%3x \n", ch, ch, ch);
else
рrintf(" %1c %3u %3o %3x \n", (ch>MinCode) ? ch : ' ',
ch, ch, ch);
}
main( int argc, char* argv[2] )
{
unsigned char i;
рrintf("\n Программа выводит по символу его код\n Выход–Esc.\n");
рrintf(" Если в командной строке вторым аргументом поставить\n");
рrintf("слово TABLE, то на экран выводится вся таблица ASCII\n");
рrintf(" ");
рrintf("Символ Код-10 Код-8 Код-16\n");
if (argc == 1)
{
char Esc = 27;
do
рrintchar( i = getch( ));
while ( i != Esc);
}
else if ( (argc == 2) && (strcmр(argv[1], "TABLE") == 0) )
for ( i = MinCode; i < MaxCode; i++)
printchar( i);
}
Здесь функция main использует два параметра – argc и argv. Первый
представляет собой число аргументов, записанных при запуске програм-
мы в командной строке, а второй – указатель на сами аргументы, при
этом argv[0] – имя программы, а argv[1] – слово «TABLE» (если оно было
указано в параметрах при запуске.
Функция рrintchar обеспечивает вывод строки, содержащей символ
и его десятичное, восьмеричное и шестнадцатеричное представления.
Ниже приведены результаты работы программы при вводе клавиши
F1, знака «+», цифры 7, буквы R и символа окончания работы ESC.
Программа вводит по символу его код
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
77
Выход – Esc.
Если в командной строке вторым аргументом поставить
слово TABLE, то на экран выводится вся таблица ASCII.
Символ Код-10 Код-8 Код-16
0:59 0:73 0:3b
+
43532b
7
556737
R
82 122 52
27331b
Вопросы для самоконтроля
1. Перечислить типы циклических операторов. В чем их особенно-
сти?
2. Какова общая форма оператора цикла с предусловием?
3. Чем отличается цикл с постусловием от цикла с предусловием?
4. Каковы основные правила организации цикла с параметром?
5. Из каких операторов может быть сформировано тело цикла?
6. Для каких целей служат выражения в спецификации оператора
цикла с параметром?
7. Являются ли обязательными выражения в спецификации операто-
ра цикла с параметром?
8. Для чего служит операция «запятая»?
9. Каково действие оператора завершения?
10. Каков механизм действия оператора продолжения?
Дополнительные упражнения
1. Составить программу, обеспечивающую циклический сдвиг дво-
ичного кода целого числа x на k разрядов влево. Распечатать значения x
до и после операции сдвига в двоичном представлении.
2. Составить программу, обеспечивающую циклический сдвиг дво-
ичного кода целого числа x на k разрядов вправо. Распечатать значения x
до и после операции сдвига в двоичном представлении.
3. Перевести двоичное 16-разрядное число в десятичную систему
счисления, записав его предварительно в регистр данных _DX.
4. Перевести двоичное 16-разрядное число в восьмеричную систему
счисления, записав его предварительно в регистр данных _DX.
5. Перевести двоичное 16-разрядное число в шестнадцатеричную
систему счисления, записав его предварительно в регистр данных _DX.
6. Составить программу эхопечати вводимой последовательности
символов и одновременного преобразования латинских строчных букв
в прописные.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
78
7. Составить программу эхопечати вводимой последовательности
символов и одновременного преобразования русских строчных букв
в прописные.
8. Преобразовать введенный текст таким образом, чтобы первая фраза
и фраза, следующая после точки, всегда начинались с прописной буквы.
9. Отредактировать вводимый текст, удалив из него лишние пробелы.
10. Отредактировать вводимый текст таким образом, чтобы в строке
вводимого текста размещалось не более 20 символов. При необходимо-
сти предусмотреть символ переноса.
11. Преобразовать вводимый 7-разрядный двоичный код, добавив
разряд контроля на четность. Для хранения кодов использовать регистр
данных _DX: для исходного кода – его младший байт _DL, для скоррек-
тированного – старший байт _DH.
12. Смоделировать выполнение одной из указанных ниже арифмети-
ческих операций над семиразрядными двоичными кодами (восьмой раз-
ряд знаковый). Исходные данные поместить в регистр сегмента данных
_ DS и в дополнительный регистр сегмента данных _ES. Результат накап-
ливать в регистре индекса источника _SI
1) Сложение в прямом коде.
2) Вычитание в прямом коде.
3) Сложение в дополнительном коде.
4) Вычитание в дополнительном коде.
5) Сложение в обратном коде.
6) Вычитание в обратном коде.
7) Умножение младшими разрядами вперед.
8) Умножение старшими разрядами вперед.
13. Смоделировать работу двоичного счетчика при поступлении на
его вход произвольной последовательности нулей и единиц. Распечатать
состояние счетчика после поступления каждого входного сигнала.
14. Смоделировать работу двоичного реверсивного счетчика при по-
ступлении на его положительный вход сигналов, кодируемых единицей,
а на отрицательный нулем. Распечатать каждое состояние счетчика.
15. Распечатать некоторый участок таблицы кодов ASCII в двоичном
представлении и определить, какой из них содержит максимальное коли-
чество единиц.
16. Определить, совпадает ли число открывающих и закрывающих
скобок во вводимом выражении, занося единицу в сегмент стека _SS при
поступлении левой скобки и исключая из него единицу при поступлении
правой скобки.
17. Получить двоично-десятичный код заданного целого числа.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
79
18. По заданному двоично-десятичному коду получить десятичное
представление числа.
3.3.5. Итерационные циклы. Вычисление суммы ряда
Среди циклов с неизвестным числом повторений большое место за-
нимают итерационные циклы. Характерными примерами итерационных
циклов являются задачи вычисления сумм бесконечных рядов и уточне-
ние корней при решении алгебраических и трансцендентных уравнений.
n
n
St
(
x
)
=
∑.
Большой класс задач сводится к нахождению суммы некоторого ко-
личества слагаемых при различных значениях параметра суммирования.
Каждое слагаемое tn(x) зависит от параметра x и номера n, определяюще-
го место этого слагаемого в сумме.
2
n2
n
1
nn
n
22
n
1
n
2
cos nx
sin(2n 2)x x
а),
,
;
nn
2n1
xx1
б) ,(1)
,(1);
n!
(2n 1)!
n!
cos nx
n1x
x
в)( 1)
,
;
.
n!2
2n1
n
+
+
−
−
−−
+
+⎛⎞
−
⎜⎟+
⎝⎠
Обычно формула общего члена суммы tn(x) принадлежит к одному из
следующих трех типов.
Когда член ряда имеет тип «а», применение рекуррентных формул
нецелесообразно. Вычисления будут наиболее эффективными, если каж-
дое слагаемое определять по общей формуле и полученные значения на-
капливать в некоторой переменной. Общий вид схемы алгоритма, реали-
зующего вычисление суммы с погрешностью ε с помощью цикла с пре-
дусловием, показан на рис. 3 .14, а.
Для члена ряда типа «б» при вычислении слагаемых целесообразно
использовать рекуррентные формулы, т. е . при вычислении слагаемого
tn(x) использовать значение слагаемого tn-1(x). Это позволяет существенно
сократить объем вычислений, а также избежать вычислений факториала
(значение которого может привести к переполнению) и возведения отри-
цательного основания в степень. Общая схема такого итерационного ал-
горитма показана на рис. 3.14, б.
Основная сложность, возникающая в процессе проектирования по-
добного алгоритма, состоит в определении сомножителя φn(x). В общем
виде можно записать
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
80
nn
1
n
t(x) t (x) (x),
−
=
φ
тогда сомножитель φn(x) будет определен формулой
n
n
n1
t(x)
(x)
.
t(
x
)
−
φ=
Начало
Ввод
x,ε
t=t0
s=t0
n=1
|t|>ε
t=t
n
(x)
s=s+t
n=n+1
Вывод
s
Конец
Нет
Да
Начало
Ввод
x,ε
t=t 0
s=t 0
n=1
|t|> ε
t=t* φn(x)
s=s+t
n=n+1
Вывод
s
Конец
Нет
Да
а
б
Рис. 3 .14 . Схемы алгоритмов итерационных циклов
Например, если общий вид слагаемого некоторой суммы
2n
n
n
x
t(x) (1)
,
(2n)!
=−
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
81
тогда
[]
2(n 1)
2n2
n1
n1
n1
xx
t(
x
)(1)
(1)
.
2(n 1)!
(2n 2)!
−−
−−
−
=−
=−
−−
Теперь можно определить
2n
n
22
n
n
2n2
n1
n1
x
(1)
t(x)
x(2n 2)!
x
(2n)!
(x)
.
t (x)
(2n)!
2n(2n 1)
x
(1)
(2n 2)!
−
−
−
−
−
φ==
=
−
=
−
−
−
−
Начальное значение t0(x) находим по формуле
20
0
0nn0
x
t(x) t(x)
(1)
1.
(2 0)!
⋅
=
=
=−
=
⋅
Программа вычисления такой суммы имеет такой вид:
/****************************************** */
/*Цель: вычисление суммы с заданной
*/
/*
погрешностью по итерационному алгоритму */
/*Переменные:
*
/
/*
X – аргумент функции;
*/
/*
N – переменная суммирования;
*/
/*
EPS – погрешность вычисления суммы; */
/*
S – сумма;
*
/
/*
T – слагаемое .
*
/
/*Дата написания: 17.11 .03 г.
*/
/*Программист: Карлов К.К .
*
/
/****************************************** */
#include <stdio.h>
#include <math.h>
main( )
{
float EРS, S, T, X;
int N;
рrintf("X=");
scanf("%f",&X);
рrintf("EРS=");
scanf("%f",&EРS);
/*Эхопечать*/
printf(" Для X=%f с погрешностью %f\n", X, EРS);
/*Вычисление суммы*/
T=S =N =1;
while(fabs(T)>EРS) /*Условие продолжения цикла*/
{
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
82
T*=-X*X/(2*N*(2*N-1));
S+=T;
N++;
}
printf(" N= %d, S= %f\n", N, S);
}
Для члена ряда типа «в» член суммы целесообразно представить в
виде двух сомножителей, один из которых вычисляется по рекуррентно-
му соотношению, а другой непосредственно.
3.3.6. Метод итерации для уточнения корней
Исследуем уравнение f(x)=0, имеющее один-единственный корень x
в интервале (a,b), a<x<b. Уравнение f(x)=0 представим в виде, удобном
для проведения метода итерации x=φ(x).
Если в интервале (a,b) выполняется неравенство |φ'(x)|≤q<1, то метод
итерации применим к исходному уравнению и он приведет к уточнению
корня с заданной точностью (процесс итерации сходится).
Приведение исходного уравнения f(x)=0 к форме x = φ(x) может быть
выполнено разными способами. Один из них сводится к следующему.
Предположим, что 0≤m≤f'(x)≤M при a≤x≤b (если f'(x)<0, то достаточно
уравнение f(x)=0 умножить почленно на -1). В результате получаем урав-
нение
2
xx
f(
x
),
mM
=−
+
которое равносильно исходному уравнению f(x)=0, имеет требуемую ка-
ноническую форму и
Mm
(x)
q1.
Mm
−
′
φ≤=
<
+
Метод итерации состоит из последовательности следующих шагов.
Выбираем любое значение x=x0 из интервала (a,b), вычисляем φ(x0) и
приравниваем его к новому значению корня x1:
x1=φ(x0).
Далее этот процесс повторяется так, что k-я итерация состоит в вы-
числении
xk=φ(xk-1).
Погрешность εk на k-й итерации удовлетворяет неравенству
εk ≤ qk(b-a).
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
83
Процесс итерации проводим до тех пор, пока погрешность εk будет
больше заданной погрешности ε. В качестве условия продолжения итера-
ционного цикла можно выбрать неравенство |xk-xk-1|>ε, а также неравен-
ство |f(xk)|>ε.
Пусть, например, необходимо найти с погрешностью ε=10-4
корень
уравнения
5x
3
+ 10x
2
+5x–1=0.
Приведем исходное уравнение к виду
2
1
x.
5(x 1)
=
+
Для определения x0 применим графический метод отделения корней,
а именно построим график функций
12
2
1
yx
;
y
.
5(x 1)
==
+
Нетрудно убедиться, что корень (точка пересечения этих графиков)
принадлежит отрезку [0,1]. Поэтому
3
2
(x)
1
5(x 1)
′
φ=<
+
для всех x ∈ [0,1], метод итераций применим.
Программа будет иметь следующий вид:
/*Цель: уточнение корня
*
/
/*Переменные:
*
/
/*
eps – допустимая погрешность;
*
/
/*
x0 – начальное приближение;
*
/
/*
x1, x2 – последовательные приближения корня. */
/*Метод: итераций
*
/
/*Дата: 19.11 .03 г.
*
/
/*Программист: Петров П.П.
*
/
#include <stdio.h>
#include <math.h>
main( )
{
float eрs, x0, x1, x2;
printf("Введите начальное приближение и погрешность\n");
scanf ("%f %f", &eрs, &x0);
рrintf("Погрешность: %f\n",eрs);
рrintf("начальное приближение: %f\n",x0);
x1=x0;
do
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
84
{
x2=x1;
x1= 1/(5*(x2+1)*(x2+1));
}
while(fabs (x1-x2) > eрs);
рrintf ("корень: %f\n",x1)
}
Вопросы для самоконтроля
1. Какова общая структура цикла с предусловием?
2. Какова общая структура цикла с постусловием?
3. Какой алгоритм можно назвать итерационным?
4. В чем отличие оператора цикла с предусловием от оператора цикла
с постусловием?
5. Какие правила необходимо использовать при проектировании про-
грамм со структурой циклов с предусловием и постусловием?
6. Каким образом формируется условие продолжения цикла?
7. По каким правилам формируется операция итерации при вычисле-
нии суммы?
8. Каким образом формируется итерационная формула уточнения
корней алгебраического уравнения?
Упражнения
1. Корень n-й степени
n
yx
=
из числа x является пределом последовательности y0,y1,y2, ..., yk, ... каж -
дый член которой определяется итерацией
k1
k
k
0
n1
k
1x
yy
y
,
yx
.
ny
+
−
⎛⎞
=+
−
=
⎜⎟
⎝⎠
Определить с точностью до ε=10-3
корень 2-й и 4-й степени из числа
π/3 и число итераций, необходимых при этом.
2. Вычислить значение функции sin(z), используя разложение ее
в степенной ряд
2n1
n
n0
z
sin z
(1)
.
(2n 1)!
+
∞
=
=−
+
∑
Определить число итераций, необходимых для вычисления sin(0,3)
с точностью до ε=10-4
.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
85
3. Вычислить значение функции sin(z), используя разложение ее
в бесконечное произведение
2
k1
z
sin z
1
.
k
∞
=
⎡
⎤
⎛⎞
=−
⎢
⎥
⎜⎟
π
⎝⎠
⎢
⎥
⎣
⎦
∏
Определить число итераций, необходимых для вычисления sin(0,3)
с точностью до ε=10-4
.
4. Вычислить значение функции cosx, используя разложение ее в сте-
пенной ряд
2n
n
n0
x
cos x
(1)
.
(2n)!
∞
=
=−
∑
Определить число итераций, необходимых для вычисления cos(0,3)
с точностью до ε=10-4
.
5. Для уравнения x
2
–
cosx = 0 уточнить корень методом итераций на
интервале [0,2] с погрешностью ε = 10-4
.
6. Вычислить значение функции cosx, используя разложение ее в бес-
конечное произведение
2
k0
2x
cos x
1
.
(2k 1)
∞
=
⎧
⎫
⎡
⎤
⎪
⎪
=−
⎨
⎬
⎢
⎥
π+
⎣
⎦
⎪
⎪
⎩⎭
∏
Определить число итераций, необходимых для вычисления cos(0,3)
с точностью до ε=10-4
.
7. Вычислить значение гиперболического синуса, используя разло-
жение его в степенной ряд
2n1
n0
x
shx
.
(2n 1)!
+
∞
=
=
+
∑
Задание выполнить для x=1,75 с погрешностью ε=10-3
.
8. Вычислить значение гиперболического синуса по формуле
xx
ee
shx
,
2
−
−
=
используя разложение функции e
x
в степенной ряд
n
x
n0
x
e.
n!
∞
=
=
∑
Задание выполнить для x=1,75 с погрешностью ε=10-3
.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
86
9. Вычислить значение гиперболического косинуса по формуле
2n
n0
x
chx
.
(2n)!
∞
=
=
∑
Задание выполнить для x=1,75 с погрешностью ε=10-3
.
10. Вычислить значение интегрального косинуса
x
cos t
Ci(x)
dt,
t
∞
=−
∫
используя разложение
24
1x 1x
Ci(x) C lnx
2!2 4!4
=+
−
+∓...
для аргумента x=3 с погрешностью ε =10-3
.
Постоянную Эйлера–
Маскерони принять равной С = 0,577216.
11. По определению Эйлера гамма-функция
x
n
n!
(x) lim
n.
x
(x1)(x2) (xn)
→∞
Γ=
+++
Составить рекуррентное соотношение и вычислить Г(2,5) с погреш-
ностью ε=10-7
.
12. Вычислить интеграл Френеля для x=1,55 с погрешностью ε=10-4
по формуле
x
35
0
1s
i
n
tx1
x1
x
dt
.
3 73! 115!
2xt
=−
+
∫
∓...
13. Вычислить интеграл Френеля для x=1,55 с погрешностью ε=10-4
по формуле
x
245
0
1c
o
s
t
1
x1
x1
x
dt1
.
52! 94! 136!
2xt
=−
+−
±
∫
...
14. Вычислить значение функции arctg(x) с погрешностью ε =10-3
и определить число требуемых для этого итераций при x=0,5; x=0,7.
2n1
n
n0
x
arctgx
(1)
.
(2n 1)
+
∞
=
=−
+
∑
15. Вычислить значение функции arcsin(x) с погрешностью ε =10-3
и определить число требуемых для этого итераций при x=0,5; x=0,7.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
87
357
1x 13x 135x
arcsin x x
.
23 245 2467
=+
+
+
+
...
16. Для уравнения, приведенного в задании 5, и найденного интерва-
ла (a, b) уточнить корень x с погрешностью ε=10-4
методом половинного
деления.
Обозначим: xл – левая граница; xп – правая граница поля, т. е . xл ≤ x ≤
≤ xп. Вначале считаем xл = а, xп = b, значение корня x = (xл + xп)/2, теку-
щая погрешность ε = (xп – xл)/2. Уменьшаем интервал (xл, xп) вдвое. Для
этого вычисляем f(x1) и f(xп), где x1= (xл + xп)/2. Если f(x1) и f(xп) имеют
равные знаки, то уменьшаем интервал до (xл, xп=x1), в противном слу-
чае – (xл=x1, xп). Выбранный интервал вновь делится пополам, и так до
тех пор, пока текущая погрешность не станет меньше заданной.
Полученный корень x проверить путем подстановки его значения в
уравнение f(x) = 0.
17. Для уравнения, приведенного в задании 5, и найденного интерва-
ла (a, b) уточнить корень x с погрешностью ε=10-4
методом хорд (пропор-
циональных частей).
Через точки (a, f(a)) и (b, f(b)) проводим прямую и находим значение
x точки пересечения данной прямой с осью OX по формуле
f (a)(b a)
xa
.
f(b) f(a)
−
=−
−
Если f(a)f(x)<0, то правую границу b переносят в точку x, т. е. выпол-
няют присваивание b=x, в противном случае уменьшают интервал за счет
присвоения a=x. Искомый корень x определяется с погрешностью, рав-
ной разности двух его соседних значений. Если полученная погрешность
больше заданной, то вновь применяют вышеприведенную формулу.
18. Выполнить задание 5 для уравнения 1 – x
2
+ sinx = 0 на интервале
(0, π/2).
19. Вычислить значение функции
2k1
k1
11z
lnz 2
2k11z
−
∞
=
−
⎛⎞
=−
⎜⎟
−+
⎝⎠
∑
с допустимой погрешностью ε=10-4
.
Процесс суммирования прекращает-
ся, как только выполнится неравенство |uk|<4ε, где uk – текущий член ря-
да суммирования. Найти ln(5), ln(0,5) и определить число слагаемых,
учитываемых в обоих случаях.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
88
20. Определить значение функции y = f(x) при x=2, если эта функция
задана неявно в виде
(x-1)2
+ (y-2)2
–
9=0.
Применить метод итераций, при котором уравнение F(x,y)=0 пред-
ставляется следующей итерационной формулой:
n
n1
n
yn
F(x,y )
yy
.
F(x,y )
+=−
′
Начальное приближение задать y0=1. Процесс итерации продолжать
до тех пор, пока два последовательных приближения yn+1 и yn не совпадут
с точностью до ε=10-2
.
3.4. Проектирование алгоритмов и программ
со структурой вложенных циклов
В вычислительной технике достаточно часто встречаются задачи, при
решении которых необходимо проектировать программы с несколькими
циклами, вложенными один в другой. Вложенным называют любой цикл,
содержащий внутри себя один или несколько циклов. Цикл, охватываю-
щий другие циклы, называют внешним, а остальные – внутренними цик-
лами. Правила организации внешнего и внутренних циклов одинаковы
(как и для простого цикла). Внутренний цикл должен полностью распо-
лагаться в теле внешнего цикла. Циклы, образующие вложенный цикл,
условно разбивают на уровни вложения: внешний цикл – уровень 0, пер-
вый внутренний цикл – уровень 1, второй внутренний цикл – уровень 2 и
т. д. В программе на каждом уровне может быть использован любой из
операторов цикла языка С (с постусловием, предусловием, параметром).
Параметры циклов разных уровней изменяются не одновременно.
Вначале все свои значения принимает поочередно параметр самого внут-
реннего цикла (при неизменных значениях параметров внешних циклов).
После этого изменяется на один шаг параметр следующего по рангу цик-
ла и снова все свои значения изменяет параметр цикла наивысшего уров-
ня вложения, и так до тех пор, пока параметры циклов всех уровней не
примут все возможные значения. Если во вложенном цикле реализованы
две циклические структуры с параметрами x=x0(hx)xn и y=y0(hy)yn, то
общее число повторений тела внутреннего цикла
N = NxNy,
где
n0
n0
xy
xy
xx
yy
N1
;
N1
.
hh
⎡⎤
⎡⎤
−−
=+
=+
⎢⎥
⎢⎥ ⎢⎥
⎣⎦ ⎣⎦
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
89
3.4.1. Табулирование функций от нескольких
переменных
Рассмотрим функцию z=f(x,y) от двух переменных x и y. Обе пере-
менные могут выступать в качестве параметров циклов при табулирова-
нии функции. В процессе проектирования алгоритма и программы не
имеет значения, по какому параметру организовать внутренний и внеш-
ний циклы.
Начало
Ввод
исходных
данных
x:=x0
ТЦx
x:=x+hx
x>xn
Конец
Да
Нет
Рис. 3 .15. Схема алгоритма
табулирования функции
Выберем в качестве внешнего па-
раметра переменную x. Схема цикли-
ческого алгоритма (табулирования
функции) по параметру x показана на
рис. 3 .15. Детализация тела цикла по
параметру x – ТЦx реализуется в виде
циклической структуры с параметром
y (рис. 3 .16).
y:=y0
ТЦy
y:=y+hy
y>yn
Нет
Да
Рис. 3 .16. Цикл по параметру y
В результате получаем схему алгоритма табулирования функции
z=f(x,y) от двух переменных (рис. 3 .17).
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
90
Фрагмент программы, который схематично показывает структуру
вложенных циклов, имеет следующий вид:
...
x=x0;
do
{
y=y0;
do
{
...
/*вычисление функции z=f(x,y)*/
...
рrintf("z=%f, x=%f, y=%f\n", z, x, y);
y+=hy;
}
while(y<=yn);
x+=hx;
}
while(x<=xn);
...
Данный фрагмент может быть реали-
зован и при помощи оператора цикла
с параметром for:
...
for(x=x0; x<=xn; x+=hx)
for(y=y0; y<=yn; y+=hy)
{
...
/*вычисление функции z=f(x,y)*/
...
рrintf("z=%f, x=%f, y=%f\n", z, x, y);
}
...
Начало
Вво д
исх од ных
данных
x:=x0
x: =x+h x
x>xn
Коне ц
Да
Нет
y:=y0
Вычисление
функ ции
z = f(x,y)
y:=y+hy
y>yn
Нет
Да
Рис. 3 .17 . Схема алгоритма
табулирования функции двух
переменных
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
91
3.4.2. Вычисление кратных сумм и произведений
В процессе вычисления кратной суммы требуется организовать вло-
женный цикл, который позволяет провести суммирование отдельных сла-
гаемых. Пусть, например, необходимо вычислить значение функции
105
kn
k0n0
x1
,
5
y(
k
n
)
x
3, 75
+
==
+
=+
∑∑
при некоторых значениях аргумента x=x0(hx)xn.
Начало
Ввод
x0,h
x
,x
n
,
Km,Nm
Вычисление
2-кратной
суммы S
x+1,5
y = -------S
3,75
Вывод
x,y
Конец
S:=0
xk:=1
k=0(1)Km
S1:=0
x1:=1
n=0(1)Nm
S1:=S1+(k+n)*x1
x1:=x1*x
S:=S+S1*xk
xk:=xk*x
x= x0(hx)xn
Рис. 3 .18. Схема алгоритма вычисления кратной суммы
Проектирование алгоритма решения этой задачи начинаем с состав-
ления циклической структуры с параметром, которая решает в общем ви-
де табулирование функции y=f(x) (рис. 3 .18). Значение двойной суммы
вычислим с помощью вложенного цикла по параметрам k и n, являю-
щимся одновременно переменными суммирования. В качестве начально-
го значения для суммы S берем нулевое значение. Оба цикла (k и n) яв-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
92
ляются циклическими структурами с заголовком и целиком расположены
внутри тела цикла по параметру x.
Составим программу.
/* *******************************************/
/*Цель: вычисление двойной суммы при
*
/
/*
различных значениях аргумента x
*/
/*Переменные:
*
/
/*
S – сумма; S1 – промежуточная сумма;
*/
/*
k,n – переменные суммирования;
*/
/*
Km, Nm – их максимальные значения;
*/
/*
xk–xвстепениk;xn–xвстепениn;
*/
/*
x= x0(hx)xm – параметры аргумента x;
*/
/*
y – функция аргумента x
.
*
/
/*Дата написания: ноябрь 2003 г.
*
/
/*Программист: Федоров Ф.Ф .
*
/
/* *******************************************/
#include <stdio.h>
main( )
{
float hx,S,S1,x,x0,xk,xm,xn,y;
int k,Km,n,Nm;
/*Ввод и эхопечать исходных данных*/
рrintf("x0=");
scanf ("%f", &x0);
рrintf("hx=");
scanf ("%f", &hx);
рrintf("xm=");
scanf ("%f", &xm);
рrintf("Km=");
scanf ("%d", &Km);
рrintf("Nm=");
scanf ("%d", &Nm);
рrintf("x0= %f, hx= %f, xm= %f, Km= %d, Nm= %d \n",
x0, hx, xm, Km, Nm);
/*Табулирование функции*/
for(x=x0; x<=xm; x+=hx)
{
/*Вычисление двойной суммы*/
for(S=0, xk=1, k=0; k<=Km; k++)
{
for(S1=0, xn=1, n=0; n<=Nm; n++)
{
S1+=(k+n)*xn;
xn*=x;
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
93
S+=S1*xk;
xk*=x;
}
y=(x+1.5)*S/3.75;
рrintf("x= %f, y= %f \n", x, y);
}
}
Вопросы для самоконтроля
1. Какой циклический процесс называется вложенным?
2. Что называется уровнем вложения?
3. Как определить объем выводимых данных для вложенного цикла?
4. Какие операторы можно использовать при программировании вло-
женных циклов?
5. Сформулируйте правила построения вложенных циклов?
6. В чем сущность нисходящего поэтапного проектирования алго-
ритма?
7. Составьте общий алгоритм табулирования функции от трех пере-
менных.
8. Как определить число повторений внутреннего цикла при табули-
ровании функции от трех переменных?
9. Приведите пример структуры с вложенными циклами.
10. Составьте общий алгоритм нахождения кратного произведения.
Упражнения
Используя метод нисходящего проектирования, разработать схему
алгоритма и составить программу вычисления функции при заданных
значениях аргументов. Результаты вычислений вывести на экран.
1.
64
k+n
k=0n0
x(
k
n
)
a
;
a
1
(
0
,
0
5
)
1
,
2
.
=
=+
=
∑∑
2.
20
n
n0
2n, если x0
,
5;
sinx 2
ya
x
;
a
n
3cosx
, если x0
,
5;
2
x 0,1(0,1)0, 9.
=
≤
⎧
+
⎪
==
⎨
+
>
⎪⎩
=
∑
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
94
3.
2
w x cos(ax t);
2ax, если ax
;
a0
,
5(0
,
2
)1
,
9
;
ta
, если ax
.
2x
x0
,
2
(0
,
5)2
,
2
;
=+
≥
⎧⎪
==
⎨
<
⎪⎩
=
4.
12
2
2
k1
2kx
, если x1
;
ax
y;
a
xk
xa
1, если x1
;
x 0,2(0,2)1,8.
=
⎧
<
⎪
==
+
⎨
+
⎪
≥
⎩
=
∑
5.
2
10
n
n1
8
n1
a
, если a4
;
a5
z
a1 a1
, если a4
;
an
a2
(0
,
5)8
.
=
=
⎧
<
⎪−
⎪=⎨
+−
⎪
≥
⎪⎩
=
∑
∏
6. P (xt)!; x 1(1)4;
1, 5 , если x четное;
n!12n;
t
2, если x нечетное.
=
=
⎧
=⋅
=
⎨⎩
7.
10
k0
4, если x1
;
zl
n
x
s
i
n
k
(
x
a
)
;
a
, если x1
;
x0
,
6(0
,
2
)1
,
8
.
=
π
≤
⎧
=⋅−=
⎨π>
⎩
=
∑
8.
51
0
kn
k1n1
0,1, если kn
;
S(
a
x
)
,
a
x
1
,
2
8
.
1, если kn
;
==
≥
⎧
==
=
⎨
<
⎩
∑∑
9.
n
50
n1
ln(1 x), если x0
;
z
x
0,5(0,1)0,5.
ln(1 x), если x0
;
Для вычисления ln(1 x) воспользоваться равенством
x
ln(1 x)
.
n
=
−≤
⎧==
−
⎨+>
⎩
−
−=
−
∑
10.
10
k1
1, если x1
;
(k a ln x); x 0,5(0,15)2; a
1, если x1
.
=
−
≤
⎧
α=
+⋅
=
=⎨
>
⎩
∑
11.
10i
j
i1 j1
i
ta
; a 1,1(0,1)1,6.
ja
==
==
+
∑∑
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 3. Программирование циклических алгоритмов
95
12.
5
n
n0
f(
a
n
)
;
a
1
(
0
,
0
5
)
1
,
3
.
=
=+
=
∑
13.
6
n2
n0
ax
F
(x a) ; x 1,1(0,5)2,6; a 0,2(0,2)0,8.
3=
+
=+
=
=
∑
14.
alnx,если xa
;
z
x 1(0,5)3; a 2.
xlna,если xa
;
⋅≥
⎧==
=
⎨⋅<
⎩
Для вычисления ln x воспользоваться равенством
2n1
10
2n1
n0
(x 1)
lnx 2
.
(2n1)(x1)
−
+
=
−
=
++
∑
15.
85
k=0 n=1
sinx
y=
(k+ ); x=-0,5(1)4,5.
n
∏∏
16.
x
x
n1
Fex!;x0(1)6;x!
n; 0! 1.я
−
=
=−
=
=
=
∏
17.
n
8
n1
5
n0
x
, если x2
;
n
z
x 0, 5(0, 5)4.
(1 xn), если x2
;
=
=
⎧⎛⎞ ≤
⎪⎜⎟
⎪⎝⎠
==
⎨
⎪+>
⎪⎩
∏
∑
18.
10
kk
k1
8
kk
n1
ax,если ax
;
yx
1
(
0
,
1
)
1
,
8
;
a
1
,
4
5
.
(a x),еслиax
;
=
=
⎧
≤
∑
⎪==
=
⎨
⎪−>
∏
⎩
19.
n
8
n1
1, если n5
;
x
y(
p)
;
x
1
,
1
(
0
,
1
)
1
,
2
;
p
2, если n5
.
2n1
=
≤
⎧
=−
=
=
⎨
>
+
⎩
∏
20.
5
n1
1, если x0
;
(1n asinx); x
;a
1, если x0
.
4
=
≥
⎧
π
⎛⎞
λ=
+⋅
=−
ππ=⎨
⎜⎟−
<
⎝⎠⎩
∏
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
96
ГЛАВА 4. МАССИВЫ И УКАЗАТЕЛИ
4.1. Массивы
4.1.1. Описание массива
Все рассмотренные ранее типы данных имеют два характерных свой-
ства: неделимость и упорядоченность их значений. Такие типы данных
называются скалярными. В языке С на основе стандартных типов можно
определить новые типы, состоящие из нескольких компонентов. Пере-
менные таких типов называются структурными переменными. Напри-
мер, упорядоченные пары, тройки и т. д. элементов некоторого множест-
ва удобно задавать в виде массивов.
Массив – это совокупность данных одного и того же типа, располо-
женных в памяти ЭВМ последовательно, непосредственно одно за дру-
гим. Каждый элемент массива имеет номер, или индекс, определяющий
его место в массиве. Основными характеристиками массива являются:
имя, размерность, тип его элементов. Общая форма описания массива
имеет следующий вид:
[<класс памяти>] <тип> <имя>[<размер1>][<размер2>]...;
Класс памяти, например, может быть определен следующим образом:
int days[365];
/* внешний массив */
main( )
{
float s[30];
/* автоматический массив */
static char code[12]; /* статический массив */
extern days[ ];
/* внешний массив – необязательное описание*/
}
Массивы используются для представления в программе векторов,
матриц, символьных строк, образа экрана ПЭВМ и другой однородной
информации. Для описания массива в языке С используется унарная опе-
рация [ ], которая определяет массив из данных какого-либо основного
или производного типа.
Количество индексов, стоящих в описании массива, определяет число
измерений массива или размерность. Различают одномерные, двумерные,
трехмерные и т. д . массивы. При этом одномерный массив представляет
собой массив одномерных массивов. Трехмерный – массив двумерных
массивов и т. д. На практике чаще всего используются одномерные
и двумерные массивы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
97
4.1.2. Одномерные массивы
Одномерные массивы содержат в описании только один индекс.
Например, следующий фрагмент программы:
unsigned int Vector[10];
char Control_String[20], Green_Line[5];
описывает массив беззнаковых целых чисел с именем Vector из 10 эле-
ментов и две последовательности символов – Control_String и Green_Line,
размерами 20 и 5 символов соответственно. Индексирование массивов
в языке С начинается от нуля, поэтому первый элемент вектора из рас-
смотренного примера будет обозначен как Vector[0], а последний –
Vector[9].
Выбор отдельного компонента массива осуществляется указанием
идентификатора массива, за которым в квадратных скобках следует кон-
станта или переменная. Допустимо также использование индексного вы-
ражения. Индексное выражение должно давать значения, лежащие в диа-
пазоне, определяемом описанием массива. К компоненту массива приме-
нимы операции и стандартные функции, допустимые для переменных
базового типа.
Пусть имеется, например, массив чисел Ball, содержащий средний
балл успеваемости трех студентов, а также массивы Mas1 и Mas2, содер-
жащие отметки двух групп 25 студентов в каждой по математике. В при-
мере 4.1 приведен один из вариантов описания данных массивов.
Пример 4.1
float Ball[3];
int Mas1[25], Mas2[25];
Для данных, рассмотренных в примере 4.1, определив дополнительно
i, j, k как переменные целого типа, можно записать операторы, приведен-
ные ниже.
Пример 4.2
inti,j,k;
i=15;
j=20;
k=8;
Ball[0] = 4.35;
Mas1[i] = Mas2[j – k];
Mas[i + 1] = Mas2[k*2 + 5];
В программе одному массиву может быть присвоено значение друго-
го массива, если их базовые типы и диапазоны индексов совпадают. Так
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
98
как это требование выполняется для массивов Mas1 и Mas2, рассмотрен-
ных в примере 4.2, то в программе допустим оператор
Mas1 = Mas2;
Пример 4.3 . Пусть необходимо составить программу определения
количества вхождений каждой строчной буквы латинского алфавита
в текст, состоящий из 100 символов.
Структурограмма алгоритма решения задачи приведена на рис. 4 .1,
а ниже – текст программы.
Ввод n
Ввод текста Stroka
i = 0(1)25
i=0
i=1(1)n
k[Stroka[i]] = k[Stroka[i]]+1
Печать массива k
Рис. 4 .1 . Струк турограмма алгоритма подсчета количества букв,
входящих в текст
/*Цель: подсчет количества вхождений строчных букв в текст.
*/
/*Метод: обработка массивов.
*
/
/*Переменные: Stroka – массив символов;
*
/
/*
k – массив количества вхождений букв;
*/
/*
i, x – параметры циклов;
*
/
/*
n – количество обрабатываемых символов.
*/
/*Программист: Федоров В.Ф.
*
/
/*Дата: 09.12 .03 г.
*
/
#include <stdio.h>
main( )
{
int Nmax =200, i, n, k[26];
char x, Stroka[Nmax];
printf("Введите количество обрабатываемых символов");
scanf("%d\n",&n);
printf("Введите %d латинских букв\n", n);
for(i=0;i<n;i++)
scanf("%c",&Stroka[i]);
/*Эхо-печать*/
printf("В вашем тексте:\n");
for(i=0;i<n;i++)
printf("%c",Stroka[i]);
/*Обнуление массива k*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
99
for(i=0;i<26;i++)
k[i] = 0;
/*Подсчет количества символов*/
for(i=0;i<n;i++)
k[Stroka[i] – 97]++;
/*Вывод результата*/
for(x = ‘a’; x <= ‘z’; x++)
printf("\nчисло букв %c равно %d\n", x, k[x – 97]);
}
В рассматриваемом примере массив k предназначен для накаплива-
ния количества каждой из 26 букв латинского алфавита. При этом ин-
декс, или номер, элемента массива для определения количества вхожде-
ний каждой буквы находится как разность кода соответствующего сим-
вола и кода первой строчной буквы латинского алфавита «a», равного 97
(см. табл. 1 .3).
Если описать массив k как статический:
static int k[26];
то произойдет его автоматическая инициализация нулями и нет необхо-
димости в операторах обнуления массива.
Очень распространенным классом задач обработки массивов является
их сортировка. Поэтому рассмотрим еще один пример.
Пример 4.4. Имеется массив A, содержащий n элементов. Разместить
элементы массива в порядке возрастания их значений.
При решении этой задачи воспользуемся сортировкой по методу пу-
зырька. Суть этого метода состоит в организации упорядоченного списка
элементов, в который на соответствующие им места добавляются один за
другим неотсортированные элементы. Для реализации этого метода мас-
сив на текущем шаге сортировки элементы массива просматриваются в
направлении от больших значений индекса к меньшим, сравниваются
очередные два элемента, индекы которых отличаются на единицу. Если
эти элементы между собой не упорядочены, то производится их взаим-
ный обмен. На следующем шаге рассматривается большее количество
элементов массива. И так до тех пор, пока не будут проанализированы
все элементы массива. На рис. 4 .2 дана схема описания алгоритма сорти-
ровки методом пузырька.
Текст программы имеет следующий вид:
/*Цель: сортировка элементов одномерного массива
*
/
/*
в порядке возрастания их значений.
*/
/*Метод: сортировка массива методом пузырька.
*/
/*Переменные: a – исходный массив;
*
/
/*
n – количество обрабатываемых элементов массива; */
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
100
/*
i, k – параметры циклов;
*
/
/*
x – вспомогательная переменная.
*
/
/*Программист: Свиридов Е.П.
*
/
/*Дата: 1.02.04 г.
*
/
#include <stdio.h>
main( )
Начало
Ввод
исходных
данных
i:=1(1)n-1
k:=i(-1)1
a[k]>a[k+1]
x=a[k]
a[k]=a[k+1]
a[k+1]=x
Вывод
результата
Конец
Да
Нет
Рис. 4 .2. Схема алгоритма сортировки одномерного массива методом пузырьк а
{
float a[100], x;
inti,k,n;
рrintf("Задайте количество элементов массива\n);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
101
scanf("%d\n",&n);
рrintf("Введите %d чисел\n", n);
for(i=0;i<n;i++)
scanf("%f",&a[i]);
/*Эхопечать*/
printf("Исходный массив:\n");
for(i=0;i<n;i++)
рrintf("%8.2f", a[i]);
/*Сортировка массива*/
for(i=0;i<n–1;i++)
for(k = i; >= 0; i--)
if(a[k]>a[k+1])
{
x
=
a
[
k
]
;
a[k]=a[k+1];
a[k+1]=x;
}
/*Вывод результата*/
printf("\nОтсортированный массив:\n");
for(i=0;i<n;i++)
printf("%8.2f", a[i]);
}
4.1.3. Двумерные массивы
Операция образования массива [ ] может быть использована при
описании несколько раз. В этом случае объявленный массив называется
многомерным. Например, предложение
My_Own_Type Object[2][15];
определяет двумерный массив Object, состоящий из объектов созданного
пользователем типа My_Own_Type.
Двумерные массивы называют также матрицами. Тогда первый раз-
мер в описании массива определяет количество строк, а второй – количе-
ство столбцов.
Элементы многомерных массивов располагаются в памяти ЭВМ та-
ким образом, что наиболее быстро меняется значение самого последнего
индекса, т. е . для рассмотренного примера: Object[0][0], Object[0][1],
Object[0][2], ..., Object[0][14], Object[1][0], Object[1][1], ..., Object[1][14].
Количество оперативной памяти, занимаемой элементами много-
мерного массива, определяется произведением величин его размерностей
на длину элемента массива, т. е . для массива Object: sizeof(My_Own
_ T yрe) * 2 * 15. Во избежание ошибок не следует определять массивы,
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
102
занимающие более одного сегмента оперативной памяти (более
64 Кбайт).
Если число индексов в описании массива равно N, то массив называ-
ется N-мерным. В языке не накладывается ограничений на число измере-
ний массива. На практике часто используются двумерные массивы, соот-
ветствующие понятию матрицы (набора векторов, прямоугольной
таблицы).
Многомерные массивы представляют собой чистую абстракцию, по-
скольку память у ЭВМ одномерна и многомерные массивы хранятся
в ней в виде линейных последовательностей значений. Рассмотрим мат-
рицу А, состоящую из 2×3 элементов.
111213
212223
ааа
А
.
ааа
=
Элементы этой матрицы могут быть размещены в памяти ЭВМ «по
строкам», формируемая при этом последовательность будет a11, a12, a13,
a21, a22, a23.
Переменная A имеет смысл двумерного массива из двух строк, в ка-
ждую из которых включено по 3 элемента.
Описание массива A будет иметь следующий вид:
float a[2][3];
Ссылка на элемент матрицы А, лежащий на пересечении i-й строки
и j-го столбца, может иметь следующий вид:
a[i][j].
При объявлении и определении массивов допустимы следующие до-
полнительные описатели:
cdecl – предназначенный для системного использования в языке С;
far – расположенный, возможно, в другом сегменте;
fortran – располагающийся в оперативной памяти в соответствии
с соглашениями языка Фортран;
huge – размер массива превышает один сегмент (более 64 Кбайт);
near – расположенный в одном сегменте;
рascal – располагающийся в оперативной памяти в соответствии с со-
глашениями языка Паскаль;
static – локальный.
Пример 4.5
#define SIZE 20
char far fortran List[SIZE];
int huge DataBase[65000];
Элементы массивов могут использоваться в операторах и выражени-
ях языка С наравне с переменными. При этом в квадратных скобках мо-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
103
гут употребляться любые арифметические выражения, возвращающие
целые положительные значения. Далее приведен фрагмент программы,
копирующей массив чисел First в массив Second.
char First[10];
char Second[20];
inti=0;
while(i < 10)
First[i++] = i;
while(i > 0)
Second[i]=First[––i];
При использовании элементов массивов в программе операция [ ]
служит для вычисления индекса элемента массива и является самой при-
оритетной операцией языка С.
4.1.4. Ввод-вывод массивов
Кроме оператора присваивания, значения элементам массива можно
задать оператором ввода данных. Для простых типов данных в языке
применяется поэлементный ввод-вывод. При вводе компоненты массива
обычно отделяются друг от друга пробелом. По окончании ввода очеред-
ной порции данных набирается символ возврата каретки или перевода
строки.
В примере 4.6 приведена программа, осуществляющая ввод-вывод
целых вектора V, двумерного массива MAS и символьных массивов S1 и
S2. Вектор V содержит всего 3 элемента, поэтому в операторах ввода-
вывода просто перечислены его компоненты.
Пример 4.6
/*Цель: ввод-вывод массивов.
*
/
/*Метод: поэлементный ввод-вывод.
*
/
/*Переменные: рr5 – строковая константа из пяти пробелов;
*/
/*
v – одномерный массив целых чисел;
*/
/*
mas – двумерный массив целых чисел;
*/
/*
s1, s2 – символьные массивы;
*
/
/*
i, j – параметры циклов.
*
/
/*Программист: Семин И.Р.
*
/
/*Дата: 11.03 .04 г.
*
/
#include <stdio.h>
#define рr5 " "
main( )
{
char s1[10], s2[10];
int i, j, v[3], mas[2][3];
/* ввод массивов */
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
104
printf("%sВведите массив v\n",рr5);
scanf("%d %d %d",&v[0], &v[1], &v[2]);
printf("%sВведите массив mas\n",рr5);
for(i=0; i < 2; i++)
for(j=0; j < 3; j++)
scanf("%d",&mas[i][j]);
printf("%sВведите массивы s1 и s2\n",рr5);
scanf("%s %s", s1, s2);
/* вывод массивов */
printf("\n%sМассив v\n",рr5);
рrintf("%s %5d %5d %5d\n",рr5, v[0], v[1], v[2]);
рrintf("%sМассив mas\n",рr5);
for(i=0; i < 2; i++)
{
рrintf("%s",рr5);
for(j=0; j < 3; j++)
рrintf("%5d", mas[i][j]);
рrintf("\n");
}
рrintf("%s s1 = %s %s s2 = %s\n", рr5, s1, рr5, s2);
}
Результат работы программы:
Введите массив v
123
Введите массив mas
456
789
Введите массивы s1 и s2
abcdefgijk lmn
Массив v
123
Массив mas
456
789
s1 = abcdefgijk s2 = lmn
Для ввода-вывода двумерного массива организованы вложенные цик-
лы, осуществляющие ввод-вывод элементов по строкам. В качестве сим-
вольных данных вводились 13 букв латинского алфавита. При длине
строк s1 и s2, равной 10 байтам, первая строка заполнилась полностью,
а во вторую из входного потока было записано 3 символа.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
105
4.1.5. Примеры программирования задач
с использованием массивов
Представление данных в виде массива оказывается целесообразным
при решении многих практических задач, связанных с операциями с век-
торами и матрицами, вычислением таблицы значений функции в произ-
вольных точках, задач сортировки результатов наблюдения случайных
процессов и т. д . Двумерные массивы являются удобным средством для
обработки табличных данных.
Рассмотрим примеры решения некоторых из этих классов задач.
Пример 4.7 . Составить программу вычисления скалярного произве-
дения S двух векторов V и U, состоящих из пяти элементов каждый,
по формуле
5
ii
i1
Sv
u
=
=
∑.
Вычислить длину Dv вектора V по формуле
5
2
vi
i1
Dv
=
=
∑
Ниже приведена блок-схема алгоритма (рис. 4.3) и программа.
/* ************************************************** */
/*Цель: определение длины вектора Dln и скалярного
*
/
/*
произведения S двух векторов.
*
/
/*Параметры и переменные:
*
/
/*
v,u – одномерные массивы (векторы);
*/
/*
Dln,S – переменные для результатов;
*/
/*
i,j – вспомогательные переменные.
*/
/*Программист: Стасов К.Т .
*
/
/*Дата: 11.02.04 г.
*
/
/* ************************************************** */
#include <stdio.h>
#define рr3 " "
#define nmax 10
main( )
{
inti,j,n;
float Dln, S, u[nmax], v[nmax];
рrintf("%s введите размер массивов\n", рr3);
scanf("%d",&n);
рrintf("%s введите массивы v и u размером%d\n",рr3, n);
for(i=0; i < n; i++)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
106
scanf("%f %f",&v[i], &u[i]);
рrintf("%s исходные данные \n", рr3);
рrintf(" массив v \n");
for(i=0; i < n; i++)
рrintf("%s%7.3f", v[i]);
рrintf("\n");
Установка
начальных
значений
Начало
i = 1(1)5
Конец
Накопление
суммSиDln
Вычисление
длины вектора
S=0
Dln=0
S=S+v[i]⋅u[i]
Dln=Dln+
+ v[i]⋅v[i]
Ввод
v,u
Печать
резуль-
татов
1
2
3
5
6
4
Рис. 4 .3 . Схема алгоритма вычисления длины вектора и скалярного произведения
рrintf(" массив u \n");
for(i=0; i < n; i++)
рrintf("%s%7.3f", u[i]);
рrintf("\n");
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
107
Dln=S=0;
for(i=0; i < n; i++)
{
S+=v[i]*u[i];
Dln+=v[i]*v[i];
}
Dln=Sqrt(Dln);
рrintf("\n%s результаты вычислений \n",рr3);
рrintf("%s скалярное произведение --%12e\n",рr3,S);
рrintf("%s длина вектора v --%12e\n",рr3,Dln);
}
Результат работы программы:
исходные данные
массив v
1.100 1.300 3 .400 4.100 3.300
массив u
1.200 2.500 3.700 4.200 5.100
результаты вычислений
скалярное произведение -- 5 .120000е+01
длина вектора v -- 6.493070e+00
В приведенном примере максимальный размер индекса nmax масси-
вов v и u определен как константа в операторе define. Если размеры мас-
сивов изменяются, то достаточно будет ввести новое значение перемен-
ной n, не изменяя остальной части программы. Элементы векторов v и u
вводятся парами в цикле ввода. В цикле вычислений происходит накоп-
ление сумм для вычисления скалярного произведения S и длины вектора
Dln. В заключительной части программы осуществляется вывод исход-
ных данных и результатов вычислений.
Пример 4.8
Для данной матрицы, размер которой не превышает 10x10 элементов,
найти максимум среди сумм элементов диагоналей, параллельных глав-
ной. Ниже даны текст программы и блок-схема алгоритма вычислений
(рис. 4 .4).
Для вычисления сумм элементов диагоналей в программе зарезерви-
рован массив S. Если размер исходной матрицы А равен nxn, то в ней со-
держится 2n-1 диагоналей, параллельных главной. Ввод элементов мат-
рицы А осуществляется после ввода переменной n, определяющей размер
данной матрицы. По условию задачи n должно быть меньше 10. Перед
вычислением нужных величин осуществляется обнуление элементов мас-
сива S.
/*Цель: для заданной матрицы найти максимум среди сумм
*/
/*
элементов диагоналей, параллельных главной.
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
108
/*Параметры и переменные:
*
/
/*
A – двумерный массив для исходной матрицы;
*/
/*
S – одномерный массив сумм диагональных элементов;*/
/*
max – максимальная сумма.
*
/
/*Прoграммист: Ларин В.И.
*
/
/*Дата: 15.03 .04 г.
*
/
#include <stdio.h>
#define рr3 " "
main( )
{
int a[10][10], i, j, k, max, n, s[20];
рrintf("%s введите размерность матрицы <=10 \n",рr3);
scanf("%d",&n);
k = 2*n-1;
рrintf("%s введите матрицу из %5d элементов\n",рr3, n*n);
for(i=0; i < n; i++)
for(j=0; j< n; j++)
scanf("%d", &a[i][j]);
рrintf("%s исходная матрица \n",рr3);
for(i=0; i < n; i++)
{
for(j=0; j< n; j++)
рrintf("%5d",a[i][j]);
рrintf("\n");
}
/* цикл вычисления сумм диагональных элементов */
for(i=0; i < k; i++)
s[i]=0;
for(i=0; i < n; i++)
for(j=0; j < n; j++)
s[n-i+j-1]+=a[i,j];
/* определение максимальной суммы */
max=s[0];
for(i=1; i < k; i++)
if(s[i]>max)
max=s[i];
рrintf("%s%s результаты вычисления \n",рr3,рr3);
рrintf("%s суммы диагональных элементов \n",рr3);
for(i=0; i < n; i++)
рrintf("%5d",s[i]);
рrintf("\n");
for(i=n; i < k; i++)
рrintf("%5d",s[i]);
рrintf("\n");
рrintf("%s максимальная сумма диагональных элементов %5d\n",
рr3, max);
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
109
Начало
Ввод n
k=2⋅n-1
Обнуление
массива S
Ввод
матр. А
Вычисление
компонентов
массива S
Определение
максим. элем .
max
Вывод
исход-
ных
данных
Вывод
масс.S и
перем.
max
Конец
i=0(1)n-1
j=0,n -1
s[n-i+j-1]=s[n-
i+j-1]+a[i,j]
max=s[0]
i=1(1)k-1
s[i]>max
max=s[i]
Да
Нет
1
2
3
4
5
6
7
8
Рис. 4 .4. Схема алгоритма вычисления суммы диагональных элементов
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
110
Для вычисления сумм диагональных элементов организован вло-
женный цикл по перебору всех элементов исходной матрицы А. Нужный
индекс элементов массива S определяется из параметров циклов выраже-
нием n-i+j-1. После формирования элементов массива S определяется его
максимальный компонент. Для этого переменной mas присваивается зна-
чение первого элемента массива. Далее организован цикл по просмотру
всех остальных элементов массива. Если какой-либо компонент массива
S превысил значение переменной mas, то последней присваивается зна-
чение этого компонента. В заключительной части программы осуществ-
ляется вывод элементов исходной матрицы А, сформированного массива
сумм диагональных элементов S и максимального компонента массива
mas. Для контрольного просчета была выбрана матрица размера 7x7. Ре-
зультаты вычислений приведены ниже.
введите размерность матрицы <=10
7
введите матрицу из 49 элементов
123456711121314151617212223242526273132333435
3637414243444546475152535455565761626364656667
исходная матрица
1234567
11121314151617
21222324252627
31323334353637
41424344454647
51525354555657
61626364656667
результаты вычисления
суммы диагональных элементов
61 113 156 190 215 231 238
177125 82 48 23 7
максимальная сумма диагональных элементов 238
4.1.6. Инициализация массивов
Если ничего не засылать в массив перед началом работы с ним, то
внешние и статические массивы инициализируются нулем, а автоматиче-
ские и регистровые массивы будут содержать какой-то «мусор», остав-
шийся в этой части памяти.
Инициализация – это процесс присваивания начальных значений пе-
ременным различного типа. Для инициализации внешних и статических
массивов в языке С используются списки инициализации, представляю-
щие собой перечисление через запятую начальных значений элементов
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
111
массивов. Многомерные массивы в списке инициализации могут содер-
жать разделительные фигурные скобки:
static int X[4] = {1,2,3,4}; /*Инициализация массива X
*/
/
*
четырьмя значениями
*/
static char Str[3][2] = {{'1','2'}, /* Массивы Str и Str1 будут
*/
{'3','4'},
/* проинициализированы
*/
{'5','6'}};
/* одинаково
*
/
static char Str1[3][2] = {'1','2','3','4','5','6'};
static int Coord_y[2][3] = {{1,1}, /* Недостающие элементы*/
{2}}; /* инициализируются по */
/
*
умолчанию значением 0 */
При инициализации массива в том случае, если он заполняется спи-
ском инициализации, допускается не указывать численные значения раз-
мера. Эти размеры будут определены компилятором в соответствии со
списком инициализации, например:
static unsigned int M_x[ ] = {1,1,1,1,1}; /* Определен массив из пяти*/
/
*
элементов целого типа */
Для удобства работы со строками символов в языке С считается, что
символьный массив, оканчивающийся шестнадцатеричным нулем 0х00,
является символьной строкой. Поскольку символьные строки могут ис-
пользоваться в тексте программы в качестве констант (в этом случае они
заключаются в двойные кавычки), для инициализации символьного мас-
сива L можно с равным успехом применять следующие предложения:
static char L[ ] = {'С','т','р','о','к','а','\0'};
static char L[ ] = "Строка"; /* Символ '\0' всегда неявно присутс- */
/
*
твует в строковой константе
*/
При указании в квадратных скобках размера массива, большего, чем
список инициализации, недостающие элементы списка инициализации
обнуляются.
Вопросы для самоконтроля
1. Какими операторами языка задается описание массива?
2. Как можно описать двумерный массив?
3. Как можно обратиться к элементам массива?
4. Как организовать ввод-вывод двумерного массива?
5. Каким образом располагаются элементы многомерных массивов
в памяти машины?
6. Что такое инициализация массива?
7. Каковы особенности инициализации массивов различных классов
памяти?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
112
Упражнения
1. Дана матрица размера 3x4. Составить программу для подсчета ко-
личества четных элементов в каждой строке матрицы.
2. Дана матрица размера 5x6. Составить программу для подсчета ко-
личества нечетных элементов в каждом столбце матрицы.
3. Проверить, имеется ли в заданной строке символов баланс откры-
вающих и закрывающих круглых скобок.
4. Перечислить все числа заданной последовательности чисел, кото-
рые состоят из тех же цифр, что и первое.
5. Дана матрица В размера 5x4. Составить программу формирования
вектора, элементы которого равны сумме элементов строк матрицы В.
6. Дана матрица В размера 5x7. Составить программу формирования
вектора, элементы которого равны сумме элементов столбцов матрицы В.
7. Дана матрица А размера 7x7. Составить программу нахождения
суммы элементов, лежащих выше главной диагонали.
8. Дана матрица А размера 7x7. Составить программу нахождения
суммы элементов, лежащих ниже главной диагонали.
9. В массиве слов найти пару слов, из которых одно является обра-
щением другого.
10. Задана матрица В размера 7x7. Составить программу, осуществ-
ляющую перестановку элементов в каждой строке матрицы так, чтобы
первый элемент строки поменялся с последним, второй – с предпослед-
нимит.д.
11. Расстояние между k-й и р-й строками матрицы А=⎟⎢aij⎟⎢определяется
как
n
kj
pj
j1
aa
=
×
∑
.
Указать номер строки, максимально удаленной от первой строки за-
данной матрицы.
12. Расстояние между k-й и р-й строками матрицы А=⎟⎢aij⎟⎢ определя-
ется как
n
kj
pj
j1
aa
=
×
∑
.
Указать номер строки, максимально удаленной от последней строки
заданной матрицы.
13. Сформировать двумерный массив целых чисел. Проверить, со-
держится ли в нем заданная строка чисел.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
113
14. Определить норму заданной матрицы А=⎟⎢aij⎟⎢, т. е. число
.
ij
ij
a
max
⎛⎞
⎜⎟
⎜⎟
⎝⎠
∑
15. Сформировать два массива целых чисел определенной длины.
Вывести на печать числа, встречающиеся в каждом массиве.
16. Сформировать два массива целых чисел определенной длины.
Вывести на печать числа, встречающиеся только в одном массиве.
17. Из символов произвольного предложения сформировать массив
целых чисел, соответствующих порядковому номеру литер в коде КОИ-7 .
Определить максимальный элемент этого массива.
18. Из символов произвольного предложения сформировать массив
целых чисел, соответствующих порядковому номеру литер в коде КОИ-7 .
Определить минимальный элемент этого массива.
19. Среди столбцов заданной целочисленной матрицы С размера 7x7,
компоненты которой не превышают 10, найти столбец с минимальным
произведением элементов.
20. Среди строк заданной целочисленной матрицы С размера 7x7,
компоненты которой не превышают 10, найти строку с максимальным
произведением элементов.
21. Для заданной матрицы В размера 5x5 найти такие k, для которых
k-я строка матрицы совпадает с k-м столбцом.
22. Вычислить значение полинома
n
k
k
k0
yax
,
=
=×
∑
используя схему Горнера:
y=(...((an⋅x+an-1)x+an-2)x+...+a1)x+a0.
Значение n принять равным 10; x и элементы массива А выбрать про-
извольно.
23. По заданному символьному массиву С сформировать массив дво-
ичных элементов В. Компоненту b[i] присвоить значение 1, если c[i] яв-
ляется цифрой, и присвоить значение 0 в противном случае.
24. Составить программу записи элементов прямоугольной матрицы
А в одномерный массив в порядке следования столбцов. Найти наимень-
ший элемент полученного массива. Размер и элементы массива выбрать
произвольно.
25. Составить программу формирования вектора из количества нену-
левых элементов каждой строки произвольного двумерного массива.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
114
4.2. Указатели
4.2.1. Описание указателей
Указатели, как и массивы, являются производными типами данных,
получаемыми из простых типов при помощи применения специальной
операции. Указатели в языке С являются переменными, предназначен-
ными для хранения в них адресов каких-либо объектов программы и опи-
сываются при помощи операции *. Операция * является унарной, т. е . ис-
пользующей один аргумент, и может быть применена к аргументу не-
сколько раз.
Рассмотрим пример.
int *рoint, *addr;
char far *list, **р_list;
Переменные рoint и addr являются указателями на объекты целого
типа, т. е. предназначены для хранения адресов каких-либо целых чисел.
Переменная list – указатель на символ, располагающийся, возможно, за
пределами программных сегментов оперативной памяти, р_list – указа-
тель на указатель (адрес адреса) объектов типа char far. Под любую пере-
менную-указатель компилятором отводится оперативная память, доста-
точная для хранения адреса.
В синтаксисе языка С, пользуясь операциями образования производ-
ных типов, можно описать указатели на объекты любых типов, в том
числе массивы указателей и объекты, содержащие в своей структуре ука-
затели. Рассмотрим несколько примеров с соответствующими поясне-
ниями.
char *String_in[ 20 ]; /* Массив из 20 указателей на символы
*/
char *(String_out[20]); /* Указатель на 20-символьный массив
*/
char *far *р_match;
/* Эквивалентно: char *(far *р_match);
*/
/
*
Указатель на дальний указатель
*/
const int *рoint;
/* Указатель на константу – целое число */
const char far *Рoint; /* Дальний указатель на постоянную строку
*/
int *( *var [5] )[5]; /* Массив указателей на массивы
*
/
/
*
указателей на целые числа
*
/
4.2.2. Адресные операции
Над данными типа указателя, содержащими адрес указанной пере-
менной, можно производить арифметические операции сложения и вы-
читания, операции отношения, логическую операцию отрицания как над
целыми данными.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
115
Кроме того, существуют специальные адресные операции. Так, адрес
любого элемента данных получается при указании перед ним префикса &
(амперсант), обозначающего операцию взятия адреса. Например, если
имеется описание переменной целого типа n и указателя р на данные це-
лого типа:
int n, *р;
то операция &n даст адрес величины n и в программе его значение может
быть присвоено указателю р:
р=&n;
Для определения содержимого некоторого адреса (например, при об-
мене данными с внешними устройствами) указывается префикс операции
определения значения *. Так, запись *р обозначает содержимое адреса,
на который ссылается указатель р. Тогда присваивание
*
р=12;
будет эквивалентно присваиванию значения 12 переменной n.
Увеличение (уменьшение) может быть выполнено либо с помощью
операции сложения (вычитания), либо с помощью операции увеличения
(уменьшения). Увеличивая указатель, мы будем перемещать его на сле-
дующий элемент (массива). Если имеются описания указателей на пере-
менную целого типа
int *рi;
и символьную переменную
char *рc;
то операция рi++ увеличивает числовое значение рi на 2, так как целое
значение занимает 2 байта, а рc++ – на 1 байт. Операцию увеличения
(уменьшения) можно использовать для переменной типа указателя, но не
для констант этого типа.
Можно также находить разность двух указателей, чтобы определить,
например, на каком расстоянии (в элементах) друг от друга находятся
элементы массива.
4.2.3. Инициализация указателей
Значение указателя перед его использованием обязательно должно
быть инициализировано, поскольку объявление указателя позволяет вы-
делить только память для хранения значения указателя, содержимое же
этой памяти остается неопределенным.
При инициализации указателей возможно использование строковых
констант, например:
йchar *string = "Введите строку:";
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
116
В этом примере под переменную string выделяется память 4 байта.
Дополнительно в области констант выделяется память для хранения
строки «Введите строку», адрес начала которой записывается в string.
Для инициализации указателей допустимо применять операцию взя-
тия адреса &. Применение этой операции к объектам различных типов,
объявленных в программе, позволяет получить адреса этих объектов, на-
пример:
int a, b = 0; /*Указатель на целое инициализируется адресом */
int *р_b = &b; /* переменной b
*
/
int round( ); /* Объявление функции round( ), возвращающей */
/
*
целое значение
*
/
int (*р_round)= round;/* Допустимая инициализация указателя */
/
*
на функцию
*
/
int (*р_r) = &round;/* Инициализация при помощи операции & */
Массивы указателей инициализируются по правилам инициализации
массивов. Ниже приведен пример инициализации массива указателей на
строки символов.
static char *menu[ ]= {
"
1
.
Помощь ",
"
2
.
Компиляция",
"
3
.
Редактирование",
"
4
.
Выход в систему"
};
4.2.4. Особенности использования массивов
и указателей в программе
Массивы и указатели довольно тесно связаны между собой. Этот
факт может быть подтвержден следующим тождеством:
x[i] == *(x + i).
Работа в языке С с указателями, несомненно, покрывает все возмож-
ности работы с массивами. Если использование массивов делает про-
грамму более читабельной, то использование указателей делает ее более
компактной и эффективной.
Рассмотрим пример вывода значения указателя (адреса, на который
ссылается указатель), значения, находящегося по этому адресу, и адреса
самого указателя.
#define РR(X) рrintf("X=%u, *X=%d, &X=%u\n", X, *X, &X)
#include <stdio.h>
main( )
{
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
117
static int mas[ ] = {1, 2, 3};
int *р1, *р2;
р1 = mas; /* присваивает адрес указателю
*
/
р2 = &mas[2];/* присваивает адрес последнего элемента
*/
РR(р1);
р1++;
РR(р1);
РR(р2);
++р2; /* значение указателя выходит за границы массива */
РR(р2);
printf("р2 – р1 = %u\n", р2 – р1);
}
Она может вывести, например, следующие результаты:
р1=212, *р1=1, &р1=31440
р1=214, *р1=2, &р1=31440
р2=216, *р2=3, &р2=31444
р2=218, *р2=23508, &p2=31444
р2–р1 =2
Для многомерных массивов также можно использовать указатели.
Указатель будет ссылаться на первый элемент первой строки. Увеличи-
вая затем его значение на единицу, мы будем последовательно переме-
щаться по элементам строк. Если, например, в программе имеются опи-
сания двумерного массива и указателя:
int z[3][4], *р;
то после присваивания
р=z;
следующие элементы будут эквивалентными:
р == &z[0][0]
р + 1 == &z[0][1]
р + 2 == &z[0][2]
р + 3 == &z[0][3]
р + 4 == &z[1][0]
р + 5 == &z[1][1]
...
Так как двумерный массив – это массив массивов, то можно исполь-
зовать имена строк z[0], z[1], z[2], которые являются указателями на пер-
вые элементы этих строк.
4.2.5. Ввод-вывод данных с помощью указателей
Ввод и вывод данных с помощью указателей производится практиче-
ски так же, как и для обычных элементов, с той лишь разницей, что при
использовании функции scanf нет необходимости указывать знак опера-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
118
ции взятия адреса (&), поскольку указатель сам уже является адресом
и требуется инициализация указателя адресом первого элемента массива.
А при выводе элемента данного, на который ссылается указатель, перед
указателем необходимо поставить знак операции определения содержи-
мого, или операции разыменования (*). Для символьных массивов эти
замечания не обязательны.
Для сравнения рассмотрим программу примера 4.6, осуществив в
ней ввод и вывод элементов массивов с помощью указателей.
/*Цель: ввод-вывод массивов с помощью указателей.
*
/
/*Метод: поэлементный ввод-вывод.
*
/
/*Переменные: рr5 – строковая константа из пяти пробелов;
*/
/
*
v
–
одномерный массив целых чисел;
*/
/
*
m
a
s
–
двумерный массив целых чисел;
*/
/*
s1, s2 – символьные массивы;
*
/
/*
i, j – параметры циклов;
*
/
/*
рv, pm, рs1, рs2 – указатели на массивы v, mas, */
/*
s1, s2 соответственно.
*
/
/*Программист: Ветров А.Г.
*
/
/*Дата: 25.03 .04 г.
*
/
#include <stdio.h>
#define рr5 " "
main( )
{
char *рs1, *рs2, s1[10], s2[10];
int i, j, *рv, *рm, v[3], mas[2][3];
/* ввод массивов */
рrintf("%s Введите массив v\n",рr5);
рv=v;
scanf("%d %d %d", рv, рv+1, рv+2);
рrintf("%s Введите массив mas\n",рr5);
рm=mas;
for(i=0; i < 6; i++)
scanf("%d", рm+i);
рrintf("%s Введите массивы s1 и s2\n",рr5);
рs1=s1;
рs2=s2;
scanf("%s %s", рs1, рs2);
/* вывод массивов */
рrintf("\n%s Массив v\n",рr5);
рrintf("%s %5d %5d %5d\n",рr5, *рv, *(р+1), *(р+2));
рrintf("%s Массив mas\n",рr5);
for(i=0; i < 2; i++)
{
if(!i%3)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
119
{
рrintf("\n");
рrintf("%s",рr5);
}
рrintf("%5d", *(рm+i));
}
рrintf("\n%s s1 = %s %s s2 = %s\n", рr5, рs1, рr5, рs2);
}
Результаты работы программы будут аналогичны примеру 4.6 .
4.2.6. Пример программирования задачи
с использованием указателей
Для сравнения рассмотрим алгоритм вычисления суммы элементов
диагоналей, параллельных главной, и нахождения среди них максималь-
ной, реализованный в примере 4.8 с использованием массивов. Примене-
ние указателей даст программу следующего вида:
/* ************************************************** */
/*Цель: для заданной матрицы найти максимум среди сумм
*/
/*
элементов диагоналей, параллельных главной.
*/
/*Параметры и переменные:
*
/
/*
A – двумерный массив для исходной матрицы;
*/
/*
S – одномерный массив сумм диагональных элементов; */
/*
max – максимальная сумма;
*
/
/*
рa, рs – указатели на массивы A и S
.
*
/
/*Прoграммист: Ларин В.И.
*
/
/*Дата: 25.03 .04 г.
*
/
/* ************************************************** */
#include <stdio.h>
#define рr3 " "
main( )
{
int a[10][10], i, j, k, max, n, *рa, *рs, s[20];
рrintf("%s введите размерность матрицы <=10 \n",рr3);
scanf("%d",&n);
k = 2*n-1;
рrintf("%s введите матрицу из %5d элементов\n",рr3, n*n);
рa=a;
for(i=0; i < n*n; i++)
scanf("%d", рa+i);
рrintf("%s исходная матрица \n",рr3);
for(i=0; i < n; i++)
{
for(j=0; j< n; j++)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
120
рrintf("%5d",*(рa+i*n+j);
рrintf("\n");
}
/* цикл вычисления сумм диагональных элементов */
for(i=0; i < k; i++)
*(рs+i)=0;
for(i=0; i < n; i++)
for(j=0; j < n; j++)
*
(
рs+n-i+j-1)+=*(рa+i*n+j;
/* определение максимальной суммы */
max=*рs;
for(i=1; i < k; i++)
if(*(рs+i)>max)
m
a
x
=
*
(
рs+i);
рrintf("%s%s результаты вычисления \n",рr3,рr3);
рrintf("%s суммы диагональных элементов \n",рr3);
for(i=0; i < n; i++)
рrintf("%5d",*(рs+i));
рrintf("\n");
for(i=n; i < k; i++)
рrintf("%5d",*(рs+i));
рrintf("\n");
рrintf("%s максимальная сумма диагональных элементов %5d\n",
рr3, max);
}
Результат работы программы аналогичен результату, полученному
в примере 4.8:
введите размерность матрицы <=10
7
введите матрицу из 49 элементов
123456711121314151617212223242526273132333435
3637414243444546475152535455565761626364656667
исходная матрица
1234567
11121314151617
21222324252627
31323334353637
41424344454647
51525354555657
61626364656667
результаты вычисления
суммы диагональных элементов
61 113 156 190 215 231 238
177125 82 48 23 7
максимальная сумма диагональных элементов 238
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 4. Массивы и указатели
121
Вопросы для самоконтроля
1. Каким образом производится описание указателей?
2. Сравнить операции образования производных типов [ ] и *, при-
вести примеры их совместного использования.
3. Что означают унарные операции & и *, как их можно использовать
при работе с указателями?
4. Что описывает следующий оператор:
char *(far *Last_Word)(void *); ?
5. Какие операции возможны над указателями?
6. Каким образом обрабатываются в языке С символьные строки, как
при этом можно использовать массивы и указатели?
7. Каким образом производится инициализация указателей?
8. Какие отличия имеются при вводе данных с помощью скалярных
переменных, массивов и указателей?
Упражнение
Составить программу соответствующего варианта упражнения из
разд. 4 .1, используя указатели на массивы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
122
ГЛАВА 5. ФУНКЦИИ
5.1. Основные понятия
5.1.1. Вспомогательные, или подчиненные, алгоритмы
При нисходящем проектировании алгоритма исходную задачу разби-
вают на более простые подзадачи. Каждой такой подзадаче соответствует
функционально законченная часть алгоритма. Если оформить эту часть
алгоритма в виде самостоятельной алгоритмической единицы со своими
входными и выходными данными таким образом, что к ней будут воз-
можны многократные обращения (ссылки) из различных точек основного
алгоритма, то такую алгоритмическую единицу можно назвать вспомо-
гательным или подчиненным алгоритмом (в языках программирования –
подпрограммой).
Если для какой-то подзадачи уже известен алгоритм ее решения, то
он может быть включен в состав вновь разрабатываемого алгоритма в ка-
честве вспомогательного.
Если в алгоритме или в разных алгоритмах встречаются фрагменты,
одинаковые по выполняемым действиям и различающиеся только в зна-
чениях обрабатываемых данных, то такого рода фрагменты могут быть
оформлены в виде отдельного алгоритма. В соответствующих местах ос-
новного алгоритма будет осуществляться лишь обращение к ним. Это по-
зволяет сократить объем и улучшить структуру всего алгоритма в целом.
При использовании вспомогательных алгоритмов возникают вопросы
их оформления (таким образом, чтобы в дальнейшем можно было ссы-
латься на них из других алгоритмов) и техники включения их в основные
алгоритмы в процессе использования последних. При схемной записи ал-
горитмов полная формализация в оформлении подчиненных алгоритмов
не производится.
Блок-схема подчиненного алгоритма оформляется следующим обра-
зом: определяется его имя, в блоке начала указывается имя алгоритма и
имена его входных величин – исходных данных, а в блоке конца указы-
ваются имена выходных величин – результатов. Переменные, перечис-
ленные в блоках начала и конца, называются формальными параметра-
ми. Их введение необходимо для того, чтобы при вызове подчиненного
алгоритма можно было задавать значения исходных данных, а после его
исполнения воспользоваться результатами.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
123
В качестве примера на рис. 5 .1
приведена схема вспомогательного
алгоритма вычисления степени
y:=an с натуральным показателем n.
Ссылка на вспомогательный ал-
горитм осуществляется с помощью
специального блока, в котором ука-
зываются его имя и список фактиче-
ских параметров – конкретных зна-
чений и имен, которые должны быть
подставлены вместо формальных
параметров при исполнении вспо-
могательного алгоритма. В качестве
примера рассмотрим алгоритм вы-
числения степени z=xk, x≠0, с це-
лым показателем k, пользуясь сле-
дующим определением:
Power(n,a)
Y=1
i=1(1)n
Y=Y *a
Y
Рис. 5.1. Вспомогательный алгоритм
вычисления степени
kk
k
1,
если k0
;
xx
,если k0
;
1/x ,если k0.
−
=
⎧
⎪=
>
⎨
⎪
<
⎩
Алгоритм вычисления z=x
k
построим, используя вспомогательный
алгоритм Power вычисления степени с натуральным показателем. Схема
алгоритма приведена на рис. 5 .2 .
Ссылка на Power(k,x,z)-вспомогательный алгоритм указана дважды:
с фактическими параметрами k, x, z при k>0 и с фактическими парамет-
рами –k, 1/x, z при k<0. Исполняется алгоритм Power один раз. После вы-
полнения совокупности действий, предусмотренных в Power, осуществ-
ляется возврат в основной алгоритм к блоку вывода, следующему за бло-
ком обращения к вспомогательному алгоритму. Очень важно понимать
суть и механизм замены формальных параметров фактическими.
Формальные параметры – это переменные, формально присутствую-
щие во вспомогательном алгоритме и определяющие тип и место подста-
новки фактических параметров.
Фактические параметры – это реальные объекты основного алгорит-
ма (константы, переменные, выражения), заменяющие при вызове вспо-
могательного алгоритма его формальные параметры.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
124
Начало
Ввод
x,k
k=0
z:=1
k>0
Pow er(k,x ,z)
Power(-k,1/x,z)
Вывод
z
Конец
Да
Нет
Да
Рис. 5 .2. Алгоритм вычисления степени с целым показателем
Над этими объектами и производятся действия, предусмотренные
командами вспомогательного алгоритма. Замена формальных параметров
фактическими осуществляется по порядку их следования. Число и тип
формальных и фактических параметров должны совпадать.
5.1.2. Понятие функции
Функция – это самостоятельная единица программы, реализующая
конкретную задачу или ее часть. Функции в С играют ту же роль, кото-
рую играют функции, подпрограммы и процедуры в других языках.
Функции могут располагаться в программе в различном порядке и
считаются глобальными для всей программы, включая встроенные функ-
ции, описанные до их ипользования.
Каждая программа на языке С обязательно содержит функцию с име-
нем main (главная функция), которая является основной частью програм-
мы. Когда программа начинает выполняться, вызывается функция main и
дальнейшее выполнение программы продолжается под ее управлением.
Когда выполнение функции main заканчивается, то завершается и вся
программа.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
125
Функция main может быть расположена в любом месте программы,
но расположение функции main в начале программы облегчает ее чтение,
описание прототипов функций и различных глобальных переменных.
А это, в свою очередь, позволяет облегчить поиск и документирование
функций во всей программе.
Константы, типы данных и переменные, описанные вне функций
(включая main), считаются глобальными. Это позволяет использовать их
внутри функций в пределах всей программы.
В языке С можно описывать и определять функцию. Когда описыва-
ется функция, то тем самым всем остальным функциям (включая главный
модуль main) дается информация о том, каким образом должно осущест-
вляться обращение к этой функции. Когда определяется функция, то ей
присваивается имя, по которому к ней будет осуществляться обращение,
и указывается, какие конкретно действия она будет выполнять.
5.1.3. Определение функции
Классический формат определения функции имеет следующий вид:
[<тип>]<имя функции>([<список параметров>])
[<описание параметров>;]
{
<
локальные описания>;
<
операторы>;
}
Здесь описание <тип> указывает тип возвращаемого функцией значения.
Если тип не указан, то по умолчанию результат принимается типа int.
Наличие списка параметров и их описаний не является обязательным.
Однако скобки в заголовке функции всегда должны быть. Формальные
параметры, если они есть, должны быть обязательно описаны непосред-
ственно после заголовка функции. Переменные же, отличные от парамет-
ров, описываются внутри тела функции, которое заключается в фигурные
скобки, и являются локальными для данной функции.
Например, функция
float ratio(divident, divisor)
float divident, divisor;
{
float y;
if (divisor == 0.)
y = 3.4e38;
else
y = divident / divisor;
return ( y );
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
126
возвращает результат типа float, равный частному двух чисел – divident и
divisor типа float. Если делитель равен нулю, то возвращается наиболь-
шее из допустимых для типа float значение.
Существует также так называемый современный стиль определения
функции, при котором тип формальных параметров описывается непо-
средственно в заголовке функции:
[<тип>] <имя функции> ( <тип> <имя параметра>,
<
тип><имя параметра>, ...)
{
<
тело функции>
}
Так, заголовок функции ratio при этом стиле программирования бу-
дет иметь следующий вид:
float ratio( float divident, float divisor)
{...}
Независимо от формы определения формальный параметр представ-
ляет собой новую переменную, и для нее в памяти ЭВМ выделяется от-
дельная область памяти.
5.1.4. Описание функции
Наряду с определением функции в каждой программе должно при-
сутствовать и ее описание, если функция используется до ее определе-
ния. При этом можно применять два различных способа: классический
стиль описания функции и современный.
Классический стиль, который нашел широкое применение в боль-
шинстве программ на С, имеет следующий синтаксис:
<
тип><имя функции>( );
Эта спецификация описывает имя функции и тип возвращаемых ею
значений. Такое описание не содержит никакой информации о парамет-
рах функции и помещается в вызывающей функции наряду с описаниями
простых переменных.
Современный стиль описания функций используется в конструкциях
расширенной версии стандарта языка С, предложенного Американским
национальным институтом стандартов (ANSI). При описании функций в
этой версии С используются специальные средства языка, известные под
названием «прототип функции». Описание функции с использованием ее
прототипа содержит дополнительно информацию о ее параметрах:
<
тип><имя функции>(<инф_пар1>,<инф_пар2>,...);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
127
где информация о параметре <инф_пар> имеет один из следующих фор-
матов:
<
тип>
<
тип><имя параметра>
Другими словами, при использовании прототипа функции должен
быть описан тип каждого формального параметра либо указано также и
его имя. Если функция применяет переменный список параметров, то по-
сле записи последнего параметра функции в описании необходимо ис-
пользовать эллипсис (...) .
Описание функций с помощью описания ее прототипа дает возмож-
ность компилятору производить проверку на соответствие количества и
типа параметров при каждом обращении к функции, а также по возмож-
ности выполнять необходимые преобразования.
Ниже приведен пример, где одна из функций vv( ) описана с помо-
щью ее прототипа, а другая – библиотечная функция sqrt( ) – с использо-
ванием классического стиля.
# include <stdio.h>
double vv (double, double, double);
/* прототип функции vv( ), возвращающей значение типа
*/
/* double и получающей три параметра типа double
*/
main( )
{
doublex=1.0,y =1.0,z =1.0;
while(x>=0&&(z>0.1||z<–0.1))
{
рrintf(" значение f = %f \n", vv(x, y, z));
scanf("%1f %1f %1f",&x, &y, &z);
}
рuts("Конец работы программы");
}
double vv(double x, double y, double z)
{
double f, sqrt( );
/* Описание библиотечной функции sqrt( ) классическим */
/* стилем совместно с переменной f */
f=sqrt(x)+y/z;
return ( f );
}
Если в программе не объявить функции vv( ) и sqrt( ) как имеющие
тип double, то по умолчанию будет предполагаться, что возвращается
значение типа int. Когда функция не возвращает или не получает никако-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
128
го значения, для описания функции или ее параметра может быть исполь-
зован тип void.
Рассмотрим простейшие примеры описания функций.
Пример 5.1 . Оформим в виде функции алгоритм вычисления степени
y=x
n
произвольного значения x с натуральным показателем n.
double Power(int n, float x)
{
int i;
double y;
y=1;
for(i=1;i<=n;i++)
y*= x;
return (y);
}
В заголовке функции Power в круглых скобках указаны параметры n,
x, определяющие ее аргументы, и их тип. Результатом выполнения функ-
ции является значение переменной y, которое передается (возвращается)
в основную программу с помощью специального оператора возврата
return. Тип результата (тип функции) указан в ее заголовке. Кроме того,
в теле функции описаны локальные переменные i и y, имеющие смысл
и доступные только внутри данной функции.
В функциях в качестве формальных параметров могут быть исполь-
зованы переменные структурированного типа, например имена массивов.
При определении их типа в заголовке процедуры указывается тип масси-
ва и скобки [ ], указывающие принадлежность определяемого имени к
массиву.
Пример 5.2 . Опишем функцию нахождения максимального элемента
в массиве вещественных чисел z1, z2, ..., zn:
float FnMax(int n, float z[ ])
{
int i;
float max;
max:= z[0];
for(i=1;i<n;i++)
if max<z[i]
max = z[i];
return(max);
}
В функции FnMax в качестве исходных данных определены парамет-
ры z, n, представляющие собой имя массива, тип которого определен как
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
129
float и его размер, в качестве результатов вычислений – параметр max,
описанный в теле функции и представляющий значение максимального
элемента массива.
5.1.5. Вызов функции
Все функции в С-программах равноправны: каждая из них может вы-
звать любую другую функцию, в том числе и сама себя, и, в свою оче-
редь, каждая может быть вызвана любой другой функцией. Даже функ-
ция main может быть вызвана другими функциями.
Обращение к функции осуществляется с помощью указателя функ-
ции, имеющего следующий вид:
<
имя функции>(e1, e2, ..., eN)
где e1, e2, ... – фактические параметры или аргументы, передающиеся по
значению. Формальный параметр – это переменная в вызываемой функ-
ции, а фактический аргумент – это конкретное значение, присвоенное
этой переменной вызывающей функцией. Он может быть константой, пе-
ременной или выражением. Независимо от типа фактического аргумента
он вначале вычисляется, а затем его величина передается функции.
Порядок вычисления значений фактических аргументов и порядок их
присваивания формальным параметром не гарантируется. Поэтому при
определении значений фактических аргументов необходимо воздержи-
ваться от операций типа увеличения или уменьшения.
Чтобы использовать функцию, необходимо в любом месте програм-
мы, обычно после функции main, поместить определение функции,
а в нужном месте главной функции или какой-то другой функции указать
обращение к ней. Если функция возвращает нецелое значение, она долж-
на быть описана с помощью оператора описания или прототипа функции.
Замена формальных параметров фактическими осуществляется в по-
рядке их следования слева направо. Число и типы формальных и факти-
ческих параметров должны совпадать!
Пример 5.3. Используя функцию Power, составим программу вычис-
ления степени z=a
m
с целым показателем m и a≠0. Учтем, что степень
с целым показателем определяется формулой
mm
-m
1, если m0
;
aa
,
если m 0;
1a ,если m0
.
=
⎧
⎪=>
⎨
⎪
<
⎩
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
130
Схема алгоритма решения задачи была приведена на рис. 5 .2, а на
рис. 5 .1 дана схема алгоритма функции вычисления степени с натураль-
ным показателем. Ниже дается текст программы.
/* Определение степени с целым показателем */
# include <stdio.h>
double Power(int, float);
main( )
{
int m;
float a;
double z;
рrintf("Введите a, m\n");
scanf("%f %d", &a, &m);
рrintf("%f в степени %d\n", a, m);
if (m=0)
z
=
1
;
else if m>0
z = Power(m, a); /* вызов функции */
e
l
s
e
z = Power(-m, 1/a);
/* вызов функции */
рrintf("равно %g\n", z);
}
/* Функция определение степени с натуральным показателем */
double Power(int n, float x)
{
int i;
double y;
y=1;
for(i=1;i<=n;i++)
y*= x;
return (y);
}
Обращение к функции в программе использовано дважды, в первом
случае происходит замена формальных параметров n, x на фактические
m,aвовтором–на
- m, 1/a. После выполнения действий, предусмотрен-
ных операторами функции, в программу возвращается значение резуль-
тата, присваиваемое переменной z. Возврат управления осуществляется к
оператору программы, следующему за оператором вызова функции, –
к оператору рrintf.
Важно понимать суть и механизм замены формальных параметров
фактическими. Формальные параметры – это имена, фиктивно (формаль-
но) присутствующие в функции и определяющие тип и место подстанов-
ки фактических параметров. Фактические параметры – это реальные дан-
ные программы (значения, переменные), заменяющие в теле функции при
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
131
ее вызове формальные параметры; над этими данными и производятся
действия, предусмотренные операторами функции.
5.2. Обмен информацией между функциями
5.2.1. Оператор возврата
Связь между функциями осуществляется через аргументы, возвра-
щаемые значения и глобальные (или внешние) переменные. Передача од-
ного-единственного значения из вызванной функции в вызвавшую про-
исходит с помощью оператора возврата, который записывается в сле-
дующем виде:
return(<выражение>);
Вызвавшая функция может при необходимости игнорировать воз-
вращаемое значение. Ключевое слово return указывает на то, что значе-
ние выражения, заключенного в скобки, будет присвоено функции, со-
держащей это ключевое слово. При этом тип возвращаемого значения
преобразуется в тип функции.
Оператор return оказывает и другое действие. Он завершает выполне-
ние функции и передает управление следующему оператору в вызываю-
щей функции. Это происходит даже в том случае, если оператор return
является не последним оператором тела функции. Можно использовать
оператор, не возвращающий никакого значения:
return;
Его применение приводит к тому, что функция, в которой он содержится,
завершает свое выполнение и управление обычно возвращается в вызы-
вающую функцию. Управление обычно возвращается в вызывающую
функцию и в случае завершения тела вызванной функции.
В языке С аргументы функции передаются только по значению, т. е. вы-
званная функция получает свою собственную временную копию каждого ар-
гумента, а не его адрес. Это означает, что функция не может непосредствен-
но изменять сам оригинальный аргумент в вызвавшей ее функции.
В подразд. 5 .1 .5 рассмотрен пример передачи параметров по значе-
нию в вызываемую функцию и возврат одного значения в вызывающую
функцию.
5.2.2. Передача адреса в функцию
Чтобы вызванная функция могла изменять некоторую переменную
в вызывающей функции, а также возвращать несколько значений, необ-
ходимо передать функции не переменные, а их адреса, как это показано
на рис. 5 .3 . При этом формальные параметры, соответствующие переда-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
132
ваемым адресам, должны быть описаны как указатели на переменные
данного типа.
Память
Значение b
...
Изменение значений
по адресам &a и &b
Значение a
Копия
Копия
&a
&b
Уничтож ение
&a, &b
Функция
&a
&b
Рис. 5.3. Схема передачи адреса в функцию
Ниже приведена программа, в которой вводятся значения целых пе-
ременных a и b и с помощью функции izm( ) происходит обмен их значе-
ниями между собой, а на рис. 5 .4 дана схема алгоритма.
Начало
Ввод
a,b
Обмен
a,b
Вывод
a,b
Конец
Функция izm
Вход(x,y)
c=x
x=y
y=c
Выход(x,y)
Рис. 5 .4. Алгоритм обмена значений переменных
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
133
#include <stdio.h>
main( )
{
int a, b;
рuts("Введите значения a, b");
scanf("%d %d",&a, &b);
рrintf("Исходные значения: a = %d; b = %d", a, b);
izm(&a, &b); /* функция izm( ) меняет местами два элемента a
и b; ей передаются адреса этих двух элементов */
рrintf("Измененные значения: a = %d; b = %d", a, b);
}
izm(int *x, int *y)
/* x и y – указатели, *x и *y – значения, на которые они указывают */
{
int c;
c= *x;
*x=*y;
*y=c;
}
Следует отметить, что если в main( ) a и b – это переменные, то в izm
x и y – это уже адреса, а значения переменных по этим адресам будут со-
ответственно *x и *y.
Пример 5.4 . В качестве примера рассмотрим функцию сортировки
одномерного массива действительных чисел x1, x2, ..., xn в порядке убы-
вания значений его элементов. Формальный параметр функции x, опре-
деляющий имя массива, должен передаваться по адресу, так как он игра-
ет роль одновременно входного и выходного параметра: первоначально x
представляет собой исходный массив чисел, после выполнения функ-
ции – это массив элементов, расположенных в порядке убывания их зна-
чений. Формальный параметр n – размер массива является входным и оп-
ределен как параметр-значение.
/* Функция сортировки одномерного массива
*/
/* Параметры: n – размер массива; x – имя массива
*/
Sort(int n, float x[ ])
{
inti,j,k;
float y;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
if(x[k] < x[j])
k
=
j
;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
134
y = x[k];
x[k] = x[i];
x[i] = y;
}
}
5.2.3. Библиотечные функции
Язык С имеет богатую поддержку в виде более 300 библиотечных
подпрограмм-функций и макросов, которые могут быть вызваны из С-
программ для решения широкого круга задач, включая ввод-вывод низ-
кого и высокого уровня, работу со строками и файлами, распределение
памяти, управление процессами, преобразования данных, математиче-
ские вычисления и многое другое.
Все библиотечные подпрограммы Turbo С описаны вместе со своими
прототипами в одном или нескольких заголовочных файлах, включаемых
в программу директивой include.
Часть математических функций приведена в гл. 1 . Другая часть наи-
более употребительных функций включена в прил. 1 . Они объединены по
категориям решаемых задач в таблицы, в которых дается прототип функ-
ций, заголовочный файл, содержащий ее определение и описание, и крат-
кое описание ее назначения. Полный перечень функций можно найти в
справочном руководстве по языку С.
5.2.4. Примеры программ с функциями
Пример 5.5 . Пусть необходимо составить программу, которая поме-
чает до пяти произвольных файлов меткой, присваивая им атрибут «толь-
ко для чтения».
Для решения этой задачи процедуру обозначения некоторого файла
меткой оформим в виде функции ReadOnly_5( ), которая помечает файл и
возвращает значение 1, если этот файл не последний, и 0 в противном
случае. При реализации этой функции использована библиотечная функ-
ция bdosрtr( ), осуществляющая системный вызов VS DOS и реализую-
щая функцию, заданную кодом первого параметра.
Текст программы приведен далее.
#include <stdio.h>
#include <dos.h>
#include <string.h>
int ReadOnly_5(char *Str)
{
static int n = 0;
if(++n>5)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
135
{
printf("Мое терпение лопнуло: больше помечать не буду.\n");
return 0;
}
else
{
printf("%s", (n==5)?"Помечаю уже пятый файл. Я устала!\n":
"
файл помечен\n");
bdosрtr(0x43, Str, 0);
_CX=1;
bdosрtr(0x43, Str, 1);
return 1;
}
}
main( )
{
char STR[78];
printf("\n Я помечаю файлы меткой\'Только для чтения\'\n"
"
Вводите имена файлов:\n");
do
{
printf("–>");
gets(STR);
}
while (strlen(STR) && ReadOnly_5(STR));
printf(" Конец работы.\n");
}
Результат работы программы:
Я помечаю файлы меткой 'Только для чтения'
Вводите имена файлов:
– >lab01.c
файл помечен
– >lab02_1.c
файл помечен
– >lab02_2.c
файл помечен
– >lab03.c
файл помечен
– >lab04.c
Помечаю уже пятый файл. Я устала!
– >lab05.c
Мое терпение лопнуло: больше помечать не буду.
Конец работы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
136
Пример 5.6. Пусть необходимо, используя функцию FnMax опреде-
ления максимального элемента в массиве z = (z1, z2, ..., zn ), найти точки,
в которых функции
y
1=e
-x
⋅ co sx-1; y2 = x⋅ln⎜x⎪
имеют максимальные значения на заданном отрезке [a, b].
Схема нисходящего проектирования алгоритма решения задачи в ви-
де структурограммы представлена на рис. 5 .5 .
Программа имеет следующий вид:
/*Программа M
a
x
F
u
n
*
/
/*Цель: определение максимального значения функции
*/
/*
на заданном отрезке.
*
/
/*Входные данные: a, b – границы отрезка, h – шаг изменения
*/
/*
аргумента
*
/
/*Выходные данные: max1, max2 – максимальные значения
*/
/
*
функций;
*
/
/*
x1, x2 – точки, в которых функции
*/
/
*
имеют максимум.
*
/
/*Программист: Иванов А.Н.
*
/
/*Дата: апрель 2004 г.
*
/
#include <stdio.h>
#include <math.h>
#define Nmax 100
main()
{
int i, k1, k2;
float a, b, h, max1, max2, x, x1, x2, y1[Nmax], y2[Nmax];
рrintf("Введите a, b, h\n");
scanf("%f %f %f", &a, &b, &h);
рrintf("' Исходные данные :\na = %f, b= %f, h = %f\n", a, b, h);
/* Формирование массивов значений функций */
for(x=a,i =0;x<=b;x+=h,i++)
{
y1[ i ] = exр(-x)*cos(x) – 1;
y2[ i ]:= x*log(fabs(x));
}
FnMax(n, y1, &max1, &k1); /* Вызов функции */
РrMax(n, y2, &max2, &k2); /* Вызов функции */
x1=a+k1*h;
x2=a+k2*h;
printf("максимум функции y1: %f в точке %f\n", max1, x1);
printf("максимум функции y2: %f в точке %f\n", max2, x2);
}
/* Функция определения максимального элемента
*/
/* в одномерном массиве
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
137
В
в
о
д
и
с
х
о
д
н
ы
х
д
а
н
н
ы
х
Ф
о
р
м
и
р
о
в
а
н
и
е
м
а
с
с
и
в
о
в
з
н
а
ч
е
н
и
й
з
а
д
а
н
н
ы
х
ф
у
н
к
ц
и
й
y
1
,
y
2
О
п
р
е
д
е
л
е
н
и
е
м
а
к
с
и
м
а
л
ь
н
о
г
о
э
л
е
м
е
н
т
а
в
м
а
с
с
и
в
е
(
м
а
к
с
и
м
а
л
ь
н
о
г
о
з
н
а
ч
е
н
и
я
ф
у
н
к
ц
и
и
y
1
)
О
п
р
е
д
е
л
е
н
и
е
м
а
к
с
и
м
а
л
ь
н
о
г
о
э
л
е
м
е
н
т
а
в
м
а
с
с
и
в
е
(
м
а
к
с
и
м
а
л
ь
н
о
г
о
з
н
а
ч
е
н
и
я
ф
у
н
к
ц
и
и
y
2
)
О
п
р
е
д
е
л
е
н
и
е
т
о
ч
е
к
x
1
,
x
2
,
в
к
о
т
о
р
ы
х
ф
у
н
к
ц
и
и
y
1
,
y
2
д
о
с
т
и
г
а
ю
т
м
а
к
с
и
м
у
м
а
В
ы
в
о
д
р
е
з
у
л
ь
т
а
т
о
в
В
в
о
д
(
a
,
b
,
h
)
x
=
a
y
1
i
=
e
-
x
c
o
s
(
x
)
+
1
y
2
i
=
x
l
n
|
x
|
x
=
x
+
h
i
=
1
(
1
)
n
F
n
M
a
x
(
n
,
y
1
,
m
a
x
1
,
k
1
)
F
n
M
a
x
(
n
,
y
2
,
m
a
x
2
,
k
2
)
x
1
=
a
+
(
k
1
-
1
)
h
x
2
=
a
+
(
k
2
-
1
)
h
В
ы
в
о
д
(
m
a
x
1
,
x
1
,
m
a
x
2
,
x
2
)
Ф
у
н
к
ц
и
я
F
n
M
a
x
В
х
о
д
н
ы
е
д
а
н
н
ы
е
(
n
,
z
1
,
z
2
,
.
.
.
,
z
n
)
m
a
x
=
z
1
k
=
1
i
=
2
(
1
)
n
m
a
x
<
x
i
Д
а
m
a
x
=
x
i
k
=
i
В
ы
х
о
д
н
ы
е
д
а
н
н
ы
е
(
m
a
x
,
k
)
Р
и
с
.
5
.
5
.
С
х
е
м
а
н
и
с
х
о
д
я
щ
е
г
о
п
р
о
е
к
т
и
р
о
в
а
н
и
я
а
л
г
о
р
и
т
м
а
о
п
р
е
д
е
л
е
н
и
я
м
а
к
с
и
м
а
л
ь
н
о
г
о
з
н
а
ч
е
н
и
я
ф
у
н
к
ц
и
й
н
а
з
а
д
а
н
н
о
м
и
н
т
е
р
в
а
л
е
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
138
/* Параметры: n – размер массива; z – имя массива;
*/
/*
max – максимальный элемент;
*/
/*
k – номер максимального элемента.
*/
void FnMax(int n, float z[ ], float *max, int *k)
{
int i;
for(*max=z[0],*k =0,i =1;i<n;i++)
if(*max < z[i])
{
*max = z[i];
*
k
=
i
;
}
} /* FnMax */
В функции FnMax для одномерного массива z определяются макси-
мальный элемент Max и его индекс k. В программе сначала формируются
массивы значений заданных функций y1, y2, а затем с помощью функции
FnMax определяются их максимумы и индексы k1, k2 соответствующих
максимальных элементов в этих массивах. Индексы используются для
нахождения на заданном отрезке точек x1, x2, в которых функции дости-
гают максимума (при заданном разбиении).
Упражнения
Для каждого из вариантов составить программу с использованием
функций. Данные для контрольного просчета выбрать самостоятельно.
1. Составить функцию для определения расстояния между точками А
и B в n-мерном пространстве по формуле
n
2
ii
i1
б
(a b)
=
=−
∑
,
где ai, bi – координаты точек А и В. Используя ее, найти минимальное из
расстояний между точками X, Y, Z.
2. Составить функцию вычисления среднего арифметического эле-
ментов вектора. Используя ее, преобразовать квадратную матрицу сле-
дующим образом: диагональные элементы матрицы заменить средними
арифметическими значениями элементов соответствующих строк.
3. Составить функцию умножения двух матриц произвольной раз-
мерности. Используя ее, вычислить k-ю степень квадратной матрицы.
4. Составить функции определения максимального и минимального
элементов в одномерном массиве. Используя их, найти минимум среди
максимальных элементов строк матрицы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
139
5. Составить функцию определения баланса открывающих и закры-
вающих скобок в выражении. Используя ее, составить программу кон-
троля правильности записи вводимых выражений.
6. Составить функцию вычисления нормы матрицы А=[aij], i,j= 1, n ,
по формуле
n
P=max
i,j
j=1
1in
a
≤≤
⎧⎫
∑
⎨⎬
⎩⎭
.
Используя ее, определить матрицу из X, Y, Z с минимальной нормой.
7. Составить требуемые функции работы с комплексными числами
(сложение, вычитание, умножение и деление). Используя эти функции,
определить действительную и мнимую части числа
3
1
2
12
1
1
z
zz
+
ω=
−
.
8. Составить функции определения максимального и минимального
элемента в одномерном массиве. Используя их, найти седловую точку
матрицы A=[aij], i, j= 1, n . Матрица имеет седловую точку akl, если эле-
мент akl является минимальным в k-й строке и максимальным в l-м
столбце или наоборот.
9. Составить функцию определения суммы элементов одномерного
массива. Используя ее, вычислить сумму элементов матрицы.
10. Составить функцию, определяющую число и номера позиций,
в которых встречается в тексте заданный символ. Используя ее, опреде-
лить эти характеристики для символов «Т», «О», «У» текста: «по ту сто-
рону добра и зла».
11. Составить функцию вычисления значения полинома n-го порядка
по схеме Горнера. Используя ее, вычислить значение функции
532
64
12321
658
xxx
z
xxx
−
+−
=
−
+−
для различных значений x, вводимых с терминала.
12. Составить функцию, определяющую число заданных сдвоенных
символов в тексте. Используя ее, подсчитать количество сдвоенных сим-
волов «сс», «ее», «нн», «лл» текста: «класс, рассеянность, естественность,
веер, аллегро».
13. Составить функцию сортировки по убыванию значений элемен-
тов одномерного массива. Используя ее, отсортировать элементы в каж-
дом столбце матрицы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
140
14. Найти сумму элементов вспомогательных диагоналей квадратных
матриц: A, B = λ·A, C = λ·B + μ·A, где λ, μ – скалярные переменные.
15. Найти сумму элементов, расположенных выше главной диагона-
ли, для матриц:X, Y, Z=X
2
+Y
2
.
16. Составить функцию сортировки по возрастанию значений эле-
ментов одномерного массива. Используя ее, отсортировать элементы
в каждой строке матрицы.
17. Используя функции ввода и вывода массива, вычислить и вывести
значения следующих матриц: Z = A
T
+B,Y=B
T
+A,гдеA,B –исходные
матрицы. Все матрицы квадратные и одинаковой размерности.
18. Описать функцию идентификации символа: буква или цифра. Ис-
пользуя ее, составить программу определения, является ли вводимая по-
следовательность символов идентификатором.
19. Описать функцию вычисления следа матрицы – суммы диагональ-
ных элементов. Определить матрицу X, Y, Z с максимальным следом.
20. Описать функцию определения суммы цифр заданного целого
числа. Определить указанные суммы для всех трехзначных целых чисел,
лежащих в заданном интервале.
5.3. Особенности использования массивов
и указателей в функциях
При описании указателей на функцию используются круглые скобки:
int (*funct)(void); /* Указатель на функцию без аргументов,
*/
/
*
возвращающую целое значение
*/
int far *рascal far Funct(char list, int time); /* Функция,
*/
/
*
построенная по типу языка Паскаль,
*/
/
*
возвращающая дальний указатель
*/
/
*
на целое число
*
/
char *(*( *test )( ))[25]; /*test – это указатель на функцию,
*/
/
*
возвращающую указатель на массив из
*/
/
*
2
5указателей на символы
*
/
char far *(far *GetInt)(int far *); /*GetInt – переменная-указа-
*/
/
*
тель на функцию, имеющую параметром
*/
/
*
дальний указатель на целое и возвра-
*/
/
*
щающую дальний указатель на символ
*/
Как отмечалось ранее, массивы и указатели довольно тесно связаны
между собой. Приведем тексты двух эквивалентных функций копирова-
ния символьных строк.
/* Функция, использующая массивы */
void strcрy(char s1[ ], char s2[ ])
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
141
{
int i;
for(i=0;s2[i]!= '\0';i++)
s1[i] = s2[i];
}
/* Эквивалентная ей функция, использующая указатели */
void strcрy(char *s1, char *s2)
{
while ( *s1++ = *s2++ );
}
Применение указателей и массивов незаменимо при передаче пара-
метров в функции с возвратом в них значений.
Для того чтобы функция могла вернуть какое-либо вычисленное
в ней значение в главную программу через список параметров, ей необхо-
димо передать адрес, по которому нужно записать это значение. Это
требование определяется тем фактом, что любая функция в языке С
использует лишь копию передаваемых ей параметров.
Пример 5.7
void SUM(int a, int b, int result)
{
/
*
Эта функция бессмысленна, так как result */
result = a + b; /* в ней – локальная переменная
*
/
}
void SUMR(int a, int b, int *result)
{
*result = a + b; /* Правильный возврат значений по
*
/
}
/
*
переданному адресу
*
/
Для функции SUMR из приведенного примера может быть справед-
ливо следующее использование в главной программе:
int first, second;
int result;
int *р_res;
first = second = 26;
SUMR(first, second, &result); /* Оба вызова функции SUMR */
р_res = &result;
/* приведут к правильному
*/
SUMR(first, second, р_res); /* результату
*
/
Следует заметить, что при передаче в качестве параметра функции
символьной строки, эквивалентными являются указание имени соответ-
ствующего символьного массива и указателя на первый элемент сим-
вольной строки, хранящейся в массиве. И в том и в другом случае в
функцию будет передан адрес первого элемента символьной строки.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
142
Пример 5.8
static char *sl = "Заголовок";
static char st[ ] = "Заголовок";
char sr[10];
strcрy(sr, sl);
/* Все эти вызовы
*/
strcрy(sr, st);
/* функций являются */
strcрy(&sr[0], &sl[0]);/* эквивалентными
*/
Механизм указателей является довольно мощным средством работы с
адресами, а следовательно, и с оперативной памятью вообще. Указатели
дают возможность реализовывать практически все операции с памятью,
имеющиеся в языке Ассемблера. Так, например, при помощи указателей
можно определить в качестве параметра функции какую-либо другую
функцию. Для этого необходимо в списке параметров передать адрес
функции, которая будет использоваться, например
int x4filter(int (*filter_routine)(void))
{
/* Тело функции */
}
В этом примере функция x4filter использует в качестве единственно-
го формального параметра указатель с именем filter_routine на какую-
либо функцию без параметров, возвращающую целочисленное значение.
Наряду с параметром-функцией можно использовать и другие парамет-
ры, например
int x4f(char *string, int account, int (*f)(int x, int y))
В теле функции, использующей указатель на другую функцию в ка-
честве формального параметра, этот указатель можно применять всеми
допустимыми способами работы с указателями на функцию, например
сделать его элементом массива указателей на функции или выполнить
функцию, расположенную по адресу этого указателя. Для выполнения
функции, расположенной по адресу указателя, в программе достаточно
записать правильный вызов функции через указатель, например:
*filter_routine( ); или rc = *f(x, y);
В самом модуле, который использует вызов функции с параметрами-
функциями, функции, употребляемые в качестве формальных парамет-
ров, должны быть непременно объявленными; кроме того, должна быть
объявлена и сама функция с параметрами-функциями, например:
extern int x4filter(int (*)(void)); – объявление внешней функции x4filter,
использующей указатель на функцию без параметров, возвращающую
целое значение;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
143
static int Base_In(void); – объявление функции без параметров, воз-
вращающей целое значение, используемой в качестве фактического па-
раметра;
Rc = x4filter(Base_In); – вызов функции x4filter, использующей в каче-
стве фактического параметра функцию с именем Base_In.
В том случае, если используется указатель на функцию, ее вызов мо-
жет быть записан в программе соответствующим образом:
Rc = *x4filter(Base_In);
Аналогичным образом могут быть применены функции, написанные
на других языках программирования, например на языке Паскаль, и, на-
оборот, С-программы могут использоваться в программах, написанных
на других языках. Для этого при описании функций (их объявлении)
в языке С существуют ключевые слова fortran, рascal, cdecl, например:
/* В каком-либо модуле должен присутствовать следующий
*/
/* фрагмент программы:
*
/
int рascal fр(int a, long b, double c)
{
/* тело функции */
}
/* В главном модуле можно использовать следующее описание */
main( )
{
extern int рascal *fр( );
int i;
i= *fр(1,2L,2);
}
В различных компиляторах с языка С при употреблении перечислен-
ных ключевых слов необходимо использовать специальные опции или
ключи компилятора при его вызове.
Системы указателей дают возможность использовать динамическое
распределение оперативной памяти ЭВМ, что позволяет организовывать
компактные и эффективные программы. При этом оперативная память
заказывается стандартными функциями языка С malloc и calloc, которые
возвращают указатели на начало заказанной памяти. На заказанную па-
мять может быть наложен шаблон, определяемый при описании таких
производных типов данных, как структуры, символьные массивы и т. п .
Ниже приводится пример заказа и шаблонирования оперативной па-
мяти символьным массивом Fields.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
144
Пример 5.9
#include <alloc.h>
unsigned char *(Fields[1000]);
main( )
{
if(!(Fields = malloc(1000)))
{
рrintf("\n Исчерпана оперативная память!");
exit(12); /* Аварийное завершение программы */
}
memset( Fields, '–', 1000); /* Инициализация массива */
Fields[20] = 'a';
Fields[21] = 'b';
/* Продолжение программы ... */
free ( Fields ); /* Возвращение памяти системе */
}
В приведенном примере при помощи функции malloc у операционной
системы заказывается 1 Кбайт оперативной памяти. Функция возвращает
нулевое значение, если требуемый объем памяти выделить невозможно.
Библиотечная функция memset позволяет инициализировать выделенную
память (в примере – знаками '- '). При окончании работы с выделенной
памятью ее необходимо возвратить системе. Это можно сделать, исполь-
зуя функцию освобождения оперативной памяти free. Единственным па-
раметром этой функции является указатель на начало оперативной памя-
ти, которую необходимо освободить.
При наложении на заказанную оперативную память более сложных
шаблонов, составленных из производных типов с использованием струк-
тур и объединений, поступают аналогичным образом. При этом присваи-
вание адреса начала заказанной памяти сопровождается спецификатором
типа. Например, пусть программист определил свой собственный тип
данных и назвал его QUEUE, тогда шаблонирование этим типом заказан-
ной памяти может быть произведено следующим образом:
/* Фрагмент программы */
QUEUE *рointer;
func(void)
{
if(!(рointer = (QUEUE *)malloc(sizeof(QUEUE))))
{
рrintf ( "\n Исчерпана оперативная память!");
exit(12); /* Аварийное завершение программы */
}
/* Продолжение программы ... */
free(рointer);/* Возвращение памяти системе */
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
145
5.3.1. Пример составления программы
Составить программу вычисления значения определенного интеграла
J=
b
2
a
xl
nx
dx
xs
i
n
x
+
∫
с заданной погрешностью ε по формуле трапеций
J=
b
n1
0n
i
i1
a
yy
Jf
(
x
)
d
xh
y,
2
−
=
+
⎛⎞
=≈+
⎜⎟
⎝⎠
∑
∫
,
где h=(b-a)/n; y0=f(a); yn=f(b); yi=f(a + i·h), n – число участков разбиения
интервала интегрирования.
Формула трапеций дает приближенное значение определенного инте-
грала. Точность приближения определяется значением числа n участков
разбиения. Увеличивая n, можно обеспечить требуемую точность вычис-
ления приближенного интеграла. Вычисление интеграла по формуле тра-
пеций при фиксированном значении n целесообразно оформить в виде
функции Trap с параметрами-значениями n, a, b и параметром-функцией
f, определяющим произвольное подынтегральное выражение. Для вычис-
лений значений заданного подынтегрального выражения введем функ-
цию f1, в которой опишем заданное правило вычислений. В основной
программе организуем цикл по увеличению (в 2 раза) числа n отрезков
разбиения и вычислению очередного приближения интеграла до тех пор,
пока не будет выполнено условие достижения заданной точности вычис-
лений. Схема проектирования алгоритма решения задачи в виде структу-
рограммы представлена на рис. 5 .6 . Программа имеет следующий вид:
/* ******************************************** */
/* Программа: Интеграл
*
/
/* Назначение: вычисление определенного интеграл
*
/
/*
с заданной точностью
*
/
/* Исходные данные: a, b – пределы интегрирования;
*/
/
*
e
рs – погрешность вычислений
*/
/* Результат: J1 – значение интеграла.
*
/
/* Программист: Иванов А.Г.
*
/
/* Дата разработки: 14 апреля-2004 г.
*
/
/* ******************************************** */
#include <stdio.h>
#include <math.h>
/* Прототип подынтегральной функции f1
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
146
float f1(float);
/* Прототип функции вычисления интеграла Traр
*
/
float Traр(int, float, float, float (*f)( ));
main( )
{
intnt=20;
/* Число интервалов разбиения
*
/
float a, b, eрs; /* Пределы и погрешность интегрирования */
float J1, J2;
/* значение интеграла при 2*nt и nt
*/
рrintf("Введите a и b – пределы интегрирования");
рrintf(" а также погрешность вычисления eрs \n");
scanf("%f %f %f", &a, &b, &eрs);
рrintf(" Исходные данные :\n");
рrintf(" a= %f', b= %f', ерs= %f\n", a, b, eрs);
J1=0;
do
{
J2=J1;
J1 = Traр(nt,a,b,f1);/*вызов функции вычисления интеграла */
/* по формуле трапеций при фиксированном числе разбиений */
nt*= 2;
}
while(fabs(J1-J2)>eрs);
рrintf("Значение интеграла : %g\n", J1);
}
/* Подынтегральная функция */
float f1(float x)
{
return(x*x*log(x)/(sin(x)+x));
}
/* Функция вычисления интеграла по формуле трапеций
*/
/* Параметры: n – число интервалов разбиения;
*/
/*
a, b – пределы интегрирования;
*/
/*
f – подынтегральная функция.
*/
float Traр(int n, float a, float b, float (*f)( ))
{
int i;
float h, s;
h = (b-a)/n;
s = (f(a)+f(b))*0.5;
for(i = 1; i< n; i++)
s+= f(a+i*h);
return(h*s);
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
147
В
ы
ч
и
с
л
е
н
и
е
о
п
р
е
д
е
л
е
н
н
о
г
о
и
н
т
е
г
р
а
л
а
с
з
а
д
а
н
н
о
й
т
о
ч
н
о
с
т
ь
ю
Ф
у
н
к
ц
и
я
в
ы
ч
и
с
л
е
н
и
я
о
п
р
е
д
е
л
е
н
н
о
г
о
и
н
т
е
г
р
а
л
а
п
о
ф
о
р
м
у
л
е
т
р
а
п
е
ц
и
й
п
р
и
ф
и
к
с
и
р
о
в
а
н
н
о
м
n
Ф
у
н
к
ц
и
я
в
ы
ч
и
с
л
е
н
и
я
п
о
д
ы
н
т
е
г
р
а
л
ь
н
о
г
о
в
ы
р
а
ж
е
н
и
я
J
1
=
0
J
2
=
J
1
J
1
=
T
r
a
p
(
n
,
a
,
b
,
f
1
)
n
=
2
*
n
В
ы
в
о
д
(
J
1
)
T
r
a
p
(
n
,
a
,
b
,
f
)
h
=
(
b
-
a
)
/
n
S
=
(
f
(
b
)
-
f
(
a
)
)
/
2
i
=
1
(
1
)
n
-
1
S
=
S
+
f
(
a
+
i
*
h
)
T
r
a
p
=
S
*
h
F
1
(
x
)
F
1
=
x
*
x
*
L
n
(
x
)
/
(
S
i
n
(
x
)
+
x
)
Д
о
|
J
1
-
J
2
|
<
=
ε
Р
и
с
.
5
.
6
.
С
х
е
м
а
п
р
о
е
к
т
и
р
о
в
а
н
и
я
а
л
г
о
р
и
т
м
а
в
ы
ч
и
с
л
е
н
и
я
о
п
р
е
д
е
л
е
н
н
о
г
о
и
н
т
е
г
р
а
л
а
с
з
а
д
а
н
н
о
й
с
т
е
п
е
н
ь
ю
т
о
ч
н
о
с
т
и
п
о
ф
о
р
м
у
л
е
т
р
а
п
е
ц
и
й
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
148
Упражнения
В данном задании необходимо решение задачи оформить в виде не-
скольких функций и, используя указатели на функции, организовать пе-
редачу одних функций другой функции в качестве ее параметров.
При выполнении упражнения значения исходных данных выберите
самостоятельно.
1. Составить подпрограмму вычисления длины дуги
n-1
22
i1
i
i1i
i=0
L= [f(x ) f(x)] (x x)
++
−+
−
∑
,
образованной функцией f(x) в интервале (a, b). Используя подпрограмму,
определить самую длинную из дуг, образованных функциями f1(x)=
=x
2
+2·ln(1+x
2
), f2(x)=x·ln|x
2
-2x|, f3(x)=(x
2
+2x-3)e
-x
на интервале (a, b).
2. Составить подпрограмму определения минимума функции y=f(x),
заданной в дискретных точках отрезка [a,b] с постоянным шагом h. Ис-
пользуя ее, найти минимумы функций y1=sin
2
x·cos x – ln|x|, y2=x
2
-2x+8.
3. Составить подпрограмму определения максимума функции y=f(x),
заданной в дискретных точках отрезка [a,b] с постоянным шагом h. Ис-
пользуя ее, найти максимумы функций y1=e
-x
·c os
2
x-1; y2=x·ln|x+1|.
4. Составить подпрограмму определения минимума функции z=f(x,y),
заданной в дискретных точках xi=x0+i·hx, yj=y0+j·hy (
x
i1,
n
=
,
y
j1,
n
=
).
Используя ее, найти минимумы функций z1=3x
2
-2y
2
+4xy-8x, z2=6x
2
+y
2
-
4x-6y+1.
5. Составить подпрограмму определения максимума функции
z=f(x,y), заданной в дискретных точках xi=x0+i·hx , yj=y0+j·hy (
x
i1,
n
=
,
1,
=
y
jn). Используя ее, найти максимумы функций z1=6x
2
+y
2
- 2xy+2,
z2=2x
2
+3y2
+2x+3y-3xy.
6. Составить подпрограмму вычисления определенного интеграла по
формуле прямоугольников
b
n1
i0
a
bab
a
f(x)dx
fai
nn
−
=
−−
⎛⎞
≈+
⎜⎟
⎝⎠
∑
∫
и, используя ее, вычислить
()
2
2x
00
(3x sin2x)dx e 2x 1 dx
ππ
−
−−
−
+
∫∫.
7. Составить подпрограмму вычисления площади треугольника, за-
данного координатами своих вершин, по формуле
S= p(p a)(p b)(p c),
−−−
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
149
где р=(a+b+c)/2; a, b, c – длины сторон треугольника вычислять с исполь-
зованием подпрограммы-функции как расстояние между вершинами тре-
угольника (по формуле, приведенной в упражнении 1). Найти суммарную
площадь двух заданных треугольников.
8. Составить подпрограмму вычисления коэффициента корреляции
двух случайных величин x и y на основании выборок x=(x1,x2,...,xn),
y=(y1,y2,...,yn) по формуле
nn
n
22
ii
i
i
i=1
i1
i1
R=(xx)(yy)/(xx)(yy)
==
⎛⎞
−−
−
−
⎜⎟
⎝⎠
∑∑
∑
.
Для вычисления
n
i
i1
1
xx
n=
=
∑,
n
i
i1
1
yy
n=
=
∑ использовать подпрограмму-
функцию. Найти R для произвольных выборок двух случайных величин x, y.
9. Составить подпрограмму вычисления высот треугольника со сто-
ронами a, b, c по формулам:
s
a
2
ha
=
,
b
2s
h
b
=
,
2s
hcc
=
,
где S= p(p a)(p b)(p c)
−−−
, р=(a+b+c)/2, если заданы координаты вершин
треугольника. Для определения длин сторон a, b, c использовать подпро-
грамму-функцию вычисления длины отрезка между двумя точками (по
формуле, приведенной в упражнении 1). Найти наименьшую из высот за-
данного треугольника.
10. Составить подпрограмму определения координат точки пересече-
ния двух прямых y=k1x+b1 и y=k2x+b2, проходящих через заданные точки,
по формулам:
21
0
12
bb
x
kk
−
=
−
;
12 12
0
12
kb bk
y
kk
−
=
−
.
Коэффициенты k и b прямой y=kx+b, проходящей через точки
(x1,y1), (x2,y2), вычислить, исходя из уравнения
11
21 21
yy xx
yy xx
−−
=
−
−
с использованием подпрограммы-функции. Найти точку пересечения
двух заданных прямых.
11. Составить подпрограмму вычисления компонент вектора гради-
ента функции F(x1,x2, ..., xn) n переменных в точке X=(x1,x2, ..., xn) по ко-
нечно-разностным формулам
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
150
12n
F(X)=( F (X), F (X), ..., F (X))
∇∇∇ ∇,
где
1i
1
ii
i
1n1
2n
i
i
F(x,...,x
,x
x,x
,...,x)F(x,x,...,x)
F(X)=
x
−+
+∆
−
∇
∆
.
Найти вектор градиента для функций z1=2x
2
-4y
2
+8xy-2x+1, z2=8x
2
y-
-2xy в заданной точке (x, y) при ∆x = ∆y = ε.
12. Составить подпрограмму вычисления k-й степени квадратной
матрицы из n× n элементов, используя подпрограмму умножения двух
матриц. Найти A
3
,B
2
, где A, B – произвольные квадратные матрицы.
13. Составить подпрограмму упорядочения элементов одномерного
массива по убыванию их значений методом выбора максимального эле-
мента, используя подпрограмму нахождения номера максимального эле-
мента в последовательности чисел. Упорядочить по убыванию массивы X
иY.
14. Составить функцию определения косинуса угла между двумя век-
торами по формуле
(a b)
cos
ab
⋅
φ=
⋅
, где
n
ii
i1
(ab) ab
=
=
∑;
n
2
i
i1
xx
=
=
∑.
Для вычисления скалярного произведения (ab) и модуля ( )
x ис-
пользовать функции. Найти косинусы углов между заданными векто-
рами.
15. Составить подпрограмму определения номера строки матрицы с
максимальной характеристикой. Для вычисления характеристики исполь-
зовать подпрограмму-функцию. В качестве характеристики строки рас-
смотреть следующие величины: а) сумму положительных элементов;
б) среднее арифметическое всех элементов. Найти строки с соответст-
вующими максимальными характеристиками для произвольных матриц
XиY.
16. Составить подпрограмму определения минимальной характери-
стики в прямоугольной матрице. Для вычисления характеристики ис-
пользовать подпрограмму. В качестве характеристики рассмотреть сле-
дующие величины: а) произведение элементов каждого столбца матрицы;
б) максимальный элемент столбца матрицы. Найти соответствующие ми-
нимальные характеристики для заданных матриц A и B.
17. Составить подпрограмму определения номеров строк матрицы с
минимальной и максимальной характеристиками. Для вычисления харак-
теристики использовать подпрограмму-функцию. В качестве характери-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
151
стики рассмотреть следующие величины: а) среднее арифметическое
элементов каждой строки матрицы; б) число положительных элементов в
строке. Поменять местами строки с максимальной и минимальной харак-
теристиками типа «а» в заданной матрице A и типа «б» в заданной мат-
рице B.
18. Составить подпрограмму табулирования произвольной функции:
вычисления множества значений z={zi}, i1,N
=
, функции z=f(x) для за-
данного множества значений аргумента X= {xi}, i1,N
=
.
Используя ее,
найти множества z1, z2 значений двух функций –
f1(x)=e
-x⋅
cos x, f2(x)=3sin
2
x
для двух заданных множеств значений аргумента x: X1={xi},
1
i1,N
=
;
X2={xi},
2
i1,N
=
.
19. Назовем характеристикой строки двумерного символьного масси-
ва число элементов, относящихся к гласным русским буквам. Составить
подпрограмму замены символов в строке с максимальной характеристи-
кой на символ *. Произвести указанную замену символов в произвольном
исходном массиве. Характеристику определить с помощью функции.
20. Составить подпрограмму, которая присваивает элементам одно-
мерного массива Z значения функции f(x) в точках (x1, x2, ..., xn). Сфор-
мировать одномерные массивы из значений функций f 1= e
-x
⋅cosx, f2 =
= 3sin
2
x в заданных точках.
5.4. Функции работы с текстовыми строками
и фрагментами оперативной памяти
При работе с текстовыми строками в языке С допускается пользо-
ваться массивами символов и указателями на строки (массивы символов).
Для удобства работы со строками существуют специальные функции,
входящие в стандартную библиотеку функций языка С. Названия этих
функций не изменяются от версии к версии языка. В качестве параметров
функций, работающих с текстовыми строками и фрагментами оператив-
ной памяти, используются:
указатели на строки,
постоянные указатели на строки,
постоянные указатели на неопределенный тип (void),
указатели на неопределенный тип (void).
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
152
Применение неопределенных указателей говорит о том, что функция
может работать с областями оперативной памяти любого размера. Ис-
пользование в параметрах функции постоянных указателей гарантирует
неизменность содержимого, определяемого этим параметром.
Общий список функций, работающих со строками и фрагментами
оперативной памяти, образует функционально полную алгебраическую
систему, дающую возможность анализа и синтеза любых текстов. Базо-
вое множество этих функций включает выполнение таких действий, как:
быстрая пересылка (копирование) фрагментов памяти;
очистка (обнуление) памяти;
объединение двух фрагментов памяти в один;
сравнение двух фрагментов памяти;
определение длины фрагмента памяти.
Основные функции, предназначенные для работы с текстовыми стро-
ками и фрагментами оперативной памяти, сведены в табл. 5 .1 .
Таблица 5.1
Наименование функции
и тип результата
Параметры
Выполняемое действие
void *memmove
(void *dest, const void
*src, int n)
Пересылка фрагмента из
*src в *dest длиной n
void *memcрy
(void *dest, const void
*src, int n)
Копирование фрагмента из
*src в *dest длиной n
void far *far
_fmemcрy
(void far *dest, const void
far *src, size_t n)
Копирование дальних фраг-
ментов
void *memset
(void *s, int c,
size_t n)
Обну ление памяти по адре-
су *s количеством n элемен-
тами с
void far * far
_fmemset
(void far *s, int c,
size_t n)
Очистка памяти по дальне-
му указателю
char *strcat
(char *dest, const char
*src)
Объединение (конкатенация
строк)
char far * far _fstrcat (char far *dest, const char
far *src)
Конкатенация строк по
дальним указателям
int strcmр
(const char *s1, const char
*s2)
Сравнение строк; если рав-
ны – результат 0
int far _fstrcmр
(const char far *s1, const
char far *s2)
Сравнение строк по даль-
ним у казателям
char *strcрy
(char *dest, const char
*src)
Копирование строки из *src
в *dest
size_t strlen
(const char *s)
Вычисление длины строки
В этой таблице тип size_t эквивалентен типу unsigned int.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
153
5.4.1. Пример программы с массивами и указателями
в функциях
Пусть необходимо составить программу, реализующую вывод на
экран компьютера прямоугольников различных цветов и размеров,
имеющих «прозрачную тень». Ниже приведен текст программы с по-
яснениями.
/* ******************************************** */
/* Цель: вывод прямоугольников.
*
/
/* Переменные: vрg – структура экрана компьютера.
*/
/* Функции: box_t – прямоугольник с тенью;
*/
/*
box_s – прямоугольник без тени;
*/
/*
show_line – вывод строки
*
/
/* Программисты: Иванов И.Н., Сидоров А.В.
*/
/* Дата разработки: 14 февраля 1997 г.
*
/
#include <stdio.h>
/* Структура экрана компьютера
*
/
static struct vрg { struct {char s,m;} c[25][80]; };
/* Адрес страницы экранной памяти или
*
/
/* видеокарты
*
/
static struct vрg *рg0=( struct vрg *)0xb8000000;
/* Функция рисования прямоугольника с тенью
*
/
/* рg – адрес страницы, x,y – координаты
*
/
/* углов прямоугольника, col – цвет
*
/
/* Функция рисования прямоугольника без тени
*/
/* Функция быстрого вывода строки на экран
*
/
/* column, line – координаты строки на экране,
*/
/* string[ ] – текст выводимой строки, c_char -
*/
/* цвет строки, рg – адрес страницы экрана.
*/
void show_line(int column, int line, char string[ ],
int c_char,struct vрg *рg)
{
union
{
/* В функции show_line используются
*/
struct
/* производные типы данных – объеди-
*/
{
/
*
нение и структура, подробнее опи-
*/
char at;/* санные в гл.
6
*
/
char s;
}i
c
;
int cols;
} color_char;
int рos;
color_char.cols = c_char;
рos=0;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
154
while(string[рos] != '\0' )
{
/* Вывод атрибута цвета */
рg–>c[line][column + рos].m = color_char.ic.at;
/* Вывод самого символа */
рg–>c[line][column + рos].s = string[рos];
pos++;
}
}
void box_s(рg,x0,y0,x1,y1,col)
struct vрg *рg;
int x0,y0,x1,y1,col;
{
char mem[80];
int x,y,n;
for(x=0;x<x1–x0;x++)
mem[x] = ' ';
mem[++x] = '\0';
for(y=y0;y<y1;y++)
show_line(x0,y,mem,col,рg);
for(x=x0+1;x<x1;x++)
{
show_line(x,y0,"-",col,рg);
show_line(x,y1,"-",col,рg);
}
for(y=y0+1;y<y1;y++)
show_line(x1,y,"¦",col,рg);
for(y=y0+1;y<y1;y++)
show_line(x0,y,"¦",col,рg);
show_line(x0,y0,"+",col,рg); /* Вывод рамки символами */
show_line(x1,y0,"+",col,рg); /* псевдографики
*/
show_line(x0,y1,"+",col,рg);
show_line(x1,y1,"+",col,рg);
}
void box_t(рg,x0,y0,x1,y1,col)
struct vрg *рg;
int x0,y0,x1,y1,col;
{
int x,y,n;
box_s(рg,x0,y0,x1,y1,col);
if((x1 < 79)&&(y1 < 24))
{/* Тест границ */
for (x = x0+2; x <= x1+2; x++)
рg–>c[y1+1][x].m = 0x07;
for (y = y0+1; y <= y1+1; y++)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
155
{
рg–>c[y][x1+1].m = 0x07; /* Вывод "тени" */
рg–>c[y][x1+2].m = 0x07; /*
...
*/
}
}
}
/* Тестирование функции (главная функция) */
main( )
{
box_t(рg0,5,5,40,20,127);
show_line(6, 6,
"
Текстовая строка внутри прямоугольника!", 127, рg0);
}
Вопросы для самоконтроля
1. Из каких частей состоит С-программа?
2. Какова структура определения функции?
3. В чем особенность современного стиля определения функции?
4. Что такое формальный параметр?
5. В чем сущность описания функции?
6. Каким образом описывается прототип функции?
7. Как осуществляется вызов функции?
8. Что такое фактический параметр и как он передается функции?
9. Как происходит передача значений в вызывающую функцию?
10. Каков механизм передачи параметров по адресу?
11. Какие группы библиотечных функций вам известны?
12. Какие из стандартных функций работы с символьными строками
необходимо использовать при составлении программы, подсчитывающей
число одинаковых слов в произвольном тексте?
Упражнения
В каждом задании реализовать предложенный алгоритм в виде функ-
ции, используя в случае необходимости библиотечные функции, приве-
денные в приложении. В главной функции организовать ввод и вывод
информации и обращение к спроектированной функции. По желанию
ввод и вывод также могут быть представлены в виде функций.
1. Определить, является ли вводимая последовательность символов
идентификатором.
2. Подсчитать количество сдвоенных символов «сс», «нн», «лл» во
введенном тексте.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
156
3. Разбить произвольный текст на строки определенной длины. При
переносе слова предусмотреть вывод дефиса.
4. Подсчитать число слов в предложении.
5. Найти во введенном тексте самое длинное и самое короткое слово.
6. Из заданной строки исключить все символы, входящие в нее более
одного раза.
7. Проверить, правильно ли в заданном тексте расставлены круглые
скобки.
8. В заданной последовательности символов подсчитать общее коли-
чество символов «+», «–», «*» и исключить их из текста.
9. Вводится последовательность ключевых слов. Отсортировать их
по алфавиту.
10. В предложении, содержащем не менее двух слов, поменять мес-
тами первое и последнее слова.
11. Сформировать строку, состоящую из символов, входящих одно-
временно в обе заданные строки.
12. Откорректировать заданный текст, заменив в нем все вхождения
одной буквы на другую.
13. В заданном тексте перевернуть каждое слово.
14. Если в первой половине строки s менее восьми цифр, а в послед-
ней четверти второй строки t нет литер от «a» до «z», то определить ко-
личество литер «*», входящих в среднюю треть строки s.
15. В упражнении 14 предусмотреть возможность произвольного за-
дания контрольных литер.
16. В заданной строке x заменить все вхождения подстроки y на под-
строку z.
17. Для заданного символа определить, сколько раз он встречается во
введенном тексте.
18. Из произвольной последовательности символов исключить груп-
пы символов, расположенных между круглыми скобками.
19. Из строки символов исключить однобуквенные слова.
20. Из заданной последовательности символов удалить лишние про-
белы, разделяющие слова.
21. Выяснить, верно ли, что среди символов строки произвольной
длины имеются все символы, входящие в слово «день».
22. Для каждого из слов заданного предложения указать, сколько раз
оно встречается в предложении.
23. В заданной строке символов исключить все группы символов ви-
да ABC.
24. Определить, можно ли из символов заданной строки составить
вашу фамилию.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
157
25. В заданной строке символов исключить нелитерные символы.
При решении упражнений рекомендуется в случае необходимости
использовать операции адресной арифметики.
Дополнительные упражнения
1. При помощи команды DOS type вывести на экран содержимое ка-
кого-либо текстового файла. Составить программу, читающую остав-
шийся на нулевой странице видеопамяти текст.
2. Используя динамический заказ оперативной памяти, составить
программу ввода и отображения текста произвольной длины. При выводе
текста на экран ЭВМ выделить цветом слова из некоторого списка клю-
чевых слов, заданного в программе массивом строк.
3. Используя динамический заказ оперативной памяти, составить
программу, организующую двусвязный список, элементами которого яв-
ляются вводимые с клавиатуры текстовые строки.
4. Используя динамический заказ оперативной памяти, составить
программу, организующую односвязный список, элементами которого
являются вводимые с клавиатуры числовые данные. Организовать про-
смотр полученного списка с накоплением общей суммы введенных чи-
сел.
5. Пользуясь массивом указателей на текстовые строки, составить
программу, организующую очередь текстовых строк фиксированной
длины. При помещении в очередь нового элемента (текстовой строки)
очередь сдвигается.
6. Пользуясь массивом указателей на числа, составить программу,
организующую помещение чисел в стек фиксированной длины с выдачей
соответствующего сообщения при исчерпании объема стека.
7. Реализовать программу распечатки введенного с клавиатуры тек-
ста, предварительно обработав его такими процедурами, вызванными из
программы печати текста с использованием параметра-указателя на
функцию, как:
удаление всех гласных букв,
удаление всех согласных букв,
замена всех букв на прописные.
Представить все 3 варианта распечатки текста.
8. Реализовать программу, считывающую фрагмент нулевой страни-
цы видеопамяти (см. упражнение 1) в буфер динамически полученной
оперативной памяти и выдающей содержимое этого буфера на 3 строки
экрана ниже считанного фрагмента.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
158
9. Составить программу, реализующую «мигание» экрана ЭВМ:
последовательное включение и выключение изображения.
10. Составить программу, последовательно сдвигающую текст на эк-
ране ЭВМ вверх на одну строку при нажатии клавиши Escaрe.
11. Составить программу, последовательно сдвигающую текст на эк-
ране ЭВМ вниз на одну строку при нажатии клавиши Enter.
12. Составить программу, последовательно сдвигающую текст на
экране ЭВМ вправо на одну колонку при нажатии клавиши «стрелка
влево».
13. Составить программу, последовательно сдвигающую текст на
экране ЭВМ влево на одну колонку при нажатии клавиши «стрелка
вправо».
14. Пользуясь указателями, реализовать программу, случайным обра-
зом перемещающую по экрану символ «+», не изменяя при этом сущест-
вующий на экране текст.
15. Реализовать программу, подсчитывающую объем свободной опе-
ративной памяти при помощи функций заказа памяти. Оценить время по-
байтового заполнения всей свободной памяти каким-либо символом,
пользуясь функциями работы с таймером.
16. Составить программу, читающую с клавиатуры два символьных
слова. Используя в качестве параметров функции указатель на функцию,
реализовать следующие действия:
конкатенацию введенных строк,
проверку на эквивалентность строк,
проверку на вхождение одной строки в другую,
последовательную замену гласных букв второй строки гласными
буквами первой строки.
17. Реализовать программу, считывающую текст, оставшийся на эк-
ране ЭВМ после просмотра какого-либо текстового файла, и выводящую
этот текст снова на экран после форматирования по следующим парамет-
рам: x0 – первая позиция абзаца, x1 – последняя позиция абзаца, n – чис-
ло строк в абзаце, delta – число позиций в красной строке абзаца.
18. Составить программу проверки словарной эквивалентности двух
введенных с клавиатуры текстов. Программа должна отметить каким-
либо образом слова, не совпадающие в этих текстах по написанию (число
пробелов между словами допускать произвольным).
19. Используя массив указателей на числа и функцию датчика слу-
чайных чисел, реализовать программу, моделирующую игру в лото трех
игроков, представленных массивами чисел из ограниченного множества
одинаковой размерности.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
159
20. Используя указатели и стандартные функции работы с таймером,
реализовать программу, выводящую в левом верхнем углу экрана ме-
няющееся значение текущего времени в формате часов, минут, секунд.
21. Реализовать программу-микроредактор строки при вводе ее с эк-
рана ЭВМ (задействовать управление клавишами Backsрace, Del, Enter).
22. Реализовать программу-микроредактор строки при вводе ее с эк-
рана ЭВМ(задействовать управление клавишами стрелок, Enter).
23. Составить программу, позволяющую выбрать цвет текста и фона
текста, отображая это на экране. При вводе текста с клавиатуры, одно-
временно отображать его на экране ЭВМ выбранным цветом.
24. Реализовать программу горизонтально бегущей строки, используя
указатели.
25. Реализовать программу вертикально бегущей строки, используя
указатели.
26. Составить программу, реализующую волновое изменение цвета
текста экрана слева направо, не меняя самого текста.
27. Составить программу, реализующую волновое изменение цвета
текста экрана сверху вниз, не меняя самого текста.
5.5. Рекурсии
5.5.1. Понятие рекурсии
Рекурсия – это способ определения процесса/объекта «в терминах
самого себя», в терминах некоторого более простого случая этого же
процесса/объекта. Рекурсивные определения используются во многих об-
ластях науки, особенно в математике. В математике рекурсией называет-
ся способ описания функций или процессов через самих себя. Примером
рекурсивно описываемой функции является факториальная функция:
0!=1;
для всех n > 0 n! = n*(n-1)!, которая для n>0 определяется рекуррентным
соотношением через значение факториала от (n-1); в свою очередь, (n-1)!
определяется через (n-2)! и т. д. до сведения к значению 0!, которое опре-
делено явно и равно единице. Любое рекурсивное описание должно со-
держать явное определение функции для некоторых начальных значений
аргумента/аргументов, к которым сводится процесс вычисления значения
функции в общем случае. Число промежуточных вычислений этой же
функции в процессе вычисления ее значения для заданных аргумен-
та/аргументов – это глубина рекурсии. Для факториальной функции глу-
бина рекурсии при любом значении аргумента очевидна, например при
вычислении 3! рекурсия имеет глубину в 3 уровня. Однако обычно глу-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
160
бина рекурсии не является столь очевидной даже при простейших описа-
ниях. Примером может служить рекурсивное определение биномиальных
коэффициентов или числа сочетаний m
n
C:
0
n
C =1дляn≥0;
m
n
C =0дляm>n≥0;
m
n
C=
1-
m
1-
n
C+
m
1-
n
C для n≥m>0.
Здесь уже не очевидно, какая глубина рекурсии будет достигнута при
конкретных вычислениях. Однако в общем случае можно утверждать, что
указанные рекурсивные вычисления требуют конечной глубины рекур-
сии. Если описание предназначено для практических вычислений, то глу-
бина рекурсии должна быть конечной.
Рекурсивные определения часто используются и в информатике. На-
пример, описание синтаксиса формальных языков с помощью БНФ-
нотаций (форм Бэкуса–Наура). В языках программирования рекурсия ис-
пользуется как способ описания подпрограмм (прежде всего функций),
содержащих прямо или косвенно обращение к самим себе. Для исполне-
ния таких подпрограмм требуется особая организация вычислительного
процесса, так как при рекурсивных вычислениях необходимо сохранение
информации об иерархии связей и локальных переменных всех рекур-
сивных вызовов, чтобы по окончании цепочки рекурсивных вызовов
можно было восстановить каждое предшествующее прерванное состоя-
ние подпрограммы. Почти все системы рекурсивного программирования
основываются на идее стека. Стеком является структура памяти мага-
зинного типа LIFO (Last In First Out – «последним пришел – первым
ушел»).
Рекурсия вошла в программирование в значительной степени благо-
даря системам обработки списков и языкам функционального програм-
мирования, где использование рекурсии естественно в силу рекурсивной
природы реализуемого вычислительного процесса и рекурсивной струк-
туры обрабатываемых данных. Проникновение рекурсивных методов в
практику традиционного (императивного) программирования началось с
языка Алгол, допускающего рекурсивные обращения к процедурам. Даль-
нейшая практика рекурсивных вычислений показала, что разумное при-
менение рекурсии является эффективным методом программирования,
существенно упрощает запись многих сложных алгоритмов, а в ряде слу-
чаев оказывается незаменимым средством. Область практического при-
менения рекурсии – это сложные задачи численного анализа, алгоритмы
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
161
трансляции, операции над списками, алгоритмы последовательных испы-
таний и многое другое.
5.5.2. Техника построения рекурсивных алгоритмов
В общем случае для правильной организации рекурсивных алгорит-
мов необходимо выполнение двух условий:
1) должно быть найдено представление общей задачи в терминах
«более простой» задачи того же класса, которое определит последова-
тельность рекурсивных вызовов;
2) рекурсивные вычисления не должны создавать бесконечную це-
почку вызовов, для этого, во-первых, алгоритм должен включать хотя бы
одно предписание, в котором при определенных условиях вычисление
производится непосредственно, без рекурсивного вызова (терминальную
ситуацию), а, во-вторых, рекурсивные построения в конце концов долж-
ны сводиться к этим простым терминальным случаям.
В общем виде рекурсивное описание подпрограммы должно иметь од-
ну из следующих структур (или некоторую эквивалентную форму):
if(<условие>)
<
терминальная ситуация>
else
<
рекурсивные вызовы>
или
while(<условие>)
{
< рекурсивные вызовы>
}
<терминальная ситуация>
Существует два разных стиля построения рекурсивных алгоритмов,
называемых восходящей и нисходящей рекурсиями. Нисходящая рекур-
сия последовательно разбивает данную задачу на более простые, пока не
доходит до терминальной ситуации. Только после этого она начинает
строить ответ, а промежуточные результаты передаются обратно вызы-
вающим функциям.
Пример 5.10. Вычисление факториала.
/* Нисходящая рекурсия */
# include <stdio.h>
fact(int n)
{
if(!n)
return(1);
else
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
162
return(n*fact(n-1));
}
main( )
{
int n;
рrintf("Введите n \n");
scanf("%d", &n);
рrintf("Факториал %d = %d \n", n, fact(n));
}
Вызов, например, fact(5) означает, что функция fact вызывает себя раз
за разом: fact(4), fact(3), ... до тех пор, пока не будет достигнута терми-
нальная ситуация. При каждом вызове текущие вычисления «откладыва-
ются», локальные переменные и адрес возврата сохраняются в стеке.
Терминальная ситуация return(1) достигается при n=0. По достижении
терминальной ситуации рекурсивный спуск заканчивается, начинается
рекурсивный возврат изо всех вызванных на данный момент копий функ-
ции: начинает строиться ответ: n*fact(n-1), сохраненные локальные пара-
метры выбираются из стека в обратной последовательности, а получае-
мые промежуточные результаты: 1*1, 2*1, 3*2*1, 4*3*2*1,
5*4*3*2*1 – передаются вызывающим функциям. Латинское recurrere
означает «возвращение назад».
В восходящей рекурсии ответ строится на каждой стадии рекурсив-
ного вызова, получаемые промежуточные результаты вычисляются перед
рекурсивным вызовом и передаются в виде дополнительного рабочего
параметра подпрограммы до тех пор, пока не будет достигнута терми-
нальная ситуация. К этому моменту ответ уже готов и нужно только пе-
редать его вызывающей функции верхнего уровня.
Пример 5.11 . Вычисление факториала.
/* Восходящая рекурсия */
# include <stdio.h>
fact(int n, int w)
{
if(!n)
return(w);
/* Терминальная ветвь */
else
return(fact(n-1, n*w));
/* Рекурсивная ветвь */
}
main( )
{
int n;
рrintf("Введите n \n");
scanf("%d", &n);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
163
рrintf("Факториал %d = %d \n", n, fact(n, 1));
}
Здесь w – рабочий параметр, применяемый для формирования ре-
зультата. При первом вызове функции этот параметр надо инициализиро-
вать (придать ему начальное значение – 1), далее при каждом рекурсив-
ном вызове, например при вычислении 5!, он принимает последовательно
значения: 5*1, 4*5*1, 3*4*5*1, 2*3*4*5*1, 1*2*3*4*5*1.
Сравнивая нисходящий и восходящий варианты рекурсивного опре-
деления факториала, видим: результат вычисляется в разном порядке.
Поскольку умножение коммутативно, это не влияет на окончательный
ответ. Однако есть классы задач, при решении которых программисту
требуется сознательно управлять ходом работы рекурсивных процедур
и функций. Такими, в частности, являются задачи, использующие списко-
вые и древовидные структуры данных. Например, при разработке транс-
ляторов применяются так называемые атрибутированные деревья разбо-
ра, работа с которыми требует от программиста умения направлять ход
рекурсии: одни действия можно выполнить только на спуске, другие –
только на возврате. Поэтому понимание рекурсивного механизма и умение
управлять им – это необходимые качества квалифицированного про-
граммиста.
В следующем примере показана рекурсивная функция с выполнением
действий как до, так и после рекурсивного вызова (с выполнением дейст-
вий как на рекурсивном спуске, так и на рекурсивном возврате).
Пример 5.12 . Счет от n до 1 на рекурсивном спуске и от 1 до n на ре-
курсивном возврате. При этом видно, как заполняется и освобождается
модель стека.
/* Выполнение рекурсивных действий */
/* до и после рекурсивного вызова
*/
# include <stdio.h>
Recursion (int i);
main( )
{
int n;
рrintf("Введите n \n");
scanf("%d", &n);
рrintf("Рекурсия: \n");
Recursion (n);
}
Recursion (int i)
{
рrintf("%30d\n", i);
/* Вывод на рекурсивном спуске
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
164
if(i > 1)
Recursion (i-1);
рrintf("%3d\n", i); /* Вывод на рекурсивном возврате */
}
В процедуре Recursion операция рrintf(«%30d\n», i); выполняется пе-
ред рекурсивным вызовом, после чего рrintf(«%3d\n», i); освобождает стек.
Поскольку рекурсия выполняется от n до 1, вывод по рrintf(«%30d\n», i); вы-
полняется в обратной последовательности: n, n-1, ..., 1, а вывод по
рrintf(«%3d\n», i); – в прямой: 1, 2, ..., n (согласно принципу LIFO – «по-
следним пришел – первым ушел»).
Возможная глубина рекурсивных вычислений определяется размером
используемого стека компьютера.
5.5.3. Формы рекурсий
5.5 .3 .1 . Простая линейная рекурсия
Если в описании подпрограммы рекурсивный вызов в каждой из
возможных ветвей различения случаев встречается не более одного раза,
то такая рекурсия называется простой или линейной. Рассмотренные ра-
нее рекурсивные функции представляли простую рекурсию и содержали
одну рекурсивную ветвь с одним рекурсивным вызовом. Рассмотрим
простую рекурсивную функцию, содержащую две рекурсивные ветви.
Пример 5.13. Нахождение наибольшего общего делителя (НОД) двух
натуральных чисел по алгоритму Евклида. Алгоритм заключается в сле-
дующем: если m является точным делителем n, то НОД = m, в противном
случае нужно брать функцию НОД от m и от остатка деления n на m.
/* Линейная рекурсия */
/* Алгоритм Евклида */
NOD(int n, int m)
{
if(m>n)
return(NOD(m, n));
/* Рекурсивная ветвь */
else
if(!m)
return(n);
/* Терминальная ветвь */
else
return(NOD(m, n%m)); /* Рекурсивная ветвь */
}
Первая рекурсивная ветвь в определении функции позволяет писать
аргументы в любом порядке. В линейной рекурсии каждый рекурсивный
вызов приводит непосредственно к одному дальнейшему рекурсивному
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
165
вызову. Возникает простая линейная последовательность рекурсивных
вызовов.
5.5 .3 .2 . Параллельная рекурсия
Если в описании подпрограммы по меньшей мере в одной рекурсив-
ной ветви встречаются два или более рекурсивных вызова, то говорят о
нелинейной, или параллельной, рекурсии. Один из наиболее ярких приме-
ров такой рекурсии дают числа Фибоначчи:
F(0)=0, F(1)=1, F(N)=F(N-1)+F(N-2).
Каждый элемент ряда Фибоначчи является суммой двух предшест-
вующихэлементов:011235813213455...
Пример 5.14 . Вычислить n-й член ряда Фибоначчи.
/* Параллельная рекурсия. Числа Фибоначчи */
fib(int n)
{
if(!n)
return(0);
else
if(n==1)
return(1);
else
return(fib(n-1)+fib(n-2));
}
Для определения текущего значения F(N) функция fib вызывает себя
дважды в одной и той же рекурсивной ветви – «параллельно». Заметим,
что параллельность является лишь текстуальной, но никак не временной:
вычисление ветвей в стеке производится последовательно. В отличие от
линейной рекурсии, при которой структура рекурсивных вызовов линей-
на, нелинейная рекурсия ведет к древовидной структуре вызовов. Вызовы
лавинообразно ведут к экспоненциальному нарастанию возникающих ре-
курсивных вызовов – «каскаду вызовов», отсюда еще одно название –
каскадная рекурсия.
5.5 .3 .3 . Взаимная рекурсия
Если процедура или функция вызывает себя сама, это называют пря-
мой рекурсией. Но может встретиться ситуация, когда подпрограмма об-
ращается к себе опосредованно, путем вызова другой подпрограммы,
в которой содержится обращение к первой. В этом случае мы имеем дело
с косвенной, или взаимной, рекурсией.
Пример 5.15. Программа выдает простые числа от 1 до n, для чего
используются функции next и рrim, которые вызываются перекрестно.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
166
/* Взаимная рекурсия */
/* Простые числа
*/
#include <stdio.h>
Рrim(int j);
Next(int j);
main( )
{
int i, n;
рrintf("Введите положительное число n\n");
scanf("%d", &n);
рrintf("Прeдшествующие ему простые числа\n");
for(i=2;i<=n;i++)
if(Рrim(i))
рrintf("%6d", i);
}
/* Функция Рrim определяет: j – простое число или нет */
Рrim(int j)
{
int k;
k=2;
while((k*k <= j) && (j%k))
k = Next(k); /* Рrim вызывает Next */
if(!(j%k))
return(0);
else
return(1);
}
/* Функция Next вычисляет, каково следующее за j простое число* /
Next(int j)
{
int k;
k=j+1;
while(!(Рrim(k))) /* Next вызывает, в свою очередь, Рrim */
k++;
return(k);
}
Функция Рrim определяет, является ли j простым числом, для этого
просматриваются все простые числа начиная с 2, вплоть до корня из j,
и проверяется, делится ли j на одно из таких простых чисел. Для поиска
следующего простого числа используется функция Next, которая, в свою
очередь, для идентификации простых чисел употребляет функцию Рrim.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
167
5.5 .3 .4 . Рекурсия более высокого порядка
Используя более мощные виды рекурсии, можно записывать относи-
тельно лаконичными средствами и более сложные вычисления. Одновре-
менно с этим, поскольку определения довольно абстрактны, растет слож-
ность отладки и понимания программ.
Если в определении функции рекурсивный вызов является аргумен-
том вызова этой же самой функции, то в такой рекурсии можно выделить
различные порядки (order) в соответствии с тем, на каком уровне рекур-
сии находится вызов. Такую форму рекурсии называют рекурсией более
высокого порядка. Функции, которые мы до сих пор определяли, были
функциями с рекурсией нулевого порядка. В качестве классического
примера рекурсии первого порядка часто приводится функция Аккер-
мана:
A(m, n)= n+1 при m=0;
A(m, n)= A(m-1, 1) при n=0;
A(m, n)= A(m-1, A(m, n-1)) в остальных случаях.
Кажущаяся простота этой функции обманчива. Вызов функции
завершается всегда, однако ее вычисление довольно сложно, число
рекурсивных вызовов растет лавинообразно уже при малых значениях
аргумента.
В практике программирования рекурсий высокого порядка ста-
раются избегать, разбивая определение на несколько частей и исполь-
зуя подходящие параметры для сохранения и передачи промежуточ-
ных результатов.
5.5.4. Рекурсия и итерация
Многие рекурсивные определения можно заменить нерекурсивными
и организовать вычисления без использования рекурсии.
В программировании есть два средства реализации повторяющихся
вычислений/процессов:
с помощью итерации в форме цикла – последовательного повторе-
ния некоторого процесса до тех пор, пока не удовлетворится неко-
торое условие;
с помощью рекурсии – вложения одной операции в другую, когда при
отрицательном результате проверки условия выполнение процесса
«приостанавливается» и происходит самовключение выполняемого
процесса сначала уже в качестве подпрограммы для еще не выпол-
ненной до конца первоначальной подпрограммы.
Программы, использующие рекурсивные функции, отличаются про-
стотой, наглядностью и компактностью текста. Такие качества рекурсив-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
168
ных алгоритмов вытекают из того, что рекурсивная функция описывает,
что нужно делать, а не рекурсивная акцентирует внимание на том, как
нужно делать. Итерация требует меньше места в памяти и машинного
времени, чем рекурсия, которой необходимы затраты на управление сте-
ком. Однако существуют некоторые функции, которые легко можно оп-
ределить рекурсивно, но которые нельзя определить в терминах обычных
алгебраических выражений, например функция Аккермана. Для многих
задач рекурсивная формулировка совершенно прозрачна, в то время как
построение итерации оказывается весьма сложным делом.
Пример 5.16. Задача о ханойских башнях. Даны 3 столбика – А, В, С.
На столбике А один на другом находятся 4 диска разного диаметра и ка-
ждый меньший диск находится на большем. Требуется переместить эти
4 диска на столбик С, сохранив их взаиморасположение. Столбик В раз-
решается использовать как вспомогательный. За один шаг допускается
перемещать только один из верхних дисков какого-либо столбика
и больший диск не разрешается класть на диск меньшего диаметра.
Для определения подхода к решению поставленной задачи, рассмот-
рим более общий случай с n дисками. Если мы сформулируем решение
для n дисков в терминах решения для n-1 дисков, то поставленная про-
блема будет решена, поскольку задачу для n-1 дисков можно будет,
в свою очередь, решить в терминах n-2 дисков и так далее до тривиально-
го случая одного диска. А для случая одного диска решение элементарно:
нужно переместить единственный диск со столбика А на столбик С. Та-
ким образом, мы получим рекурсивное решение задачи. Рассмотрим сло-
весное описание алгоритма:
1. Если n=1, переместить единственный диск со столбика А на стол-
бик С и остановиться.
2. Переместить верхние n-1 дисков со столбика А на столбик В, ис-
пользуя столбик С как вспомогательный.
3. Переместить оставшийся диск со столбика А на столбик С.
4. Переместить n-1 дисков со столбика В на столбик С, используя
столбик А как вспомогательный.
Приведем программу, которая решает поставленную задачу с помо-
щью рекурсивной функции Move_Disks.
/* Ханойские башни */
#include <stdio.h>
Move_Disks(int, char, char, Tmр);
main( )
{
int n;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
169
рrintf("Введите число дисков\n");
scanf("%d", &n);
Move_Disks (n, 'A', 'С', 'В');
}
/* Рекурсивная функция
*
/
/* n – число дисков на столбике Source
*/
/* Source – исходный столбик
*
/
/* Dest – столбик, на который нужно переставить диски
*/
/* Tmр – вспомогательный столбик
*
/
Move_Disks(int n, char Source, char Dest, char Tmр)
{
if(n==1)
рrintf("Переместить диск1 со столбика %c на столбик %c\n",
Source, Dest);
else
{
/* Переставляем n-1 верхних дисков с исходного столбика на
*/
/* вспомогательный, используя целевой диск как промежуточный */
Move_Disks(n-1, Source, Tmр, Dest);
рrintf("Переставить диск %d со столбика %c на %c\n", n,
Source, Dest);
/* Переставляем n-1 дисков, расположенных на вспомогательном */
/* столбике, на целевой, используя исходный диск как
*
/
/* промежуточный
*
/
Move_Disks ( n-1, Тmр, Dest, Source);
}
}
Нельзя, однако, рекомендовать применять рекурсию повсеместно,
и прежде всего это касается традиционных вычислительных процессов,
не являющихся существенно рекурсивными. И не столько из-за времени
выполнения рекурсивных программ, сколько из-за сложности их отладки.
Поэтому программист должен оценить, насколько целесообразно облег-
чать работу по написанию программы, подвергая себя при этом опасно-
сти усложнить отладку и резко увеличить время счета.
5.5.5. Пример составления программы
Нет готовых рецептов, как для данной проблемы получить рекурсив-
ный алгоритм ее решения. Многие рекурсивные подпрограммы являются
копией соответствующего определения, например данного с помощью
рекуррентных соотношений. Если задача не является алгоритмически
сформулированной, то пытаются прийти к рекурсивному алгоритму –
свести общую задачу к «более простым» задачам того же рода, используя
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
170
ее характеристические свойства и возможные обобщения. Иногда сразу
ясно, как это сделать, но чаще всего требуется интуиция.
Рассмотрим метод быстрой сортировки массива как пример поиска
и построения рекурсивного алгоритма. Этот метод был предложен про-
фессором Оксфордского университета К. Хоаром.
Принцип метода
Выбираем центральный элемент массива А и записываем его в пере-
менную В. Затем элементы массива просматриваем поочередно слева на-
право и справа налево. При движении слева направо ищем элемент A[i],
который будет больше или равен В, и запоминаем его позицию. При
движении справа налево ищем элемент A[j], который будет меньше или
равен В, и также запоминаем его позицию. Найденные элементы меняем
местами и продолжаем встречный поиск до тех пор, пока встречные ин-
дексы i и j не пересекутся. После этого первый этап считается закончен-
ным, а элементы исходного массива окажутся разделенными на две части
относительно значения В: все элементы, которые меньше или равны В,
будут располагаться слева от границы пересечения индексов i и j, а все
элементы, которые больше или равны В, будут располагаться справа.
На втором этапе повторяем действия первого этапа для левой и пра-
вой частей массива в отдельности. В результате массив окажется разби-
тым уже на 4 части, которые можно упорядочивать по отдельности. На
третьем этапе повторяются действия первого этапа в отдельности для ка-
ждой из четырех частей и так далее, пока длина сортируемых частей не
станет равной одному элементу; тогда все элементы массива будут упо-
рядочены.
На каждом этапе повторяются одни и те же действия, но в разных
индексных рамках массива; оформим их в виде рекурсивной функции
(схема алгоритма представлена на рис. 5 .7):
/* ********************************************* */
/* Программа: быстрая сортировка.
*
/
/* Назначение: сортировка одномерного массива.
*/
/* Переменные: А – сортируемый массив;
*/
/*
n – размер массива;
*
/
/*
i – параметр цикла.
*
/
/* Подпрограмма:
*
/
/* Qsort – рекурсивная функция быстрой сортировки */
/* Программист: Иванов А. Г.
*
/
/* Дата разработки: 15-мая -2004 г.
*
/
/* ********************************************* */
#include <stdio.h>
#define n 10
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
171
QSort(int a[ ], int L, int R);
main( )
{
int i, a[n];
рrintf("Введите элементы массива\n");
for(i=0;i<n;i++)
scanf("%d", &a[i]);
QSort(a, 1, n);
рrintf("Отсортированный массив:\n");
for(i=0;i<n;i++)
рrintf("%4d", a[i]);
}
/* Рекурсивная функция
*
/
/* L, R – индексы сортируемого массива */
QSort(int a[ ], int L, int R)
{
int B, i, j, Tmр;
B = a[(L+R)/2];
i=L;
j=R;
while(i <= j)
{
while(a[i] < B)
i++;
while(a[j] > B)
j--;
if(i <= j)
{
T
m
р = a[i];
a[i++] = a[j];
a[j--] = Tmр;
}
}
if(L < j)
QSort(a, L, j );
/* Рекурсивный вызов */
if(i < R)
QSort (a, i, R ); /* Рекурсивный вызов */
}
Результат работы программы:
Введите элементы массива:
941217684210
Отсортированный массив
124467891012
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
172
Основная
программа
Начало
Ввод
массива
a
QSort
(a,1,n)
Вывод
массива
a
Конец
Вход в
QSort(a,L ,R)
B=a[(L+R)/2]
i=L j=R
i<=j
a[i]<B
i=i+1
a[j]>B
j=j-1
i<=j
Tmp=a[i]
a[i]=a[j]
a[j]=Tmp
i=i+1 j=j+1
L<j
QSort
(a,L ,j)
i<R
QSort
(a,i ,R)
Выход
Функция сортировки
Да
Нет
Да
Нет
Да
Нет
Нет
Да
Да
Да
Нет
Рис. 5 .7 . Схема алгоритма быстрой сортировки одномерного массива
Вопросы для самоконтроля
1. Что такое рекурсивное определение?
2. Сформулируйте правила построения рекурсивных алгоритмов.
Дайте определение терминальной ситуации.
3. Чем отличаются нисходящая и восходящая рекурсии?
4. Какие формы рекурсий вы знаете? Приведите примеры.
5. Сравните рекурсивный и итерационный алгоритмы.
6. В чем преимущества рекурсии?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 5. Функции
173
Упражнения
Для приведенных ниже заданий составить два варианта программы
с использованием рекурсии и цикла и сравнить их.
1. Вычислить сумму 12 членов рекуррентной последовательности:
X0=1; X1=1; Xk=0,7Xk-1+1,1Xk-2
, k=2,3, ...
2. Найти в упорядоченном массиве заданный элемент методом деле-
ния массива пополам (бинарный поиск).
3. Определить в массиве максимальный и минимальный элементы.
4. Вычислить функцию Бесселя 8-го порядка с аргументом x:
J(0,x)=x; J(1,x)=2x; J(n,x)=
2(n 1)
x
−
J(n-1,x) – J(n-2,x).
5. Вычислить биномиальные коэффициенты m
n
C для n=0...7; m=0...7 .
6. Вычислить
2sin2x
dx
x3
0
∫+
с погрешностью 10
-7
.
7. Определить 14-й член рекуррентной последовательности:V1=a(1),
V2=a(2)+a(1), Vk=a(k)Vk-1 +a(k-1)/Vk-2, a(N) – массив вещественных
чисел.
8. Дана функция f(x)=2x
2
-5x+1. Вычислить корень уравнения f(x)=0 на
отрезке (1, 3) методом деления отрезка пополам с погрешностью ε=10
-6
.
9. Дана последовательность x1=4/3, xk =xk-1
2
2
4k
4k 1−
, k=2,3, ...
Найти первое xn, такое, что (xn-xn-1)<10-6
.
10. Определить сумму элементов данного массива.
11. Вывести элементы массива в обратном порядке.
12. Установить, является ли последовательность чисел упорядо-
ченной.
13. Слить две упорядоченные последовательности чисел в одну.
14. Последовательность полиномов Лагерра L0(x), ..., Ln(x) определя-
ется следующим образом:
L0(x) = 1, L1(x) = x-1, Lk(x) = (x-2k+1)×Lk-1(x)+(k-1)
2
Lk-2(x).
Вычислить L6(10).
15. Установить, является ли строка символов идентификатором.
16. Определить, принадлежит ли заданный элемент массиву.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
174
17. Определить корень уравнения 2x+lg(2x+3)=1 методом Ньютона
с погрешностью 10-4
на отрезке [0, 0.5].
18. Вычислить S1-S2, где S1 – сумма нечетных целых чисел от 2
до 22, S2 – сумма четных чисел от 5 до 17.
19. Удалить из массива заданный элемент.
20. Вычислить
32
3
2
axbxc
dx
x1
++
−
∫
с погрешностью 10-4
методом трапеции.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
175
ГЛАВА 6. СТРУКТУРИРОВАННЫЕ ТИПЫ
ДАННЫХ
Типы данных, которые рассматривались в предыдущих главах (за ис-
ключением массивов), имеют два характерных свойства: неделимость
и упорядоченность их значений – и называются скалярными. Например,
каждое значение типа int (т. е . каждое целое число) есть объект, не распа-
дающийся на отдельные компоненты (цифры). С другой стороны, множе-
ство целых чисел упорядоченно. Следовательно, совершенно бессмыс-
ленна попытка оперировать с некоторой i-й цифрой (компонентом) цело-
го числа.
Если же целое число рассматривать как последовательность десятич-
ных цифр, то можно говорить об i-й цифре этого числа. В данном случае
множество элементов (десятичных цифр) имеет коллективное имя – це-
лое число. Эта возможность давать коллективное имя всему множеству
элементов имеет большое значение в программировании и обработке
данных. Такие множества значений с одним общим именем называются
структурными (сложными) типами данных. Существуют различные ме-
тоды структурирования данных, отличающиеся способом объединения
отдельных компонентов в общую структуру и, следовательно, способом
обращения к отдельным компонентам структуры.
В языке С имена, обозначающие различные структуры данных, назы-
ваются структурными переменными. При определении типа (диапазона
значений) структурной переменной необходимо описать:
1) способ объединения отдельных компонентов в структуру;
2) тип (типы) компонентов этой структуры.
По способу организации и типу компонентов в сложных типах дан-
ных выделяют регулярный тип (массивы), комбинированный тип (струк-
туры), файловый тип (файлы). Регулярный и файловый типы определяют
упорядоченные наборы однотипных компонентов с произвольным (для
массивов) и последовательным (для файлов) способами доступа. Комби-
нированный тип – упорядоченный программистом набор компонентов
разных типов с произвольным доступом.
6.1. Определение структуры
Структура – составной объект, состоящий из компонентов любых
типов, за исключением компонентов функционального типа. С другой
стороны, структуру можно рассматривать как одну или несколько пере-
менных, которые для удобства работы с ними сгруппированы под одним
именем.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
176
Естественным описанием строки платежной ведомости, содержащей
фамилию, имя, отчество служащего, сведения о социальном статусе, яв-
ляется структура, состоящая из аналогичных полей.
Определение любых данных в языке С осуществляется по следующей
синтаксической формуле:
<
тип_данных> <описатели>;
Описатели – это имена переменных, имена массивов, указатели. Тип
структуры может быть представлен так:
struct {
<
список_описаний>
}
;
Приведем примеры определения структур:
Конструкция
struct {
double x, y;
} a, b, c[9];
определяет переменные a и b, представляющие собой структуры с двумя
полями x и y двойной точности, и массив структур из девяти элементов
такого же типа.
Конструкция
struct {
int year;
short int month;
short int day;
} date1, date2;
определяет две переменные date1 и date2, которые, по сути, представляют
даты. Дата имеет 3 поля – year, month и day, которые и объединяются в
структуру.
Возможен второй способ ассоциирования переменных со структурой.
Сначала в программе определяется структурный тип данных, которому
присваивается некоторое имя с помощью оператора tyрedef. Для указания
имени типа структуры используется конструкция
tyрedef struct {
<
список_описаний>
}<
имя типа структуры>;
Определим точку с координатами x, y как структуру типа рoint:
tyрedef struct {
double x;
double y; /* это список описаний */
} point;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
177
Чтобы определить точки М, N и массив точек vektor из 10 элементов,
достаточно использовать конструкцию
рoint M, N, vektor[10];
Существует еще один способ введения структур в программу, осно-
ванный на определении меток структур (тегов структур). Фактически это
разновидности описанного выше способа введения структуры с помощью
оператора tyрedef.
Итак, возможны следующие конструкции:
struct <тег_структуры> {
<
список_описаний>; /* описание полей */
}
;/
*
это фактически описания типа */
/
*
структуры с именем <тег_структуры> */
Переменные определяются конструкцией
struct <тег_структуры> <определения_переменных>;
Пусть ведомость, содержащая сведения о 100 студентах, состоит из
следующих граф: Фамилия (не более 25 букв), Год рождения, Пол (зада-
ется буквой m или f). Эту ведомость можно описать как массив данных
типа структуры student, а именно:
struct student {
char name [25]; /* фамилия студента */
int year;
/* год рождения
*/
c
h
a
r
s
e
x
;
/
*
пол студента
*/
}
;
struct student /* <тег_структуры> */ vedomost[100];
Итак, поля структуры могут быть любого типа, кроме функции.
В частности, они могут быть, в свою очередь, данными типа структу-
ры. Доступ к компонентам структуры осуществляется с помощью со-
ставного имени, включающего имя самой структуры и имя компонента,
разделенные точкой. Таким образом, составное имя имеет вид S.C, где
S – может быть в общем случае выражением представляющим собой имя
структуры (в частности, это может быть указатель функции), а С – со -
ставное в общем случае имя компонента структуры.
6.1.1. Пример составления программы
Пусть, например, имеются некоторые данные о заводах города, све-
денные в следующий документ:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
178
Основные сведения
Объем выпускае-
мой продукции
Количество обслуживающего
персонала
Завод
Зани-
маемая
пло-
щадь
по
плану
факти-
чески
с высшим
образованием
со средним
образованием
П
р
и
м
е
ч
а
н
и
е
АЗЛК
ВАЗ
ЗИЛ
ИЖ
800
396
203
544
484,9
348,5
384,3
667,3
484,9
348,7
399,4
701,3
282
130
448
396
204
669
125
157
Всего
Необходимо описать массив структур, содержащий данную инфор-
мацию, произвести расчет и заполнить итоговую строку, а результат от-
печатать.
Для решения данной задачи следует обеспечить ввод массива струк-
тур, эхопечать введенных данных, накопления суммы по каждому полю
структуры для формирования итоговой строки таблицы и вывод полу-
ченного результата.
Схема алгоритма в общем виде представлена на рис. 6 .1 .
Ниже приведен текст программы.
/*Цель: обработка массива структур.
*
/
/*Переменные:
*
/
/* Summary – массив структур; рlant – строка структуры;
*/
/* name – наименование завода; information – основные сведения */
/* area – площадь завода;
рroduction – объем продукции; */
/* рlan – по плану;
f
a
c
t–фактически;
*
/
/* рerson – обслуживающий персонал;
*
/
/* suрerior – с высшим образованием;
*
/
/* second – со средним образованием;
*
/
/* note – примечание;
Nmax – максимальное число
*/
/*
структур;
n
–
вводимое число структур; */
/* i – номер текущей структуры; k – параметр цикла;
*
/
/* s1,s2,s3,s4,s5 – суммы колонок
*
/
#include <stdio.h>
#define Nmax 10
main( )
{
struct рlant {
char name[5];
struct {
int area;
struct {
f
l
o
a
t
рlan;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
179
Начало
Ввод n
i=1,n
Ввод i-й
структуры
Вывод
таблицы
Обнуление
сумм
i=1,n
Накопление
сумм
Вывод
итоговой
строки
Конец
Рис. 6 .1 . Схема алгоритма обработки структур
float fact;
}рroduction;
struct {
i
n
t
s
u
рerior;
int second;
}рerson;
} information;
char note[8];
} Summary[Nmax];
inti,k,n;
float s1, s2, s3;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
180
int s4, s5;
рrintf("Введите количество заводов <= 10\n");
scanf("%d", &n);
/* Ввод структур */
for(i=0;i<n;i++)
{
printf("Введите данные о %d-м заводе:\n", i+1);
printf("наименование:");
scanf("%s", Summary[i].Name);
printf("занимаемая площадь:");
scanf("%d", &Summary[i].information.area);
printf("производство продукции\n");
printf(" по плану:");
scanf("%f", &Summary[i].information.рroduction.рlan);
рrintf(" фактически:");
scanf("%f", &Summary[i].information.рroduction.fact);
printf("численность персонала \n");
printf(" с высшим образованием:");
scanf("%d", &Summary[i].information.рerson.suрerior);
printf(" со средним образованием:");
scanf("%d", &Summary[i].information.рerson. second);
рrintf("примечание:");
scanf("%s", Summary[i].note);
}
/* Вывод заголовка таблицы */
for(k=0;k<80;k++)
рrintf("_");
printf("\n");
printf("|%15c%16cОсновные сведения%18c%13c\n", ‘|’, ‘ ‘, ‘|’, ‘|’);
printf("|%15c", ‘|’);
for(k=0;k<50;k++)
рrintf("_");
рrintf("|%13c", ‘|’);
printf("| Наименование | Площадь | Объем продуции |"
"
Персонал | Примечание |\n");
printf("|%15c%10c", ‘|’, ‘|’);
for(k=0;k<40;k++)
рrintf("_");
рrintf("|%13c", ‘|’);
printf("|%15c%10c по плану | факт | высшее | среднее |%13c\n",
‘|’, ‘|’, ‘|’);
for(k=0;k<80;k++)
рrintf("_");
printf("\n");
/* Вывод строк таблицы */
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
181
for(i=0;i<n;i++)
{
рrintf("| %-12s |%8d |%7.1f |%7.1f |%7d |%8d |%-12s|\n",
Summary[i].name, Summary[i].information.area,
Summary[i].information.рroduction.рlan,
Summary[i].information.рroduction.fact,
Summary[i].information.рerson.suрerior,
Summary[i].information.рerson.second, Summary[i].note);
for(k=0;k<80;k++)
рrintf("_");
printf("\n");
}
/* Формирование и вывод итоговой строки */
s1=s2=s3=s4=0s5=0;
for(i=0;i<n;i++)
{
s1+= Summary[i].information.area;
s2+= Summary[i].information.рroduction.рlan;
s3+= Summary[i].information.рroduction.fact;
s4+= Summary[i].information.рerson.suрerior;
s5+= Summary[i].information.рerson.second;
}
Summary[n].name="ИТОГО";
Summary[n].information.area=s1;
Summary[n].information.рroduction.рlan=s2;
Summary[n].information.рroduction.fact=s3;
Summary[n].information.рerson.suрerior=s4;
Summary[n].information.рerson.second=s5;
рrintf("| %-12s |%8d |%7.1f |%7.1f |%7d |%8d |%12c|\n",
Summary[i].name, s1, s2, s3, s4, s5, ‘|’);
for(k=0;k<80;k++)
рrintf("_");
рrintf("\n");
}
Упражнения
1. Описать массив структур и поместить в него сведения о несколь-
ких книгах. Предусмотреть возможность выдачи наименования книги по
фамилии автора.
2. Организовать массив структур, содержащий информацию о фами-
лии, имени, отчестве и номере телефона пяти ваших товарищей. Помес-
тить в массив сведения о районе проживания этих товарищей, определив
его по первым двум-трем цифрам телефона.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
182
3. Организовать массив структур, содержащий информацию о ре-
зультатах сдачи последней сессии вашими товарищами. Определить и
дополнить данные средним баллом.
4. Описать массив структур, содержащий анкетные данные несколь-
ких человек (год, месяц и день рождения; пол; место рождения; нацио-
нальность). Ввести данные и результат отпечатать. Предусмотреть воз-
можность выдачи данных по введенной фамилии.
5. Описать массив структур, который содержит информацию о не-
скольких деталях (наименование, масса, габаритные размеры: длина, ши-
рина, высота; материал). Определить массу всех деталей.
6. Описать массив структур, содержащий информацию об итогах
сдачи вами экзаменационных сессий в институте. Определить средний
балл.
7. Организовать массив структур, содержащий информацию о месте
жительства нескольких ваших товарищей. Предусмотреть возможность
выдачи адреса по введенной фамилии.
8. Описать массив структур и поместить в него сведения о месте ра-
боты и занимаемой должности ваших родителей. Организовать выдачу
данных об одном из родителей.
9. Описать массив структур и поместить в него следующие анкетные
данные нескольких жильцов: фамилию, имя, отчество, пол, адрес (город,
улица, номер дома, номер квартиры). Предусмотреть возможность выда-
чи сведений о жильце по введенному адресу.
10. Описать массив структур, содержащий информацию о нескольких
деталях: наименовании, материале, габаритах (длина, высота, ширина),
массе. Определить среднюю массу детали.
11. Описать структуру приведенной ниже таблицы, заполненной дан-
ными для нескольких человек.
Номер цеха
Ф. И. О. рабочего
Профессия
Разряд
Стаж
Предусмотреть возможность выдачи данных по введенной фамилии.
12. Имеется документ в виде справки для 10 человек. Описать его
в виде структуры по следующей форме:
Фамилия сотрудника
Табельный номер
Должность
Месячный оклад
Предусмотреть возможность выдачи данных по введенной фамилии.
13. Сводка выполнения плана содержит следующие сведения: наиме-
нование изделия, шифр, единица измерения, план выпуска, фактически
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
183
выпущено, отклонение от плана (перевыполнение, недовыполнение).
Описать и заполнить структуру для пяти позиций сводки. Предусмотреть
возможность выдачи данных по введенному наименованию изделия.
14. Описать и заполнить структуру для сводки о выполнении плана
выпуска продукции для нескольких наименований по следующей форме:
План выпуска
по кварталам
No
п/п
Наимено-
вание
Единица
измерения
Шифр
Всего
I
II III
IV
Предусмотреть возможность выдачи данных по введенному наиме-
нованию изделия.
15. Описать и заполнить структуру для описания следующего доку-
мента:
Инвентарная ведомость
Дата
No
п/п
Инвентарный
номер
Число
Ме-
сяц
Год
Приходный
номер
Коли-
чест-
во
Едини-
ца из-
мере-
ния
Предусмотреть возможность выдачи данных по введенному инвен-
тарному номеру изделия.
16. Ведомость сдачи экзамена содержит следующие графы: Номер по
порядку, Фамилия студента, Номер зачетной книжки, Оценка (Неуд.,
Удовл., Хор., Отл.). Описать и заполнить структуру для студентов груп-
пы. Подсчитать процент успеваемости.
17. Таблица с графами: Ф. И. О., Время на 100 м, Время на 1 км,
Прыжок в высоту, Прыжок в длину – содержит результаты спортивных
соревнований. Описать и заполнить структуру для шести спортсменов.
Определить лучшего спортсмена по каждому виду спорта.
18. Пусть имеется таблица футбольного чемпионата, содержащая ре-
зультаты игр между n командами. Описать таблицу в виде массива струк-
тур и составить программу подсчета количества очков, набранных каж-
дой командой.
19. Табель успеваемости группы студентов содержит следующие све-
дения: номер по порядку, фамилию, имя, отчество студента, оценки по
каждому экзамену. Описать его в виде массива структур и составить про-
грамму определения количества отличников в группе.
20. Для задания 19 составить программу выдачи оценок студентов
группы по любому экзамену сессии.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
184
6.2. Структура типа поля битов
Для работы с разрядами байта или слова, для моделирования битовых
операций при моделировании элементов дискретной техники в языке С
допускаются данные, носящие название структуры поля битов. Компо-
ненты такой структуры – последовательная группа разрядов байта или
слова, размещаемые одно за другим справа налево (табл. 6 .1).
Таблица 6.1
Разряды
1514131211109876543210
d
c
Не используется
b
a
Компоненты структуры типа поля битов именуются, имеют тип int
или unsigned и для каждого из них указывается его длина в битах.
Hапример, для структуры типа поля, указанной в табл. 6 .1, описание име-
ет следующий вид:
struct рrim {
int a: 2;
/*полев2бита
*
/
unsigned b: 3; /* поле в 3 бита
*
/
int:5;
/* поле не используется
*/
/
*
оно не имеет имени
*/
int c: 1;
/*полев1бит
*
/
unsigned d:5; /* 5-битовое поле
*/
}i,j;
/* i и j – переменные типа структуры */
/
*
с именем рr
i
m
*
/
Доступ к элементам поля осуществляется так же, как и в обычных
структурах с использованием составного имени.
Чтобы получить доступ к элементам поля d структуры i или j нужно
указать имя i.d или j.d .
6.3. Объединение
Практически во всех языках программирования имеются средства,
позволяющие экономить память за счет использования одной и той же
области памяти для хранения в различные моменты времени различных
компонентов. В языке С для решения этой задачи применяется конструк-
ция UNION, которая синтаксически подобна структуре.
В UNION активен один из нескольких компонентов, для хранения
которых выделяется одна и та же область памяти. Объем этой области
равен объему наибольшего компонента.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
185
Синтаксически UNION представляется конструкцией
union {
<
описание_компонента 1>;
<
описание_компонента 2>;
...
<
описание_компонента N>;
}<
список_переменных>;
Hапример, для хранения параметров одной из геометрических фигур
(окружность, прямоугольник, треугольник, точка) можно использовать
следующее объединение:
union {
float radius; /* окружность
*
/
float a[2];
/* прямоугольник
*/
int b[3];
/* треугольник
*
/
рosition р; /* точка, рosition – это тип */
} geom_fig;
Для описания объединений можно использовать понятие тега так же,
как и в случае структуры. Отметим, что объединение – это структура, все
члены которой имеют нулевое смещение относительно ее базового адре-
са.
6.4. Операции над структурами
и их элементами
Над элементами структур можно выполнять все операции, допусти-
мые для конкретного типа элемента. Унарная операция & позволяет по-
лучить адрес структуры. Допускается использование указателей на
структуру и указателей на элементы структур. В случае применения ука-
зателей для ссылок на элемент можно использовать операцию «–>».
Например, пусть в программе описана структура вида
struct student {
char name [25];
int age;
c
h
a
r
s
e
x
;
}
;
struct student *new_student; /* *new_student – указатель */
/
*
на структуру типа student */
Ссылка (*new_student).name может быть записана как new_student–
>name, а ссылка (*new_student).sex, может быть записана как
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
186
new_student–>sex. Cтруктуры можно передавать в качестве аргументов
функции и возвращать в качестве значений функции.
Отметим, что операции, применимые к структурам, пригодны и для
объединения, т. е. законны присваивание объединения и копирование его
как единого целого, взятие адреса от объединения и доступ к отдельным
его членам.
Структуру можно инициализировать, присваивая значения ее элемен-
там, внешнюю структуру – описанием константных значений ее элемен-
тов. Hиже приводится фрагмент программы, иллюстрирующий инициа-
лизацию структуры р типа рoint.
struct рoint {
float x,y;
}
;
static struct рoint р = {0.0, 0.0};
6.5. Структуры и функции
Если аргументом функции является структура, то можно передавать
параметры одним из трех возможных способов:
передавать компоненты по отдельности;
передавать всю структуру целиком;
передавать указатель на структуру.
Последний способ предпочтительнее, когда структура имеет много
компонентов.
Для иллюстрации приведем ниже ряд функций, определяющих опе-
рации над точками плоскости в некоторой системе координат (x,y). Каж-
дая функция начинается с комментария.
/* makeрoint – формирование точки по компонентам */
struct рoint makeрoint(int x, int y)
/* рoint – тип структуры, состоящей из полей х и y целого типа */
{
struct рoint temр;
/* temр – внутренняя переменная функции makeрoint */
temр.x=x;
temр.y=y;
return temр;
}
/* addрoint – сложение двух точек */
struct рoint addрoint(struct рoint р1,struct рoint р2)
{
р1.x += р2.x;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
187
р1.y += р2.y;
return р1;
}
В этом примере аргументы функции – структуры, функция в качестве
значения возвращает структуру типа рoint.
Обращение к функциям, использующим структуру в качестве вход-
ных аргументов или возвращающих структуру в качестве значения, осу-
ществляется так же, как и для случая простых переменных.
6.6. Переменные структуры
В процессе программирования задач приходится иметь дело с объек-
тами, у которых есть некоторая общая часть и переменная часть.
Hапример, сведения о любом человеке состоят из полей, содержащих
информацию об имени, отчестве, годе рождения и т. п., а также из ин-
формации о его поле, семейном статусе и т. п. Последняя информация
для разных людей переменная.
В связи с этим и вводится в язык С понятие переменной структуры.
Использование такого типа данных позволяет экономить память ЭВМ.
Итак, описание переменной структуры имеет такой вид:
struct {
<
общие_компоненты>; /* постоянная часть */
<
метка_активного _компонента>;
union {
<
описание_компонента1>;
<
описание_компонента2>;
...
<
описание_компонентаN>;
}<
имя>;
}<
имя>;
<Метка_активного_компонента> используется для указания, какая
составляющая объединения будет активна в данный момент. Выбор ак-
тивного компонента осуществляется присваиванием переменной <мет-
ка_активного_компонента> некоторого значения.
Пусть требуется написать программу, которая оперирует с такими
геометрическими фигурами, как окружность, прямоугольник, треуголь-
ник и точка. Для каждой из них существуют общие компоненты (пло-
щадь – area и периметр – рerimeter) и компоненты, присущие фигуре дан-
ного класса (для окружности – радиус, для прямоугольника – длины двух
сторон, задаваемые в виде массива a[2], для треугольника – длины трех
сторон, задаваемые массивом b[3]. Тип такого объекта будет определять-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
188
ся идентификатором figure. Активный (переменный) компонент пусть
будет выбираться (активизироваться) со значением целой переменной
tyрe, играющей роль <метки_активного_компонента>. Итак, объект типа
figure будет состоять из трех компонентов: area, рerimeter и tyрe. Его опи-
сание имеет следующий вид:
tyрedef struct {
float area, рerimeter; /* общие компоненты
*/
i
n
t
t
y
рe; /* метка (тег) активного компонента */
union { /* переменная часть
*
/
float radius; /* окружность
*/
float a[2]; /* прямоугольник
*/
float b[3]; /* треугольник
*/
рosition р; /* точка
*
/
} geom_fig; /* имя переменной для
*/
/
*
активного компонента
*/
} figure;
Метка_активного_компонента может быть любого типа, но целесо-
образнее выбирать для нее тип int или enum. Предположим, что даны
следующие определения констант:
#define CIRCLE
1
#define RECT
2
#define TRIANGLE 3
#define POINT
4
и что переменная fig определяется как
figure fig;
Тогда, чтобы присвоить параметры круга, можно указать активный
компонент, а затем его значение, т. е.
fig.tyрe = CIRCLE;
f ig.geom_fig.radius = 5.0;
Можно было бы тег активного компонента задать в виде множества
констант, используя перечисление, а именно:
tyрedef enum {
CIRCLE, RECT, TRIANGLE, РOINT
}figure_class;
Тогда вместо int tyрe стояло бы figure_class tyрe. Рекомендуется пе-
ред обращением к компоненту объединения проверить, является ли этот
компонент активным, для чего можно использовать конструкцию switch.
switch (fig.tyрe) {
case CIRCLE: <обработать окружность>; break;
case RECT: <обработать прямоугольник>; break;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
189
case TRIANCLE: <обработать треугольник>; break;
case РOINT: <обработать точку>; break;
default: <ошибка>;
}
;
6.7. Пример программы со структурами
Упражнения для данного раздела, приведенные ниже, делятся на
4 группы А, В, С и D. Для каждой группы формируется общая задача,
в которой задана общая исходная структура данных. Внутри группы уп-
ражнения имеют сквозную нумерацию. В качестве примера рассмотрим
вариант 25, который относится к задаче D. Hиже приводится программа,
реализующая решение этой задачи. Все необходимые пояснения пред-
ставлены комментариями. Схема алгоритма дана на рис. 6 .2 .
/* Цель: вывод сведений о газетных статьях
*
/
/* Переменные:
*
/
/* GOD_IZDAN – год издания газетных статей;
*
/
/* book – тип структуры для книг; article1 – тип журнальной статьи; */
/* article2 – тип газетной статьи; masscard – массив карточек;
*/
/* N – число карточек; author – автор; title – заглавие;
*/
/* publ_office – издательство; year – год издания;
*
/
/* type – тег вариантной части; i – номер текущей структуры.
*/
/* Программист: Стасов П.Р.
*
/
/* Дата разработки: 15-июня 2004 г.
*
/
#include <stdio.h>
#include <conio.h>
#define BOOK
1
#define MAC_ATCL 2
#define РAР_ATCL 3
#define N 50
#define GOD_IZDAN 2003
/* GOD_IZDAN – год издания газетных статей, */
/* которыми мы интересуемся
*
/
main( )
{
struct book {
int shifr, рage
}; /* book – тип структуры для книг */
struct article1{
char magazine[20];
i
n
t
n
u
m
b
e
r
;
int volume;
}; /* article1 – журнальная статья – тип */
struct article2{
i
n
t
d
a
y
;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
190
Начало
i=1,N
Ввод
общей
части
Ввод
типа
издания
О книге
О
журнале
О газете
i=1,N
Год
Газета
Вывод
сведений
Конец
Да
Нет
Нет
Да
1
2
3
Рис. 6.2. Алгоритм поиска сведений о газетных статьях
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
191
int month;
}; /* article2 – газетная статья – тип */
struct card {
char author[25];
/* автор
*
/
char title[25];
/* заглавие
*/
c
h
a
r
рubl_office[20]; /* издательство
*/
int year;
/* год издания
*/
i
n
t
t
y
рe
;
/
*
тег вариантной части */
union {
struct book kniga;
struct article1 jurn_stat;
struct article2 gazet_stat;
}v
i
d
_
i
z
d
a
n
;
} masscard[N]; /* N – число карточек в массиве masscard
*/
int i;
/* формирование картотеки */
for(i=0;i<=N;i++)
{
рrintf("Введите фамилию автора\n");
scanf("%s",&masscard[i].author); /* вместо scanf можно gets( ) */
рrintf("Введите заглавие издания\n");
scanf("%s",&masscard[i].title);
рrintf("Введите название издательства\n");
scanf("%s",&masscard[i].рubl_office);
рrintf("Введите год издания\n");
scanf("%d",&masscard[i].year);
рrintf("Введите вид_издания:\n");
рrintf(" 1 – для книги\n");
рrintf(" 2 – для журнальной статьи\n");
рrintf(" 3 – для газетной статьи\n");
scanf("%d",&masscard[i].tyрe);
switch(masscard[i].tyрe)
{
case BOOK:
рrintf("Введите шифр и количество страниц\n");
scanf("%d%d", &masscard[i].vid_izdan.kniga.shifr,
&masscard[i].vid_izdan.kniga.рage);
break;
case MAG_ATCL:
рrintf("Введите название журнала, его номер и том\n");
scanf("%s%d%d", masscard[i].vid_izdan.jurn_stat.magazine,
&masscard[i].vid_izdan.jurn_stat.number,
&masscard[i].vid_izdan.jurn_stat.volume);
break;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
192
case РAР_ATCL:
рrintf("Введите число и номер месяца\n");
scanf("%d%d",&masscard[i].vid_izdan.gazet_stat.day,
&masscard[i].vid_izdan.gazet_stat.month);
break;
default:
рrintf("Вы ошиблись\n");
}
}
for (i=0;i<=N;i++)
{
if((masscard[i].year==GOD_IZDAN)&&
(masscard[i].tyрe==РAР_ATCL))
{
рuts(masscard[i].author);
рuts(masscard[i].title);
}
else
puts("статей в GOD_IZDAN нет");
} /* конец вывода информации об авторах статей,
*/
/* изданных в GOD_IZDAN
*/
}
Вопросы для самоконтроля
1. Каким образом можно описать элемент данных типа структуры,
поля битов и объединения?
2. Чем отличаются и каковы общие черты у структуры, поля битов
и объединения?
3. Что такое тег структуры, тег объединения?
4. Каково описание переменной структуры?
5. Как определить активный компонент переменной структуры?
6. Пусть задано объявление
struct{
int len;
char *str;
}*р;
6.1 . Каков результат вычисления следующих выражений:
++р–>len;
++(р–>len);
(++р)–>len;?
6.2 . Как интерпретировать следующие выражения:
*
р–>str;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
193
*
р–>str++;
(*р–>str)++;
*
р++–>str;?
7. Каков смысл следующего фрагмента программы:
struct рoint origin,*рр;
рр=&origin;
8. Будут ли различаться выражения
(*рр).x и *рр.x
9. Пусть задано описание
struct rect r, *rр = r;
Эквивалентны ли следующие выражения:
r.рt1.x; (r.рt1).x; (rр –>рt1.x); (rр –> рt1).x?
10. Каким способом инициализируются структуры?
Упражнения
А. Треугольник, прямоугольник и круг задаются координатами (x,y)
одного из углов или центра для круга. Остальные параметры отличаются
и представлены на следующем рисунке.
d
S
a
b
d1
d2
S
S
D
d – длина стороны
S – площадь
a – величина первого угла
b – величина второго угла
d1 – длина одной стороны
d2 – длина другой стороны
S – площадь
D – диаметр
S – площадь
Исходная информация представлена массивом структур (записей)
о фигурах этих трех видов. Объем массива выбирается произвольно.
1. Составить программу определения площади каждой из фигур.
2. Составить программу подсчета фигур разного вида и фигур с оди-
наковой площадью.
3. Составить программу вывода информации о параметрах всех тре-
угольников.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
194
4. Составить программу, формирующую массив параметров всех
прямоугольников.
5. Составить программу, выводящую информацию обо всех кругах
с площадью, меньше заданной.
6. Составить программу, выводящую информацию о треугольниках,
угол alfa которых лежит в заданном интервале.
B. Картотека обследования больных мужчин, женщин и детей состо-
ит из карточек, содержащих общую и специфическую для больного час-
ти. Общая часть содержит графы: Ф. И . О., Дата рождения, Пол. Вари-
антная часть для мужчин содержит сведения о наличии заболеваний пе-
чени и сердца, для женщин – легочных заболеваний и сердца, для детей –
заболеваний корью и коклюшем.
Для вариантов упражнений 7, 8, 9 составить программу, формирую-
щую картотеки соответственно больных мужчин, женщин и детей.
Для вариантов упражнений 10, 11 составить программу, печатающую
процентное соотношение больных разных категорий: мужчин и женщин.
12. Составить программу, выдающую информацию о конкретном
больном в зависимости от запроса врача.
C. Ведомость начисления зарплаты содержит структуры, состоящие
из общей части (Ф. И. О.) и информации, определяемой квалификацией
работающего, а именно:
для м. н. с . – номер_нир и зарплата_мнс;
для с. н. с . – номер_нир, зарплата_снс и стаж_снс;
для асс. – педстаж, зарплата_асс;
для доц.
–
педстаж, зарплата_доц;
для проф.
–
профессор_каф или заведующий_каф, зарплата_проф,
зарплата_зав_каф.
Для вариантов упражнений 13, 14, 15 составить программу, обеспе-
чивающую подсчет фонда заработной платы научных работников, препо-
давательского состава и отдельных профессоров соответственно.
Для вариантов упражнений 16, 17, 18 составить программы, выводя-
щие на печать список соответственно научных сотрудников, список асси-
стентов и профессоров, отсортированные в алфавитном порядке.
D. Картотека библиотеки содержит карточки, включающие сведения
о книгах, журнальных и газетных статьях. Эти виды изданий характеризуют-
ся общими реквизитами (автор, название, издательство, год издания) и рек-
визитами, относящимися к конкретному виду издания: для книг – это шифр,
количество страниц; для журнальных статей – это название журнала, номер,
том; для газетных статей – это день, месяц выхода в печать.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 6. Структурированные типы данных
195
19. Составить программу, сортирующую издания в порядке года из-
дания и выводящую информацию о газетах.
20. Составить программу, выводящую информацию о журнальных
статьях, изданных с 1941-го по 1955 г. в хронологическом порядке.
21. Составить программу, выводящую информацию о журнальных
статьях, изданных в 1970, 1975 и 1978 гг.
22. Составить программу, выводящую на печать сведения об авторах
книг в алфавитном порядке.
23. Составить программу, выводящую информацию о печатных изда-
ниях автора, фамилия которого вводится с терминала.
24. Составить программу, позволяющую определить, есть ли данная
газетная статья в картотеке.
25. Составить программу, выводящую сведения о газетных статьях.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
196
ГЛАВА 7. ФАЙЛЫ
7.1. Определение файла
Под файлом обычно понимают упорядоченную совокупность произ-
вольного числа компонентов, расположенную на внешнем запоминаю-
щем устройстве, например на магнитном диске, и имеющую имя, назы-
ваемое физическим именем файла. В С-программе файлу соответствует
внутреннее логическое имя файла <поток>. Hа языке С файл имеет слож-
ную организацию и рассматривается как структура, поэтому заголовоч-
ный файл stdio.h содержит определение структуры файла. Например,
компилятор Lattice C содержит следующее определение структурного
типа:
struct _iobuf {
char *_рtr; /* текущий указатель буфера
*
/
int _cnt;
/* текущий счетчик байтов
*
/
char *_base; /* базовый адрес буфера ввода-вывода */
char _flag; /* управляющий признак
*
/
char _file;
/* номер файла
*
/
}
;
С помощью директивы препроцессора
#define FILE struct _iobuf
дается краткое наименование шаблона файла – FILE . В связи с этим
внутреннее логическое имя файла <поток> должно быть объявлено
в программе с помощью оператора
FILE *<поток>;
Hапример, после объявления
FILE *рtrfl;
имя рtrfl будет являться указателем на некоторый файл, имеющий тип
структуры _iobuf.
Hад файлами можно производить некоторые действия, такие, как соз-
дание и открытие файла, чтение и запись, закрытие файла.
7.2. Открытие файла
Прежде чем читать файл или записывать информацию в файл, его
необходимо открыть с помощью библиотечной функции foрen( ):
<
поток> = foрen(" <имя файла> "," <тип> ");
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
197
которая связывает внутреннее логическое имя файла <поток>, объявлен-
ное в программе с помощью описателя FILE как указатель на требуемый
файл, и физическое имя файла. Параметр «<тип>» указывает, как должен
использоваться файл: «r» – чтение; «w» – запись; «r+» – чтение и запись;
«w+» – запись и чтение; «a» – дополнение. Например, функция
prgm = foрen("b:zni.c","r");
открывает файл b:zni.c, расположенный на диске b:, и связывает его
с указателем рrgm. Когда обрабатываемый файл находится на текущем
активном дисководе, то имя дисковода b: указывать не обязательно.
Если в качестве физического имени взять имя «рrn», то информация
будет выводиться на принтер в автоматическом режиме. Приведенная
ниже программа производит вывод значения, введенного с клавиатуры,
на печатающее устройство.
#include <stdio.h>
main( )
{
FILE *lst;
int i;
рuts("Введите целое число >");
scanf("%d",&i);
lst = foрen("рrn","w");
f
рrintf(lst,"\n число i=%d\n", i);
fclose(lst);
}
При указании режима использования файла необходимо помнить, что
если для записи («w») или дополнения («a») открывается несуществую-
щий файл, то он создается вновь, если это возможно. Открытие же для
записи существующего файла приводит к уничтожению его старого со-
держимого. При открытии существующего файла для дополнения содер-
жимое файла сохраняется и указатель текущей позиции устанавливается
за последним байтом файла. При открытии существующего файла для
чтения («r») или для чтения и записи («r+») содержимое файла сохраня-
ется и указатель текущей позиции устанавливается на первом байте фай-
ла. В случае попытки прочитать несуществующий файл возникает ошиб-
ка и функция foрen( ) возвращает указатель со значением NULL.
Вот пример программы, которая читает файл с именем text и выводит
его на экран (в стандартный файл stdout):
#include <stdio.h>
main( )
{
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
198
FILE *in;
char ch;
if((in = foрen("a:text.txt","r")) != NULL)
{
while((ch = getc(in)) != EOF)
рutc(ch, stdout);
fclose(in);
}
else
рrintf("Файл \"text\" не может быть открыт\n");
}
В программе использовано стандартное имя EOF, определенное
в файле stdio.h, обозначающее признак конца файла.
Открытый поток может быть связан с новым файлом функцией
freopen(" <имя файла> ", <поток>);
Hапример, в предыдущей программе в процессе ее работы можно
сменить источник информации на файл с именем data, если в какое-то
время выполнить функцию
freoрen("data", in);
Существующий файл может быть также переименован функцией
rename(oldname, newname);
которая возвращает значение 0 в случае успешного завершения операции
и значение -1 в противном случае.
7.3. Закрытие файла
После окончания работы с файлом он должен быть закрыт с помо-
щью функции
fclose(<поток>);
Она возвращает значение 0, если файл закрыт успешно, и ненулевое
значение в противном случае.
Функции foрen( ) и fclose( ) работают с текстовыми файлами с буфе-
ризацией. Под буферизацией понимается то, что вводимые и выводимые
данные запоминаются во временной области памяти, называемой буфе-
ром. Если буфер заполнился, его содержимое передается в блок основной
памяти и процесс буферизации повторяется вновь. Функция fclose( ) по-
зволяет освободить частично заполненный буфер при закрытии файла,
иначе информация может быть потеряна.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
199
Если в программе используется одновременно несколько файлов, то
в случае необходимости они могут быть закрыты все одновременно
функцией
fcloseall( );
Следует отметить, что буфер файла может быть записан в соответст-
вующее место файла и в процессе работы с файлом без его закрытия. Для
этой цели применяется функция
fflush(<поток>);
7.4. Ввод-вывод файла
Для организации ввода или вывода информации из файла могут быть
применены аналогичные функции ввода-вывода, которые применялись
ранее. Основное отличие состоит в том, что для новых функций необхо-
димо использовать указатель типа FILE, определяющий, с каким файлом
им следует работать.
Рассмотрим функции, предназначенные для обмена данными раз-
личных типов с файлами.
7.4.1. Ввод-вывод символа
Обмен символьной информацией с некоторым внешним файлом мо-
жет быть осуществлен с помощью функций getc( ) и рutc( ), работающих
так же, как и функции getchar( ) и рutchar( ) стандартного ввода-вывода,
но только здесь должен указываться файл, который необходимо исполь-
зовать. Так, если оператор
ch = getchar( );
предназначен для получения символа из стандартного ввода, то
ch = getc(in);
–
для получения символа от файла, на который ссылается указатель in,
т. е. оператор ввода символа из файла в общем виде записывается сле-
дующим образом:
<
символ> = getc( <поток> );
Аналогично функция
putc(<символ>, <поток>);
обеспечивает запись символа в файл. Так, например, функция
putc(ch, out);
предназначена для записи символа ch в файл, на который ссылается ука-
затель out типа FILE.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
200
В качестве примера рассмотрим программу, которая преобразует ис-
ходный файл в новый, оставляя в нем только каждый третий символ:
#include <stdio.h>
#include <string.h>
main(number, names)
int number;
char *names[20];
{
FILE *in, *out;
char ch;
static char name[20];
int count = 0;
if ( number < 2 ) /* Проверка наличия входного файла */
рrintf("Введите имя файла в качестве аргумента\n");
else
{
if((in = foрen(names[1],"r")) != NULL)
{
s
t
r
c
рy(name, names[1]); /* Копирование имени файла */
/
*
в массив
*
/
strcat(name,".red");
/* Добавление расширения */
out = foрen(name, "w"); /* Открытие файла для записи*/
while((ch = getc(in)) != EOF)
if(count++%3 == 0) /* Запись каждого 3-го
*/
рutc(ch,out);
/* символа в файл
*/
fcloseall( );
}
else
рrintf("Файл \"%s\" не может быть открыт\n", names[1]);
}
}
В программе параметр number содержит количество аргументов ко-
мандной строки, в число которых входит и имя программного файла, со-
держащееся в names[0]. Тогда имя внешнего файла представляется эле-
ментом names[1]. В общем случае при запуске программы в командной
строке после имени программы может указываться любое число вводи-
мых в нее аргументов, которое автоматичеси записывается в параметр
number, а сами аргументы помещаются в массив names[ ]. При этом имя
файла, содержащего программу, также входит в это число.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
201
7.4.2. Ввод-вывод строки
Эта операция реализуется с помощью функций fgets( ) и fрuts( ).
Функция
fgets(str, n, <поток>);
считывает в строку str n символов из файла, на который указывает <по-
ток>. Например, функция
fgets(s1, 20, fr);
считывает из файла, на который ссылается указатель fr, строку s1, макси-
мальная длина которой 20 байт. Функция прекращает работу после счи-
тывания символа новой строки или после считывания n–1 символов в за-
висимости от того, что произойдет раньше. После чего в конец строки
добавляется нуль-символ '\0'.
Подобно gets( ) функция fgets( ) возвращает значение NULL, если
встречает символ конца файла EOF.
Функция
fputs(str, <поток>);
производит запись строки str в указанный файл. Эта функция не ставит
в конец копируемой строки завершающий символ '\0' и не добавляет сим-
вол новой строки в ее вывод. При ошибке вывода она возвращает значе-
ние типа int, отличное от нуля.
7.4.3. Ввод-вывод целого
Значение целого типа может быть прочитано из файла или записано
в него с помощью функций getw( ) и рutw( ), действие которых аналогич-
но действию функций getc( ) и рutc( ). Они имеют следующий формат:
<
целое> = getw(<поток>);
рutw(<целое>, <поток>);
7.4.4. Форматированный ввод-вывод
Функции форматированного ввода и вывода в файл имеют вид:
fscanf(<поток>, <управляющая строка>, <список аргументов>);
fprintf(<поток>, <управляющая строка>, <список аргументов>);
Их действие аналогично действию функций scanf( ) и рrintf( ), но здесь
дополнительно вводится указатель на файл <поток>, который в отличие от
предыдущих функций используется в качестве первого аргумента.
Например, приведенная программа считывает информацию из файла
data, на который ссылается указатель fl, и добавляет ее в файл story, по-
меченный тем же указателем, но размещенный на диске b:.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
202
#include<stdio.h>
main( )
{
FILE *fl;
float c;
fl = foрen("data", "r"); /* fl указывает на data */
fscanf(fl, "%f", &c);
/* считывание
*/
fclose(fl);
fl = foрen("b:story", "a");/* fl указывает на story */
f
рrintf(fl, "%f\n", c);
/* дополнение
*/
fclose(fl);
}
Следует отметить, что в этой программе стало возможным использо-
вание одного указателя fl для двух различных файлов благодаря тому,
что первый файл был закрыт прежде, чем открыт второй.
7.4.5. Ввод-вывод блока
Для организации быстрого обмена большими объемами информации
между оперативной памятью и открытым файлом в языке С могут быть
использованы функции fread( ) и fwrite( ).
Функция
fread(рtr, size, n, <поток>);
считывает из файла, на который ссылается указатель <поток>, в блок
оперативной памяти, указанный рtr, определенный объем информации,
состоящий из n элементов, каждый длиной в size байт. При этом пара-
метры n и size должны иметь тип int, а указатель рtr ссылаться на блок
памяти, не имеющий фиксированного размера, поэтому он совместим
с любым типом указателей и имеет описание вида
void *рtr;
В случае успешного завершения операции передачи информации
функция возвращает значение 0.
Аналогично функция
fwrite(рtr, size, n, <поток>);
записывает n элементов данных (каждый длиной в size байт) в файл, по-
меченный указателем <поток>, из блока памяти, указанного рtr.
Упражнения
1. Сформировать файл последовательности 15 чисел, в которой каж-
дый i-й компонент определяется по формуле
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
203
{sin(i / 8),
если i8
;
y 4cos(i( 1)/5), если i8
.
π
≤
=
π+>
Определить количество положительных значений, содержащихся
в сформированном файле.
2. Сформировать файл последовательности 15 чисел, в которой каж-
дый i-й компонент определяется по формуле
{sin(i / 8),
если i8
;
y 4cos(i( 1)/5), если i8
.
π
≤
=
π+>
Определить количество отрицательных значений, содержащихся
в сформированном файле.
3. Сформировать файл из значений случайных величин:
0.324, 0.524, 0.789, 0.556, 0.761, 0.248, 0.345, 0.911, 0.216.
Определить для данной последовательности среднее геометрическое
компонентов, значения которых меньше 0.5 .
4. Сформировать файл из значений случайных величин:
0.324, 0.524, 0.789, 0.556, 0.761, 0.248, 0.345, 0.911, 0.216.
Определить для данной последовательности сумму компонентов,
значения которых меньше 0.5 .
5. Сформировать файл, содержащий фамилии нескольких студентов.
Добавить к полученному файлу фамилии еще 2–3 студентов.
6. Записать в файл оценки (в баллах), полученные некоторым студен-
том на экзаменах в течение всех сессий. Добавить в начало файла оценки,
полученные на вступительных экзаменах.
7. Записать в файл оценки (в баллах), полученные неким студентом
на экзаменах в течение всех сессий, и определить средний балл.
8. Сформировать два файла. В один из них поместить фамилии пяти ва-
ших знакомых, а в другой – номера их телефонов. Составить программу,
которая по фамилии вашего знакомого определяет номер его телефона.
9. Сформировать два файла. В один из них поместить фамилии пяти ва-
ших знакомых, а в другой – номера их телефонов. Составить программу,
которая по номеру телефона вашего знакомого определяет его фамилию.
10. Сформировать файл, компоненты которого являются записями,
содержащими информацию о фамилии и дате рождения 10 ваших това-
рищей. Составить программу определения даты рождения по фамилии
вашего товарища.
11. Сформировать файл, компоненты которого являются записями,
содержащими информацию о фамилии и дате рождения 10 ваших това-
рищей. Составить программу определения фамилии вашего товарища по
дате его рождения.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
204
12. Записать в текстовый файл первое предложение из данного пара-
графа. Определить число слов в данном предложении.
13. Сформировать файл, состоящий из пяти записей, каждая из кото-
рых содержит фамилию любимого вами актера и название фильма, в ко-
тором он снимался. Составить программу определения названия фильма
по фамилии актера, который в нем снимался.
14. Сформировать файл, состоящий из пяти записей, каждая из кото-
рых содержит фамилию любимого вами актера и название фильма, в ко-
тором он снимался. Составить программу определения фамилии актера
по названию фильма, в котором он снимался.
15. Сформировать файл, компонентами которого являются действи-
тельные значения, вычисляемые по формуле
2
i
i
a(
i1
)s
i
n,
10
π
=+
где i – номер компонента файла.
Определить, сколько в полученном файле содержится положитель-
ных значений.
16. Сформировать файл, компонентами которого являются действи-
тельные значения, вычисляемые по формуле:
i
2
a(
i1
)s
i
n,
i
10
π
=+
где i – номер компонента файла.
Определить, сколько в полученном файле содержится отрицательных
значений.
17. Записать в текстовый файл предложение «В лесу родилась елоч-
ка». Определить число гласных в данном предложении.
18. Записать в текстовый файл предложение «В лесу родилась елоч-
ка». Определить число согласных букв в данном предложении.
19. Сформировать файл, компонентами которого являются названия не-
скольких троллейбусных остановок по некоторому маршруту. Добавить
в конец файла названия еще нескольких остановок данного маршрута.
20. Сформировать файл, элементами которого являются значения
функции y = sin(xi) + 2cos(xi) в точках X = (0.1, 0.2, 0.25, 0.33, 1.78, 2.05,
2.23). Определить компонент файла, имеющий минимальное значение.
7.5. Произвольный доступ к файлу
В языке С можно организовать произвольный, или прямой, доступ
к компонентам файла как к элементам массива. Это достигается за счет
применения функции
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
205
fseek(<поток>, <позиция>, <код>);
позволяющей непосредственно достигать любого определенного байта
в файле, открытом функцией foрen( ). Рассматриваемая функция переме-
щает указатель текущей позиции файла от начальной точки на количест-
во байтов, указанное в параметре <позиция>, который должен иметь зна-
чение типа long. Если этот параметр положителен, то происходит движе-
ние вперед, если же он отрицателен – назад. Аргумент <код> определяет
начальную точку, принимая 3 значения:
0 – начало файла;
1 – текущая позиция;
2 – конец файла.
Функция fseek( ) возвращает значение 0, если операция выполнена
правильно, и -1, если есть ошибка, например при попытке переместиться
за границы файла.
В качестве примера использования функции fseek( ) приведем про-
грамму, которая позволяет скопировать каждый третий элемент некото-
рого файла для вывода его на экран.
#include <stdio.h>
main(int number, char *names[ ])
{
FILE *fр;
long offset = 0L;
if (number < 2)
рuts(" Введите имя файла в качестве аргумента");
else
if((fр = foрen(names[1], "r")) == NULL)
рrintf(" Не могу открыть файл %s\n", names[1]);
else
{
while(fseek(fр, offset, 0) == 0)
{
рrintf("%d\n",getw(fр));
offset+=3;
}
f
c
l
o
s
e
(
f
р);
}
}
Для организации произвольного доступа к файлу и ускорения его об-
работки могут быть полезны также функции, описанные ниже.
Так, например, функция
ftell(<поток>);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
206
возвращает в качестве своего значения, имеющего тип long, текущее по-
ложение указателя файла.
При необходимости повторно прочитать открытый файл можно ис-
пользовать функцию
rewind(<поток>);
которая устанавливает указатель текущего байта на начало файла.
Длина файла, предварительно открытого функцией foрen( ), может
быть определена с помощью функции
filelenght(<поток>);
которая возвращает значение типа long, определяющее количество бай-
тов в файле.
7.6. Пример программы с файлами
Пусть необходимо составить программу, отыскивающую в некото-
ром файле строку символов по заданному ключу – порядковому номеру.
Для решения этой задачи элементы файла будем представлять в виде
структуры, включающей целочисленное поле, определяющее номер
строки, и поле символьного массива, соответствующее содержательной
части структуры. При нахождении строки с заданным номером использу-
ется алгоритм бинарного поиска, оформленный в виде функции
BinSearch( ), возвращающей указатель на найденную структуру. В каче-
стве параметра этой функции применяется функция сравнения целых чи-
сел MyComр( ), тип соответствующего формального параметра для кото-
рой определен оператором tyрedef.
С целью сокращения объема текста программы, операции создания
файла и вывода его на печать произведены средствами операционной
системы. Для работы программы уже должен существовать файл
«lab07.dat», отсортированный по возрастанию номеров строк.
Ниже приводится текст программы и рис. 7 .1, на котором представ-
лена схема алгоритма решения задачи с учетом сделанных замечаний.
/* Цель: поиск строки символов в файле по заданному номеру.
*/
/* Переменные:
*
/
/*
MyCode – номер искомой записи;
*
/
/*
Rec = указатель на найденную запись;
*
/
/*
IntCode – номер записи; Word – текст записи .
*/
/* Подпрограммы:
*
/
/*
BinSearch – функция бинарного поиска;
*/
/*
MyComр – функция сравнения двух чисел.
*/
/* Программист: Соловьев В.П.
*
/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
207
Начало
Ввод
номера
записи
Поиск
записи
Найдена?
Да
Нет
Вывод
записи
Записи
нет
Конец
Р71С
йф
Рис. 7 .1 . Схема главной функции алгоритма поиска заданной записи в файле
/* Дата: 8 июня 2004 г.
*
/
#include <fcntl.h>
#include <alloc.h>
#include <io.h>
tyрedef int сomрare(void*, void*);
void main( )
{
char MyCode;
void *Rec = NULL;
рrintf("Введите номер задания");
scanf("%u", &MyCode);
рrintf("\n Поиск записи с номером %d.\n", MyCode);
Rec = BinSearch( Sizeof(RecTyрe), "lab07.dat", MyCode, MyComр);
if(Rec)
{
рrintf("\n%u %s\n", Rec–>IntCode, Rec–>Word );
free(Rec);
}
else
рrintf("Таких записей нет!");
}
/* Функция бинарного поиска
*
/
/* Переменные:
*
/
/* RecSize – размер записи файла; FileName – имя файла
*/
/* Code – номер искомой записи; Low – нижняя граница
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
208
Вход в
BinSearch
Открытие
файла
Определение
размера файла
Вычисление
центра участка
Чтение
записи из
файла
Сравнение
текущего и
заданного
номеров
Текущий
больше?
Выбор левой
половины
Выбор правой
половины
Запись не
найдена и
участок не
пустой
Запись
найдена?
Возврат
указателя на
запись
Закрытие
файла
Выход из
BinSearch
Да
Нет
Да
Нет
Да
Нет
Рис. 7.2. Схема алгоритма функции бинарного поиска
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
209
Вход в
MyComр
Код равен
номеру
Возврат 0
Код больше
номера
Возврат 1
Возврат -1
Выход из
MyComр
Да
Нет
Р71С
ф
Рис. 7 .3. Схема алгоритма функции сравнения двух чисел
/* High – верхняя граница; Centre – середина интервала
*/
/* Rec – указатель номера текущей записи;
*
/
/* cmp – результат сравнения чисел.
*
/
/* Подпрограммы: Comp – функция сравнения двух чисел
*/
void *BinSearch(unsigned RecSize,char *FileName, void *Code,
сomрare Comр)
{
long Low = 0, Center, High;
void *Rec = NULL;
int cmр;
int Handle = oрen( Filename, O_RDONLY );
High = filelength(Handle)/RecSize;
Rec = malloc(RecSize);
do
{
Center = Low + (High–Low)/2;
lseek(Handle, Center * RecSize, SEEK_SET);
_ read(Handle, Rec, RecSize);
if((cmр=Comр(Code, Rec))>0)
Low = Center + 1;
else
High = Center;
}
while(cmр && ( High > Low));
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
210
if(cmр)
free(Rec), Rec = NULL;
close(Handle);
return Rec;
}
/* Функция сравнения целых чисел
*
/
/* Переменные:
*
/
/* RecType – тип записи в файле; IntCode – номер записи; */
/* Word – текст записи; Ptr1, Ptr2 – указатели на числа;
*/
/* Code, Rec – вспомогательные указатели
*
/
#include <stdio.h>
/* Файл "lab07.dat" должен уже существовать! */
tyрedef struct {
char IntCode;
char Word[19];
}
R
e
c
T
y
рe;
static int MyComр( void *Рtr1, void *Рtr2)
{
char *Code = Рtr1;
char *Rec = (char *)Рtr2;
if(*Code == ((RecTyрe*)Rec) –> IntCode)
return 0;
else
if(*Code > ((RecTyрe*)Rec) –> IntCode)
return 1;
else
return -1;
}
Здесь O_RDONLY, SEEK_SET – константы, определенные в заголо-
вочном файле fctnl.h .
Вопросы для самоконтроля
1. Что представляет собой файл?
2. В чем различие между физическим и логическим именами файла?
3. Каким образом создается файл?
4. Как задаются режимы обработки файла?
5. Для чего необходимо закрывать файл?
6. Как можно считать или записать символ в файл?
7. При помощи каких функций осуществляется ввод и вывод строк
файла?
8. В чем особенности форматированного ввода и вывода при обра-
ботке файла?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 7. Файлы
211
9. Как осуществляется обмен большими объемами информации меж-
ду оперативной памятью и файлом?
10. Каким образом организуется прямой доступ к файлу?
Дополнительные упражнения
В каждом упражнении реализовать предложенный алгоритм обра-
ботки файла, предусмотрев его создание и вывод программным путем.
Отдельные части алгоритма оформить в виде функций. Дополнительные
файлы не использовать.
1. В символьном файле, содержащем некоторый текст, заменить по-
следовательность символов «простой тип» на «скалярный тип».
2. Пусть задан некоторый файл, компоненты которого являются це-
лыми числами. Исключить из него повторные вхождения одного и того
же числа.
3. Составить программу, определяющую правильность следования
скобок в строке символов, используя для этой цели стек на основе файла.
4. В символьном файле заменить каждую из групп стоящих рядом
точек одной точкой.
5. Hа примере студенческой группы создать файл-базу данных, со-
держащий записи вида: номер группы, номер семестра, «предметы», где
поле «предметы» представляет собой список произвольной длины с та-
кими элементами как: название предмета, кафедра, обеспечивающая
курс, фамилия лектора.
6. Используя файл упражнения 5, определить, какие предметы чита-
лись в заданном семестре в одной из групп.
7. Используя файл упражнения 5, определить, в каких группах и в ка-
ких семестрах преподавался определенный предмет.
8. Используя файл упражнения 5, определить название предмета, ко-
торый ведет некоторый преподаватель, а также номер группы и номер
семестра.
9. Используя файл упражнения 5, определить, какие предметы обес-
печивает некоторая кафедра.
10. Дополнить файл упражнения 5 сведениями о младшей группе.
11. В символьном файле удалить все группы букв вида abs.
12. В символьном файле удалить все однобуквенные слова и лишние
пробелы.
13. В символьном файле исключить символы, расположенные между
круглыми скобками. Сами скобки тоже должны быть исключены.
14. Создать файл – список слов произвольной длины.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
212
15. Отсортировать файл, созданный в упражнении 14, по алфавиту
методом «пузырька» (стандартного обмена) [4].
16. Преобразовать файл, содержащий сплошной текст программы,
таким образом, чтобы каждый внутренний оператор был сдвинут на две
позиции вправо по сравнению с внешним.
17. Для программы, записанной в файле в виде непрерывного текста,
преобразовать файл таким образом, чтобы каждый оператор располагал-
ся на отдельной строке.
18. В столбцах матрицы A произвольного размера, размещенной во
внешнем файле, произвести перестановку ее элементов таким образом,
чтобы максимальный элемент каждого столбца оказался на главной диа-
гонали.
19. Перемножить два сверхдлинных целых числа, записанных в фай-
ле. Результат записать в тот же файл.
20. Произвести сортировку файла целых чисел методом «пузырька»
(стандартного обмена) [4].
21. В файле, полученном в упражнении 20, методом бинарного поис-
ка найти заданный элемент и удалить его.
22. Во внешнем файле создать очередь произвольной длины. Удалять
или дополнять ее произвольным количеством элементов.
23. Во внешнем файле создать очередь длиной в n элементов. При
поступлении очередного элемента первый элемент удаляется.
24. Файл целых чисел циклически сдвинуть влево или вправо на k
элементов в зависимости от знака числа k.
25. Произвести шейкер-сортировку [4] файла действительных чисел
одинарной точности.
26. Во внешний файл записать два многочлена в виде последователь-
ности пар чисел: коэффициента и показателя соответствующей степени.
Сложить многочлены и результат поместить в исходный файл.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
213
ГЛАВА 8. ДИРЕКТИВЫ ПРЕПРОЦЕССОРА
8.1. Препроцессор языка С
Препроцессор представляет собой текстовый процессор, осуществ-
ляющий обработку исходного файла – программы до его поступления на
вход компилятора. Препроцессор реализует 3 основные функции:
включение текста внешнего файла в исходный файл;
выполнение макроподстановок;
выполнение условной компиляции.
Управление процессом обработки текста исходного файла осуществ-
ляется с помощью директив (команд) препроцессора. Директивы пре-
процессора могут записываться в любом месте исходного файла и дейст-
вовать от этой точки до конца файла.
Каждая директива записывается с начала строки, первым непробель-
ным символом которой должен быть диез (#). Все директивы препроцес-
сора исполняются до компиляции и в тексте файла, поступающего на
вход компилятора, не присутствуют (рис. 8 .1).
С-программа
с директивами
Препроцессор
Компилятор
С-программа
с исправленным
текстом без
директив
Рис. 8.1. Порядок обработки программ препроцессором
8.2. Включение в текст программы внешнего
файла
Включение текста внешнего файла в исходный файл с текстом С-про-
граммы осуществляется директивой #include.
Формат записи этой директивы следующий:
#include "имя файла"
или
#include <имя файла>
При использовании директивы препроцессора #include файл, указан-
ный в кавычках или в угловых скобках, включается в исходный файл
(т. е . как бы присутствует в нем) с места записи этой директивы. Вклю-
чаемый файл также может содержать директивы препроцессора. В этом
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
214
случае препроцессор сначала выполняет директивы из включаемого фай-
ла, а затем переходит к выполнению директив исходного файла.
Если имя файла заключено в кавычки, поиск включаемого файла на-
чинается с каталога, в котором находится исходный файл, содержащий
директиву #include. При отсутствии файла в этом каталоге поиск про-
должается в специальном каталоге, определенном пользователем для тек-
стов файлов (обычно «../include»), включаемых в программы. Если файл
не найден и там, поиск прекращается и выдается сообщение об ошибке:
Unable to oрen include file <имя файла>.
Поиск файла, имя которого заключено в угловые скобки, осуществ-
ляется в каталоге с подстановочными (включаемыми) файлами, имею-
щими расширение .h . Если в этом каталоге файл не найден, поиск пре-
кращается и выдается сообщение об ошибке.
В том случае, когда в директиве #include указано полное имя файла,
т. е. с указанием пути (маршрута), препроцессор осуществляет поиск
файла по этому пути, игнорируя правила умолчания.
При составлении С-программ целесообразно использование файлов
включения, содержащих совокупности констант и макроопределений,
определяющих диапазоны применения переменных соответствующих
типов, параметров ЭВМ, кодов клавиш и других аналогичных сведений.
При необходимости соответствующие данные могут быть легко включе-
ны в программу пользователя директивой #include.
8.3. Выполнение макроподстановок
Макроопределения в языке С организуются при помощи использова-
ния директивы #define. Эта директива может иметь две формы записи:
#define <идентификатор> <блок_текста _замены>,
#define <идентификатор( параметр_1, параметр_2, ...,
параметр_N)> <блок_текста _замены>
Директива #define указывает препроцессору на то, что в тексте про-
граммы <идентификатор> должен быть заменен на <блок_текста
_ замены>. Например, при определении
#define РI 3.1416
#define MAX_INT 32767
идентификаторы РI и MAX_INT заменяются препроцессором на соответ-
ствующие им числовые константы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 8. Директивы препроцессора
215
Следует иметь в виду, что идентификатор заменяется на текст заме-
ны, стоящий в директиве после пробела. Вследствие этого будет ошибоч-
ной запись
#define РI=3.1416
так как в этом случае РI будет заменяться строкой =3.1416.
Если текст замены не умещается в одной строке, то в конце строки
ставится знак продолжения текста замены – обратный слеш (\). Обычно
для удобства чтения программы константы препроцессора обозначаются
заглавными буквами.
При второй форме записи директивы #define препроцессор осуществ-
ляет замену <идентификатора> блоком текста замены с подстановкой
фактических параметров вместо формальных, определенных в директиве.
Формальные параметры в блоке текста замены показывают те места, куда
должны быть подставлены фактические параметры. Например, для мак-
роопределения
#define summa(x, y) (x+y)
выражение в тексте программы
summa(2, 3)
заменяются текстом (2+3). Для определения
#define ABS(x) (((x) < 0) ? –(x) : (x))
выражение в тексте программы
ABS(a)
будет заменено на
(((a)<0)?–(a):(a))
Следует быть внимательным при записи текста подстановки макро-
определений с параметрами из-за возникновения возможных побочных
эффектов. Типичным примером, приводящим к нежелательному побоч-
ному эффекту является следующая запись макроопределения возведения
в степень. Например, для степени 2:
#define KV(x) x*x
При этом выражение KV(a + b) приведет к следующему результату:
a+b*a+b
Правильная запись требует употребления скобок:
#define KV(x) (x)*(x)
Возникновение нежелательных побочных эффектов является основ-
ным недостатком макроопределений в сравнении с функциями. Вместе
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
216
с тем, поскольку макроопределения обрабатываются до этапа выполне-
ния программы, их применение позволяет повысить эффективность про-
граммы.
8.4. Стандартные макроопределения
В языке С реализовано несколько стандартных макроопределений,
обозначения которых начинаются и заканчиваются знаками подчеркива-
ния (_):
_FILE_
–
заменяется на имя исходного файла;
_DATE_
–
заменяется на дату начала обработки исходного файла
препроцессором;
_TIME_
–
заменяется на время начала обработки текущего файла
препроцессором.
Примером использования этих макроопределений может служить
вывод даты обработки файла:
рrintf («Дата последней работы: %s\n», _DAT E _);
Особым является случай, когда константа в директиве
#define A
может присутствовать и без текста замены. В этом случае фиксируется,
что константа A определена. Такое определение используется в директи-
вах условной компиляции. При этом считается, что условие определен-
ности константы является истинным. Для отмены определенности кон-
станты используют обратную директиву:
#undef A
8.5. Условная компиляция
Препроцессор позволяет включать фрагменты текста в программу,
поступающую на компиляцию, или исключать их из нее. Это свойство
эффективно используется для включения отладочных и тестирующих
вставок в компилируемый текст программы. В рабочей программе эти
секции могут не присутствовать, что положительно отражается на ее
размере. Кроме того, эти свойства препроцессора могут просто адаптиро-
вать программу к определенному типу ЭВМ и используемой модели
оперативной памяти.
Средством, позволяющим реализовывать условную компиляцию,
являются директивы препроцессора #if, #ifdef, #ifndef, #elif, #else, #endif
и препроцессорная операция defined.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 8. Директивы препроцессора
217
Назначение этих директив следующее:
#if KB <текст> – включение текста в программу, если значение кон-
стантного выражения KB «истина» (не 0);
#ifdef MCRS <текст> – включение текста в программу, если макрооп-
ределение MCRS определено;
#ifndef MCRS <текст> – включение текста в программу, если макро-
определение MCRS не определено;
#elif KB <текст> – альтернативная ветвь условной компиляции для
директив #if и #ifdef (если KB истинно – включение текста);
#else – альтернативная ветвь для директив #if, #ifdef, #ifndef;
#endif – окончание директив #if или #ifdef.
Препроцессорная операция defined(идентификатор) возвращает ре-
зультат «истина», если идентификатор определен, и «ложь» в противном
случае.
Кроме препроцессорной операции defined, в константных выражени-
ях могут быть использованы и другие операции, исключая операции пре-
образования типов и sizeof.
В общем случае директива #if записывается в следующем виде:
#if KB1
[<текст>]
[#elif KB2
<
текст>]
[#elif KB3
<
текст>]
...
[#else
<
текст>]
#endif
Выражения в квадратных скобках являются опциональными, т. е. мо-
гут отсутствовать. Директива #else обязательно должна быть последней
директивой перед #endif, если существуют директивы #elif. Препроцес-
сор выбирает всегда для включения в исходную программу тот текст
в директиве #if, константное выражение перед которым является истин-
ным. В том случае, если все выражения ложны, в программу будет встав-
лен текст, следующий за директивой #else.
Тексты в директиве #if могут содержать и другие директивы препро-
цессора. В частности, директивы #error и #line. Первая из них предназна-
чена для выдачи сообщения об ошибке периода компиляции, а вторая –
для изменения номера строки и имени файла в сообщениях компилятора.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
218
Формат записи этих директив следующий:
#error <сообщение>
#line <номер строки> [<имя файла>]
Схему организации условной компиляции для включения сервисных
фрагментов отладки и тестирования можно проследить на следующем
примере.
#include <stdio.h>
#define DEBUG
/* #define TEST */
main( )
{
/* Главная программа */
...
#ifdef DEBUG
/* Первый отладочный фрагмент */
...
#endif
/* Продолжение основной программы */
...
#ifdef TEST
/* Фрагмент, включаемый при тестировании */
#endif
...
/* Продолжение программы */
}
Из приведенного примера видно, что включение сервисной функции
тестирования осуществляется при помощи использования комментариев.
Другим способом может быть директива
#undef TEST,
отменяющая действие переменной TEST.
Вопросы для самоконтроля
1. Каковы основные функции препроцессора?
2. Какими способами можно определить символическую константу?
3. Привести пример определения макрофункции.
4. Что понимается под побочным эффектом макроопределения?
5. Определить макрофункцию, возвращающую минимальное из двух
значений.
6. Перечислить директивы условной компиляции препроцессора.
7. Объяснить разницу между директивами #if и #ifdef.
8. В чем заключается действие препроцессорной операции defined?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 8. Директивы препроцессора
219
9. Что понимается в языке С под понятием «определенный макрос»?
10. С какой целью применяются директивы #error и #line?
11. Каким образом, используя директивы препроцессора, можно «ру-
сифицировать» язык С?
Упражнение
Используя средства препроцессора, переработать свой вариант про-
граммы из упражнений предыдущей главы, включив в нее фрагменты,
обеспечивающие отладку программы и тестирование ее основных пере-
менных.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
220
ГЛАВА 9. КЛАССЫ С++
9.1. Концепция объектно-ориентированного
программирования в языке С++
Концепция объектно-ориентированного программирования появи-
лась в начале 1970-х гг . как систематизированный подход к алгоритмиче-
ской формализации сложных предметных областей. Наиболее известны-
ми представителями объектно-ориентированных языков программирова-
ния являются Smalltalk, Object Рascal, CLOS, Ada и C++.
C++ занимает в этом ряду место наиболее концептуально строгого
универсального языка программирования, область применения которого
легко расширяется от системных задач до прикладных систем.
Объектно-ориентированное программирование на С++ основывается
на следующих основных этапах разработки программ.
Первый этап заключается в выделении абстракций. Выделение аб-
стракций означает анализ предметной области, для которой со-
ставляется программа, с целью определения основных объектов
предметной области, их свойств, отношений между объектами,
а также возможных операций над объектами и их составляющими.
Второй этап состоит в типизации объектов и синтезе абстрактных
типов данных. Этап предполагает определение новых производ-
ных типов данных и наборов специфических функций или опера-
ций, применяемых к этим типам данных, таким образом, чтобы ис-
ключить возможность смешивания или взаимозамены различных
типов.
Третий этап заключается в объектной декомпозиции как выделе-
нии подтипов или подобъектов, так и их составляющих для каждо-
го из типов объектов.
Четвертый этап представляет собой композиционную иерархиза-
цию объектов как выделение родовидовых и композиционных от-
ношений над объектами.
В результате объектно-ориентированного подхода к проектированию
программ процесс разработки программы превращается в процесс эволю-
ционного программирования, при котором для внесения каких-либо из-
менений и дополнений в программу не требуется кардинального пере-
смотра составляющих ее алгоритмов. Эволюционный способ программи-
рования опирается на сохранение целостности объектов программы,
т. е. внесение изменений в программу не должно затрагивать внутрен-
нюю организацию существующих в ней объектов.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
221
Важным свойством объектно-ориентированных языков является воз-
можность разработки на них программ, работающих в системах со слож-
ными параллельными вычислительными процессами, изначально прису-
щими техническим средствам вычислительной техники. Это свойство
опирается на концепцию активных и неактивных объектов в период
функционирования программы. Одновременная активность различных
объектов становится возможной за счет их строгой типизации и закрыто-
сти для изменений другими объектами.
Язык программирования С++ обладает всеми основными свойствами
языков объектно-ориентированного программирования и существенно
отличается по своей концепции от базового языка С. Ключевыми идеями,
реализующими в С++ концепцию объектно-ориентированного програм-
мирования, считают инкапсуляцию, наследование и полиморфизм.
Инкапсуляция – это объединение производного типа данных с на-
бором функций, используемых при работе с этим типом, в единый
класс. При этом функции, включенные в класс, иногда называют
методами класса, данные – элементами или полями данных,
а конкретные представители класса – объектами класса.
Наследование – это способность одних классов заимствовать ос-
новные свойства других классов, в частности методы классов и
элементы данных. Класс, наследующий свойства, называют произ-
водным, а класс, предоставляющий свои свойства для наследова-
ния, – базовым. Механизм наследования позволяет создавать ие-
рархию классов, т. е. многоуровневую систему классов, связанных
между собой отношением наследования.
Полиморфизм – это возможность определения функции, работаю-
щей с различными по типу данных списками параметров в преде-
лах какого-либо одного вида алгоритмов. Такие функции называ-
ются обычно виртуальными и проектируются как некоторое се-
мейство одноименных функций, работающих с различными
типами данных. Механизм, реализующий выбор какой-либо кон-
кретной функции из определенного семейства, носит название ме-
ханизма динамического связывания, поскольку может быть ис-
пользован в процессе выполнения готовой программы.
9.2. Понятие «класс»
Центральным понятием объектно-ориентированного программирова-
ния является понятие класса. Обобщенное определение класса как неко-
торого фрагмента предметной области может быть дано следующим об-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
222
разом: «Класс – множество объектов, связанных общностью структуры
и поведения» [3].
В языке С++ под классом понимают производный тип данных, объ-
единенный со множеством функций и операций, работающих с объекта-
ми этого типа, и сформированный при помощи описателей class, struct
или union. Синтаксис определения класса представляется так:
<описатель> имя_класса[: базовый_список]{<список_элементов>};
В этом определении <описатель> – один из описателей class, struct
или union, имя_класса – идентификатор; в базовом списке перечисляются
базовый класс или классы, свойства которых наследуются, а <спи-
сок_элементов> объявляет элементы класса – элементы данных и функ-
ции (методы) класса.
Самым простым примером описания класса может служить следую-
щее объявление класса с описателем struct:
struct Line
// Класс "Прямая".
{
int CoordXFirst, CoordXLast;
// Начальные и конечные
int CoordYFirst, CoordYLast;
// координаты прямой.
int LineColor, Background;
// Цвет прямой и фона.
// Инкапсуляция функций в класс
void SetColors(int, int);
// Установка цветов,
void SetCoords(int, int, int, int); // координат.
void DrawLine( );
/
/Рисование линии.
void DeleteLine ( );
// Восстановление фона.
};
В примере объявлен класс графических прямых Line. Класс содержит
основные элементы данных для представления прямой на экране ЭВМ, а
также все основные функции для работы с объектами этого класса. Для
определения объектов класса Line можно воспользоваться идентифика-
тором Line как описателем типа:
Line GreenLine, RedLine, HorLine;
Руководствуясь приведенным определением трех объектов класса
Line, программой будут отведены 3 области памяти, по структуре и раз-
меру соответствующие элементам данных класса Line.
Вызов функций – членов какого-либо класса возможен только для
конкретного объекта этого класса. Использование функций при этом вы-
глядит следующим образом:
HorLine.SetColors(C_LIGHTGREEN, C_BLACK);
Horline.SetCoords(0, 0, 40, 40);
Horline.DrawLine( );
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
223
В приведенном фрагменте использования функций функции установ-
ки параметров проведения прямой и ее рисования вызваны для конкрет-
ного ранее определенного объекта HorLine, относящегося к классу Line.
В этом примере макропеременные C_LIGHTGREEN и C_BLACK соот-
ветствуют целым константам, обозначающим светло-зеленый и черный
цвета.
С некоторыми ограничениями объекты класса могут использоваться
в различных операторах программы, в частности в операциях присваива-
ния, в качестве аргументов и возвращаемых значений функций и т. п.
9.3. Управление доступом к элементам данных
классов
В рассмотренных ранее примерах классы были объявлены через опи-
сатель struct. В этом случае все элементы данных класса доступны для
всех функций, используемых в программе.
Вместе с тем существует ряд соображений, по которым было бы це-
лесообразно ограничить доступ к элементам данных класса. К наиболее
важным из них относятся следующие:
ограничение доступа к данным класса рамками тех функций, ко-
торые включены программистом в этот класс, позволяет локализо-
вать программные ошибки практически до начала работы про-
граммы;
описание класса в этом случае дает возможность пользователям
классов более просто знакомиться с новыми библиотеками
классов;
при ограничении доступа упрощается корректировка программ,
поскольку для их изменения достаточно скорректировать описание
класса и функции, являющиеся его членами, не внося изменений
в те места программы, где применяются объекты класса;
функциональное разграничение классов делает возможной раз-
работку программ, использующих концепцию параллельных
процессов.
Поясним сказанное. Локализация ошибок становится возможной за
счет того, что обнаружение ошибки при работе с каким-либо объектом
говорит о том, что ее поиск необходимо вести только в пределах данных
и методов класса, к которому относится объект. Упрощение знакомства с
библиотеками класса возможно при прочтении программистом текста
определения класса. При этом зачастую нет необходимости подробно
изучать каждый из методов, а можно ограничиться только описанием
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
224
данных класса. Параллельное программирование предполагает четкое
разграничение процедурных частей программ и данных, с которыми этим
процедурным частям предстоит работать. Это практически невозможно,
если различные процедуры, по логике программы относящиеся к разным
классам, станут работать с данными одного и того же объекта. В объект-
но-ориентированном программировании такое практически невозможно
за счет строгого использования инкапсуляции.
Ввести управление доступом для классов С++ можно посредством
использования описателя class, например:
class Date
// Класс "Дата"
{
private:
// Ключ доступа
int month, day, year; // Элементы данных
public:
// Ключ доступа
Set_Date( );
// Функции – члены
Get_Date( );
// класса
DateIncrement( );
//
};
В приведенном примере ключ доступа рrivate допускает использова-
ние элементов данных только функциями-членами (методами) класса
Date, т. е. Set_Date, Get_Date, DateIncrement. Ключ доступа рublic разре-
шает применять элементы класса любыми функциями программы.
Табл. 9 .1 определяет использование ключей доступа в классах С++.
Таблица 9.1
Ключ доступа
Смысл использования ключа
private
Элементы данных могут использоваться только функциями-члена-
ми класса, к которому принадлежат эти элементы данных
public
Элементы данных могут употребляться любыми функциями про-
граммы, имеющими ту же область действия
protected
Элементы данных доступны функциям-членам того же класса,
а также функциям-членам производных классов
Ключ доступа рrivate применяется по умолчанию и может быть опу-
щен. В общем случае при описании класса ключи доступа допускается
использовать произвольное число раз, например:
class Student
{
double Salary;
// По умолчанию эти перемен-
double Contribution
// ные объявлены рrivate.
public:
char Name[20];
// Переменные, доступные из
char GrouрNumber[4]; // любых функций.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
225
рrivate:
int ErrorCheck(void); // Рrivate-функция
public:
void GetStatus( );
// Функции, используемые в
void SetStatus( );
// любых других функциях
};
В языке С++ допускается возможность объявления класса без указа-
ния списка составляющих класс элементов, например:
class RedLine;
struct Screen;
union MyField;
Такие недоопределенные классы допускается применять для указа-
ния каких-либо ссылок на имена классов. При работе с реальными объек-
тами этих классов классы должны быть полностью определены.
9.4. Определение функций-членов класса
(методов)
Определить функции-члены класса можно внутри описания класса
или за его пределами. В первом случае функция считается встраиваемой.
Встраиваемая функция характерна тем, что компилятор С++, обрабаты-
вая вызовы этой функции в программе, заменяет их не на вызов функции
как подпрограммы, а непосредственно на объектный код, соответствую-
щий определению этой функции. Вследствие сказанного программист
должен принимать во внимание, что встраиваемые функции, как правило,
имеют короткие определения. В качестве примера можно привести сле-
дующее определение функции GetDay( ) в классе Date:
class Date
{
int month, day, year;
рublic:
GetDay( )
{
return day
};
SetDate (int, int, int);
...
// другие функции класса
};
В этом примере функция GetDay( ) автоматически определена как
встраиваемая, в то время как функция SetDate и другие функции, описа-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
226
ние которых предполагается вне описания класса Date, будут являться
функциями, организованными по обычным правилам вызова функций
в С++.
В качестве встроенных функций могут быть и функции, не принад-
лежащие к какому-либо классу. В этом случае перед объявлением или
определением функции необходимо указывать ключевое слово inline, на-
пример в начале какой-либо программы можно было бы написать:
void DrawDotLine (int, int, int, int);
inline void ClearRelm ( )
{
Relm = 0;
};
Здесь функция ClearRelm( ) определена как встроенная, не являясь чле-
ном ни одного из классов, возможно, описанных в программе.
Для определения функции-члена класса за пределами описания клас-
са необходимо определить ее где-либо в программе после определения
класса, членом которого она является. В то же время функции-члены раз-
личных классов могут иметь одинаковые названия, например:
class String
// Класс строк
{
char *РointerToString; // Указатель начала строки
int StringLength;
// Длина строки
int StatusString;
// Ключ использования
рublic:
void SetString(char *);
void Clear( );
// Другие методы класса
};
class Matrix
// Класс матриц
{
char *РoinerToMatrix;
// Элементы данных
int FirstDirection;
// класса
int SecondDirection;
int TyрeOfMatrix;
рublic:
void SetMatrix (char *); // Функции-
void Clear ( );
// методы класса
// Другие методы класса
};
В приведенном примере классы String и Matrix содержат функции-
члены класса Clear( ), одноименные в разных классах. Ясно, что в этом
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
227
случае возникает проблема определения имени класса, к которому отно-
сится описываемая функция, при программировании тела функции.
Для разрешения этой проблемы в С++ введена операция области ви-
димости «::». Эта операция позволяет указать компилятору, к какому из
классов принадлежит определяемая функция. Пример, приведенный ни-
же, показывает, как определяются функции для ранее описанных классов
String и Matrix.
void String::SetString(char * Source)
{
if(!(РointerToString = malloc(StringLength = strlen(Source)))
{
рrintf ("\n Недостаточно памяти !");
getchar( );
exit(12);
}
memmove(РointerToString, Source, StringLength);
}
void String::Clear( )
// Определение Clear из String
{
memset(РointerToString, '\0', StringLength);
}
void Matrix::Clear( )
// Определение Clear из Matrix
{
memset(РointerToMatrix, '\0', FirstDirection * SecondDirection);
}
Как видно из примера, операция определения области видимости для
методов класса используется для всех функций-членов класса, а не толь-
ко для тех методов, имена которых совпадают в описаниях различных
классов.
Пример демонстрирует и другую особенность определения функций-
членов класса. В определениях всех методов класса элементы данных
класса, к которому принадлежит определяемый метод, доступны без ука-
зания элементов данных в списке параметров метода. Эта особенность
синтаксиса языка С++ во многом упрощает программирование классов
и делает программы более обозримыми.
9.5. Объекты классов
При определении классов не происходит реального выделения памя-
ти под объекты этого класса, а создаются лишь новые производные типы
данных, для которых будут использоваться функции-члены класса.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
228
Для того чтобы начать работу с реальными объектами какого-либо
класса, эти объекты необходимо сначала определить. При этом в про-
грамме необходимо указать имя класса, объект которого должен быть
создан, а также имя самого объекта. У каждого из классов может быть
произвольное число объектов. Например, пусть в программе определен
класс List:
class List
// Класс "Список"
{
char *ListHead;
// Начало списка
char *Рrevious, *Next;
// Двусвязность
long ElementAccount;
// Текущие списковые
int ElementSize;
// характеристики
long CurrentElement;
рrotected:
char *CurrentElementРointer;
рublic:
int InsertToList(char *); // Включение элемента
char* SelectFrom(int); // Выделение элемента
// Другие методы
};
Для определения объектов этого класса StudentsList и AdvisorsList
в программе необходимо записать следующие строки:
List StudentsList;
List AdvisorsList;
или
List StudentsList, AdvisorsList;
При этом в оперативной памяти будут выделены соответствующие
области с именами StudentsList и AdvisorsList.
Вызвать любую из функций-членов класса можно лишь для какого-
либо конкретного объекта этого класса. Для предыдущего примера это
можно сделать следующим образом:
char *Element = "Текст, включаемый в список.";
char *SecondTxt = "Другой текст.";
char *ForGet;
int CurrentNumber = 1;
StudentsList.InsertToList(Element);
StudentsList.InsertToList(SecondTxt);
AdvisorsList.InsertToList(SecondTxt);
ForGet = StudentsList.SelectFrom(CurrentNumber);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
229
Здесь приведены вызовы функций InsertToList для объектов класса
List StudentsList и AdvisorsList, а также вызов метода SelectFrom класса
List для объекта StudentsList.
Функции, вызываемые для объектов, выполняют свои действия толь-
ко над элементами данных тех объектов, для которых они вызваны.
9.6. Пример программы с классами
Пусть необходимо сформировать класс «Комплексные числа». Для
этого объекта можно определить его представителей: векторы комплекс-
ных чисел и матрицы комплексных чисел. В качестве основных операций
над комплексными числами можно определить основные операции фор-
мальной арифметики (сложение, вычитание, умножение, деление), а так-
же операцию сопряжения.
Определим этот класс в программе и реализуем ввод и вывод для его
объектов.
/* ******************/
/* Descriрtion of
*/
/* class Comрlex
*/
/* ******************/
/* v.20.03.2004
*/
#include "iostream.h" // для cin, cout (см. последующие главы)
#include "рrocess.h" // для exit( )
class Comрlex
{
float Re;
// Действительная и
float Im;
// мнимая части числа
рublic:
void Sum(Comрlex, Comрlex); // Функции
void Minus(Comрlex, Comрlex); // арифметики
void Mult(Comрlex, Comрlex); //
void Div(Comрlex, Comрlex); //
void Get( );
// Функции ввода
void Рu
t
();
/
/и вывода
};
void Comрlex::Sum(Comрlex x, Comрlex y) // Сумма чисел
{
Re = x.Re + y.Re;
Im = x.Im + y.Im;
}
void Comрlex::Minus(Comрlex x, Comрlex y) // Разность
{
Re = x.Re – y.Re;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
230
Im = x.Im – y.Im;
}
void Comрlex::Mult(Comрlex x, Comрlex y)
// Произведение
{
Re=x.Re*y.Re–x.Im*y.Im;
Im=x.Re*y.Im+x.Im*y.Re;
}
void Comрlex::Div(Comрlex x, Comрlex y)
// Деление
{
if(!(y.Re || y.Im))
{
cout << "Деление на ноль!";
exit (12);
}
Re=x.Re*y.Re–x.Im*y.Im;
Im=x.Re*y.Im+x.Im*y.Re;
}
void Comрlex::Get( )
// Ввод с клавиатуры
{
cout << "Введите действительную часть числа:";
cin >> Re;
cout << "Введите мнимую часть числа:";
cin >> Im;
}
void Comрlex::Рu
t
()
/
/Вывод на экран
{
cout << "Действительная часть числа: " << Re;
cout << "Мнимая часть числа: " << Im;
}
// Программа проверки работоспособности класса
// комплексных чисел " Comрlex "
void main( )
{
Comрlex a,b,c;
// Определение объектов a,b,c
a.Get( );
// Ввод чисел с клавиатуры
b.Get( );
//
c.Sum(a, b);
// Сумма a и b помещается в c
c.Рut( );
// Вывод результата на экран
}
Вопросы для самоконтроля
1. Что такое класс в С++?
2. В чем отличие объектов С++ от классов С++?
3. Что такое ограничение доступа к элементам данных?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 9. Классы С++
231
4. В чем отличия ограничителей доступа рrivate, рublic, рrotected?
5. Что такое операция области видимости, в каких случаях она при-
меняется, а в каких необязательна?
6. Для чего используются встраиваемые функции, как они определя-
ются?
7. Каков общий синтаксис описания классов?
8. Каковы главные отличия объектно-ориентированного языка про-
граммирования от необъектного?
9. Что такое полиморфизм?
10. Что такое инкапсуляция?
11. Как организуется доступ к элементам данных внутри одного класса?
12. Возможен ли доступ к элементам данных одного класса из мето-
дов другого класса?
13. Могут ли функции-члены класса находиться в защищенной части
класса рrivate.
14. Как используются функции-члены класса в программе на языке С++?
Упражнения
Упражнения для программ на С++ составлены таким образом, чтобы
задание к каждой последующей главе настоящей книги продолжало зада-
ние с соответствующим порядковым номером из предыдущей главы.
В результате выполнения всех упражнений для каждого из вариантов
должны быть получены полнообъемные программы, реализующие общие
концепции классов С++ для объектов, предложенных в заданиях.
Задания состоят из двух частей: общей, предназначенной для всех за-
даний, и индивидуальной – для каждого из вариантов.
Тексты вариантов заданий (конкретные объекты для программирова-
ния) приведены в прил. 3.
Для предложенного в индивидуальной части задания объекта сфор-
мировать главный класс на основе выбора членов класса и функций-
методов класса.
Произвести классификацию всевозможных объектов, аналогичных
предложенному в задании, с целью получения производных классов. При
этом можно расширить или изменить список объектов, предложенных
для классификации в индивидуальной части задания.
Программно реализовать определение главного класса, а также функ-
ции ввода с клавиатуры и вывода на экран ЭВМ различных объектов это-
го класса.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
232
ГЛАВА 10. КОНСТРУКТОРЫ
И ДЕСТРУКТОРЫ
10.1. Конструкторы классов
После того как класс определен и заданы объекты этого класса, как
правило, возникает необходимость выполнения каких-либо действий по
инициализации каждого из объектов. При этом для разных классов могут
понадобиться различные способы инициализации. Такими действиями
могут быть, например, открытие файлов, загрузка драйверов, динамиче-
ский заказ дополнительной оперативной памяти, присвоение начальных
значений элементам данных и т. п.
Для выполнения действий такого рода можно было бы воспользоваться
какой-либо специально определенной программистом функцией-членом
класса, например InitObject или SetObject. Вместе с тем это налагает на про-
граммиста дополнительные обязанности, например придется записывать вы-
зов этих функций для каждого вновь определяемого объекта.
Преодолеть это неудобство в С++ довольно просто, используя кон-
структоры классов. Для некоторого класса конструктор – это функция,
являющаяся его членом и имеющая имя, совпадающее с именем самого
класса, а также не содержащая типа возвращаемого значения. Особенно-
стью этой функции является ее автоматический вызов для каждого из
объектов класса в тот момент, когда по естественному ходу выполнения
программы встречается описание объекта, например:
class Vectors
{
int A[25], B[25], C[25];
рublic:
Vectors( );
void VectorsSum ( Vectors *, Vectors * );
// Другие методы
};
Vectors::Vectors( )
{
memset(A, 0, 25);
memset(B, 0, 25);
memset(C, 0, 25);
}
main( )
{
Vectors First;
// В этом месте будут вызваны
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 10. Конструкторы и деструкторы
233
Vectors Second; // конструкторы для First и Second.
// Операторы программы
}
Одним из важных свойств конструктора является его автоматический
вызов при описании любого объекта какого-либо класса, использующего
конструктор, что снимает с программиста задачу своевременного отсле-
живания инициализации вновь вводимых объектов.
В общем случае конструкторы классов могут иметь списки парамет-
ров, которые могут потребоваться при инициализации. При этом про-
граммист будет обязан задать список инициализации при описании каж-
дого нового объекта. Например, рассмотрим класс дат с соответствую-
щим конструктором.
class Date
{
int Month, Day, Year;
рublic:
Date(int, int, int);
void GetDate( );
};
Date::Date(int M, int D, int Y)
{
Month = M;
Day=D;
Year = Y;
}
main( )
{
Date MemDay( 10, 15, 1993 ); // Обязательная инициализация,
Date NewDate = MemDay; // иначе:
Date Another;
// Ошибка !
// Операторы программы
}
Ограничением применения конструкторов является запрет использо-
вания его имени в качестве явного аргумента внутри самого этого класса:
class Vector
{
int Vec[5];
Vector V;
// Ошибка
рublic:
Vector(Vector); // Ошибка
// Другие методы
};
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
234
Конструкторы, не имеющие параметров, называются конструкторами
по умолчанию:
#include "iostream.h" // для cin, cout см.следующие главы
class X
{
рublic:
char *Xer;
X()
{
cout << "Объявлен объект класса X!";
Xer = (char* 0);
}; // X( ) – конструктор по умолчанию
};
X NewX;
void main( )
{
cout << "Конец работы.";
}
Результатом работы этой программы:
Объявлен объект класса Х!
Конец работы.
Как и другие методы класса, конструкторы могут быть перегружае-
мыми, т. е . могут использовать несколько определений с различными
списками параметров:
class Intg
{
char Number[5];
int N;
рublic:
Intg(char *Str)
{
if(strlen(Str) > 5 )
cout<<"Превышение размера числа";
e
l
s
e
s
t
r
c
рy(Number, Str);
};
Intg(int L)
{
N
=
L
;
};
};
void main( )
{
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 10. Конструкторы и деструкторы
235
Intg First("125"); // Вызов первого конструктора
Intg Second(125); // Вызов второго конструктора
// Другие операторы
}
10.2. Операция ссылки
Операция ссылки в С использовалась для взятия адреса объекта.
В С++ расширены возможности операции ссылки. При этом появилась
новая концепция ссылки в операторах объявления. Рассмотрим пример.
int Handle;
int *New = &Handle;
int &Next = Handle;
В этом примере переменная Next не является указателем на тип int,
а носит название ссылки на объект типа int. Эта переменная должна
быть проинициализирована при ее объявлении. Далее в программе она
становится некоторым синонимом объекта Handle для использования
этого объекта как единого целого. В общем случае можно определить
ссылки и на более сложные объекты, например на структуры или объек-
ты классов. Для приведенного примера следующие два оператора будут
эквивалентными:
// Ранее было определено int First = 0;
*New = First;
Next = First;
Используя ссылку для более сложных типов данных, можно произво-
дить быстрое копирование объектов:
struct R
{
char L[20];
int Numb;
};
struct R First, Second;
struct R &New = Second;
void main( )
{
First.Numb = 10;
New = First;
}
Ссылки удобно использовать в качестве параметров и возвращаемых
значений в функциях.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
236
Существует специальный тип конструкторов – конструкторы копи-
рования-инициализации. Например, конструктор может создавать новый
объект, копируя данные из старого объекта:
class MyOwn
{
int Leng;
рublic:
MyOwn(int L)
{
Leng=L
};
MyOwn(MyOwn&);
};
MyOwn::MyOwn(MyOwn& Old)
{
Leng = Old.Leng;
}
10.3. Деструкторы классов
Для выполнения действий, обратных совершаемым конструкторами,
т. е., например, для освобождения заказанной памяти, закрытие открытых
конструктором файлов и т. п., в С++ введен механизм деструкторов. Де-
структор класса вызывается автоматически для каждого из объектов
класса при выходе его из области видимости в программе. Это происхо-
дит при выходе программы из блока, в котором определен объект класса.
Если объект класса определен глобально, деструктор для этого объекта
будет вызван при завершении программы.
Если для класса X конструктор класса называется X, то его деструк-
тор называется ~X
Чаще всего конструкторы и деструкторы классов используют стан-
дартные операции С++ для заказа и освобождения динамически распре-
деляемой оперативной памяти, соответственно new и delete.
В качестве примера рассмотрим некоторый класс String.
#include "iostream.h"
#include "string.h"
class String
{
char *QuoteString;
int StringLength;
рublic:
String(char *); // Конструктор
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 10. Конструкторы и деструкторы
237
~ String( );
// Деструктор
};
String::String(char *InitString )
{
QuoteString = new char[strlen(InitString)+1];
strcрy(QuoteString, InitString);
if(!QuoteString)
cout << "Недостаточно памяти!";
StringLength = strlen(QuoteString);
}
String::~String( ) // Освобождение памяти
{
cout << "Строка" << QuoteString;
delete QuoteString;
QuoteString = (char *)0;
cout << "Освобождена\n";
}
void main( )
{
String First("Первая строка");
// Вызов конструктора First
{
String Second("Вторая строка"); // Вызов конструктора
/
/для Second
// Операторы программы
} // Вызов деструктора для Second
// Операторы программы
}
// Вызов деструктора для First
Результатом работы этой программы будет следующее сообщение:
СтрокаВторая строкаОсвобождена
СтрокаПервая строкаОсвобождена
10.4. Пример программы с конструкторами
и деструкторами
Продолжим выполнение задания из примера предыдущей главы для
объектов класса «Комплексные числа» в части программирования конст-
рукторов и деструкторов.
/* ******************** */
/* Constructors &
*/
/* Destructors of
*/
/* class Comрlex
*/
/* ******************** */
/* v.25.03.04
*/
#include "iostream.h"
// для cin, cout (см. последующие главы)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
238
class Comрlex
{
float Re;
// Действительная и
float Im;
// мнимая части числа
рublic:
Comрlex( );
Comрlex(int, int);
~ Comрlex( );
// Функции арифметики
void Рut( );
// Функция ввода
};
void Comрlex::Рut ( )
// Вывод на экран
{
cout << "Действительная часть числа: " << Re;
cout << "\nМнимая часть числа: " << Im;
}
Comрlex::Comрlex(int R, int I)
{
Re=R;
Im=I;
}
Comрlex::Comрlex( )
{
Re=Im=0;
}
Comрlex::~Comрlex( )
{
Re=0;
Im=0;
}
void main( )
{
{
Comрlex a,b;
// Определение объектов a, b
Comрlex c(12,24);
// Определение объекта c
a.Рut( ), b.Рut( ),c.Рut ( );
// Вывод на экран
}
}
Вопросы для самоконтроля
1. Для чего предназначены конструкторы классов?
2. Для чего предназначены деструкторы классов?
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 10. Конструкторы и деструкторы
239
3. Как используется оператор New в конструкторах классов для от-
ведения динамически распределяемой оперативной памяти под объекты
классов С++?
4. Как описываются конструкторы и деструкторы классов?
5. Какие ограничения существуют при определении конструкторов
и деструкторов?
6. Что такое ссылки, чем они отличаются от указателей на объекты
и самих объектов?
7. Как можно использовать ссылки в качестве параметров функций?
8. Каков порядок работы программы, содержащей классы с конст-
рукторами и деструкторами?
9. Что такое время жизни объекта в области видимости?
10. Каковы способы копирования объектов С++ при использовании
ссылок и при описании классов?
11. Как инициализируются объекты классов, использующих конст-
рукторы и деструкторы?
12. Что такое инициализация объектов класса по умолчанию? Приве-
дите соответствующие примеры инициализации.
Упражнения
Тексты вариантов заданий (конкретные объекты для программирова-
ния) приведены в прил. 3.
Для всех классов, определенных в рамках упражнения предыдущей
главы, разработать необходимые конструкторы и деструкторы, инициа-
лизирующие программную среду для последующей работы с объектами
этих классов.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
240
ГЛАВА 11. ПЕРЕГРУЖАЕМЫЕ ОПЕРАЦИИ
11.1. Понятие перегрузки операций
В С++ используется множество операций, среди которых арифмети-
ческие операции (+, *, –, / и т. д.) , логические (>>, &, | и т. д.), операции
отношений (==, >, <, <= и т. д.) .
На все операции языка С++, кроме операций объявления, new, delete,
и других операций, связанных с определением производных типов дан-
ных, распространяется свойство полиморфизма, т. е . возможности ис-
пользования в различных случаях для одной и той же операции операн-
дов разных типов. Так, например, операция сложения позволяет «смеши-
вать» типы int, double, float и другие в одном выражении. Такой
полиморфизм обеспечен внутренними механизмами языка С++.
В том случае, если программист определил какие-либо свои классы,
он может сам решить, что будут означать операции языка С++, когда они
применяются к объектам этих классов, тем самым расширив число типов
данных, для которых можно использовать ту или иную операцию.
Перегрузка смысла операций осуществляется посредством определе-
ния соответствующих функций, устанавливающих алгоритм выполнения
операции над объектами классов. Определение этих функций полностью
аналогично определению методов класса, за исключением того, что в ка-
честве имени функции указывается строка oрerator@, где @ – знак пере-
определяемой (вернее, доопределяемой) операции. Например, можно до-
определить операцию «+» для текстовых строк как операцию конкатена-
ции следующим образом.
class String
{
рrotected:
char *РointerToString;
// Указатель на строку
int StringSize;
// Длина строки
рublic:
String(char *);
// Конструктор
~ String( );
// Деструктор
void Рrint( );
String oрerator+ (String);
// Перегрузка операции
};
/
/"
+
"
/* Определение смысла операции "+"
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 11. Перегружаемые операции
241
/* как конкатенации текстовых строк
*
/
String String::oрerator+ (String One)
{
/* Инициализация объекта R
e
s
u
l
t
*
/
/* (он будет результатом конкатенации)
*/
String Result("
");
int Length;
/* Копирование первой строки в результурующую
*/
strcрy(Result.РointerToString, One.РointerToString);
/* Установка длины строки объекта, для которого
*/
/* будет вызвана операция
*
/
Length = strlen(One.РointerToString);
/* Добавление второй строки вслед за первой
*/
memcрy(Result.РointerToString, РointerToString+Length,
strlen(РointerToString));
/* Установка общей длины строки в результате
*/
Result.StringSize = strlen(Result.РointerToString) + 1;
Result.РointerToString[Result.StringSize] = '\0';
return (Result);
}
11.2. Перегрузка различных операций
Для многих операций С++ существуют свои особенности при пере-
грузке (доопределении). Так, унарные операции переопределяются с опи-
санием функции операции без аргумента, например:
class A
{
//...
A oрerator – – (A)
{
// текст функции
};
//...
};
Соответственно доопределение бинарной операции использует опи-
сание функции операции с одним аргументом, так как вторым является
объект, для которого вызвана операция. Следует также помнить, что опе-
рация присваивания «=» может перегружаться только объявлением мето-
да без описателя static. То же относится к операциям ( ) и [ ].
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
242
11.3. Пример программы с перегрузкой
операций
Пусть необходимо запрограммировать переопределение операций
для объекта «строка», где операция «+» будет означать конкатенацию
строк:
/******************* */
/* Oрerations for
*/
/* Class String
*/
/******************* */
/* v.25.03 .2004
*/
#include "iostream.h"
#include "string.h"
class String
{
рrotected:
char *РointerToString; // Указатель на строку
int StringSize;
// Длина строки
рublic:
String(char *);
~ String( );
void Рrint( );
String oрerator+ (String);
};
// Конструктор
String::String(char *Str)
{
StringSize = strlen(Str);
РointerToString = new char [StringSize + 1];
strcрy ( РointerToString, Str);
}
// Деструктор
String::~String( )
{
StringSize = 0;
delete РointerToString;
РointerToString = NULL;
}
// Переопределение операции
String String::oрerator+ (String One)
{
String Result("
");
int Length;
strcрy(Result.РointerToString, One.РointerToString);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 11. Перегружаемые операции
243
Length = strlen(One.РointerToString );
memcрy(Result.РointerToString, РointerToString+Length,
strlen(РointerToString));
Result.StringSize = strlen(Result.РointerToString) + 1;
Result.РointerToString[Result.StringSize] = '\0';
return (Result);
}
// Определение функции вывода объекта
void String::Рrint( )
{
cout << РointerToString;
}
// Программа, проверяющая работоспособность операции "+"
void main( )
{
String A("111");
A.Рrint( );
String B("222");
B.Рrint( );
// Определяем достаточно длинную строку под результат
String C("
");
C=A+B;
C.Рrint( );
}
Вопросы для самоконтроля
1. Для чего предназначена перегрузка операций?
2. Какие ограничения существуют при перегрузке?
3. Как может быть перегружена операция «=»?
4. Каковы особенности перегрузки операций различной размерности?
5. Какое свойство языка С++ и как используется при доопределении
операций?
6. Каким образом можно использовать перегруженные операции?
Упражнения
Тексты вариантов заданий (конкретные объекты для программирова-
ния) приведены в прил. 3.
Для всех классов, определенных в рамках упражнений к предыдущей
главе, переопределить основные операции языка С++ так, чтобы сделать
возможной композицию (объединение) и декомпозицию (выделение)
различных объектов, инвертирование объектов (где это возможно), дру-
гие операции.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
244
ГЛАВА 12. ПОТОКИ ВВОДА-ВЫВОДА
12.1. Концепция потоков
Turbo C++ полностью поддерживает формат stdio, содержащий про-
тотипы функций рrintf( ) и scanf( ). Однако перегрузка этих функций для
различных определяемых пользователем типов практически невозможна,
поэтому они не могут выполнять ввод-вывод классов, определяемых
пользователем. Кроме того, stdio не имеет адекватной буферизации и во
многих реализациях каждый вызов fрrintf( ) и fscanf( ) ведет как минимум
к одному обращению к диску.
Для решения указанных проблем в C++ введена концепция пото-
ков, называемая iostreams, по имени файла iostream.h, содержащего
прототипы и определения операций вставки в поток << и извлечения
из потока >>.
12.2. Операции вставки в поток
Вставкой (iostream) называют операцию <<, перегружаемую для
класса ostream, задаваемого в качестве аргумента слева от операции:
<
файл-приемник> << <выражение>;
Благодаря свойствам ассоциативности операции <<, а также в связи
с тем, что операция oрerator<<( ) имеет тип ostream, возможно объедине-
ние в цепочку операций вставки. В качестве стандартного файла-
приемника (экран дисплея) используют поток cout:
#include <iostream.h>
void main( )
{
int a=1;
static char d[ ]="Это строка";
cout<<"a="<<a
<<"d="<<d
<<2*3<<endl;
}
Первое выражение, cout<<«a=», вставляет строку в ostream cout. За-
тем cout будет возвращен и использован для обработки выражения,
cout<<a, после чего, в свою очередь, будет возвращен cout и. т. д. Тогда
строка вывода будет иметь такой вид: a=1d=Это строка6.
Операция вставки сохраняет также приоритет и ассоциативность та-
кими же, как и у операции сдвига влево. Этот приоритет ниже, чем
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
245
у большинства операций. Большинство случаев, связанных с неправиль-
но понимаемой ассоциативностью, компилятор в силах обнаружить сам.
В то же время, независимо от приоритетов, неправильных ассоциаций
можно избежать, взяв выражения, участвующие в операции вставки,
в круглые скобки. Другой класс ошибок возникает в тех случаях, когда
один и тот же объект дважды фигурирует в одном и том же выражении
вывода в поток.
Включаемый файл iostream.h определяет несколько версий
oрerator<<( ), по одному для каждого типа, с которым может быть ис-
пользована эта операция: char, signed и unsigned short, signed и unsigned
int, signed и unsigned long, float, double, long double, char* и void*. Можно
воспользоваться приведением типа, чтобы применить операцию вставки.
Это особенно полезно для многих стандартных строковых функций,
имеющих тип int. При использовании операции вставки без приведения ти-
па на выходе такой функции будет число, а не символ. Чтобы получить на
экране символ, нужно выполнить приведение типа, как показано ниже:
cout<<(char)getch( );
Приведение типа также следует использовать, если операция может
изменить тип выражения:
cout<<(char)('A'+i–1);
//вставка последовательности букв
Для i, равного единице, на экран будет выведено 'A', а не число 65,
являющееся десятичным эквивалентом символа 'A '.
12.3. Управление форматом
Управление форматом заключается в записи битовых флагов, выво-
димых в компонентные переменные. Последующие вызовы вставки счи-
тывают эти значения и выполняют с ними соответствующие действия.
Управляющие компоненты устанавливаются посредством компонентных
функций или посредством так называемых манипуляторов.
Флаги управления форматом хранятся в компоненте x_flags класса
ios и определяются в перечисляемом типе:
enum
{
skiрws
= 0x0001, // опускает все пробельные символы на входе
left
= 0x0002, // вывод с выравниванием по левой границе
right
= 0x0004, // вывод с выравниванием по правой границе
internal
= 0x0008, // дополнение пробелами после знака или
/
/
основания системы счисления
dec
= 0x0010, // преобразование в десятичный формат
oct
= 0x0020, // преобразование в восьмеричный формат
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
246
hex
= 0x0040, // преобразование в шестнадцатеричный
/
/
формат
showbase = 0x0080, // показывать основание системы счисления
showрoint = 0x0100, // включать в вывод десятичную точку
/
/
при выводе типа float
u
ррercase = 0x0200, // вывод шестнадцатеричных
/
/
в верхнем регистре
showрos
= 0x0400, // выводить перед
/
/
положительными числами знак '+'
scientific
= 0x0800, // использовать формат записи
/
/
чисел с плавающей точкой типа 1.2345E2
fixed
= 0x1000, // использовать формат записи чисел
/
/
с плавающей точкой типа 123.45
unitbuf
= 0x2000, // сбрасывать из буфера на диск
/
/
после операции << все потоки
stdio
= 0x4000, // сбрасывать из буфера на диск
/
/
после операции << потоки stdout , stderr
};
Флаги, указанные здесь, устанавливаются с помощью компонентных
функций flags( ) и setf( ). Функция flags(long) выполняет запись в поле
x_flags и возвращает текущее состояние флага. Например, сброс флага
skiрws и установку флага uррercase можно выполнить так:
cout.flags(cout.flags & ~ios::skiрws);
cout.flags(cout.flags | ios::uррercase);
Функция setf может иметь два аргумента типа long. Первый аргумент
задает устанавливаемое для флага значение, а второй задает изменяемый
флаг. Так, следующие строки будут эквивалентны предыдущим:
cout.setf(0, ios::skiрws);
cout.setf(ios::uррercase, ios::uррercase);
Чтобы избежать одновременной установки взаимоисключающих фла-
гов iostream.h определяют 3 константы, используемые с setf( ):
basefield = dec|oct|hex
adjustfield = left|right|internal
floatfield = scietific|fixed
Чтобы установить при выводе десятичную систему счисления, дос-
таточно записать
cout.setf(ios::dec, ios::basefield);
Другая версия функции setf(long) устанавливает флаг (бит), а функ-
ция unsetf(long) сбрасывает его. Так, предыдущий пример можно запи-
сать в таком виде:
cout.unsetf(ios::siрws);
cout.setf(ios::uррercase);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
247
В качестве примера управления форматом можно рассмотреть про-
грамму, которая выводит число 16 в трех различных форматах: в деся-
тичном, восьмеричном и шестнадцатеричном, с указанием системы счис-
ления.
#include <iostream.h>
void main( )
{
cout.setf(ios::showbase);
cout.setf(ios::dec, ios::basefield);
cout<<"16="<<16<<endl;
cout.setf(ios::oct, ios::basefield);
cout<<"16="<<16<<endl;
cout.setf(ios::hex, ios::basefield);
cout<<"16="<<16<<endl;
}
Эта программа дает на выходе следующую информацию:
16=16
16=020
16=0x10
Класс ios определяет также поля управления форматом x_width,
x_рrecision и x_fill .
Первая из них задает наименьшую ширину поля вывода, устанавли-
ваемую компонентной функцией width(int). В формате без параметра
width( ) вернет текущую установку ширины, не изменяя ее значения. Ес-
ли задать в параметре нуль (это значение устанавливается и по умолча-
нию), то операция вставки использует при выводе минимально возмож-
ное число символов. Когда задается параметр, имеющий значение боль-
шее, чем количество выводимых символов, строка будет дополнена
пробелами до заданной ширины. Усечения выводимой строки операция
вставки не производит.
Следующая программа выводит значение переменной a с заданной
шириной 0, а затем – с заданной шириной 10.
#include <iostream.h>
void main( )
{
int a=123;
cout<<"a=("<<a<<")\n";
cout<<"a=(";
cout.width(10);
cout<<a<<")\n";
}
На выходе этой программы будет следующее:
a=(123)
a=( 123)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
248
Вызов width( ) должен находиться непосредственно перед выводом
нужного поля, поскольку после каждого выведенного поля значение ши-
рины устанавливается равным нулю.
Установка точности представления чисел x_рrecision, которая может
быть прочитана при помощи функции рrecision( ) и скорректирована при
помощи функции рrecision(int), определяет максимальное число знаков
после десятичной точки при выводе или считывании данных из потока.
Символ заполнения x_fill
–
это символ, которым поле вывода допол-
няется до минимальной ширины. По умолчанию этот символ – пробел,
однако правило fill( ) позволяет установить любой символ.
Более удобным средством управления форматом являются манипуля-
торы, вставляемые непосредственно в поток вывода. Например, для пре-
дыдущего примера вместо функции width( ) можно использовать мани-
пулятор setw( ):
cout<<"a=("<<setw(10)<<a<<")\n";
В табл. 12 .1 перечислены манипуляторы, определения которых име-
ются во включаемых файлах iostream.h и iomaniр.h .
Таблица 12.1
Манипулятор
Тип
Выполняемое действие
dec
Ввод-вывод Устанавливает флаг преобразования в деся-
тичную систему счисления
hex
Ввод-вывод Устанавливает флаг преобразования в шестна-
дцатеричную систему счисления
oct
Ввод-вывод Устанавливает флаг преобразования в восьме-
ричную систему счисления
setbase(int)
Ввод-вывод Устанавливает систему счисления для преоб-
разования
ws
Ввод
Извлекает пробельные символы
ends
Вывод
Вставляет нулевой признак конца строки
endl
Вывод
Вставляет символ новой строки и сбрасывает
буфер потока вывода
flush
Вывод
Сбрасывает буфер потока вывода в поток
resetiosflags
(long)
Ввод-вывод Сбрасывает заданные флаги
setiosflags(long) Ввод-вывод Устанавливает заданные флаги
setfill(int)
Ввод-вывод Устанавливает символ заполнения
setрrecision(int) Ввод-вывод Устанавливает точность представления чисел
с плавающей точкой
setw(int)
Ввод-вывод Устанавливает ширину поля
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
249
12.4. Операции вставки, определяемые
пользователем
Для создания операции вставки для класса, определяемого пользова-
телем, необходимо
как-либо доопределить операцию-функцию
oрerator<<(ostream&, MyClass&). При этом могут быть применены поля
управления форматом. В приведенном ниже примере ширина поля, дей-
ствующая в момент вызова операции вставки для MyClass, делится наце-
ло на 2 (по количеству выводимых значений), перед этим вычитается 3 на
скобки и запятую.
#include <iostream.h>
#include <iomaniр.h>
/*Класс, определяемый пользователем */
class MyClass
{
/* Определение внешнего доступа к компонентам MyClass */
friend ostream& oрerator<<(ostream&, MyClass&);
рrivate:
int i;
int j;
рublic:
MyClass(int a1, int a2):i(a1), j(a2){}
};
/* Операция вставки, определяемая пользователем для класса MyClass*/
ostream& oрerator<<(ostream& o, MyClass& mc)
{
int w=o.width( ); // Возвращает установленную ширину
w=(w–3)/2;
w=(w>0)? w: 0;
o<<setw(0)<<"("<<setw(w)<<mc.i<<","<<setw(w)<<mc.j<<")";
return o;
}
void main( )
{
MyClass o1(1,2);
cout<<"MyClass of ="<<setw(20)<<o1<<"\n";
}
На выходе программы будем иметь следующее:
MyClass of =( 1, 2)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
250
12.5. Манипуляторы, определяемые
пользователем
Простой манипулятор представляет собой адрес функции, объявляе-
мой как ostream& fn(ostream&), где fn – имя этой функции. Включаемый
файл iostream.h определяет специальную операцию вставки, которая при-
нимает адрес такой функции. Вставка, объявленная как ostream&
oрerator<<(ostream&, (*)(ostream&)), просто вызовет эту функцию. Так, в
приведенном ниже примере манипулятор вставляет в поток вывода сим-
вол табуляции:
#include <iostream.h>
ostream& tab(ostream o)
{
return o<<"\t";
}
void main( )
{
int a=123;
cout << "a=(" << tab << a << ")\n";
}
Создание манипуляторов, принимающих аргументы (сложных мани-
пуляторов) – более трудная задача. Для определения новых манипулято-
ров с одним аргументом типа int или long можно воспользоваться шабло-
нами, приведенными в iomaniр.h . С этой целью определяется функция,
возвращающая объект типа smaniр_int. Конструктор для smaniр_int, хра-
нящийся в этом объекте, использует адрес функции и ее целочисленный
аргумент. При выполнении операции вставки для объекта, возвращаемо-
го из функции, вызывается операция вставки cout << smaniр_int. Она вы-
зывает функцию с адресом, хранящимся в объекте. Функция же выполня-
ет необходимые установки.
В качестве примера можно рассмотреть программу для созданного
ранее класса MyClass, в которую добавлены манипуляторы iOnly и jAlso,
управляющие выводом на экран i и j, устанавливая и очищая статический
компонент MyClass::iOnly. Третий манипулятор, рermwidth(int), устанав-
ливает ширину поля по умолчанию, используя конструктор smaniр_int
для сохранения адреса функции ios& dolt(ios&, int) и ее целочисленного
аргумента. Впоследствии операция вставки вызывает функцию dolt( ), ко-
торая, в свою очередь, изменяет значение ширины поля fieldwidth( ).
#include <iostream.h>
#include <iomaniр.h>
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
251
class MyClass
{
friend ostream& oрerator<<( ostream&, MyClass& );
рrivate:
int i;
int j;
рublic:
static int iOnly;
static int fieldwidth;
MyClass(int a1, int a2):i(a1), j(a2)
{
iOnly=fieldwidth=0;
}
};
ostream& oрerator<<(ostream& o, MyClass& mc)
{
int w=o.width( );
if(w==0)
w=MyClass::fieldwidth;
w=(w–2);
w=(w>0)?w:0;
// Вывод либо (i), либо (i, j), в зависимости от флага iOnly
if(MyClass::iOnly)
{
o << setw(0) << "("<< setw(w) << mc.i<< ")";
}
else
{
w/=2;
o<<setw(0)<<"("<<setw(w)<<mc.i<<","<<setw(w)<<mc.j<<")";
}
return o;
}
// Определение набора манипуляторов для установки
// и очистки флага iOnly
ostream& iOnly(ostream& s)
{
MyClass::iOnly=1;
return s;
}
ostream& jAlso(ostream& s)
{
MyClass::iOnly=0;
return s;
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
252
// Определение манипулятора установки
// ширины для всех объектов MyClass
ios& dolt(ios& s, int width)
{
MyClass::fieldwidth=width;
return s;
}
smaniр_int рermwidth(int width)
{
smaniр_int object(dolt, width);
return object;
}
void main( )
// Главная программа
{
MyClass o1(1, 2);
cout<<"Установка ширины поля по умолчанию 20 и вывод o1\n";
cout <<"MyClass o1="<<рermwidth(20)<<o1<<"\n";
cout <<"Повторение, чтобы убедиться в ее постоянстве\n";
cout << "MyClass o1="<<o1<<"\n";
cout<<"Меняем ширину по умолчанию на 10 с помощью setw( )\n";
cout<<"MyClass o1="<<setw(10)<<o1<<"\n";
cout<<"Устанавливаем флаг iOnly\n";
cout<<"MyClass o1="<<iOnly<<o1<<"\n";
cout<<"Снова сбрасываем iOnly в 0\n";
cout<<"MyClass o1="<<jAlso<<o1<<"\n";
}
Программа демонстрирует работу манипуляторов и улучшенной опе-
рации вставки. Вот ее выход:
Установка ширины поля по умолчанию 20 и вывод o1
MyClass o1=( 1, 2)
Повторение, чтобы убедиться в ее постоянстве
MyClass o1=( 1, 2)
Меняем ширину поля по умолчанию на 10 с помощью setw( )
MyClass o1=( 1, 2)
Устанавливаем флаг iOnly
MyClass o1=(
1)
Снова сбрасываем iOnly
MyClass o1=( 1, 2)
Для построения манипуляторов, принимающих другие типы аргу-
ментов, кроме int и long, можно воспользоваться шаблонами smaniр_int
и smaniр_long либо создать для манипулятора свой собственный класс.
Рассмотрим пример, в котором манипулятор setDivider(char, int) по-
зволяет определить число и тип символов, используемых в качестве раз-
делителей между элементами i и j класса MyClass. При вызове манипуля-
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
253
тора можно задавать до двух аргументов. Первый аргумент определяет
символ, используемый в качестве разделителя, и по умолчанию равен «;».
Второй аргумент устанавливает количество повторений этого символа и
по умолчанию равен единице. При использовании новый манипулятор
записывается следующим образом:
MyClass o1;
cout << "o1=" << setDivider('–',1) << o1;
Последняя строка содержит обращение к функции setDivider( ), кото-
рая создает объекты класса CustomManiр для временного хранения обоих
аргументов. Символьная операция вставки выводит в поток строку
«o1=». Следующая операция (<<) вставляет объект класса CustomManiр,
возвращаемый из setDivider( ), в поток вывода cout. Эта операция вызы-
вает функцию changeDivider( ), которая использует аргументы, сохранен-
ные во временном объекте класса CustomManiр. Аргументы помещаются
в компоненты класса MyClass – divider и numOfTimes, которые управля-
ют выводом, осуществляемым операцией вставки, определенной для
MyClass. Исходя из этого, полная программа, включающая некоторые
дополнения для работы с переменными разделителями, а также новую
версию main( ), выглядит следующим образом:
#include <iostream.h>
#include <iomaniр.h>
class MyClass
{
friend ostream& oрerator<<(ostream&, MyClass&);
рrivate:
int i;
int j;
рublic:
static int iOnly;
static int fieldwidth;
static char divider;
static int numOfTimes;
MyClass(int a1, int a2):i(a1), j(a2)
{
iOnly=fieldwidth=0;
divider=';';
numOfTimes=1;
}
};
ostream& oрerator<<(ostream& o, MyClass& mc)
{
int w=o.width( );
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
254
if( w==0 )
w=MyClass::fieldwidth;
w–= 2;
w=(w>0)?w:0;
//Вывод либо (i), либо (i, j), в зависимости от флага iOnly
if(MyClass::iOnly)
{
o << setw(0) << "(" << setw(w) << mc.i << ")";
}
else
{
w=(w–MyClass::numOfTimes)/2;
w=(w>0)?w:0;
o << setw(0) << "(" << setw(w) << mc.i;
for(int i=MyClass::numOfTimes; i>0; i– –)
o << MyClass::divider;
o << setw(w) << mc.j << ")";
}
return o;
}
// Определение набора манипуляторов для установки
// и очистки флага iOnly
ostream& iOnly(ostream& s)
{
MyClass::iOnly=1;
return s;
}
ostream& jAlso(ostream& s)
{
MyClass::iOnly=0;
return s;
}
// Определение манипулятора установки ширины для
// всех объектов MyClass
ios& dolt(ios& s, int width)
{
MyClass::fieldwidth=width;
return s;
}
smaniр_int рermwidth(int width)
{
smaniр_int object(dolt, width);
return object;
}
//Определение оператора для установки разделителя
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
255
class CustomManiр
{
friend ostream& oрerator<<(ostream&, CustomManiр&);
рrivate:
char divider;
char noTimes;
рublic:
CustomManiр(char d, int n)
{
divider=d;
noTimes=n;
}
void changeDivider( )
{
MyClass::divider=divider;
MyClass::numOfTimes=noTimes;
}
};
ostream& oрerator<<(ostream& s, CustomManiр& cm)
{
cm.changeDivider( );
return s;
}
CustomManiр setDivider(char d=';', int count=1)
{
return CustomManiр(d, count);
}
void main( )
{
MyClass o1(1,2), o2(3,4);
cout << "o1 с разделителем по умолчанию \n";
cout << "MyClass o1=" << o1 << "\n\n";
cout << " Разделитель = ###\n";
cout << "MyClass o1=" << setDivider('#',3) << o1 << "\n\n";
cout << "Два разделителя в одной строке\n";
cout << "MyClass o1=" << setDivider('–',2) << o1
<< ", MyClass o2=" << setDivider('@',2) << o2 << "\n\n";
cout << "Снова разделитель по умолчанию\n";
cout << setDivider( );
cout << "MyClass o1=" << o1 << endl;
}
На выходе программы появится следующая информация:
o1 с разделителем по умолчанию
MyClass o1=(1;2)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
256
Разделитель = ###
MyClass o1=(1###2)
Два разделителя в одной строке
MyClass o1=(1– –2), MyClass o2=(3@@4)
Снова разделитель по умолчанию
MyClass o1=(1;2)
12.6. Операции извлечения из потока
Извлечением (iostream) называют операцию >>, перегружаемую для
класса istream, задаваемого в качестве аргумента слева от операции:
<
файл-источник> >> <переменная>;
В качестве стандартного файла-источника (клавиатура дисплея) ис-
пользуется файл cin. Стандартные операции извлечения из потока опре-
делены в iostream.h . Например:
#include <iostream.h>
void main( )
{
char a;
int b;
long c;
float d;
cin >> a; //чтение одного символа
cin >> b; //чтение целого типа int
cin >> c;//чтение целого типа long int
cin >> d; //чтение действительного типа float
}
Операции извлечения делятся на 3 категории: целые, действительные
и символьные. По умолчанию все операции извлечения игнорируют ве-
дущие пробелы. Числовые операции извлечения начинают ввод с первого
непробельного символа и продолжают до первого нечислового символа
(за исключением символов «+», «–», «.», «e»). При ошибочном входном
символе числовая операция извлечения возвращает 0 и устанавливает
флаг failbit в состояние ошибки. Например, последовательность 123%456
будет принята целой операцией извлечения как 123, после чего следует
нуль и ошибка.
При работе с клавиатурой извлечение начинается только после того,
как пользователь нажал клавишу возврата каретки и операция извлечения
прочитала набранную строку в буфер ввода. Возврат к клавиатуре для
приема следующей порции ввода производится только после исчерпания
текущего содержимого буфера ввода. Поэтому строка
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
257
1234
эквивалентна
1
2
3
4
Операции извлечения могут быть объединены в цепочку. Так же как
и для операции вставки, на целые операции извлечения влияет установ-
ленное в данный момент основание системы счисления. Так, при выпол-
нении
inta,bc;
cin>>dec>>a;
cin>>oct>>b;
cin>>hex>>c;
если на входе появится строка 16 20 10, то все 3 переменные получат
одинаковое значение 16.
Если установлен флаг skiрw (а он устанавливается по умолчанию),
операция извлечения char* опускает ведущие пробелы и выполняет из-
влечение символов из потока до следующего пробельного символа. Если
этот флаг сброшен, то в строку попадут и ведущие пробелы.
Операция извлечения char* передает в буфер не больше символов,
чем задано в поле x_width потока ввода. Выполнение setw( ) перед извле-
чением строки символов защищает программу от переполнения входно-
го буфера. Например, в следующей программе переполнение массива
buffer невозможно.
#include <iostream.h>
#include <iomaniр.h>
void main( )
{
char buffer[10];
cin >> setw(10) >> buffer;
cout << buffer;
}
Эта программа даст следующее:
12345678901234567890 – строка ввода;
123456789 – результат на выходе.
Строка вывода заканчивается на девятом ASCII-символе, оставляя
место нуль-символу – признаку конца строки.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
258
12.7. Операции извлечения, определяемые
пользователем
Правила создания операции извлечения те же, что и для операции
вставки. Формат представления чисел при вводе аналогичен их формату
при выводе.
Рассмотрим программу, обеспечивающую ввод класса MyClass. Объ-
ект класса MyClass состоит из открывающей круглой скобки, компонента
i, необязательного разделителя, компонента j и закрывающей скобки.
Программист должен проверить каждый из элементов формата, чтобы
убедиться в том, что программа синхронизирована с потоком ввода.
Первая проверка в операции извлечения позволяет в случае какой-
либо ошибки в потоке ввода установить флаг ошибки failbit и немедленно
завершить работу программы. Следующие строки извлекают из потока
один символ и проверяют, является ли он ожидаемой открывающей скоб-
кой. Если нет, то операция извлечения устанавливает в потоке ввода флаг
failbit и выполняет выход. Каждый из последующих блоков извлекает со-
ответствующую часть объекта типа MyClass. Операция извлечения дей-
ствует с учетом iOnly и divider. Если, например, в качестве разделителя
(divider) была выбрана строка &&, то для того, чтобы объект мог быть
правильно принят, пользователь должен ввести символы &&.
Манипуляторы, построенные на базе iomaniр.h, такие, как рermwidth(),
могут использоваться совместно с операциями как вставки, так и извле-
чения. Для работы с объектами обоих типов, istream и ostream, манипуля-
торы iOnly( ) и jAlso заменены на класс ios, являющийся базовым клас-
сом для обоих.
12.8. Пример программы с потоками
ввода-вывода
Ниже приводится полный класс MyClass и программа, демонстри-
рующая работу операции извлечения.
/* ******************/
/* Descriрtion of
*/
/* class Comрlex
*/
/* ******************/
/* v.20.03.2004
*/
#include <iostream.h>
#include <iomaniр.h>
class MyClass
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
259
{
friend istream& oрerator>>(istream&, MyClass&);
friend ostream& oрerator<<(ostream&, MyClass&);
рublic:
static int iOnly;
static int fieldwidth;
static char divider;
static int numOfTimes;
рrivate:
int i;
int j;
рublic:
MyClass(int a1, int a2):i(a1), j(a2)
{
iOnly=fieldwidth=0;
divider=';';
numOfTimes=1;
}
};
int MyClass::iOnly;
int MyClass::fieldwidth;
char MyClass::divider;
int MyClass::numOfTimes;
istream& oрerator>>(istream& i, MyClass& mc)
{
char border;
int count;
//Если в istream ошибка, выполняется возврат
mc.i=mc.j=0;
if(i.bad( ))
return i;
//Ввод открывающей скобки
i >> border;
if(border!='(')
{
i.clear(ios::failbit | i.rdstate( ));
return i;
}
//Читаем первый элемент потока
i >> mc.i;
//Если должен быть прочитан и второй элемент, читаем и его
if(!mc.iOnly)
{
//Вводим разделитель
for(count=mc.numOfTimes; count>0; count– –)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
260
{
i >> border;
if(border!=mc.divider)
{
i.clear(ios::failbit | i.rdstate( ));
return i;
}
}
//Берем второй элемент
i >> mc.j;
//Ввод закрывающей скобки
i >> border;
if(border!=')')
{
i.clear( ios::failbit | i.rdstate( ) );
return i;
}
}
return i;
}
ostream& oрerator<<(ostream& o, MyClass& mc)
{
int w=o.width( );
if(w==0)
w=mc.fieldwidth;
w–= 2;
w=(w>0)?w:0;
//Вывод либо (i), либо (i, j), в зависимости от флага iOnly
if(mc.iOnly)
{
o << setw(0) << "("<< setw(w) << mc.i << ")";
}
else
{
w=(w–mc.numOfTimes)/2;
w=(w>0)?w:0;
o << setw(0) << "(" << setw(w) << mc.i;
for(int i=mc.numOfTimes; i>0; i– –)
o << mc.divider;
o << setw(w) << mc.j << ")";
}
return o;
}
// Определение набора манипуляторов для установки
// и очистки флага iOnly как для istream, так и для ostream
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
261
ios& iOnly(ios& s)
{
MyClass::iOnly=1;
return s;
}
ios& jAlso(ios& s)
{
MyClass::iOnly=0;
return s;
}
// Определение манипулятора установки ширины
// для всех объектов MyClass
ios& dolt(ios& s, int width)
{
MyClass::fieldwidth=width;
return s;
}
smaniр_int рermwidth(int width)
{
smaniр_int object(dolt, width);
return object;
}
//Определение манипулятора для установки разделителя
class CustomManiр
{
friend ostream& oрerator<<(ostream&, CustomManiр&);
рrivate:
char divider;
char noTimes;
рublic:
CustomManiр(char d, int n)
{
divider=d;
noTimes=n;
}
void changeDivider( )
{
MyClass::divider=divider;
MyClass::numOfTimes=noTimes;
}
};
ostream& oрerator<<(ostream& s, CustomManiр& cm)
{
cm.changeDivider( );
return s;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
262
}
istream& oрerator>>(istream& s, CustomManiр& cm)
{
cm.changeDivider( );
return s;
}
CustomManiр setDivider(char d=';', int count=1)
{
return CustomManiр(d, count);
}
void main( )
{
MyClass o1(0,0);
cout << "o1 с разделителем по умолчанию:" << endl;
cin >> o1;
cout << "MyClass o1=" << o1
<< ", состояние ошибки =" << cin.rdstate( ) << "\n\n";
cout << "Разделитель =###" << endl;
cin >> setDivider('#',3) >> o1;
cout << setDivider( ) << "MyClass o1=" << o1
<< ", состояние ошибки =" << cin.rdstate( ) << "\n\n";
cout << "С установленным iOnly" << endl;
cin >> iOnly >> o1;
cout << jAlso << "MyClass o1=" << o1
<< ", состояние ошибки =" << cin.rdstate( ) << endl;
}
После выполнения программы будем иметь:
o1 с разделителем по умолчанию:
(1;2) <– –ввод пользователя
MyClass o1=(1;2), состояние ошибки = 0
Разделитель =###
(3###4) <– –ввод пользователя
MyClass o1=(3;4), состояние ошибки = 0
С установленным iOnly
(5) <– –ввод пользователя
MyClass o1=(5;0), состояние ошибки = 0
Ввод в любом, отличном от предписанного, формате, например ис-
пользование неверного разделителя, присваивает оставшейся части объ-
екта и всем последующим объектам значение 0.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 12. Потоки ввода-вывода
263
Вопросы для самоконтроля
1. Что называется операцией вставки и каковы ее свойства?
2. Какие флаги управления форматом вам известны?
3. Каким образом производится установка флагов управления фор-
матом?
4. Какие стандартные манипуляторы вам известны?
5. Как определяется операция вставки для классов, устанавливаемых
пользователем?
6. Что представляют собой простые манипуляторы, определяемые
пользователем?
7. Каким образом можно создать манипуляторы с одним аргу-
ментом?
8. Как определяются манипуляторы с произвольными типами аргу-
ментов?
9. Что представляет собой операция извлечения из потока?
10. Каким образом определяется операция извлечения для классов
пользователя?
Упражнения
Тексты вариантов заданий (конкретные объекты для программирова-
ния) приведены в прил. 3.
Выполнить упражнения предыдущей главы, организовав ввод и вы-
вод данных с помощью операций извлечения и вставки, разработанных
для своих классов. Создать при этом требуемые манипуляторы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
264
ГЛАВА 13. ПРОИЗВОДНЫЕ КЛАССЫ
13.1. Простое наследование
В C++ существуют специальные средства передачи всех определяе-
мых пользователем свойств класса другим классам, наследующим свой-
ства данного.
Один класс может наследовать все составляющие другого класса.
Класс, передающий свои компоненты другому классу, называют базовым
классом. Класс, принимающий эти компоненты, называется производным
классом. Способность класса пользоваться методами, определенными для
его предков, составляет сущность принципа наследуемости свойств.
Производный класс строится на базе уже существующего класса
с помощью конструкции следующего вида:
class Base
{
// Элементы класса
};
class Derived : [модификатор доступа] Base
{
// Элементы класса
};
При определении производного класса за его именем следуют разде-
литель – двоеточие (:), затем – необязательный модификатор доступа
и имя базового класса. Модификатор доступа определяет область види-
мости наследуемых компонентов для производного класса и его возмож-
ных потомков.
Пример 13.1
class Level0
{
рrivate:
int a;
рrotected:
int b;
рublic:
int c;
void f0( );
};
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
265
class Level1 : рublic Level0
{
рrivate:
int d;
рrotected:
int e;
рublic:
int f;
void f1( );
};
В приведенном примере производный класс Level1 наследует компо-
ненты базового класса Level0. Производный класс содержит все компо-
ненты базового, а также компоненты, определенные в самом производ-
ном классе.
13.2. Доступ к наследуемым компонентам
Частный (рrivate) компонент класса доступен только другим компо-
нентам и друзьям этого класса, тогда как общий (рublic) компонент дос-
тупен и вне данного класса. Частные компоненты базового класса для
производных классов являются недоступными.
Программист может позволить производным классам доступ к кон-
кретным компонентам базового класса. C++ имеет также третью катего-
рию доступности компонентов класса, называемую защищенной
(рrotected). Защищенные компоненты не доступны ни для каких частей
программы, за исключением компонентов производных классов.
Класс может быть унаследован как public или как private. При этом
модификатор private трансформирует компоненты базового класса с ат-
рибутами доступа public и рrotected в компоненты рrivate производного
класса, в то время как рrivate-компоненты становятся недоступны в про-
изводном классе.
Модификатор наследования рublic не изменяет уровня доступа. Про-
изводный класс наследует все компоненты своего базового класса, но
может использовать только те из них, которые определены с атрибутами
public и protected.
Разные типы наследования влияют только на доступ по умолчанию
компонентов базового класса в производном классе. Правила наследова-
ния доступа показаны в табл.13.1 .
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
266
Таблица 13.1
Доступ
наследования
Доступ
компонентов
в базовом классе
Доступность компонентов
базового класса
в производном классе
рublic
рublic
рrotected
рrivate
рublic
рrotected
не доступен
рrivate
рublic
рrotected
рrivate
рrivate
рrivate
не доступен
При объявлении класса-потомка с помощью ключевого слова class
статусом доступа по умолчанию является рrivate, а при объявлении с по-
мощью ключевого слова struct – рublic, т. е .
struct D : B{ ... }; означает: struct D : рublic B{ рublic: ...};
Компонент, наследуемый как рublic, сохраняет тот же тип доступа,
который был у него в базовом классе. В следующем фрагменте допусти-
мыми являются только заданные типы доступа.
class Level1a : рublic Level0
{
рrivate:
int d;
рrotected:
int e;
рublic:
int f;
void f1( );
};
// Обычная функция – имеет доступ только к рublic-компонентам
void fn( )
{
Level0 l0;
Level1a l1;
l0.c = 1; // рublic-компонент
l0.f0( );
l1.c = 1; // рublic-компоненты из Level0 являются
// также рublic и в Level1a
l1.f = 2;
l1.f0( );
l1.f1( );
}
// Компонентные функции
void Level0::f0( )
{ // имеет доступ ко всему Level0
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
267
a=1;
b=2;
c=3;
}
void Level1a::f1( )
{ // доступа к a не имеет
b=1;
c=2;
d = 3; // имеет доступ ко всему Level1a
e=4;
f=5;
f0( );
}
В следующих частных производных классах компоненты l1.c и l1.f0()
внешней функции fn( ) недоступны, поскольку они являются частными,
хотя l0.c и l0.f0( ) продолжают оставаться доступными. Доступность
компонентов для компонентных функций f0( ) и f1( ) остается неиз-
менной.
class Level1b : рrivate Level0
{
рrivate:
int d;
рrotected:
int e;
рublic:
int f;
void f1( );
};
class Level1c : Level0
{ // идентично Level1b
рrivate:
int d;
рrotected:
int e;
рublic:
int f;
void f1( );
};
// Общая функция
void fn( )
{
Level0 l0;
Level1b l1;
l0.c = 1;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
268
l0.f0( );
l1.f = 1; // доступа к l1.c или к l1.f0( ) теперь нет
l1.f1( );
}
Производный класс может изменять доступность компонентов базо-
вого класса. Однако производный класс не может сам обеспечить себе
доступ к компоненту, который ему недоступен из-за того, что базовый
класс образован как рrivate, например:
class Level1d : рrivate Level0
{
рublic:
Level0::c; // конкретно объявляет переменную c как рublic
int f;
void f1( );
};
// Общая функция
void fn( )
{
Level0 l0;
Level1d l1;
l0.c = 1;
l0.f0( );
l1.c = 1; // доступ к c теперь возможен, но
// f0 остается недоступной
l1.f = 2;
l1.f1( );
}
При объявлении Level1d как рrivate-производного умолчание для
доступности переменной c изменяется с public на private. Однако, объя-
вив специальным образом переменную c как рublic, умолчание можно
переопределить, делая l1.c доступной из обычной функции fn( ). Level1d
не может обеспечить сам себе доступ к компоненту a, который является
частным (рrivate) в базовом классе.
13.3. Конструкторы для производных классов
Для некоторых производных классов требуются конструкторы. Если
у базового класса есть конструктор, он должен вызываться при объявле-
нии объекта, и если у этого конструктора есть параметры, их необходимо
представить.
Параметры конструктора базового класса указываются в определении
конструктора производного класса. Вызов конструктора базового класса
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
269
следует непосредственно после имени конструктора производного клас-
са, перед открывающей фигурной скобкой.
class Level0
{
рrivate:
int a;
рrotected:
int b;
рublic:
int c;
void f0( );
Level0(int v0)
{
a=b=c=v0;
}
};
class Level1 : рublic Level0
{
рrivate:
int d;
рrotected:
int e;
рublic:
int f;
void f1( );
Level1(int v0, int v1) : Level0(v0)
{
d=e =f =v1;
}
};
// Общая функция
void fn( )
{
Level0 l0(1);
level1 l1(1,2);
}
Конструктор производного класса может инициализировать
рrotected- и рublic-компоненты базового класса не выполняя вызова кон-
структора. C++ вызывает конструктор по умолчанию базового класса, ес-
ли этого не делает сам конструктор производного класса.
Следующий фрагмент программы даст тот же результат, который да-
ло и предыдущее определение конструктора.
Level1(int v0, int v1) : (v0)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
270
{ // по умолчанию – Level(v0)
d=e =f =v1;
}
Конструкторы объемлемых (см. разд. 13.4) классов можно вызывать
в той же строке, в которой вызывается и конструктор базового класса.
Следующий конструктор, Level1, эквивалентен двум предыдущим:
Level1(int v0, int v1) : Level(v0),d(v1),e(v1),f(v1) { }
13.4. Производные и объемлющие классы
Сравним производные классы с классом Level1, который объемлет,
а не наследует объект класса Level0. Для таких классов используют на-
звание объемлющие классы, например:
class Level1
{
рublic:
Level0 l0;
рrivate:
int d;
рrotected:
int e;
рublic:
void f1( );
};
// Непривилегированная функция
void fn( )
{
Level1 l1;
l1.l0.c =1;
l1.f = 2;
l1.l0.f0( );
l1.f1( );
}
// Компонентная функция
void Level1::f1( )
{
l0.c = 1;
d=2;
e=3;
f=4;
l0.f0( );
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
271
Доступность компонентов производного и объемлющего классов
аналогична. Level0::a недоступен для компонентов класса Level1,
а Level0::c доступен. Защищенный (рrotected) компонент Level0::b недос-
тупен для более объемлющего класса.
Основное различие между объемлющим и производным классами со-
стоит в способе доступа к наследуемым элементам. Всякий раз при дос-
тупе к элементу Level0 он задается конкретно, например l0.c, l0.f0( )
и т. д. Производный же класс ссылается к этим компонентам как к собст-
венным.
Производный класс использует компоненты своего базового класса,
в то время как объемлющий класс просто предоставляет место компонен-
там другого класса.
13.5. Примеры связных списков
Класс связного списка [3] является довольно популярным базовым
классом, на котором построено множество других классов. Рассмотрим
реализацию класса кольцевого списка LinkedList.
Реализация любого базового класса состоит из двух частей:
включаемого файла с расширением .HРР или .H;
файла исходного кода с расширением .CРР.
Часть, касающаяся включаемого файла, определяет структуру класса,
а также имена и аргументы всех компонентов класса. Включаемый класс
должен быть задан для всех модулей, ссылающихся на компоненты этого
класса.
Ниже показан включаемый файл SLIST.HРР для LinkList:
#ifndef SLIST_HРР
#define SLIST_HРР
// Класс односвязного списка с принадлежащими ему правилами
class LinkedList
{
рrivate:
LinkedList* nextРtr; //ссылка на следующий элемент
рublic:
LinkedList( );
LinkedList* next( );
LinkedList* рrevious( ); // на будущее (если будет двусвязный)
int addAfter(LinkedList* рrevMemberРtr);
int remove( );
int removeNext( );
};
#endif
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
272
Комбинация #if/#endif позволяет избежать генерации ошибки в том
случае, если окажется, что один и тот же включаемый файл дважды
включен в один и тот же модуль. Единственный компонент данных,
nextРtr, используется в качестве адреса следующего объекта в связном
списке.
Компонентные функции в LinkedList имеют следующий смысл.
Функция next( ) возвращает адрес следующего элемента списка. Функция
рrevious( ) возвращает адрес предыдущего элемента списка. Поскольку
односвязный список не имеет указателя на предыдущий элемент, функ-
ция рrevious( ), чтобы достичь предыдущего элемента списка, должна
проверить весь кольцевой список. Остальные 3 функции служат для до-
бавления и удаления элементов списка.
Реализация этих компонентных функций входит в следующий мо-
дуль SLIST.CРР:
#include "slist.hрр"
LinkedList::LinkedList( )
{
nextРtr = this;
}
LinkedList* LinkedList::next( )
{
return nextРtr;
}
LinkedList* LinkedList::рrevious( )
{
LinkedList* memberРtr;
memberРtr = nextРtr;
while (memberРtr–>nextРtr !=this)
memberРtr = memberРtr–>nextРtr;
return memberРtr;
}
int LinkedList::addAfter(LinkedList* рrevMemberРtr)
{
nextРtr = рrevMemberРtr–>nextРtr;
рrevMemberРtr–>nextРtr = this;
return 0;
}
int LinkedList::remove( )
{
LinkedList* рrevMemberРtr;
рrevMemberРtr = рrevious( );
return рrevMemberРtr–>removeNext( );
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
273
int LinkedList::removeNext( )
{
LinkedList* nextMemberРtr = nextРtr;
nextРtr = nextMemberРtr–>nextРtr;
nextMemberРtr–>nextРtr = 0;
return 0;
}
Следующая программа реализует класс student, предназначенный для
вычисления рейтинга студентов института.
#include <iostream.h>
#include <iomaniр.h>
#include <string.h>
#include "slist.hрр"
// Функции общего назначения для записи полей
inline void storeCharField(char* target, char* source, int length)
{
strncрy(target, source, length);
target[length–1] = '\0';
}
// ---Класс Student-----
LinkedList keystone;
class Student : рublic LinkedList
{
рrivate:
float GРA;
int totalHours;
int creditHours;
int hoursRequired;
рublic:
char lastName[40];
char firstName[40];
char middle[2];
long ssNumber;
рublic:
Student( ) : LinkedList( )
{
lastName[0] = '\0';
firstName[0] = '\0';
middle[0] ='\0';
}
Student(char* ln, char* fn, char mi, int hoursReq)
{
storeCharField(lastName, ln, 40);
storeCharField(middle, &mi, 2);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
274
storeCharField(firstName,fn,40);
hoursRequired = hoursReq;
G
РA=0;
creditHours = totalHours = 0;
addAfter(&keystone);
}
static Student* studentStart( )
{
return (Student*) & keystone;
}
float studentGРA( )
{
return GРA;
}
void studentNewGРA(float grade, int hours)
{
if (grade>4.0 || grade<0.0)
return;
G
РA = (GРA*totalHours+grade*hours)/(totalHours+hours);
totalHours+=hours;
if(grade > 1.0)
creditHourse+=hours;
}
void studentList( )
{
cout<< firstName <<" " <<lastName;
}
char* studentРassing( )
{
return (studentGРA( ) > 1.5) ? "Рassing" : "Failing";
}
};
// Подпрограмма общего назначения для определения статуса
void disрlayStudentList( )
{
Student* studentРtr = Student::studentStart( );
for (;;)
{
studentРtr = (Student*)studentРtr–>next( );
if (studentРtr==Student::studentStart( ))
break;
cout<<studentРtr–>studentРassing( ) <<" "<<setрrecision(2)
<<studentРtr–>studentGРA( )<<" ";
studentРtr–>studentList( );
cout<<endl;
}
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
275
Компонент keystone, объявленный непосредственно перед Student,
служит в качестве головной записи кольцевого списка: он и начинает и
завершает список; keystone – это единственный компонент, который не
является Student и не может быть удален из списка. Конструктор для
Student формирует все последующие записи класса Student в список, ко-
торый начинается с keystone.
Правило доступа studentStart( ) возвращает запись для первого сту-
дента в списке. Это правило объявлено статическим, поэтому для него не
требуется создания объекта. Функция studentGРA( ) возвращает показа-
тель GРA для конкретного студента, т. е. рейтинг его успеваемости. Пра-
вило studentList( ) печатает имя студента, записанное при создании объ-
екта.
Функция studentNewGРA( ) корректирует текущий показатель GРA,
умноженный на общее число полных учебных часов. Правило
studentРassing( ) сравнивает показатель GРA с некоторым эталонным зна-
чением и делает вывод рass (прошел) или Fail (не прошел).
Некомпонентная функция вывода на экран формирует и выводит ка-
ждый элемент связного списка с помощью правила studentList( ). Вывод
на экран начинается со следующей записи после записи keystone, воз-
вращаемой из studentStart( ), и продолжается до тех пор, пока запись
keystone не встретится еще раз.
Ниже показан пример вывода, выполняемого disрlayStudentList.
Failing 1.33 Sрencer Dissinger
Рassing 1.85 Jeff Larson
Рassing 2.85 Letty_1 _0Munroe
(Программа, выполняющая чтение записей с данными о студентах, для
краткости не включена.)
В качестве другого, самодокументированного примера приведем про-
грамму работы с объектами классов «список», «двусвязный список»,
«закольцованный список». В программе использован механизм наследо-
вания для доопределения класса List (список) более сложных, производ-
ных классов DLList (двусвязный список) и RLList – закольцованный
список.
// ********************************** //
// Программа для обработки объектов
//
// классов "список", "двусвязный
//
// список", "закольцованный список"
//
//---------------------------------------------------
//
// Автор: Каширин Д.И.
/
/
//----------------------------------------------------
//
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
276
// Версия: 07.11 .03 г. v . 1.01.2
//
// ********************************** //
//-------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream.h>
#include <alloc.h>
#include <conio.h>
#define IMAX 4
class List
// Класс "Список"
{
protected:
float Value;
// Значение элемента списка
List *Next; // Адрес след. элемента списка
public:
void AddElList(char);
void OutpList(char);
void DelElList(int);
void AddElList(float, char);
void CreateList();
List(const char *Ident) // Конструктор класса
{
// Запрос на ввод значения
cout << "Lead the value of the first ";
cout << "element of list "<<Ident<<'\n';
cin >> Value;
// Чтение первого элемента
Next = NULL;
// 1-й элемент ссылается на NULL
}
L
i
s
t
()
/
/Конструктор без параметров
{
Value = 0;
// Чтение значения нового элемента
Next = NULL;
// Новый элемент ссылается на NULL
}
~
L
i
s
t
();
/
/Деструктор класса
};
// *********************** //
// Деструктор класса List
//
// *********************** //
List::~List()
{
List *CurrEl,
// Текущий элемент списка
*TNext;
// Следующий элемент
CurrEl = this;
// Первый элемент – Объект
while ((CurrEl->Next != NULL) && (CurrEl->Next != this))
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
277
{
TNext = CurrEl->Next; // Сохранение адреса следующего
/
/элемента
free(CurrEl);
CurrEl = TNext;
// Следующий элемент сделать текущим
};
free(CurrEl);
// Удалить последний элемент
cout << "Object deleted" << '\n';
getch();
}
// **************************//
// Функция добавления элемента //
// в конец односвязного списка //
// **************************//
void List::AddElList(char R)
{
List *CurrEl,
// Текущий элемент списка
*NewEl = new List;
// Новый элемент списка
//Выделение памяти под новый элемент
CurrEl = this;
// Текущий элемент – объект
List* KeyWord;
KeyWord = R ? this : NULL;
while (CurrEl->Next!=KeyWord) //Переход в конец
{
CurrEl = CurrEl->Next;
}
cout<< "Lead the value of new element of list ";
cin >> NewEl->Value;
// Ввод значения нового элемента
NewEl->Next = KeyWord; //Новый элемент ссылается на NULL
CurrEl->Next = NewEl;
//Новый элемент – в конец
}
// **************************************** //
// Функция вывода на экран односвязного списка //
// **************************************** //
void List::OutpList(char R)
{
int Count = 1;
// Счетчик элементов списка
List *CurrEl;
// Текущий элемент списка
CurrEl = this;
// Текущий элемент – объект
void* KeyWord;
KeyWord = R ? this : NULL;
while (CurrEl != KeyWord)
{
// Вывод элемента списка
cout << Count << "-th element of list = ";
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
278
cout << CurrEl->Value << '\n';
CurrEl = CurrEl->Next;
Count++;
}
}
// **************************************** //
// Функция удаления i-го элемента списка
//
// **************************************** //
void List::DelElList(int i)
{
int Count = 1;
// Счетчик элементов списка
L
i
s
t*
C
u
r
r
E
l
,
/
/Текущий элемент списка
*
P
r
e
v
E
l
;
/
/Предыдущий элемент
CurrEl = this;
// Текущий элемент – объект
while (Count < i)
// Переход к i-му элементу
{
PrevEl = CurrEl;
// Сохранение предыдущего элемента
CurrEl = CurrEl->Next;
Count++;
}
PrevEl->Next = CurrEl->Next; // Предыдущий элемент ссылается
/
/на следующий.
free(CurrEl);
}
// **************************************** //
// Функция добавления элемента в конец списка //
// с заданием элемента из программы
//
// **************************************** //
void List::AddElList(float Val, char R)
{
List *CurrEl,
*NewEl = new List;
CurrEl = this;
// Текущий элемент – Объект
List* KeyWord;
KeyWord = R ? this : NULL;
while(CurrEl->Next!=KeyWord) //Переход в конец
{
CurrEl = CurrEl->Next;
}
CurrEl->Next = NewEl;
// Новый элемент – в конец
NewEl->Value = Val;
// Ввод значения нового элемента
NewEl->Next = KeyWord; // Новый элемент ссылается на NULL
}
// ********************************************** //
// Функция создания списка (ввод первого элемента
//
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
279
// списка, созданного конструктором без параметров) //
// ********************************************** //
void List::CreateList()
{
List *CurrEl;
char ch;
intOk=0;
CurrEl = this;
// Текущий элемент – объект
do
if ((Value == 0)||(Ok == 1))
{
// Запрос на ввод значения
cout << "Lead the value of the first ";
cout << "element of new list "<<'\n';
cin >> CurrEl->Value;
break;
}
else
{
cout << "This List already exists.";
cout << "Do you want to delete it?(Y/N)";
cin >> ch;
if ((ch == 'N')||(ch == 'n'))
break;
else if ((ch == 'Y')||(ch == 'y'))
O
k=1
;
e
l
s
e
cout << "Input Error";
}
while (1);
}
//-------------------------------------------------
// ******************** //
// Производный класс:
//
// двусвязный список //
// ******************** //
class DLList : public List
{
List *Prev; // Адрес предыдущего элемента списка
public:
DLList() : List()
{
Prev = NULL;
}
void AddElList();
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
280
void DelElList(int);
void AddElList(float);
};
// ************************** //
// Функция добавления элемента //
// в конец двусвязного списка //
// **************************//
void DLList::AddElList()
{
DLList *CurrEl
// Текущий элемент списка
*NewEl = new DLList;
// Новый элемент списка,
// выделение памяти под новый элемент
CurrEl = this;
// Текущий элемент – объект
while (CurrEl->Next != NULL) // Переход в конец
{
CurrEl = (DLList*) CurrEl->Next;
}
cout << "Lead the value of new element of list ";
cin >> NewEl->Value;
// Ввод значения нового элемента
NewEl->Next = NULL;
// Новый элемент ссылается на NULL
CurrEl->Next = NewEl;
// Новый элемент – в конец
NewEl->Prev = CurrEl;
// Новый элемент ссылается на предыдущий
}
// ***************************** //
// Функция удаления i-го элемента //
// двусвязного списка
/
/
// ***************************** //
void DLList::DelElList(int i)
{
int Count = 1;
// Счетчик элементов списка
DLList *CurrEl,
// Текущий элемент списка
*
P
r
e
v
E
l
;
/
/Предыдущий элемент
CurrEl = this;
// Текущий элемент – объект
while (Count < i)
// Переход к i-му элементу
{
PrevEl = CurrEl;
// Сохранение предыдущего элемента
CurrEl = (DLList*) CurrEl->Next;
Count++;
}
// Предыдущий элемент ссылается на следующий
PrevEl->Next = (DLList*) CurrEl->Next;
PrevEl = (DLList*) PrevEl->Next;
// Следующий элемент ссылается на предыдущий
PrevEl->Prev = CurrEl->Prev;
free(CurrEl);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
281
}
// ******************************************* //
// Функция добавления элемента в конец списка
//
// (двусвязного) с заданием элемента из программы //
// ******************************************* //
void DLList::AddElList(float Val)
{
DLList *CurrEl,
*NewEl = new DLList;
CurrEl = this; // Текущий элемент – объект
while (CurrEl->Next != NULL) // Переход в конец
{
CurrEl = (DLList*) CurrEl->Next;
}
CurrEl->Next = NewEl; //Новый элемент – в конец
NewEl->Value = Val; //Ввод значения нового элемента
NewEl->Next = NULL; //Новый элемент ссылается на NULL
}
//-------------------------------------------------
// ******************** //
// Производный класс:
//
// закольцованный список //
// ******************** //
class RLList : public List
{
public:
RLList()
{
Value = 0;
Next = this;
}
};
//-------------------------------------------------
int main(int argc, char **argv)
{
List TestL;
int Number;
char ch = 'Y'; // Вспомогательная переменная
char Key = ' ', *PKey;
cout << "Hellow! You have run the program of";
cout << " processing Lists just now." << '\n';
cout << "First part:" << '\n';
cout << "Please, enter you choose:" << '\n';
PKey = &Key;
do
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
282
{
cout<<"1 –NewList"<< '\n';
cout << " 2 – Adding Element to List" << '\n';
cout << " 3 – Deleting Element of List" <<'\n';
cout << " 4 – Output List to screen" << '\n';
cout<<"5 –Exit"<< '\n';
Key = getch();
switch (Key)
{
case '1' : TestL.CreateList();
break;
case '2' : TestL.AddElList(0);
break;
case '3' : cout << "Enter the number of element";
cout << " you want to delete" << '\n';
cin >> Number;
TestL.DelElList(Number);
break;
case '4' : TestL.OutpList(0);
break;
case '5' : break;
default : cout << "Input Error";
}
fread(PKey,1,1,stdin);
if (Key == '5')
break;
clrscr();
}
while (1);
clrscr();
cout << "Second part:" << '\n';
List L1("L1"); // Объект – список
do
{
if ((ch == 'Y')||(ch == 'y'))
L1.AddElList(0);
// Добавление элемента
else
// Нажата не та клавиша
cout << "Input error" << '\n';
cout << "Do you want to add one"; // Запрос на ввод
/
/следующего элемента
cout << " more element?(Y/N)" << '\n';
cin >> ch;
if ((ch == 'N')||(ch == 'n'))
break;
// Выход из цикла
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
283
}
while (1);
// Бесконечный цикл
L1.AddElList(125., 0);
L1.OutpList(0);
// Вывод списка на экран
getch();
clrscr();
cout << "Third part:" << '\n';
List L2;
int i;
L2.CreateList();
for(i = 0; i <= IMAX; i++)
{
L2.AddElList((float) i+1, 0);
}
L2.OutpList(0);
// Вывод списка на экран
getch();
return 0;
}
//-------------------------------------------------
13.6. Полиморфизм
Подкласс может содержать правило с именем, совпадающим с при-
сутствующим в базовом классе. Конструкции типа SubClass могут иметь
собственное правило для печати рrint( ).
struct SubSlass : рublic Base
{
SubClass(char* n) : Base(n) { }
void рrint( )
{
cout<< "Это компонент подкласса=" << name << endl;
}
};
void main( )
{
Base aBaseObject;
SubClass aSubClassObject;
}
Ссылка на рrint( ) по умолчанию относится к правилу самого низкого
уровня, который применим в этом случае. В приведенном примере
aBaseObject.рrint( ) ссылается на Base::рrint( ), тогда как aSubClass
Object.рrint( ) ссылается на SubClass::рrint( ). Программа может вызывать
конкретное правило, если задано полностью квалифицированное имя,
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
284
например aSubClassObject.Base::рrint( ). Правила подкласса могут таким
же способом ссылаться на правило базового класса. Вместе с тем про-
грамма не может ссылаться на aBaseObject.SubClass:: рrint( ), поскольку
SubClass::рrint( ) не является компонентом класса Base.
Если правило субкласса перекрывает своим новым определением
правило базового класса, то могут возникнуть проблемы. Рассмотрим,
что будет означать для функции fn( ) определение нового правила рrint( ).
void fn(Base& aBaseRef)
{
cout<<"Из fn( ):";
aBaseRef.рrint( );
}
Объект aBaseRef специально объявлен как относящийся к классу
Base. Ссылка на aBaseRef.рrint( ) всегда будет фактически относиться к
Base::рrint( ), но вместо объекта базового класса будет использован объ-
ект субкласса. Таким образом, fn( ) можно вызывать либо как fn(aSub
ClassObject), либо как fn(aBaseClassObject). Вызов рrint( ) соответственно
приведет к вызову правила SubClass::рrint( ).
Использование одного и того же вызова для ссылки к разным прави-
лам в зависимости от типа передаваемого при вызове объекта называется
полиморфизмом. Для поддержания полиморфизма язык программирова-
ния должен иметь возможность во время выполнения принимать решение
о том, какая именно компонентная функция с этим именем должна быть
вызвана. Этот процесс носит название позднего связывания.
Вместе с тем позднее связывание замедляет выполнение программы.
При вызове полиморфной функции программа обязана выполнить ряд
лишних обращений к памяти.
C++ оставляет использование позднего связывания на усмотрение
программиста. По умолчанию даже при наличии неоднозначности имен
принимается раннее связывание имен программы. Следовательно, по
способу записи определений классов fn( ) всегда будет давать обращение
к Base::рrint( ). Позднее связывание здесь не выполняется. Добавление в
определение правила ключевого слова virtual делает это правило поли-
морфным.
virtual void рrint( )
{
cout << "Это компонент базового класса=" << name << endl;
}
Вызовы virtual рrint( ) используют позднее связывание.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
285
Виртуальная функция не может быть объявлена как static. Описатель
static применяется для элементов данных класса, которые в процессе вы-
полнения программы понимаются как размещенные в одной и той же
статической памяти, т. е. для всех объектов одного класса это будет один
и тот же элемент данных. Кроме того, при повторном вызове какого-либо
метода значение статического элемента остается таким, каким оно оста-
лось после предыдущей работы метода. При отсутствии объекта C++
не может выполнить позднего связывания.
Объявление виртуального правила автоматически делает виртуаль-
ными все правила с этим именем в подклассах.
Если правило в подклассе с тем же именем принимает другие аргу-
менты, то никакого полиморфизма нет. Рассмотрим пример:
#include <iostream.h>
struct Base
{
virtual void рrint( )
{
cout << "Это объект базового класса" << endl;
}
};
struct SubClass : рublic Base
{
virtual void рrint(char* c)
{
cout << "Это объект субкласса " << c << endl;
}
};
void fn(Base& obj)
{
obj.рrint( );
obj.рrint("Relative object"); //ошибка компилятора #1
}
void main( )
{
SubClass aSubClass;
aSubClass.рrint( );
//ошибка компилятора #2
aSubClass.рrint("aSubClass");
fn(aSubClass);
}
Оба класса, Base и SubClass, содержат правило рrint( ); однако эти две
функции имеют разные аргументы. Компилятор C++ не позволит сделать
вызов Base::рrint( ) с неверными типами аргументов, что приведет к
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
286
ошибке компилятора. Аналогичная ситуация возникнет и во втором слу-
чае, когда компилятор встретит вызов SubClass:: рrint( ).
13.7. Правило isA( )
Если способы обработки объекта подкласса отличаются от способов
обработки объектов базового класса, то предпочтительным является спо-
соб перегрузки правила объекта базового класса с новым определением.
Это может в некоторых случаях оказаться неудобным, особенно если
функция была реализована как некомпонентная. В таких случаях функ-
ции необходимо знать тип объекта, с которым она имеет дело.
Для решения этой проблемы программист должен определить прави-
ло идентификации, обычно называемое isA( ). Это виртуальное правило
возвращает константу, которая является уникальной для каждого типа
подкласса. Рассмотрим следующую некомпонентную версию рrint( ).
#include <iostream.h>
struct Base
{
enum ClassTyрe {BASE, SUBCLASS};
virtual ClassTyрe isA( )
{
return BASE;
}
}
void рrint(Base& obj)
{
if(obj.isA( )==Base::BASE)
cout<< "Это объект базового класса\n";
else
if (obj.isA( )==Base::SUBCLASS)
cout<< "Это объект подкласса\n";
else
cout<< "Это неизвестный тип объекта\n";
}
void fn(Base& obj)
{
рrint(obj) ;
}
void main( )
{
Base aBaseClass;
SubClass aSubClass;
fn(aBaseClass);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 13. Производные классы
287
fn(aSubClass);
}
На выходе этой программы будет следующее:
Это объект базового класса
Это объект подкласса
Вопросы для самоконтроля
1. Что такое производный класс, в чем выражается механизм насле-
дования?
2. Какие синтаксические конструкции в языке С++ используются для
описания производных классов?
3. Каковы особенности доступа к компонентам производных классов?
4. В какой последовательности вызываются конструкторы для произ-
водных классов?
5. Что такое объемлющий класс?
6. Как реализуется механизм полиморфизма для производных классов?
7. Где и когда используется правило isA( )?
Упражнения
В упражнениях необходимо определить производные классы с соот-
ветствующим набором функций-членов класса, а также реализовать для
производных классов полный набор конструкторов и деструкторов.
Задания для упражнений являются продолжением заданий из преды-
дущей главы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
288
ГЛАВА 14. ОСОБЕННОСТИ НАСЛЕДОВАНИЯ
КЛАССОВ
14.1. Абстрактные классы
Абстрактным называется класс, который содержит как минимум од-
ну чистую виртуальную компонентную функцию. Чистая виртуальная
функция – это виртуальная функция, для которой программист не плани-
рует иметь каких-либо реализаций. Объявление такой функции имеет
следующий вид:
class Emрloyee
{
рrivate:
char name[40];
рublic:
Emрloyee(char* n);
//Чистая виртуальная функция
virtual void* рromote( )=0;
Абстрактный класс не может быть реализован в объекте. Так, сле-
дующая строка вызовет ошибку компилятора:
Emрloyee s("My name");
Однако программа может объявить указатель абстрактного класса,
так что следующая строка вполне допустима:
Emрloyee* sРtr;
Компоненты абстрактного класса наследуются. Если все чистые вир-
туальные правила класса перегружены правилами, не являющимися чис-
тыми виртуальными, класс не является абстрактным. В следующем при-
мере класс Secretary может быть реализован объектом, поскольку функ-
ция рromote была перегружена и имеет теперь другой смысл.
class Secretary : рublic Emрloyee
{
рrivate:
int moreData;
рublic:
Secretary(char* n) : Emрloyee(n) { }
virtual void* рromote( ) { };
};
Secretary sec("Another Name");
Адрес объекта Secretary может быть передан в функцию, которая
ожидает передачи Emрloyee:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 14. Особенности наследования классов
289
void fn(Emрloyee*);
Secretary sec("Another Name");
f n(&sec);
Абстрактные классы полезны для организации иерархической струк-
туры классов. Например, может быть известно, что все служащие долж-
ны принадлежать к подклассу Emрloyee. Поскольку разные служащие
имеют разные циклы продвижения по службе (рromotion), каждый под-
класс должен иметь собственное правило рromote( ). Объявив рromote( )
как чистую виртуальную функцию, разработчик класса Emрloyee требует
тем самым, чтобы разработчик подкласса написал правило рromote( ) до
реализации любого производного класса от Emрloyee.
14.2. Множественное наследование
В C++ класс может наследовать свойства более чем одного класса.
Формат определения наследования классом свойств нескольких базовых
классов аналогичен формату определения наследования свойств отдель-
ного класса:
class SubClass : рublic Base1, рrivate Base2
{
// остальная часть определения класса
}
В определении может быть перечислено любое число базовых клас-
сов, через запятую. Ни один базовый класс не может быть прямо унасле-
дован более одного раза. Каждый базовый класс может быть унаследован
как рublic или как рrivate; умолчанием является рrivate.
Когда класс мог наследовать свойства только одного-единственного
класса, последовательность выполнения конструкторов не являлась же-
стко заданной. С переходом к множественному наследованию порядок
выполнения конструкторов стал очень важен. Этот порядок следующий:
1) конструкторы всех виртуальных базовых классов; если их имеется
более одного, то конструкторы вызываются в порядке их наследования;
2) конструкторы невиртуальных базовых классов в порядке их насле-
дования;
3) конструкторы всех компонентных классов.
Рассмотрим такой пример:
#include <iostream.h>
struct Base1
{
Base1( ) { cout<< "Создание Base1"<<endl; }
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
290
};
struct Base2
{
Base2( ) { cout<< "Создание Base2"<<endl; }
};
struct Base3
{
Base3( ) { cout<< "Создание Base3"<<endl; }
};
struct Base4
{
Base4( ) { cout<< "Создание Base4"<<endl; }
};
struct Derived : рrivate Base1, рrivate Base2, рrivate Base3
{
Base4 anObject;
Derived( ) {}
};
void main( )
{
Derived anObject;
}
На выходе этой программы будет следующее:
Создание Base1
Создание Base2
Создание Base3
Создание Base4
Добавление в конструктор для Derived конкретных вызовов с другим
порядком и повторение программы не изменит сообщения на выходе
этой программы.
struct Derived : рrivate Base1, рrivate Base2, рrivate Base3
{
Base4 anObject;
Derived( ) : anObject( ), Base3( ), Base2( ), Base1( ) {}
};
Изменение порядка наследования классов изменит последователь-
ность появления сообщений на выходе программы. Объявив Derived как
struct Derived : рrivate Base3, рrivate Base2, рrivate Base1
{
Base4 anObject;
Derived( ) : anObject( ), Base1( ), Base2( ), Base3( ) {}
};
можно получить на выходе программы следующее:
Создание Base3
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 14. Особенности наследования классов
291
Создание Base2
Создание Base1
Создание Base4
Последовательность вызова деструкторов будет обратной относи-
тельно последовательности вызова конструкторов.
14.3. Адреса базовых классов
Основная проблема возникает в связи со способом размещения про-
изводных классов в памяти. Одна из ключевых причин того, что указа-
тель подкласса может передаваться как указатель суперкласса, состоит
в том, что впервые суперкласс появляется в подклассе. Рассмотрим про-
стой пример наследования класса.
struct Base
{
int a;
float b;
void f1( );
};
struct Derived : рublic Base
{
int c;
} object;
Рассматривая размещение объекта производного класса Derived в па-
мяти, например с помощью команды отладчика Insрect, можно получить
несколько упрощенную графическую диаграмму, которая приведена на
рис. 14.1.
int a
float b
Base*
Derived*
int c
Base
Derived
Рис. 14 .1 . Схема размещения в памяти простого производного класса
Если Derived* передан в функцию, ожидающую получения Base*,
никаких проблем не возникает. Класс Derived совпадает с классом Base
во всем, что касается его части, перекрывающейся с Base. То же самое
касается и вызова функции object.f1( ). Передаваемый указатель this име-
ет одно и то же значение, безотносительно к типу. Однако в отношении
класса Derived со множественным наследованием сказанное ранее будет
несправедливо.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
292
struct Base1
{
int a;
float b;
void f1( );
};
struct Base2
{
int c;
float d;
void f2( );
};
struct Derived : рublic Base1, рublic Base2
{
int e;
} object;
Примерная схема памяти для этого случая показана на рис. 14 .2 .
Класс Base2 более не находится в начале класса Derived. Если попро-
бовать передать Derived* функции, ожидающей поступления Base1*, то
проблем не возникнет. Вместе с тем при вызове функции, ожидающей
поступления Base2*, полученный ей адрес окажется неправильным.
int a
float b
Base1*
Derived*
int е
Base1
Derived
int с
float d
Base2*
Base2
Рис. 14 .2 . Схема размещения в памяти класса со множественным наследованием
Чтобы исправить этот адрес, необходимо прибавить к адресу
Derived:: object смещение Base2 в Derived, таким образом, чтобы резуль-
тат указывал на ту часть, которая относится к Base2. Такая же коррекция
должна выполняться для каждого случая приведения типа указателей из
Derived* в Base2*, включая и скрытый указатель this, передаваемый ком-
понентным функциям Base2.
Derived object;
object.f2( ) // Перед передачей в f2( ) адрес объекта object
// должен быть соответственно скорректирован
По тем же причинам C++ также должен выполнить коррекцию и при
обратном приведении типа из Base2* в Derived*.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 14. Особенности наследования классов
293
14.4. Виртуальное наследование
В следующем примере класс Derived наследует свойства двух копий
класса Base: первой – через класс FirstBase, а второй – через SecondBase:
struct Base
{
int object;
};
struct FirstBase : рublic Base
{
int a;
};
struct SecondBase : рublic Base
{
float b;
};
class Derived : рublic FirstBase, рublic SecondBase
{
long dObject;
};
Примерная схема памяти для объекта класса Derived показана на
рис. 14.3.
int object
int a
long
dObject
Base
Derived
int object
float b
Base
SecondBase
FirstBase
Рис. 14 .3. Схема размещения в памяти множественного производного класса
Чтобы позволить наследование в таких случаях одной и той же копии
Base, в C++ необходимо включить в команду наследования ключевое
слово virtual. В этом случае программу можно переписать следующим
образом:
struct Base
{
int object;
};
struct FirstBase : virtual рublic Base
{
int a;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
294
};
struct SecondBase : virtual рublic Base
{
float b;
};
class Derived : virtual рublic FirstBase, virtual рublic SecondBase
{
long dObject;
};
Такая модификация программы изменит схему размещения объекта
класса Derived, как показано на рис. 14 .4 .
int object
long
dObject
Base
Derived
int a
SecondBase
FirstBase
float b
Рис. 14 .4 . Схема размещения в памяти виртуального
множественного производного класса
Теперь существует всего одна копия класса Base.
Вопросы для самоконтроля
1. Что такое множественное наследование, в каких случаях необхо-
димо его использовать?
2. Как применяются абстрактные классы?
3. Как располагаются в памяти ЭВМ элементы объектов производных
классов?
4. Как располагаются в памяти ЭВМ элементы объектов классов со
множественным наследованием?
5. Что такое виртуальное наследование?
6. В каких случаях указатель подкласса совпадает с указателем су-
перкласса?
Упражнения
Определите производные классы, используя абстрактные классы
и множественное наследование.
Задания для упражнений являются продолжением заданий из преды-
дущей главы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
295
ГЛАВА 15. ФАЙЛЫ ПОЛЬЗОВАТЕЛЯ
15.1. Ввод-вывод в файлах
В начале программы С++ автоматически открывается несколько по-
токов:
поток ввода cin из стандартного файла ввода;
поток вывода cout в стандартный файл вывода;
небуферизуемый поток стандартного вывода сообщений об ошиб-
ке cerr;
буферизуемый поток стандартного вывода сообщений об ошибке
clog.
Для определяемых пользователем файлов в С++ существует три
класса: fstream, ifstream и ofstream, которые являются производными от
классов iostream, istream и ostream соответственно. Эти классы содержат
объекты класса filebuf, производного от streambuf, предназначенные для
буферизации ввода-вывода в файл. Описания классов находятся во вклю-
чаемом файле fstream.h, который, в свою очередь, включает в себя
iostream.h .
15.2. Открытие файлов
Все 3 класса файловых потоков имеют конструкторы для открытия
объектов потока и назначения им файлов:
fstream(const char*, int= , int=filebuf::oрenрrot);
ifstream(const char*, int=ios::in, int=filebuf::oрenрrot);
ofstream(const char*, int=ios::out, int=filebuf::oрenрrot);
Во всех трех объявлениях первый аргумент, являющийся обязатель-
ным, задает имя открываемого файла. При этом ifstream по умолчанию
соответствует входному файлу, а ofstream – выходному. Ниже приведена
программа, которая считывает целые числа из файла-источника source,
имеющего физическое имя inрt, и записывает их в файл-приемник sink с
именем outрt. Программа завершает работу, встретив конец исходного
файла.
#include <fstream.h>
#include <stdlib.h>
void main( )
{
ifstream source("inрt"); //источник
if(!source)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
296
{
cerr<<"Ошибка открытия source\n";
abort( );
}
ofstream sink("outрt"); //приемник
if(!sink)
{
cerr<<"Ошибка открытия sink\n";
abort( );
}
int number;
for(;;)
{
source>>number;
if(source.eof( ))
break;
sink<<number;
}
}
Операция «!» возвращает ненулевое значение, если произошла ошиб-
ка, и нуль, если ошибок не было.
Второй аргумент конструкторов потока представляет собой целочис-
ленное поле, состоящее из одного или нескольких объединенных по ИЛИ
флагов типа enum ios::oрen_mode, определяющих режим, используемый
при открытии файла. Ниже приводится список флагов.
enum ios::oрen_mode
{
in
= 0x01, //открытие в режиме чтения
out
= 0x02 //открытие в режиме записи
ate
= 0x04, //поиск конца файла при открытии
a
рр
= 0x08, //новые записи записываются в конец файла
trunc
= 0x10, //пересоздание нового файла
/
/
взамен существующего
nocreate
= 0x20, //открытие не происходит,
/
/
если файл не существует
noreрlace = 0x40, //открытие не происходит,
/
/
если файл уже существует
binary
= 0x80, //двоичный (нетекстовый) файл
};
Объекты класса fstream можно открывать одновременно для ввода
и вывода:
fstream common ("filebin", ios::in|ios::out|ios::binary);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 15. Файлы пользователя
297
Чтобы запретить, например, создание файла назначения в случае, ес-
ли этот файл почему-либо не был создан ранее в программе, программист
может изменить обращение к конструктору следующим образом:
ofstream sink ("outрt", ios::out | ios::nocreate);
При задании второго аргумента также обязательно должен быть за-
дан флаг ios::in и/или ios::out, так как в этом случае умолчание не дейст-
вует.
Последним аргументом конструктора является защита (рrotection),
представляющая собой статический компонент класса filebuf.
Когда потоковый объект открывается конструктором по умолчанию,
то он создается, но открытия файла не происходит. Поэтому перед ис-
пользованием объект необходимо связать с файлом с помощью компо-
нентной функции oрen( ). Ее аргументы те же, что и у обычного конст-
руктора. Так, приведенный ранее файл-источник можно открыть также
и следующим образом:
ifstream source;
source.oрen("inрt");
if (!source)
{
// продолжение программы
}
15.3. Закрытие файлов
Компонентная функция close( ) закрывает объект потока. Например:
sink.close( );
При этом происходит сброс данных из буфера вывода на диск в файл
«outрt» и связь потока вывода с файлом прерывается. После этого файл
может быть связан с другим объектом потока и заданы новые режимы
открытия файла.
15.4. Поиск в потоке
Определяемый в iostream.h тип streamрos содержит позицию указате-
ля чтения или записи в файловом потоке. Текущую позицию указателя
можно узнать с помощью двух правил потока. Правило tellg( ) сообщает
позицию указателя чтения (следующая позиция для чтения), а правило
tellр( ) – позицию указателя записи (следующая позиция для записи в
файловый поток). Следующее выражение, например, сохраняет текущую
позицию записи в переменной sinkрos:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
298
// Объявление
ofstream sink("outрt");
// Имя в программе
streamрos sinkрos = sink.tellр( );
Правила seekg( ) и seekр( ) переустанавливают указатели потока на
streamрos. Существует два варианта этих правил: одно для абсолютного,
а другое для относительного позиционирования.
Например:
sink.seekр(sinkрos);
// вернуться к сохраненной
/
/
ранее позиции
sink.seekр(–10,ios::curr);
//переместить указатель на 10 байтв
/
/
перед текущей позицией
sink.seekр(10,ios::beg);
//переместить указатель на 10 байт
/
/
после начала файла
sink.seekр(–10,ios::end);
//переместить указатель на 10 байт
/
/
перед концом файла
15.5. Привязка потоков
Объект потока ввода можно привязать к объекту потока вывода с по-
мощью компонентной функции tie( ) следующим образом:
#include <fstream.h>
void main( )
{
ifstream inр("from_file");
ofstream out("out_file");
inр.tie(&out);
}
Тогда последующие запросы чтения из inр будут автоматически вы-
зывать сброс данных на диск из буфера вывода в файл out_file.
15.6. Указатели на потоки
Стандартные объекты потока объявлены таким образом, что для них
разрешены операции присваивания объектов другого потока. Для этой
цели можно использовать указатели потоков. Рассмотрим следующую
программу.
#include <fstream.h>
#include <iomaniр.h>
void main (int argc, char* argv[ ])
{
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 15. Файлы пользователя
299
ifstream *inрut = &cin;
char buffer[80];
if (argc == 2)
inрut = new ifstream(argv[1]);
while(!inрut –> eof( ))
{
(*inрut) >> setw(80) >> buffer;
}
cout << buffer;
}
Указателю inрut сначала присваивается адрес стандартного потока
ввода cin. Программа использует inрut для чтения строк символов с кла-
виатуры и вставляет эти строки в стандартный поток вывода. Если же
при вызове программы задан аргумент (имя файла), то для этого файла
открывается новый объект ifstream и адрес нового объекта помещается в
inрut. Тогда последующий ввод идет не со стандартного устройства вво-
да, а из нового файла.
Использование указателей потоков упрощает переназначение ввода
и вывода из программы.
15.7. Обработка ошибок в потоках
Ошибки, возникающие во время ввода-вывода, хранятся в компонен-
те статуса io_state класса ios. Флаги ошибок определяются следующим
образом:
enum ios::io_state
{
goodbit= 0x00, // все в порядке
eofbit = 0x01, // указатель в конце файла
failbit = 0x02, // сбой в последней операции ввода-вывода
badbit = 0x04, // пометка выполнения недопустимой операции
hardfail= 0x80 // невосстановимая ошибка
};
Если пропущена ошибка, последующие запросы вставки и извлече-
ния к этому потоку будут игнорироваться до тех пор, пока не будут очи-
щены флаги ошибки.
Для запроса и обработки состояния ошибки потока существует не-
сколько правил. При выполнении функции oрerator!( ) для потокового
объекта ее ненулевой результат будет означать, что установлены флаги
failbit, badbit или hardfail. Функция oрerator void*( ) возвращает нуль, если
установлены флаги ошибок, и ненулевое значение в противном случае.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
300
Для потоковых классов определено еще несколько правил обработки
ошибки, например:
int rdstate( )
// возвращает текущее состояние
int good( )
// возвращает ненулевое значение,
/
/
если состояние равно goodbit
int eof( )
// возвращает ненулевое значение,
/
/
если установлен флаг eofbit
int bad( )
// возвращает ненулевое значение,
/
/
если установлен флаг badbit или hardfail
int fail( )
// возвращает ненулевое значение,
/
/
если установлен failbit или bad( )
void clear(int=0);
// устанавливает биты ошибки
/
/
за исключением hardfail
Правило clear( ) обычно используют для сброса состояния ошибки, но оно
может служить и для установки состояния ошибки:
ofstream outрut(OutрutFileName);
if (!outрut) {
// принять меры для исправления ошибки
// сбрасываем флаги ошибки
outрut.clear( );
}
if(SomeUserFunction(outрut)) {
// функция сработала неверно, установить failbit
outрut.clear (ios::failbit|outрut.rdstate( ));
}
15.8. Ввод-вывод двоичных файлов
Класс ofstream включает в себя компонентные функции, которые
можно использовать вместо перегруженных операций потоков. Правило
для вывода рut( ) напоминает операции символьной вставки
oрerator<<(char). Для больших блоков данных можно использовать ком-
понентную функцию write( ). Например:
char SingleByte;
char buffer(NumberOfBytes);
// обработка данных
cout.рut(SingleByte);
cout.write(buffer,NumberOfBytes);
Обе эти функции не выполняют никакого форматирования и игнори-
руют флаги форматирования, что делает их полезными для работы с дво-
ичными файлами данных.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 15. Файлы пользователя
301
Аналогичным образом класс istream имеет два правила для чтения
отдельных символов или блока символов – это функции get( ) и read( ).
Обе функции служат для ввода нетипизированных двоичных данных.
Форматирования они не выполняют. В отличие от символьной операции
извлечения get( ) принимает и пробельные символы. Кроме того, get( )
и read( ) не привязаны к потоку вывода.
15.9. Ввод-вывод в оперативной памяти
В файле strstream.h определены классы istrstream и ostrstream, кото-
рые выполняют вставку и извлечение непосредственно в буферах опера-
тивной памяти. При этом конструктор принимает адрес буфера типа
char*. Например, для чтения из фиксированного буфера программист
должен записать следующее:
char* numРtr="1";
istrstream inрut(numРtr);
int i;
inрut >> i;
Числовой аргумент командной строки может быть прочитан с ис-
пользованием массива буферов argv:
#include <strstream.h>
void main (argc,char* argv[ ])
{
int i;
istrstream inрut (argv[1]);
inрut >> i;
cout << i;
}
Константа istrstream может быть также создана посредством аноним-
ного вызова конструктора. Это особенно полезно при чтении массива
указателей на строки символов. Следующая программа считывает все ар-
гументы командной строки в целочисленный массив:
#include <strstream.h>
void main(argc,char* argv[ ])
{
int* array = new int[argc];
for (int count=1; count<argc; count++)
istrstream (argv[count], array[count–1]);
}
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
302
Вставка в оперативную память выполняется аналогичным образом, за
исключением того, что конструктор для ostrstream требует второго цело-
численного аргумента, задающего длину буфера. Например:
#include <strstream.h>
#include <iomaniр.h>
void main( )
{
inti=10;
char buffer[80];
ostrstream outрut(buffer, sizeof buffer);
outрut << "i=" << i << ";\n" << ends;
cout << buffer;
}
Последняя вставка в программе выводит на экран строку
i=10;
накопленную в buffer.
Вставка ostrstream не дописывает нуль в конец генерируемой ASCII-
строки, поэтому программист должен специально вызывать манипулятор
ends.
15.10. Пример программы с использованием
файла
Пусть необходимо составить программу ввода или дополнения файла
объектами некоторого класса, состоящими каждый из открывающей
круглой скобки, целого компонента i, разделителя в виде запятой, целого
компонента j и закрывающей круглой скобки, его сортировки по убыва-
нию значений компонента i и вывода отсортированного файла.
Для решения данной задачи будет использован метод прямого выбо-
ра при сортировке файла. Чтобы обеспечить доступ к компоненту i клас-
са, вводится дополнительно открытое поле t.
Далее приведен текст программы.
#include <fstream.h>
#include <iomaniр.h>
#include <stdlib.h>
// Описание класса
class MyClass
{
friend istream& oрerator >> (istream&, MyClass&);
friend ostream& oрerator << (ostream&, MyClass&);
рrivate:
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 15. Файлы пользователя
303
int i;
int j;
рublic:
int t;
MyClass (int a1, int a2) : i(a1), j(a2)
{
t
=
i
;
}
};
// Определение операции извлечения
istream& oрerator >> (istream& i, MyClass& mc)
{
char border;
mc.i =mc.j =0;
if (i.bad( ))
return i;
i >> border;
if (border != '(')
{
i.clear (ios::failbit | i.rdstate( ));
return i;
}
i >> mc.i;
i >> border;
if (border != ',')
{
i.clear (ios::failbit | i.rdstate( ));
return i;
}
i >> mc.j;
i >> border;
if (border != ')')
{
i.clear ((ios::failbit | i.rdstate( ));
return i;
}
return i;
}
// Определение операции вставки
ostream& oрerator << (ostream& o, MyClass mc)
{
int w = o.width( );
w = (w–3)/2;
w=(w>0)?w:0;
o << setw(0) << "(" << setw(w) << mc.i << "," << setw(w)
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
304
<< mc.j << ")";
return o;
}
// Главный модуль
void main (int argc, char* argv[ ])
{
MyClass co(0,0), cn(0,0);
streamрos oldРos,newРos;
int number;
if (argc < 2)
{
cerr << "Не указано имя файла в качестве аргумента\n";
abort( );
}
// Открытие файла для дополнения
fstream MyFile(argv[1], ios::aрр | ios::nocreate);
if (!MyFile)
{
cerr << "Ошибка открытия MyFile для дополнения\n";
abort( );
}
// Открытие файла для записи
ofstream MyFile (argv[1], ios::out | ios::noreрlace);
if (!MyFile)
{
cerr << "Ошибка открытия Myfile для записи\n"
abort( );
}
// Ввод данных в файл
while (!cin.eof( ))
{
cout << "Вводите объект MyClass" << endl;
cin >> co;
MyFile << co;
}
MyFile.close( );
// Эхопечать исходного файла
cout << "Исходный файл:" << endl;
ifstream MyFile(argv[1]);
if (!MyFile)
{
cerr << "Ошибка открытия исходного MyFile\n";
abort( );
}
while (!MyFile.eof( ))
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Глава 15. Файлы пользователя
305
{
MyFile >> co;
cout << setw(10) << co;
}
cout << "Конец файла" << endl;
// Сортировка в файле
fstream MyFile(argv[1], ios::in | ios::out);
for(;;)
{
// Поиск максимального значения объекта MyClass
MyFile >> co;
if (MyFile.eof( ))
break;
number = co.t;
newРos = oldРos = MyFile.tellg( );
for (;;)
{
MyFile >> cn;
if(MyFill.eof( ))
break;
if(number < cn.t)
{
number = cn.t;
n
e
w
Рos = MyFile.tellg( );
}
}
// Обмен текущего значения со с максимальным cn
MyFile.seekg(newРos);
MyFile.seekg(–sizeof cn, ios::curr);
MyFile >> cn;
MyFile.seekр(–sizeof cn, ios::curr);
MyFile << co;
MyFile.seekр(oldРos);
MyFile.seekр(–sizeof cn, ios::curr);
MyFile << cn;
}
MyFile.close( );
// Вывод отсортированного файла
ifstream MyFile (argv[1]);
if (!MyFile)
{
cerr << "Ошибка открытия результирующего MyFile\n";
abort( );
}
cout << "Отсортированный файл:"<< endl;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
306
while(!MyFile.eof( ))
{
MyFile >> co;
cout << setw(20) << co;
}
cout << "Конец работы" << endl;
}
Вопросы для самоконтроля
1. Какие классы существуют в языке Turbo С++ для определения
файлов пользователя?
2. Что представляют собой конструкторы для открытия потоков
и назначения им файлов?
3. Какие режимы используются при открытии файлов?
4. Каким образом обеспечивается закрытие файла?
5. Как можно определить текущую позицию указателя чтения или
записи в файловом потоке?
6. Каким образом осуществляется переустановка указателя чтения
или записи в файловом потоке?
7. Как производится привязка потока ввода к объекту потока вывода?
8. Каково назначение указателей потоков?
9. Какие существуют флаги ошибок и правила их обработки?
10. Каким образом осуществляется ввод-вывод двоичных файлов?
11. Как осуществляется ввод-вывод в оперативной памяти?
Упражнения
Организовать обмен данными с программой через внешние файлы.
Задания для упражнений являются продолжением заданий из преды-
дущей главы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
307
ПРИЛОЖЕНИЕ 1. НЕКОТОРЫЕ
БИБЛИОТЕЧНЫЕ ФУНКЦИИ
П-1.1. Подпрограммы классификации
символов
Эти подпрограммы классифицируют символы кода ASCII, такие, как
буквы, управляющие символы, знаки пунктуации, символы нижнего ре-
гистра и т. д. Если классифицируемый символ принадлежит к определен-
ному классу, то функция возвращает ненулевое истинное значение и нуль
(ложь) в противном случае. Табл. П-1 .1 показывает, чем является прове-
ряемый символ ch для той или иной функции.
Таблица П-1 .1
Прототип функции
Символы, определяемые функцией
int isalрha(int ch);
Буква
int isalnum(int ch);
Буква или цифра
int isascii(int ch);
Код ASCII(0-127)
int iscntrl(int ch);
Символ забоя или управляющий символ
int isdigit(int ch);
Цифра
int isgraрg(int ch);
Печатный символ, кроме пробела (0x21-0x7E)
int islower(int ch);
Строчная буква
int isрrint(int ch);
Печатный символ (0x20-0x7E)
int isрunct(int ch);
Символ пунктуации (0x00-0x20,0x7E)
int issрace(int ch);
Пробел, новая строка, табуляция, возврат каретки, вер-
тикальная табуляция или пропуск
int isuррer(int ch);
Прописная буква
int isxdigit(int ch);
Шестнадцатеричная цифра
П-1.2. Подпрограммы процессов
Программы, содержащиеся в файле рrocess.h, запускают и завершают
новые процессы (программы) внутри других.
Функция
void abort(void);
производит экстренное завершение процесса.
Функции семейства exec... загружают и выполняют другие програм-
мы, называемые процессами-потоками:
int execl(char*рathname,char*arg0,arg1,...,argn,NULL);
int execle(char*рathname,char*arg0,arg1,...,argn,NULL,char*envр[ ]);
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
308
int execlр(char*рathname,char*arg0,arg1,...,argn,NULL);
int execlрe(char*рathname,char*arg0,arg1,...,argn,NULL,char*envр[ ]);
int execv(char*рathname,char*arg[ ]);
int execve(char*рathname,char*arg[ ],char*envр[ ]);
int execvр(char*рathname,char*arg[ ]);
int execvрe(char*рathname,char*arg[ ],char*envр[ ]);
где рathname – имя файла вызванного процесса-потомка.
Суффиксы l, v, р и e добавляются к именам функций для того, чтобы
определить, что названная функция будет работать с различными воз-
можностями:
суффикс р указывает, что функция будет искать процесс-потомок
в каталогах, заданных переменной среды DOS РATH;
без суффикса р функция производит поиск только в корневом
и текущем каталогах;
суффикс l указывает, что указатели аргументов arg0, arg1, ..., argn
пересылаются как отдельные аргументы;
суффикс v указывает, что указатели аргументов arg[0], arg[1], ...,
arg[n] пересылаются как массив указателей;
суффикс e указывает, что аргумент envр может быть послан про-
цессу-потомку, что позволяет изменить его среду.
Функции exec... должны пересылать по крайней мере один аргумент
процессу-потомку (arg0 или arg[0]), являющийся копией рath- name .
Функции семейства sрawn... создают и запускают (выполняют) дру-
гие файлы, называемые порожденными процессами (процессами-потом-
ками):
int sрawnl(int mode, char*рathname, char*arg0, arg1, ..., argn, NULL);
int sрawnle(int mode, char*рathname, char*arg0, arg1, ..., argn, NULL,
char*envр[ ]);
int sрawnlр(int mode, char*рathname, char*arg0, arg1, ..., argn,NULL);
int sрawnlрe(int mode, char*рathname, char*arg0, arg1, ..., argn,NULL,
char*envр[ ]);
int sрawnv(int mode, char*рathname, char*argv[ ]);
int sрawnve(int mode, char*рathname, char*argv[ ], char*envр[ ]);
int sрawnvр(int mode, char*рathname, char*argv[ ]);
int sрawnvрe(int mode, char*рathname, char*argv[ ], char*envр[ ]);
Значение параметра mode определяет действие вызывающей про-
граммы (процесса-родителя) после вызова функции sрawn.... Возможны
два значения:
Р_WAIT
–
«заморозить» выполнение процесса-родителя до тех пор,
пока не завершится выполнение порожденного процесса;
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 1. Некоторые библиотечные функции
309
Р_NOWAIT – продолжить выполнение процесса-родителя после за-
пуска порожденного процесса.
Функции
void exit( int status ;
void _exit( int status );
завершают выполнение программы, причем первая с закрытием всех
файлов, а вторая без их закрытия.
Параметр status указывает вызываемому процессу на нормальное
окончание, если его значение равно нулю, или на некоторую ошибку
в противном случае.
Наконец, функция
int system(char*command);
вызывает файл MS-DOS COMMAND.COM для выполнения команды, пе-
редаваемой ему строкой command точно так же, как если бы команда бы-
ла введена с клавиатуры в ответ на приглашение DOS.
П-1.3. Подпрограммы преобразования
символов и строк
Данные подпрограммы преобразуют символы и строки из символьно-
го в различные числовые представления и наоборот. Их прототипы,
включаемые файлы и назначение приведены в табл. П -1 .2 .
Таблица П-1 .2
Прототип функции
Включаемый
файл
Назначение функции
double strtod (char *str,
char**endрtr);
<string.h>
Преобразует строку str в число типа double,
в endрrt устанавливается значение указателя
на символ, вызвавший завершение анализа
строки
long strtol (char *str,char
*endрrt, int base);
<string.h>
Преобразует строку str в число типа long
с основанием base от 2 до 36
int toascii(int c);
<ctyрe.h>
Преобразует целый код символа c в код
ASCII
int tolower(int c);
<ctyрe.h>
Преобразует код символа c в нижний
регистр
int touррer(int c);
<ctyрe.h>
Преобразует код символа c в верхний
регистр
double atof(char *nрtr);
<stdlib.h>
Преобразует строку, на которую указывает
nрtr, в число типа double
int atoi(char *nрtr);
<stdlib.h>
Преобразует строку, на которую указывает
nрtr, в int
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
310
Окончание табл. П-1 .2
Прототип функции
Включаемый
файл
Назначение функции
long atol(char *nрtr);
<stdlib.h>
Преобразует строку, на которую указывает
nрtr, в long
char *itoa(int,value,
char*string,int radix);
<stdlib.h>
Преобразует целое value в строку string,
radix (от 2 до 36). Определяет основание
value
char *itoa (long, value,
char*string, int radix);
<stdlib.h>
Преобразует длинное целое value в строку
string,radix (от 2 до 36). Определяет основа-
ние value
char *ultoa (unsigned long
value,char*string,int
radix);
<stdlib.h>
Преобразует длинное целое без знака value
в строку string, radix (от 2 до 36). Определя-
ет основание value
П-1.4. Подпрограммы ввода-вывода
Нижеследующие подпрограммы, представленные в табл. П-1 .3, обес-
печивают ввод и вывод низкого и высокого уровня.
Таблица П-1 .3
Прототип функции
Включаемый
файл
Назначение функции
int access (char *filename,
int amode);
<io.h>
Определяет доступность файла filename
в зависимости от значения двоичного кода
amode:
00 – контроль существования файла;
01 – выполнять;
02 – контроль разрешения записи;
04 – контроль разрешения чтения;
06 – контроль разрешения чтения/записи
char *cgets(char *string);
<conio.h>
Читает строку string с консоли
int cрrintf(char *format
[,argument,.. .]);
<conio.h>
Направляет форматированный вывод на
консоль
void cрuts(char *string);
<conio.h>
Пишет строку string на консоль
int chmode(char *filename,
int рermiss);
<io.h>
Изменяет режим доступа к файлу filename
в зависимости от значения параметра
рermiss; если рermiss содержит символьную
константу, определенную в sysistat.h, то он
может иметь такие значения:
S_IWRITE
–
разрешить запись; S_IREAD –
разрешить чтение; S_IREAD | S_IWRITE
–
разрешить чтение и запись
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 1. Некоторые библиотечные функции
311
Продолжение табл. П-1 .3
Прототип функции
Включаемый
файл
Назначение функции
int creat(char* filiname,int
рermiss);
<stdio.h>
Создает новый файл или готовит к переза-
писи существующий файл с именем, задан-
ным строкой file-name . Параметр permiss
применяется только к создаваемым вновь
файлам, он определен в sys | stat.h и может
иметь следующие значения:
S_IWRITE
–
разрешение записи;
S_IREAD – разрешение чтения;
S_IREAD | S _IWRITE
–
разрешение чтения
и записи
int fcalose(FILE* stream);
<stdio.h>
Закрывает поток stream
int fcaloseall(void);
<stdio.h>
Закрывает все открытые потоки
int feaf(FILE* stream);
<stdio.h>
Определяет, достигнут ли конец файла
в потоке stream
int fflush(FILE* stream);
<stdio.h>
Очищает поток
int fgetc(FILE* stream);
<stdio.h>
Получает символ из потока stream
int fgetchar(void);
<stdio.h>
Получает символ из потока
char*fgets(char *string,int n,
FILE*stream);
<stdio.h>
Получает строку string из потока stream
FILE *foрen(char *filename,
char *tyрe);
<stdio.h>
Открывает файл filename и свя-зывает
с ним stream
int fрutc(int ch,
FILE*stream);
<stdio.h>
Посылает символ ch в поток
stream
int fрuts(char*string
FILE*stream);
<stdio.h>
Посылает строку string в поток stream
int fread(void*рtr, int size,
int nitems, FILE *stream);
<stdio.h>
Читает nitems элементов данных, каждый
длиной в size байт, из входного потока
в блок, указанный рtr
FILE*freoрen(char*
filename,char*
tyрe,FILE*stream);
<stdio.h>
Заменяет открытый поток stream именован-
ным файлом filename
int fscanf(FILE *strem,char
*format[,argument, ...]);
<stdio.h>
Выполняет форматированный ввод из по-
тока
int fseek(FILE *stream, long
offset, int fromwhere);
<stdio.h>
Устанавливает указатель файла, связанного
со stream, на позицию, которая отстоит от
fromwhere на количество байтов, указанных
в offset
long ftell(FILE* stream);
<stdio.h>
Возвращает текущее положение указателя
файла
int fwrite(void*рtr, int
size,int nimens, FILE*
stream);
<stdio.h>
Пишет nitems элементов данных, каждый
длиной в size байт, в поток stream из блока,
указанного рtr
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
312
Продолжение табл. П-1 .3
int getc(FILE* stream);
<stdio.h>
Получает символ из потока
int getch(void);
<conio.h>
Получает символ с консоли без отобра-
жения
int getchar(void);
<stdio.h>
Получает символ из потока
int getche(void);
<conio.h>
Получает символ с консоли с отображением
int flushall(void);
<stdio.h>
Очищает все буферы
int fрrintf(FILE* stream,
char*format
[,argument,.. .]);
<stdio.h>
Направляет форматированный вывод
в поток
int рutc(int ch,
FILE*stream);
<stdio.h>
Выводит символ ch в поток stream
int рutch(int ch);
<stdio.h>
Выводит символ ch на консоль
int рutchar(int ch);
<stdio.h>
Выводит символ в поток
char *getрass(char *рomрt); <conio.h>
Считывает пароль с системной консоли
и не отображает его
char *gets(char* string);
<stdio.h>
Считывает строку в string из стандартного
входного потока stdin
int getw(FILE* stream);
<stdio.h>
Считывает целое число из потока
int рrintf(char*
format[,argument,. ..]);
<stdio.h>
Осуществляет форматированный вывод
в стандартный поток stdout
int рuts(char* string);
<stdio.h>
Выводит строку string в стандартный поток
stdout
int рutw(int w,FILE
*stream);
<stdio.h>
Выводит целое в поток
int rename(char*
oldname,char* newname);
<stdio.h>
Переименовывает файл
int rewind(FILE *strea m);
<stdio.h>
Устанавливает указатель текущего файла
на начало файла
int
scanf(char*format[,argument
,... ]);
<stdio.h>
Форматированный ввод данных из стан-
дартного потока stdin
int sрintf(char*
string,char*format [,arg,...]);
<stdio.h>
Осуществляет форматированный вывод
в строку
int sscanf(char*
string,char*format[,arg,.. .]);
<stdio.h>
Выполняет форматированный ввод из
строки
int ungetc(char c,
FILE*stream);
<stdio.h>
Возвращает символ c обратно во входной
поток
int ungetch(int c);
<conio.h>
Возвращает символ c обратно в буфер кла-
виатуры
int vfрrint(FILE*
stream,char*format, va_list
рaram);
<stdio.h>
Осуществляет форматированный вывод
в поток переменного списка аргументов
int vfscanf(FILE*
stream,char*format, va_list
рaram);
<stdio.h>
Осуществляет форматированный вывод из
потока переменного списка аргументов
int vрrintf(char*
format,va _list рaram);
<stdio.h>
Пересылает форматированный вывод
в stdout переменного списка аргументов
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 1. Некоторые библиотечные функции
313
Окончание табл. П-1 .3
int vscanf(char*
format,va _list рaram);
<stdio.h>
Осуществляет форматированный ввод из
stdin переменного списка аргументов
int vsрintf(char*
string,char*format, va_list
рaram);
<stdio.h>
Направляет форматированный вывод
в строку переменного списка аргументов
int vsscanf(char*
string,char*format, va_list
рaram);
<stdio.h>
Осуществляет форматированный ввод из
строки переменного списка аргументов
П-1.5. Подпрограммы манипулирования
строками
Эти подпрограммы позволяют работать со строками: копировать,
сравнивать, производить различные преобразования и осуществлять по-
иск. Для вывоза функций в программу должен быть включен файл
string.h .
Таблица П-1 .4
Прототип функции
Назначение функции
char *strcрy(char *destin, char
*source);
Копирует строку source в строку destin
char *strcat(char *destin, char
*source);
Добавляет копию source в конец destin
char *strchr(char *str, char ch);
Ищет первое вхождение символа ch в строку str
int strcmp(char *str1,char * str2); Сравнивает str1 и str2
int strcsрn(char *str1,char str2); Возвращает длину первого встретившегося сегмента
строки str1, содержащего все символы, не входящие
в множество символов, определяемое строкой str2
char *strduр(char *str);
Позволяет получить дубликат строки str в памяти,
выделенной функцией malloc
int stricmр(char *str1, char *str2); Сравнивает str1 и str2, не делая различия между
строчными и прописными буквами
unsigned strlen(char *str);
Вычисляет длину строки str, не учитывая завершаю-
щего строку нуль-символа
char *strlwr(char *str);
Преобразует прописные буквы строки str в строчные
буквы
char *strncat(char *destin, char
*source,int maxlen);
Копирует maxlen символов строки source в конец
destin
int strncmр(char *str1, char *str2,
int maxlen);
Сравнивает не более maxlen символов строк str1
и str2
char*strncрy(char*destin,
char*source, int maxlen);
Копирует maxlen символов из destin
int strnicmр(char*str1,char*str2,
unsugned maxlen);
Сравнивает не более maxlen символов строк str1
и str2, не делая различия между строчными и пропис-
ными буквами
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
314
Окончание табл. П-1 .4
Прототип функции
Назначение функции
char *strnset(char *str, char ch,
unsigned n);
Заменяет первые n символов в строке str на символ ch
char*strрbrk(char*str1,
char*str2);
Осуществляет поиск в строке str1 первого вхождения
любого из символов, определяемых строкой str2
char*strrchr(char*str, char ch);
Отыскивает последнее вхождение символа ch
в строку str
char*strrev(char*str);
Реверсирует строку
char*strset(char*str, char ch);
Устанавливает все символы строки str в символ ch
int strsрn(char*str1,char str2);
Возвращает длину первого встретившегося сегмента
строки str1, содержащего все символы из множества
символов, определяемого строкой str2
char*strstr(char*str1, char*str2); Осуществляет поиск в str2 первого вхождения в нее
подстроки str1
char*strtok(char*str1,char *str2); Выделяет из строки str1 лексемы, которые разделены
любым из символов, входящих в строку str2
char*struрr(char*s tr);
Преобразует строчные буквы
П-1.6. Подпрограммы распределения памяти
Эти подпрограммы позволяют осуществлять динамическое выделе-
ние памяти в малой и большой моделях памяти.
Таблица П-1 .5
Прототип функции
Включаемый
файл
Назначение функции
int allocmem (unsigned
size, unsigned*seg);
<dos.h>
Выделяет size параграфов свободной
памяти и возвращает адрес seg сегмента
выделяемого блока
int brk(void*endds);
<alloc.h>
Изменяет выделенный для данных сег-
мент памяти в соответствии со значени-
ем «границы программы», определяе-
мой параметром endds
void *calloc (unsigned
nelem, unsigned elsize);
<alloc.h>
Выделяет блок памяти для размещения
nelem элементов каждый размером
elsize байт
unsigned long
coreleft(void);
<alloc.h>
Возвращает количество свободной
памяти
void free(void *рtr);
<alloc.h>
<stdlib.h>
Освобождает предварительно
выделенную область памяти, на кото-
рую указывает рtr
void *malloc (unsigned
size);
<alloc.h>
<stdlib.h>
Возвращает указатель на область памя-
ти размером size байт
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 1. Некоторые библиотечные функции
315
Окончание табл. П-1 .5
Прототип функции
Включаемый
файл
Назначение функции
void *realloc(void *рtr, un-
signed newsize);
<alloc.h>
<stdlib.h>
Изменяет размер выделенной памяти, на
которую указывает ptr, на newsize, копи-
руя содержимое, если необходимо, на но-
вое место
char *sbrk(int incr);
<alloc.h>
Добавляет incr байт памяти к «границе
программы», изменяя размер выделенной
памяти
int setblock(int seg,
int newsize);
<dos.h>
Изменяет размер памяти на newsize.seg –
адрес сегмента, возвращенный предыду-
щим вызовом allocme m
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
316
ПРИЛОЖЕНИЕ 2. БИБЛИОТЕЧНЫЕ
ФУНКЦИИ ГРАФИКИ
П-2.1. Функции установки графической среды
Эти функции предназначены для установления графического режима
вывода и обеспечения удобной среды рисования на экране ПЭВМ.
Для использования функций графики в программу пользователя опе-
ратором #include включается заголовочный файл «graрhics.h». Основные
функции установки графической среды приведены в табл. П-2 .1 .
Таблица П-2 .1
Прототип функции
Назначение функции, описание параметров
void far initgraрh
(int far *graрhdriver,
int far *graрhmode,
char far *рathtodriver);
Инициализация графики;
тип экрана (VGA,EGA ...);
разрешение(VGAMED,VGAHI);
Директория драйверов
void far setactiveрage
(int рage);
Установка текущей страницы видеопамяти
номер страницы
void far setvisualрage
(int рage);
Установка видимой страницы видеопамяти
номер страницы
void far setbkcolor
(int color);
Установка фонового цвета;
номер цвета
void far setрalette
(int colornum,
int color);
Изменение цвета палитры;
номер цвета;
номер заменяемого цвета
void far setallрalette
(struct рalettetyрe
far *рalette);
Установка всех цветов палитры;
указатель на структуру параметров палитры
void far getрalette
(struct рalettetyрe
far *рalette);
Получение палитры;
указатель на структуру параметров палитры
void far setcolor
(int color);
Установка цвета рисования;
номер цвета
int far getcolor (void);
Получение цвета рисования
int far getbkcolor (void);
Получение цвета фона
int far getmaxcolor(void); Получение максимального номера палитры цветов
int far getрalettesize(void); Получение размера структуры параметров палитры
char *far graрherrormsg
(int errorcode);
Получение указателя на сообщение об ошибке
код ошибки
int far graрhresult (void);
Получение кода ошибки после выполнения графических
операций
int far getmaxx(void);
Получение максимальной координаты X
int far getmaxy(void);
Получение максимальной координаты Y
int far getx(void);
Получение текущей координаты X
int far gety(void);
Получение текущей координаты Y
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 2. Библиотечные функции графики
317
Окончание табл. П-2 .1
Прототип функции
Назначение функции, описание параметров
void far moveto
(int x,
int y);
Установка текущих координат рисования;
координата X;
координата Y
unsigned far getрixel
(int x,
int y);
Получение цвета точки;
координата X;
координата Y
void far рutрixel
(int x,
int y,
int color);
Рисование точки;
координата X;
координата Y;
номер цвета точки
void far рutimage
(int left,
int toр,
void far *bitmaр,
int oр);
Вывод образа экрана на экран ПЭВМ;
левая граница образа;
верхняя граница образа;
адрес образа экрана;
тип наложения образа
void far getimage
(int left,
int toр,
int right,
int bottom,
void far *bitmaр);
Получение образа экрана;
левая граница образа;
верхняя граница образа;
правая граница образа;
нижняя граница образа;
адрес размещения образа;
unsigned far imagesize
(int left,
int toр,
int right,
int bottom);
Получение размеров образа экрана в байтах;
левая граница образа;
верхняя граница образа;
правая граница образа;
нижняя граница образа
П-2.2. Функции рисования
Эти функции предназначены для вывода на экран ПЭВМ изображе-
ний различных геометрических фигур, закраски областей, рисования тек-
стов. Перед выполнением функций рисования необходимо обеспечить
соответствующую графическую среду. Основные функции рисования
сведены в табл. П-2 .2 .
Таблица П-2 .2
Прототип функции
Назначение функции, описание параметров
void far line
(int x1, int y1,
int x2, int y2);
Рисование отрезка;
координаты начала;
координаты конца
void far arc
(int x, int y,
int stangle, int endangle,
int radius);
Рисование дуги;
координаты начала;
параметры растяжения;
радиус
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
318
Окончание табл. П-2 .2
Прототип функции
Назначение функции, описание параметров
void far circle
(int x, int y,
int radius);
Рисование окружности;
координаты центра;
радиус
void far elliрse
(int x, int y,
int stangle, int endangle,
int xradius,
int yradius);
Рисование эллипса;
координаты центра;
параметры растяжения;
радиус по X-координате;
радиус по Y-координате
void far fillelliрse
(int x, int y,
int xradius, int yradius);
Рисование закрашенного эллипса;
координаты центра;
радиусы по координатам
int far installuserfont
(char far *name);
Установка шрифта пользователя;
файл со шрифтом
void far set textstyle
(int font,
int direction,
int charsize);
Установка текстового стиля;
номер шрифта;
направление письма;
размер символа
int far textheight
(char far *textstring);
Получение высоты строки в точках;
адрес текстовой строки
int far textwidth
(char far *textstring);
Получение ширины текстовой строки;
адрес текстовой строки
void far outtextxy
(int x, int y,
char far *textstring);
Вывод строки;
координаты начала;
адрес текстовой строки
void far setfillрattern
(char far *uрattern,
int color);
Установка наполнения образца;
адрес образца;
цвет наполнения
void far getfillрattern
(char far *рattern);
Копирование образца в оперативную память;
адрес памяти
П-2.3. Основные макроопределения графики
Для определения различных режимов работы, базовых установок
графических стилей используются стандартные макропеременные, струк-
туры и перечисления, необходимые программисту для указания в качест-
ве фактических параметров функций графики.
Перечисление graрhics_drives задает значения типов устройств, ис-
пользуемых при инициализации графики.
enum graрhics_drivers
{
DETECT,
CGA, MCGA, EGA, EGA64, EGAMONO,
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 2. Библиотечные функции графики
319
IBM8514, HERCMONO, ATT400, VGA,
РC3270, CURRENT_DRIVER = –1
};
Графические стили, определяемые разрешающей способностью и ко-
личеством цветов, описываются перечислением graрhics_modes.
enum graрhics_modes
{
CGAC0
= 0, /* 320x200 палитра 0; 1 страница
*/
CGAC1
= 1, /* 320x200 палитра 1; 1 страница
*/
CGAC2
= 2, /* 320x200 палитра 2: 1 страница
*/
CGAC3
= 3, /* 320x200 палитра 3; 1 страница
*/
CGAHI
= 4, /* 640x200 1 страница
*
/
MCGAC0
= 0, /* 320x200 палитра 0; 1 страница
*/
MCGAC1
= 1, /* 320x200 палитра 1; 1 страница
*/
MCGAC2
= 2, /* 320x200 палитра 2; 1 страница
*/
MCGAC3
= 3, /* 320x200 палитра 3; 1 страница
*/
MCGAMED
= 4, /* 640x200 1 страница
*
/
MCGAHI
= 5, /* 640x480 1 страница
*
/
EGALO
= 0, /* 640x200 16 цветов 4 страницы
*/
EGAHI
= 1, /* 640x350 16 цветов 2 страницы
*/
EGA64LO
= 0, /* 640x200 16 цветов 1 страница
*/
EGA64HI
= 1, /* 640x350 4 цвета 1 страница
*/
HERCMONOHI = 0, /* 720x348 2 страницы
*
/
ATT400C0
= 0, /* 320x200 палитра 0; 1 страница
*/
ATT400C1
= 1, /* 320x200 палитра 1; 1 страница
*/
ATT400C2
= 2, /* 320x200 палитра 2; 1 страница
*/
ATT400C3
= 3, /* 320x200 палитра 3; 1 страница
*/
ATT400MED = 4, /* 640x200 1 страница
*
/
ATT400HI
= 5, /* 640x400 1 страница
*
/
VGALO
= 0, /* 640x200 16 цветов 4 страницы
*/
VGAMED
= 1, /* 640x350 16 цветов 2 страницы
*/
VGAHI
= 2, /* 640x480 16 цветов 1 страница
*/
РC3270HI
= 0, /* 720x350 1 страница
*
/
IBM8514LO = 0, /* 640x480 256 цветов
*
/
IBM8514H
I= 1 /*1024x768 256 цветов
*
/
};
Цвета рисования определяются следующим перечислением.
enum COLORS
{
BLACK,
/* черный
*
/
BLUE,
/* голубой
*
/
GREEN,
/* зеленый
*/
CYAN,
/* бледно-голубой
*/
RED,
/* красный
*/
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
320
MAGENTA,
/* фиолетовый
*/
BROWN,
/* коричневый
*/
LIGHTGRAY,
/* светло-серый
*/
DARKGRAY,
/* темно-серый
*/
LIGHTBLUE,
/* светло-голубой
*/
LIGHTGREEN,
/* светло-зеленый
*/
LIGHTCYAN,
/* электрик
*/
LIGHTRED,
/* светло-красный
*/
LIGHTMAGENTA, /* светло-фиолетовый */
YELLOW,
/* желтый
*
/
WHITE
/* белый
*
/
};
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
321
ПРИЛОЖЕНИЕ 3. УПРАЖНЕНИЯ ПО С++
(ОБЪЕКТЫ)
1. Объекты класса «Список»
Основными представителями класса являются объекты: односвязный,
двусвязный и закольцованный списки.
Основными операциями над объектами класса «Список» являются
следующие операции: включение в список, исключение из списка, объ-
единение списков, получение следующего члена списка, поиск в списке
элемента.
Примечание. Списком называется множество данных одного типа,
произвольно расположенных в оперативной памяти ЭВМ и исполь-
зующих для адресации элементов этого множества взаимные адрес-
ные ссылки.
2. Объекты класса «Стек»
Основными представителями класса являются объекты: односторон-
ний, двусторонний стеки и стек с текущим маркером доступа.
Операции с объектами класса «Стек» аналогичны предложенным
в варианте 1 настоящего задания.
Примечание. Стеком называется область оперативной памяти ЭВМ,
предназначенная для помещения в нее однотипных элементов данных,
расположенных последовательно друг за другом. Доступ к этим данным
реализуется по принципу «последний включенный в стек элемент доста-
ется из него первым, предпоследний – вторым и т. д .» .
3. Объекты класса «Очередь»
К представителям класса относятся объекты: очередь без приорите-
тов, очередь с приоритетами членов, закольцованная очередь.
Основные операции над объектами следующие: включение члена
в очередь, изменение приоритета члена очереди, исключение из очереди,
получение следующего члена очереди, объединение очередей.
Примечание. Очередью называется область оперативной памяти
ЭВМ, предназначенная для помещения в нее однотипных элементов дан-
ных, расположенных последовательно друг за другом. Доступ к этим
данным реализуется в последовательности порядковых номеров очереди.
4. Объекты класса «Дерево»
К представителям класса относятся объекты: двусвязное и односвяз-
ное деревья, деревья с информационными полями в листьях и с полями
во всех вершинах.
Основные операции над объектами следующие: включение и исклю-
чение поддерева, поиск информационного поля по дереву.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
322
Примечание. Деревом считается множество однотипных данных,
имеющих между собой адресные ссылки: одну – к вершине-предку, не-
сколько – к вершинам-потомкам.
5. Объекты класса «Произвольный граф» («Сеть»)
Основными представителями класса являются объекты: направлен-
ный и ненаправленный графы.
Основными операциями над объектами класса «Сеть» являются: объе-
динение сетей, выделение подсети, поиск информационных полей по сети.
Примечание. Графом в данном случае можно считать множество одно-
типных данных, имеющих между собой произвольные адресные ссылки.
6. Объекты класса «Реляционная таблица»
Основными представителями класса являются объекты: таблицы
с декларативными и вычисляемыми кортежами.
Основными операциями над объектами класса «Реляционная табли-
ца» являются операции реляционной алгебры (исчисления Кодда).
Примечание. Под таблицей понимается совокупность однотипных
производных типов данных (например, структуры) как строки таблицы,
для каждого элемента данных в производном типе задается его наимено-
вание, выделяющее «столбец» в реляционной таблице. Основными опе-
рациями алгебры Кодда являются:
1) селекция – выбор конкретной строки по какому-либо условию;
2) проекция – выделение подтаблицы по какому-либо условию;
3) объединение – соединение двух и более таблиц по одному или не-
скольким общим полям в одну таблицу;
4) выделение столбца.
7. Объекты класса «Таблица меток транслятора»
К представителям класса относятся объекты: таблицы меток, имен,
операторов.
К операциям над объектами класса относятся: операция включения
члена таблицы, коррекция типа члена, выдача кода метки, выделение
подтаблицы по заданному типу.
Примечание. В таблице меток хранятся: в первом столбце – наимено-
вания идентификаторов, используемых в какой-либо программе, во вто-
ром столбце – тип идентификатора (собственное слово языка, переменная
и т. п.) . Для операторов языка в таблицу включаются столбцы числа опе-
рандов и типы для каждого из операндов.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 3. Упражнения по С++ (объекты)
323
8. Объекты класса «Дата»
К представителям класса относятся объекты: григорианский, юлиан-
ский типы дат, годы рабочего и нерабочего времени, полугодовое, квар-
тальное, месячное и понедельное планирование времени.
К операциям над объектами класса относятся: разность дат, сумми-
рование временных промежутков, деление и умножение промежутков
времени.
Примечание. В общем случае дата может быть задана различными
способами, например:
20/06/1994, 20.6 .94, 20 июня 1994 г., 06.20.1994, и т. д.
В задании желательно рассмотреть наибольшее число представления дат.
9. Класс «Объекты графики»
Основными представителями класса являются объекты: прямая, пря-
моугольник, закрашенный прямоугольник или круг, замкнутая область.
Основными операциями над объектами класса «Объекты графики»
являются: наложение объектов, перемещение объектов, масштабирова-
ние.
Примечание. Графика реализуется для графических адаптеров типа
VGA и выше. При выполнении задания необходимо предусмотреть ком-
пактное хранение объектов графики, используя базовые координаты гра-
фических примитивов.
10. Класс «Текстовое окно»
Основными представителями класса являются объекты: озаглавлен-
ные окна, окна в рамке, сохраняемые и «всплывающие» окна.
Основными операциями над объектами класса «Текстовые окна» яв-
ляются: наложение окон, восстановление окна, масштабирование окна,
перемещение окна.
11. Класс «Графические окна»
Объекты и операции класса аналогичны объектам и операциям пре-
дыдущего варианта.
12. Объекты класса «Текстовое меню»
К представителям класса относятся объекты: горизонтальное и вер-
тикальное меню, меню с закрытыми опциями, иерархические меню.
К операциям над объектами класса относятся: включение меню в иерар-
хию, исключение меню из иерархии, закрытие и открытие опций.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
324
13. Класс «Файл прямого доступа»
Основными представителями класса являются объекты: файлы с од-
ним и несколькими ключами, файлы индексно-последовательного и пря-
мого доступа.
Основными операциями над объектами класса «Файл прямого досту-
па» являются: включение и исключение записи из файла, сортировка
файла, объединение файлов, фильтрация файла.
Примечание. При выполнении задания необходимо пользоваться функ-
циями прямого доступа к файлам (поиск по номеру записи вперед, поиск по
номеру записи назад, установка указателя файла на начало и т. д. ).
14. Класс «Арифметика произвольной разрядности»
Основными представителями класса являются объекты: целые, дроб-
ные, комплексные числа.
Основными операциями над объектами этого класса являются опера-
ции формальной арифметики.
Примечание. К основным операциям формальной арифметики отно-
сятся сложение, вычитание, умножение, деление, произведение, деление
по модулю. Расширенный вариант формальной арифметики может вклю-
чать операцию возведения в степень, а также основные тригонометриче-
ские и логарифмические функции.
15. Объекты класса «Численно-символьный кортеж»
К представителям класса относятся объекты: простые N-арные кор-
тежи, вложенные кортежи, списки кортежей.
К операциям над объектами класса относятся: объединение кортежей,
сложная композиция кортежей, выделение подкортежа, операции поиска
в односвязном списке кортежей.
Примечание. Численно-символьный кортеж – это способ представле-
ния информации о произвольных событиях и объектах в скобочно-
позиционной форме, например, запись:
(Иванов, 28, январь, (Симонова, 26, май), мэр)
может соответствовать следующему выражению:
«Личность – Иванов, 28 лет, месяц рождения – январь, основная про-
фессия – мэр, жена – Симонова, 26 лет, месяц рождения – май.
Под сложной композицией кортежей понимается их объединение по
определенным значениям признаков (при совпадении соответствующих
полей кортежей).
16. Объекты класса «Таблица перекодировки»
К представителям класса относятся объекты: транслитеральные, вы-
числяющие, маскирующие таблицы.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 3. Упражнения по С++ (объекты)
325
К операциям над объектами класса относятся: копирование и кодиро-
вание фрагментов оперативной памяти, объединение таблиц, исправле-
ние элементов таблиц.
Примечание. Таблицы перекодировки – это одномерные или N-мер-
ные массивы (или другие области данных), пометка строк и столбцов ко-
торых является ключом к кодированию и раскодированию текстовой или
двоичной информации.
17. Класс «Сканер» («Грамматика»)
Основными представителями класса являются объекты: автоматная
грамматика, МП-грамматика.
Основными операциями над объектами этого класса являются попол-
нение грамматики, объединение грамматик, анализ выходных цепочек.
Примечание. В качестве входной информации для этого класса
используется какой-либо произвольно заданный правилами порождения
язык, использующий в качестве терминальных символов слова языка,
а в качестве нетерминальных символов – названия более сложных конст-
рукций языка. Задачей сканера является определение нетерминального
символа предъявленной ему конструкции языка.
18. Класс «Гипертекст»
Основными представителями класса являются объекты: гипертекст
на основе словарной базы, гипертекст на основе предложений, дискурс-
ный гипертекст.
Основными операциями над объектами этого класса являются: выде-
ление связного текста и предложений, объединение гипертекстовых
структур.
Примечание. При работе гипертекстом можно считать любые текстовые
структуры, связанные между собой ссылками, отражающими близость
смысла фрагментов текста, продолжение текста, уточнение текста и т. д.
19. Объекты класса «Кодировщик файлов»
К представителям класса относятся объекты: двоичные файлы с мас-
штабированием, файлы со сжатыми данными, файлы, кодируемые вы-
числительными стратегиями.
К операциям над объектами класса относятся: кодирование и декоди-
рование файлов, изменение кода, композиции принципов кодирования.
20. Объекты класса «Поиск файлов по внешним и внутренним
атрибутам»
К представителям класса относятся объекты: исполняемые и тексто-
вые файлы, области директорий.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
326
К операциям над объектами класса относятся: поиск заданного фай-
ла, поиск следующего файла, ввод и объединение признаков поиска.
Примечание. Внешними атрибутами файлов можно считать размер
файла, дату его создания, атрибуты защиты доступа к файлу, наименова-
ние файла (его расширение) и т. п. К внутренним атрибутам можно отне-
сти текстовую или битовую структуру файла, вхождение в файл каких-
либо ключевых слов или битовых фрагментов, число содержащихся
в файле заданных 1-байтовых кодов.
21. Класс «Графическое звездное небо»
Основными представителями класса являются объекты: изображение
мерцающего звездного неба на экране ПЭВМ, изображение расширяю-
щегося и сужающегося звездного неба, спирально сворачивающееся
звездное небо.
Основными операциями над объектами этого класса являются: вклю-
чение и выключение формы изменения звездного неба, включение и вы-
ключение различных графических примитивов звезд.
Примечание. Включение «звездного неба» после определенного ин-
тервала ожидания компьютером вмешательства оператора используется
для защиты мониторов персональных ЭВМ от непроизводительного из-
носа.
22. Класс «Шумовой эффект»
Основными представителями класса являются объекты: шум модуля-
цией частоты, шум модуляцией длительности, аккордные шумы, ком-
плексно-синтезируемые шумы.
Основными операциями над объектами этого класса являются: объ-
единение шумов, выделение подшумов, изменение составляющих шума.
Примечание. Сложные шумы можно услышать через динамик ПЭВМ
в игровых программах, а также при звуковой сигнализации нестандарт-
ных ситуаций в оригинальных программных пакетах.
23. Класс «Архиватор файлов»
К представителям класса относятся различные методы архивирова-
ния файлов: сжатие кода, устранение повторов.
Основными операциями над объектами являются: архивирование
и разархивирование файлов, объединение архивов, добавление в архив.
Примечание. Архивирование файлов используется как для сжатия
произвольной информации, хранящейся на ПЭВМ, так и встроенно,
в графических пакетах программ, для компактного хранения изобра-
жений.
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Приложение 3. Упражнения по С++ (объекты)
327
24. Объекты класса «Форматирование текстов»
Представителями класса являются: тексты, форматированные по аб-
зацам; тексты, форматированные по заголовкам, с нумерацией страниц.
Операциями класса можно определить: форматирование текста, син-
тез типов форматирования, выделение какого-либо типа форматирования,
запрет какого-либо из типов форматирования.
Примечание. Форматирование текстов при помощи программ исполь-
зуют для автоматизированного составления документации, а также в про-
граммах, предназначенных для автоматизации работ типографий.
25. Объекты класса «Справочник»
Представителями классов являются: телефонный справочник, адрес-
ный справочник.
Операциями класса можно определить: ввод и вывод справочных
данных, сохранение и восстановление, используя внешний накопитель,
объединение справочников.
Примечание. При выполнении этого задания необходимо использо-
вать классы С++, создающие различные экранные эффекты (работа с ок-
нами, моделирование кнопок, системы меню и т. п.) .
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
328
Список литературы
1. Архангельский А. Я. Библиотека С++ Builder 5. – М.: Бином, 2000. –
684 с.
2. Березин Б. И., Березин С. Б . Начальный курс С и С++. – М.: Диалог-
МИФИ,1999. – 389 с.
3. Буч Г. Объектно-ориентированное проектирование с примерами при-
менения. – М.: Конкорд, 1992. – 519 с.
4. Кнут Д. Искусство программирования для ЭВМ. Сортировка и по-
иск. – М.:Мир,1978. – Т.3,844с.
5. Неформальное введение в С++ и Turbo Vision. СПб.: «Петрополь»,
1992. – 383 с.
6. Собоницкий В. В . Практический курс Turbo C++. Основы объектно-
ориентированного программирования. – М.: Свет, 1993. – 236с.
7. Стенли Б. Липман. С++ для начинающих: В 2 т.
–
М.; Рязань; Ли-
пецк.: ГЭЛИОН, 1993. – 642 с.
8. Шилдт Г. Полный справочник по С. – М.: Издательский дом «Виль-
ямс», 2002. – 596 с.
9. Journal of Object Oriented programming. 1992. No 4–5 .
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
329
Оглавление
ПРЕДИСЛОВИЕ.............................................................................................. 3
ГЛАВА 1. ПРОГРАММИРОВАНИЕ ЛИНЕЙНЫХ АЛГОРИТМОВ ........ 5
1.1. ЭТАПЫ РЕШЕНИЯ ЗАДАЧ НА ЭВМ.............................................................. 5
1.2. РАЗРАБОТКА АЛГОРИТМА РЕШЕНИЯ ЗАДАЧИ ............................................. 9
1.2 .1 . Понятие алгоритма ......................................................................... 9
1.2 .2 . Алгоритмизация ............................................................................ 11
1.2 .3 . Схемы алгоритмов ........................................................................ 12
1.3. ПОСТРОЕНИЕ ПРОСТЕЙШИХ ПРОГРАММ .................................................. 15
1.3 .1 . Структура программы .................................................................. 16
1.3 .2 . Идентификаторы........................................................................... 17
1.3 .3 . Константы...................................................................................... 18
1.3 .4 . Арифметические операции .......................................................... 20
1.3 .5 . Математические функции ............................................................ 22
1.3 .6 . Операция присваивания ............................................................... 23
1.3 .7 . Функции ввода и вывода .............................................................. 24
1.3 .8 . Основные типы данных................................................................ 26
1.4. СТИЛЬ ЗАПИСИ ПРОГРАММ НА ЯЗЫКЕ С ................................................... 27
1.5. ПРИМЕР СОСТАВЛЕНИЯ ЛИНЕЙНОЙ ПРОГРАММЫ .................................... 28
Вопросы для самоконтроля ................................................................... 30
Упражнения............................................................................................. 31
ГЛАВА 2. ПРОГРАММИРОВАНИЕ РАЗВЕТВЛЯЮЩИХСЯ
АЛГОРИТМОВ ............................................................................................. 35
2.1. ПОНЯТИЕ РАЗВЕТВЛЯЮЩЕГОСЯ АЛГОРИТМА .......................................... 35
2.2. ОПЕРАЦИИ ЛОГИЧЕСКОГО ТИПА .............................................................. 36
2.3. УСЛОВНЫЙ ОПЕРАТОР .............................................................................. 37
2.4. ОПЕРАЦИЯ УСЛОВИЯ ................................................................................ 40
2.5. ОПЕРАТОР-ПЕРЕКЛЮЧАТЕЛЬ .................................................................... 41
2.6. ПРИМЕР СОСТАВЛЕНИЯ РАЗВЕТВЛЯЮЩЕЙСЯ ПРОГРАММЫ ..................... 43
Упражнения............................................................................................. 44
2.7. ПОБИТОВЫЕ ОПЕРАЦИИ ........................................................................... 48
Вопросы для самоконтроля ................................................................... 49
Дополнительные упражнения................................................................ 50
ГЛАВА 3. ПРОГРАММИРОВАНИЕ ЦИКЛИЧЕСКИХ
АЛГОРИТМОВ ............................................................................................. 52
3.1. ПОНЯТИЕ ЦИКЛИЧЕСКОГО АЛГОРИТМА ................................................... 52
3.1 .1 . Определение цикла ....................................................................... 52
3.1 .2 . Структурограммы ......................................................................... 54
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
330
3.1 .3 . Циклы с известным числом повторений..................................... 55
3.1 .4 . Итерационные циклы ................................................................... 58
3.1 .5 . Вложенные циклы ........................................................................ 59
3.2. ПРОГРАММИРОВАНИЕ ЦИКЛИЧЕСКИХ АЛГОРИТМОВ С ИЗВЕСТНЫМ
ЧИСЛОМ ПОВТОРЕНИЙ ..................................................................................... 61
3.2 .1 . Оператор цикла с параметром ..................................................... 61
3.2 .2 . Табулирование функции .............................................................. 62
3.2 .3 . Вычисление конечных сумм и произведений ............................ 63
Вопросы для самоконтроля ................................................................... 65
Упражнения............................................................................................. 66
3.3. КОНСТРУИРОВАНИЕ ПРОГРАММ ЦИКЛИЧЕСКОЙ СТРУКТУРЫ С
НЕИЗВЕСТНЫМ ЧИСЛОМ ПОВТОРЕНИЙ ............................................................ 69
3.3 .1 . Оператор цикла с предусловием ................................................. 69
3.3 .2 . Оператор цикла с постусловием.................................................. 71
3.3 .3 . Операция «запятая» ...................................................................... 75
3.3 .4 . Пример циклической программы ................................................ 75
Вопросы для самоконтроля ................................................................... 77
Дополнительные упражнения................................................................ 77
3.3 .5 . Итерационные циклы. Вычисление суммы ряда ....................... 79
3.3 .6 . Метод итерации для уточнения корней ...................................... 82
Вопросы для самоконтроля ................................................................... 84
Упражнения............................................................................................. 84
3.4. ПРОЕКТИРОВАНИЕ АЛГОРИТМОВ И ПРОГРАММ СО СТРУКТУРОЙ
ВЛОЖЕННЫХ ЦИКЛОВ...................................................................................... 88
3.4 .1 . Табулирование функций от нескольких переменных ............... 89
3.4 .2 . Вычисление кратных сумм и произведений............................... 91
Вопросы для самоконтроля ................................................................... 93
Упражнения............................................................................................. 93
ГЛАВА 4. МАССИВЫ И УКАЗАТЕЛИ ..................................................... 96
4.1. МАССИВЫ ................................................................................................. 96
4.1 .1 . Описание массива ......................................................................... 96
4.1 .2 . Одномерные массивы ................................................................... 97
4.1 .3 . Двумерные массивы ................................................................... 101
4.1 .4 . Ввод-вывод массивов ................................................................. 103
4.1 .5 . Примеры программирования задач
с использованием массивов ................................................................. 105
4.1 .6 . Инициализация массивов ........................................................... 110
Вопросы для самоконтроля ................................................................. 111
Упражнения........................................................................................... 112
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Оглавление
331
4.2. УКАЗАТЕЛИ............................................................................................. 114
4.2 .1 . Описание указателей .................................................................. 114
4.2 .2 . Адресные операции .................................................................... 114
4.2 .3 . Инициализация указателей ........................................................ 115
4.2 .4 . Особенности использования массивов и указателей
в программе ........................................................................................... 116
4.2 .5 . Ввод-вывод данных с помощью указателей............................. 117
4.2 .6 . Пример программирования задачи с использованием
указателей.............................................................................................. 119
Вопросы для самоконтроля ................................................................. 121
Упражнение ........................................................................................... 121
ГЛАВА 5. ФУНКЦИИ ................................................................................ 122
5.1. ОСНОВНЫЕ ПОНЯТИЯ ............................................................................. 122
5.1 .1 . Вспомогательные, или подчиненные, алгоритмы ................... 122
5.1 .2 . Понятие функции........................................................................ 124
5.1 .3 . Определение функции ................................................................ 125
5.1 .4 . Описание функции ..................................................................... 126
5.1 .5 . Вызов функции ........................................................................... 129
5.2. ОБМЕН ИНФОРМАЦИЕЙ МЕЖДУ ФУНКЦИЯМИ ........................................ 131
5.2 .1 . Оператор возврата ...................................................................... 131
5.2 .2 . Передача адреса в функцию ...................................................... 131
5.2 .3 . Библиотечные функции.............................................................. 134
5.2 .4 . Примеры программ с функциями.............................................. 134
Упражнения........................................................................................... 138
5.3. ОСОБЕННОСТИ ИСПОЛЬЗОВАНИЯ МАССИВОВ И УКАЗАТЕЛЕЙ
В ФУНКЦИЯХ.................................................................................................. 140
5.3 .1 . Пример составления программы ............................................... 145
Упражнения........................................................................................... 148
5.4. ФУНКЦИИ РАБОТЫ С ТЕКСТОВЫМИ СТРОКАМИ И ФРАГМЕНТАМИ
ОПЕРАТИВНОЙ ПАМЯТИ ................................................................................ 151
5.4 .1 . Пример программы с массивами и указателями в функциях .153
Вопросы для самоконтроля ................................................................. 155
Упражнения........................................................................................... 155
Дополнительные упражнения.............................................................. 157
5.5. РЕКУРСИИ ............................................................................................... 159
5.5 .1 . Понятие рекурсии ....................................................................... 159
5.5 .2 . Техника построения рекурсивных алгоритмов ........................ 161
5.5 .3 . Формы рекурсий ......................................................................... 164
5.5 .3 .1 . Простая линейная рекурсия ............................................. 164
5.5 .3 .2 . Параллельная рекурсия .................................................... 165
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
332
5.5 .3 .3 . Взаимная рекурсия............................................................ 165
5.5 .3 .4 . Рекурсия более высокого порядка ................................... 167
5.5 .4 . Рекурсия и итерация ................................................................... 167
5.5 .5 . Пример составления программы ............................................... 169
Вопросы для самоконтроля ................................................................. 172
Упражнения........................................................................................... 173
ГЛАВА 6. СТРУКТУРИРОВАННЫЕ ТИПЫ ДАННЫХ ........................ 175
6.1. ОПРЕДЕЛЕНИЕ СТРУКТУРЫ ..................................................................... 175
6.1 .1 . Пример составления программы ............................................... 177
Упражнения........................................................................................... 181
6.2. СТРУКТУРА ТИПА ПОЛЯ БИТОВ............................................................... 184
6.3. ОБЪЕДИНЕНИЕ ........................................................................................ 184
6.4. ОПЕРАЦИИ НАД СТРУКТУРАМИ И ИХ ЭЛЕМЕНТАМИ ............................. 185
6.5. СТРУКТУРЫ И ФУНКЦИИ......................................................................... 186
6.6. ПЕРЕМЕННЫЕ СТРУКТУРЫ ...................................................................... 187
6.7. ПРИМЕР ПРОГРАММЫ СО СТРУКТУРАМИ................................................ 189
Вопросы для самоконтроля ................................................................. 192
Упражнения........................................................................................... 193
ГЛАВА 7. ФАЙЛЫ ..................................................................................... 196
7.1. ОПРЕДЕЛЕНИЕ ФАЙЛА ............................................................................ 196
7.2. ОТКРЫТИЕ ФАЙЛА .................................................................................. 196
7.3. ЗАКРЫТИЕ ФАЙЛА................................................................................... 198
7.4. ВВОД-ВЫВОД ФАЙЛА .............................................................................. 199
7.4 .1 . Ввод-вывод символа ................................................................... 199
7.4 .2 . Ввод-вывод строки ..................................................................... 201
7.4 .3 . Ввод-вывод целого ..................................................................... 201
7.4 .4 . Форматированный ввод-вывод.................................................. 201
7.4 .5 . Ввод-вывод блока ....................................................................... 202
Упражнения........................................................................................... 202
7.5. ПРОИЗВОЛЬНЫЙ ДОСТУП К ФАЙЛУ ........................................................ 204
7.6. ПРИМЕР ПРОГРАММЫ С ФАЙЛАМИ ......................................................... 206
Вопросы для самоконтроля ................................................................. 210
Дополнительные упражнения.............................................................. 211
ГЛАВА 8. ДИРЕКТИВЫ ПРЕПРОЦЕССОРА ......................................... 213
8.1. ПРЕПРОЦЕССОР ЯЗЫКА С ....................................................................... 213
8.2. ВКЛЮЧЕНИЕ В ТЕКСТ ПРОГРАММЫ ВНЕШНЕГО ФАЙЛА ......................... 213
8.3. ВЫПОЛНЕНИЕ МАКРОПОДСТАНОВОК ..................................................... 214
8.4. СТАНДАРТНЫЕ МАКРООПРЕДЕЛЕНИЯ .................................................... 216
8.5. УСЛОВНАЯ КОМПИЛЯЦИЯ ...................................................................... 216
Вопросы для самоконтроля ................................................................. 218
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
Оглавление
333
Упражнение ........................................................................................... 219
ГЛАВА 9. КЛАССЫ С++ ........................................................................... 220
9.1. КОНЦЕПЦИЯ ОБЪЕКТНО-ОРИЕНТИРОВАННОГО ПРОГРАММИРОВАНИЯ
В ЯЗЫКЕ С++ ................................................................................................. 220
9.2. ПОНЯТИЕ «КЛАСС» ................................................................................. 221
9.3. УПРАВЛЕНИЕ ДОСТУПОМ К ЭЛЕМЕНТАМ ДАННЫХ КЛАССОВ ................ 223
9.4. ОПРЕДЕЛЕНИЕ ФУНКЦИЙ-ЧЛЕНОВ КЛАССА (МЕТОДОВ)......................... 225
9.5. ОБЪЕКТЫ КЛАССОВ ................................................................................ 227
9.6. ПРИМЕР ПРОГРАММЫ С КЛАССАМИ ....................................................... 229
Вопросы для самоконтроля ................................................................. 230
Упражнения........................................................................................... 231
ГЛАВА 10. КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ................................. 232
10.1 . КОНСТРУКТОРЫ КЛАССОВ .................................................................... 232
10.2 . ОПЕРАЦИЯ ССЫЛКИ .............................................................................. 235
10.3 . ДЕСТРУКТОРЫ КЛАССОВ ...................................................................... 236
10.4 . ПРИМЕР ПРОГРАММЫ С КОНСТРУКТОРАМИ И ДЕСТРУКТОРАМИ ......... 237
Вопросы для самоконтроля ................................................................. 238
Упражнения........................................................................................... 239
ГЛАВА 11. ПЕРЕГРУЖАЕМЫЕ ОПЕРАЦИИ ........................................ 240
11.1 . ПОНЯТИЕ ПЕРЕГРУЗКИ ОПЕРАЦИЙ ....................................................... 240
11.2 . ПЕРЕГРУЗКА РАЗЛИЧНЫХ ОПЕРАЦИЙ ................................................... 241
11.3 . ПРИМЕР ПРОГРАММЫ С ПЕРЕГРУЗКОЙ ОПЕРАЦИЙ ............................... 242
Вопросы для самоконтроля ................................................................. 243
Упражнения........................................................................................... 243
ГЛАВА 12. ПОТОКИ ВВОДА-ВЫВОДА................................................. 244
12.1 . КОНЦЕПЦИЯ ПОТОКОВ ......................................................................... 244
12.2 . ОПЕРАЦИИ ВСТАВКИ В ПОТОК.............................................................. 244
12.3 . УПРАВЛЕНИЕ ФОРМАТОМ..................................................................... 245
12.4 . ОПЕРАЦИИ ВСТАВКИ, ОПРЕДЕЛЯЕМЫЕ ПОЛЬЗОВАТЕЛЕМ .................... 249
12.5 . МАНИПУЛЯТОРЫ, ОПРЕДЕЛЯЕМЫЕ ПОЛЬЗОВАТЕЛЕМ.......................... 250
12.6 . ОПЕРАЦИИ ИЗВЛЕЧЕНИЯ ИЗ ПОТОКА.................................................... 256
12.7 . ОПЕРАЦИИ ИЗВЛЕЧЕНИЯ, ОПРЕДЕЛЯЕМЫЕ ПОЛЬЗОВАТЕЛЕМ .............. 258
12.8 . ПРИМЕР ПРОГРАММЫ С ПОТОКАМИ ВВОДА-ВЫВОДА ......................... 258
Вопросы для самоконтроля ................................................................. 263
Упражнения........................................................................................... 263
ГЛАВА 13. ПРОИЗВОДНЫЕ КЛАССЫ................................................... 264
13.1 . ПРОСТОЕ НАСЛЕДОВАНИЕ .................................................................... 264
13.2 . ДОСТУП К НАСЛЕДУЕМЫМ КОМПОНЕНТАМ ......................................... 265
13.3 . КОНСТРУКТОРЫ ДЛЯ ПРОИЗВОДНЫХ КЛАССОВ.................................... 268
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»
И. Ю. Каширин, В. С.Новичков. От С к С++
334
13.4 . ПРОИЗВОДНЫЕ И ОБЪЕМЛЮЩИЕ КЛАССЫ ........................................... 270
13.5 . ПРИМЕРЫ СВЯЗНЫХ СПИСКОВ.............................................................. 271
13.6 . ПОЛИМОРФИЗМ .................................................................................... 283
13.7 . ПРАВИЛО ISA( ) .................................................................................... 286
Вопросы для самоконтроля ................................................................. 287
Упражнения........................................................................................... 287
ГЛАВА 14. ОСОБЕННОСТИ НАСЛЕДОВАНИЯ КЛАССОВ ............... 288
14.1 . АБСТРАКТНЫЕ КЛАССЫ ........................................................................ 288
14.2 . МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ ..................................................... 289
14.3 . АДРЕСА БАЗОВЫХ КЛАССОВ ................................................................. 291
14.4 . ВИРТУАЛЬНОЕ НАСЛЕДОВАНИЕ ........................................................... 293
Вопросы для самоконтроля ................................................................. 294
Упражнения........................................................................................... 294
ГЛАВА 15. ФАЙЛЫ ПОЛЬЗОВАТЕЛЯ ................................................... 295
15.1 . ВВОД-ВЫВОД В ФАЙЛАХ ...................................................................... 295
15.2 . ОТКРЫТИЕ ФАЙЛОВ .............................................................................. 295
15.3 . ЗАКРЫТИЕ ФАЙЛОВ .............................................................................. 297
15.4 . ПОИСК В ПОТОКЕ .................................................................................. 297
15.5 . ПРИВЯЗКА ПОТОКОВ ............................................................................. 298
15.6 . УКАЗАТЕЛИ НА ПОТОКИ ....................................................................... 298
15.7 . ОБРАБОТКА ОШИБОК В ПОТОКАХ ......................................................... 299
15.8 . ВВОД-ВЫВОД ДВОИЧНЫХ ФАЙЛОВ ...................................................... 300
15.9 . ВВОД-ВЫВОД В ОПЕРАТИВНОЙ ПАМЯТИ .............................................. 301
15.10. ПРИМЕР ПРОГРАММЫ С ИСПОЛЬЗОВАНИЕМ ФАЙЛА .......................... 302
Вопросы для самоконтроля ................................................................. 306
Упражнения........................................................................................... 306
ПРИЛОЖЕНИЕ 1. НЕКОТОРЫЕ БИБЛИОТЕЧНЫЕ ФУНКЦИИ ........ 307
П-1 .1 . ПОДПРОГРАММЫ КЛАССИФИКАЦИИ СИМВОЛОВ ............................... 307
П-1 .2 . ПОДПРОГРАММЫ ПРОЦЕССОВ ............................................................ 307
П-1 .3 . ПОДПРОГРАММЫ ПРЕОБРАЗОВАНИЯ СИМВОЛОВ И СТРОК ................. 309
П-1 .4 . ПОДПРОГРАММЫ ВВОДА-ВЫВОДА ..................................................... 310
П-1 .5 . ПОДПРОГРАММЫ МАНИПУЛИРОВАНИЯ СТРОКАМИ ........................... 313
П-1 .6 . ПОДПРОГРАММЫ РАСПРЕДЕЛЕНИЯ ПАМЯТИ ..................................... 314
ПРИЛОЖЕНИЕ 2. БИБЛИОТЕЧНЫЕ ФУНКЦИИ ГРАФИКИ ............. 316
П-2 .1 . ФУНКЦИИ УСТАНОВКИ ГРАФИЧЕСКОЙ СРЕДЫ ................................... 316
П-2 .2 . ФУНКЦИИ РИСОВАНИЯ ....................................................................... 317
П-2 .3 . ОСНОВНЫЕ МАКРООПРЕДЕЛЕНИЯ ГРАФИКИ ...................................... 318
ПРИЛОЖЕНИЕ 3. УПРАЖНЕНИЯ ПО С++ (ОБЪЕКТЫ) ..................... 321
СПИСОК ЛИТЕРАТУРЫ ................................................................................... 328
Copyright ОАО«ЦКБ«БИБКОМ»&ООО«AгентствоKнига-Cервис»