Вы находитесь на странице: 1из 668

руководство ДЛЯ начинающих

Второе издание
А Beginner's Guide
Second Edition

HERBERT SCHILDT

МсGга\\'-Нill/0sЬогпе

J'\C\\' \"OJ'k
Cllicago SaIl r:raIlCISCO
Lj,IЮII 1\1;JIJI'i(\ Мсхн;о Cil\' Мilап
L,1'11I\01l
;-\l'\" Dcll11 ~ап.lШJl SCOII\ Sll1gal"JI'(" SY(\IlC\' ')urUlllU
РУКОВОДСТВО дпя начинающих

Второе издание

ГЕРБЕРТ шилдт


!\10СКВ(\ • C<J.hkt-ПетсрGург
2005
• KIlt'B
ББК 32.973.26-018.2.75
Ш57
УДК 681.3.07

Издательский дом "ВИЛЬЯМС"

Зав. редакцией е.н. Трuгyб

Перевод с английского и редакция н.м Ручка

По общим вопросам обращайтесь в Иэдательс.:киЙ дом "Вильямс" по адресу:


info@williamspubIishing.com. hllp://ww\v. williamspubI ishing.com
115419. Москва. aJя 783; 03150. Киев. aJя 152

Шилдт, Герберт.

Ш57 С++: руководство для начинаlOЩИХ, 2-е издание. : Пер. сангл. - М. : Издатель-
ский дом "Вильямс", 2005. - 672 с. : ил. - Парал. тит. аш·л.

ISBN 5-8459-0840-Х (рус.)

в этой книге описаны ocHoBныe средства языка С++, которые необходимо осво­
ить начинающему программисту. После рассмотрения элементарных поWJТИЙ
(переменнblX, операторов, инструкций управления, функций, классов и объеК10В)
читатель легко перейдет к изучениlO таких БОJtее сложных тем, как перегрузка опе­
раторов, механизм обработки исключительных ситуаций (искточений), наследова­
ние, полиморфизм, виртуальные функции, средства ввода-вывода и шаблоны. Aв'rop
справочника - общепризнанный авторитет в области программирования на языках
С и С++, Java и С# - включил в свою книry множество тестов для саМОКОНТРОJIJl,
которые ПОЗВОJIJlЮТ быстро проверить степень освоеиия материала, а также раздcлы
"воnpосов и ответов", способствующие более глубокому изучения основ програм­
мирования даже на начальном этапе.

ББК 32.973.26-018.2.7S

все названИJI про'1'8ммных ПРОДУКТОВ JПIJlIIЮI"CЯ зapemстрированными тoproaыMH марками с001'ВCl'Cl1i)'Ю1IIИX


фирм.
НикаКaJI часть настоящ~ro Н3Д8.НIUI нн В каких. целях не мож<:Т бlolТЪ воспроизведена в J(aкoA бы то НlI было
форме н J(UHMH бы ТО HII было средствами. будь то :mCКТPOHНI.le или мех.анические. ВКЛЮЧaJI фотокопнрова­
нне н запнсь на магнитный носитель. ссли на зто нст письменно:'О разрешении издательства Osbome Мediз.
Authorized trnпslаliоп from the Епglish luпguаgе editiоп publisllcd Ьу Osbome РuЫishiпg. Copyrighl © 2004 Ьу
Тhc МсGrпw-Нi11 Соmр3пiеs.
АН righls reserved. No ршt of this book тау Ье reproduced or trапsmittеd iп IlПУ form or Ьу anу mеалs. еlесtrопiс
ог mесhалiсаl. iпсludiпg рhОlосоруiпg. reсогdiпg or Ьу anу iпfоrmаtiоп slocage rctrievul syslem. withoul реnnissiоп
from Ihe Publisher.
Russiзп lзпguаgе editiоп published Ьу Williams РuЫishiпg House ассогdiпg 10 tbe АgreemеПI wilh R&l
ЕПlеrprises lnlоmаliопаl. Copyrighl © 2005

ISBN 5-8459-0840-Х (рус.) © Издательскнli дом "Вильямс". 2005


JSBN 0-07-223215-3 (8.Нгл.) © Тhc МсGrзw-Нill Соmралies. 2004
Оглавление

Об авторе 15
Введение 17
Модуль 1. Основы С++ 21
Модуль 2. Типы данных и операторы 69
Модуль З. Инструкции управления 109
Модуль 4. Массивы, СТрОI<И и указатели 157
Модуль 5. Введение в фУНI<ЦИИ 211
Модуль 6. О функциях подробнее 261
МОДУЛЬ 7. Еще о типах данных и операторах ЗОЗ
Модуль 8. I<лассы и объекты З49
Модуль 9. О классах подробнее З97
Модуль 10. Наследование 465
МОДУЛЬ 11. С++-система ввода-вывода 521
Модуль 12. Исключения, шаблоны и кое-что еще 571
Приложение А. Препроцессор 639
Приложение Б. Использование устаревшего С++-компилятора 65з
Предметный указатель 656
Содержание

Об авторе 15
Введение 17
Как организована эта книга 17
Практичсские навыки 1.8
Тсст для самоконтроля 18
Вопросы для текущего контроля 18
Спросим у опытного программиста 18
Учсбные проекты 18
Никакого предыдущего опыта в области программирования не требуется 18
Требуемое программное оБССllсчение 19
Программный код - из Web-пространства 19
Для далЫlсйшего изучения программирования 19
Модуль 1. Основы С++ 21
1.1. Из истории создания С++ 22
Язык С: начало эры современного программирования 23
Предпосылки ВОЗIШКlЮВСНИЯ языка С++ 24
Рождение С++ 25
Эволюция С++ 26
1.2. Связь С++ с языками Java и С# 27
1.3. Объектно-ориентированное программирование 29
Инкапсуляция 30
Полиморфизм 31
Наследование I
32
1.4. Создание, компиляция и выполнение С++-программ 32
Ввод текста программы 34
Компилирование программы 34
Выполнение программЬ! 35
Построчныii "разбор полетов" первого ПРИl\fера IJporpaMMbl 36
Обработка синтаксических ошибок 38
1.5. Использование нсремснных 1\0
1.6. Использование операторов 41
1.7. Сt"пываllие данных с клавиатуры 44
Вариации на тему вывода данных 46
Познакомимся еше с одним ТIlIЮМ даJlНЫХ 47
1.8. Использование IfНСТРУКЦИЙ управления if и (ог 52
С++: PYI<OBOACTBO для. начинающих 7

Инструкция if 52
Цикл for 54
1.9. Использование блоков кода 56
Точки с запятой и расположение инструкций 58
Практика отступов 59
1.10. Понятие о функциях 62
Библиотеки С++ 64
1.12. Ключевые слова С++ 65
1.13. Идентификаторы 66
Модуль 2. Типы данных и операторы 69
Почему типы данных столь важны 70
2.1. Типы данНЫХ С++ 70
Целочисленный тип 72
Символы 75
Типы данных с плавающей точкой 77
Тип данных Ьооl 78
Тип void 80
2.2. Литералы 83
Шестнадцатеричные и восьмеРИЧllые литералы 84
Строковые литералы 84
Управляющие символьные последователыfстии 85
2.3. Со:щаиие инициализироваlfНЫХ переменных 87
Инициализация псременной 87
Динамическая инициалllзация переменных 88
Операторы 89
2.4. Арифметические операторы 89
Инкремент и декремент 90
2.5. Операторы отношений и логические операторы 92
2.6. Оператор присваинания 98
2.7. Составные операторы приспаивания 99
2.8. П реобраэопание типов в операторах присваиваиия 100
Выражения 101
2.9. Прео6раэование типов в оыраЖСIlИЯ-Х 101
Пре06разооаIlИЯ, связанные с типом bool 1()2
2.10. Приведение тшlOВ 102
2.11. Использование пробелов If КРУГЛЫХ скобок 104
8 Содержание

МОДУЛЬ 3. ИНСТРУКЦИИ управления 109


3.1. Инструкция if 110
Условное выражение 112
Вложенные if-инструкции 114
"Лестничная" конструкция if-else-if 115
3.2. Инструкция switch 117
Вложенные инструкции switcb 121
3.3. Цикл for 125
Вариации на тему цикла for 127
Отсутствие элементов в определении цикла 129
Бесконечный цикл for 130
Циклы без тела 131
Объявление управляющей переменной цикла в заголовке
инструкции for 132
3.4. Цикл while 134
3.б. Использование инструкции break для выхода из цикла 143
3.7. Использование инструкции сопtiпuе 145
3.8. Вложенные циклы 151
3.9. Инструкция goto 152
МОДУЛЬ 4. Массивы, строки и ука3атели 157
4.1. Одномерные массивы 158
На границах массивов без пограничников 162
4.2. Двумерные массивы 163
4.3. Многомерные массивы 165
4.4. Строки 169
Основы представления строк 169
Считывание строк с клавиатуры 170
4.5. Некоторые библиотечные функции обработки строк 173
Функция strcpyO 173
Функция strcatO 173
Функция strcmp() 173
Функция strlenO 174
Пример использования строковых функций 174
Использование признака завершения строки 175
4.б. Инициализация массивов 177
Инициализация "безразмерных" массивов 180
4.7. Массивы строк 182
4.8. Указатели 183
С++: PYI<OBOACTBO для начинающих 9

Что представляют собой указатели 184


4.9. Операторы, используемые с указателями 185
О важности базового типа указателя 186
Присваивание значений с помощью указателей t 89
4.10. Использование указателей в выражениях 190
Арифметические операции над указателями 190
Сравнение указателей. 192
4.11. Указатели и массивы 193
Индексирование указателя 196
Строковые константы 198
~ассивыуказателей 203
Соглашение о нулевых указателях 205
4.12. ~ноroуровневая непрямая адресация 206
Модуль 5. Введение в функции 211
Основы использования функций 212
5.1. Общий формат С++-Функций 212
5.2. Создание функции 213
5.3. Использование аргументов 215
5.4. Использование инструкции return 216
Возврат значений 220
5.5. Использование функций в выражениях 222
Правила действия областей видимости функций 224
5.6. Локальная область видимости 225
5.7. Глобальная область видимости 231
5.8. Передача указателей и массивов в качестве аргументов функций 235
Передача функции указателя 235
Передача функции массива 237
Передача функциям строк 240
5.9. Возвращение функциями указателей 242
Функция mainO 243
5.10. Передача аргументов командной строки функции mainO 244
Передача числовых аргументов командной строки 246
5.11. Прототипы функций 248
Стандартные заголовки содержат прототипы функций 250
5.12. Рекурсия 250
Модуль 6. О функциях подробнее 261
6.1. Два способа передачи аргументов 262
6.2. Как в С++ реализована передача аргументов 262
10 Содержание

6.3. Использование указателя ДЛЯ обесПечения вызова по ссылке 264


6.4. Ссылочные параметры 266
6.5. Возврат ссылок 271
6.6. Независимые ссылки 275
Ограничения при использовании ссылок 276
6.7. Пере грузка функций 277
Автоматическое пре06разование типов и п€регрузК2 282
6.8. Аргументы, передаваемые функции 110 УМО.lчанИю 292
Сравнение возможности передачи аргументов по умолчаllИЮ
с перегрузкой функций 294
Об использовании аргументов, псредаваемых по умолчанию 296
6.9. Перегрузка функций и неОДlIозначность 298
Модуль 7 303
Еще о типах данных и операторах 303
7.1. Специфи каторы типа const и volatile 304
Спеl!ифик:атор типа
const 304
Спецификатор типа уоl .. Ше 307
Специфю<аторы классов памяти З08
Спецификатор класса памяти auto ЗОВ
7.2. Спецификатор класса памяти extern 308
7.3. Статические переменные 3\0
7.4. Рсшстровые переменные 315
7.5. Перечисления 318
7.6. Ключевое слово typedef 322
7.7. Поразрядные операторы 322
ПоразряДныс операторы И, ИЛИ, исключающее ИЛИ и НЕ 323
7.8. Операторы сдвига ЗЗО
7.9. Оператор "знак вопроса" ЗЗ9
7.10. Оператор "запятая" 340
7.11. Составные опсраторы I1РИСваивания 342
7.12. Использованис ключевого слова sjzcof 343
С~ОДllая таблица приоритеТО8 С++-операторов 345
Модуль 8. Классы и объекты 349
В.1. Общи ii формат объявления класса 350
В.2. Определение класса и со:щание объектов 351
8.3. Добавление в класс функций-членов 357
8.4. Конструкторы и деструкторы 3б7
С++: РУКОВОДСТВО АЛ~ начинающих 11

8.5. Параметризованные конструкторы 370


Добавление конструктора в класс Vehicle 373
Альтернативный вариант инициализации объекта 374
8.6. Встраиваемые функции 376
Создание встраиваемых функций R объявлении класса 378
8.7. Массивы объектов 388
8.8. Инициализация массивов объектов 389
8.9. Ука:~атели на объекты 391
Модуль 9. О классах подробнее 397
9.1. Переl"рузка КОIiСТРУКТОРОВ 398
9.2. Присваивание объектов 399
9.3. Передача объектов функциям 402
Конструкторы. деструкторы и передача объектов 403
Передача объектов по ссылке 405
Потенциальные проблемы при передаче параметров 407
9.4. Возвращение объектов функциями 408
9.5. Создание и использование конструктора копии 411
9.6. Функции-"друзья" 415
9.7. Структуры и объединения 421
Структуры -121
Объединения 424
9.8. Ключевое слово this 428
9.9. Персгрузка операторов 430
9.10. Перегрузка операторов с использованием Функций-членов 431
О значении порядка операндов 435
Использование функций-членов для Ilерегрузки
унарных операторов 435
9.11. Перегрузка операторов с ИСПО-!lьзованисм функций-не членов класса 441
Испо~ьзование Функций-"друзей" для переrpузки
унарных операторов 447
Советы по реализащш перегрузки операторов 448
Модуль 10. Наследование 465
10.1. ПОlIятие о наследовании 466
Доступ к членам класса и наследование ·170
10.2. Управление доступом к членам базового класса 4. 74.
10.3. Использование защищенных членов 477
10.4. Вызов конструкторов базового I(ласса -183
12 Содержание

10.5. Создание многоуровневой иерархии 493


10.6. Наследование нескольких базовых классов 497
10.7. Когда выполняются функции конструкторов и деструкторов 498
10.8. Указатели на производные типы 501
Ссылки на производные типы 5(12
10.9. Ви.ртуальные функции и полиморфизм 502
Понятие о виртуальной функции 502
Наследование виртуальных функций 50б
Зачем нужны виртуальные функции .507
Применеtlие виртуальных функций .509
10.10. Ч исто виртуальные функции и абстрактн ые классы 514
Модуль 11. С++-система ввода-вывода 521
Сравнение старой и новой С++-систем ввода-вывода 522
11.1. Потоки С++ 523
Встроенные С++-потоки 524
11.2. Классы потоков 524
11.3. Перегрузка операторов ввода-вывода 526
Создание перегруженных операторов вывода 527
Использование функций-"друзей" для перегрузки
операторов вывода 529
Перегрузка операторов ввода 530
Форматированный ввод-вывод данных 533
11.4. Форматирование данных с использованием
функций-членов класса ios 5З3
11.5. Использование манипуляторов ввода-вывода 540
11.6. Создание co6cтвeНlfЫx манипуляторных функций 543
Файловый ввод-вывод данных 545
11.7. Как открыть и закрыть файл 546
11.8. Чтение и запись текстовых файлов 549
11.9. Неформатированный ввод-вывод данных n двоичном режиме 551
Считывание и запись в файл блоков данных 554
11.10. Использование других функций ввода-вывода 556
Версии функции getO 557
Функция getlineO 558
Функция обнаружения конца файла 559
Функции peekO и putbackO 559
Функция flushO 559
С++: PYI<OBOACтeO дl\Я начинающих 13

11.11. Произвольный доступ 565


11.12. Получение информации об операциях ввода-вывода 568
Модуль 12. Исключения, wаблоны и кое-что еще 571
12.1. Обработка исключительных ситуаций 572
Основы обработки исключительных ситуаций 572
Использование нескольких саtсh-инструкций 578
Перехват всех исключений 581
Определение исключений, генерируемых функциями 583
Повторное генерирование исключения 585
Шаблоны 587
12.2. Обобщенные функции 587
Функция с двумя обобщенными типами 590
, Явно заданная перегрузка обобщенной функции 591
12.3. Обобщенные классы 594
Явно задаваемые специализации классов 597.
12.4. Динамическое распределение памяти 603
Инициализация динамически выделенной памяти 60i
Выделение памяти для массивов 608
Выделение памяти для объектов 609
12.5. Пространства имен 614
Понятие пространства имен 615
Инструкция usiпg 619
Неименованные пространства имен 622
Пространство имен std 622
12.6. Статические члены класса 623
Статические члены данных класса 623
Статические функции-члены класса 626
12.7. Динамическая идентификация типов (RТГI) 628
12.8. Операторы приведения типов 633
Оператор dупarnic_сast 633
Оператор сопst_cast 635
Оператор static_cast 635
Оператор reinterpret_cast 635
Приложение А. Препроц&Ссор 639
Директива #define 640
Макроопределения, действующие как функции 642
Директива #error 644
14 Содержание

Директива #include 644


ДиреКПIВы условной )(омпиляции 645
Директивы#if, #else, #еlif и #endif 6,15
Директивы#ifdef и #ifl1def МВ
Директива #ulldef 649
ИСПОЛJ,ЗО8ание оператора defined 649
Директива #line 650
Директива #pragma 650
Операторы препроцессора "#" и "##" 651
Зарезероированные макроимена 652
Приложение Б. Использование YCTapeBUlero C++-КОМПИI\IПОра 653
Два простых измеиения 655
Предметный указатель 656
Об авторе
Герберт Шилдт (Herbert Schildt) - признанный авторитет u области програм­
мирования на языках С, С++ Java и С#, профеССИОIl3ЛЬНЫЙ Wiпdоws-програм­
мист, член комитетов ANSI/ISO, ПРИlIимавших стандарт ДЛЯ языков С и С++.
Продано свыше 3 миллионов экземпляров его книг. Они переоедены на все самые
распространенные языки мира. Шилдт - автор таких бестселлероu, как [[олн~й
справочник по С, Полный справочник по С++, Полный cnpae01mUK"0 С#, /lолный
С1lравочник noJava 2, и многих других книг, включая: Руководсm(ю для UQЧUlfаю­
ЩlLТ по С, Ру'Кооодcmво для llаЧ.llнающux ,10 С# И Руководство дл.яНО1lШШЮЩUХ по
Java 2. Ш илдт - обладатель степени маrnстра в области ВЫ'IИС.'lитеЛbtюЙ техни­
ки (университет шт. ИллиноЙс). Его контактный телефон (о консулы'ационном
отделе): (217) 586-4683.
Введение
Язык С++ предназначен для разработки высокопроизводительного ПРQГРамм­
ного обеспечения и чрезвычайно популярен среди ПРОll>аммистов. При этом
он обеспечивает концеl1туальный фундамент (синтаксис If стиль), на который
опираются другие ЯЗЫIШ программирования. Не случайно ведь потомками С++
стали такие почитаемые языки, как С# и ]ауа. Более того, С++ можно назвать
униоерсальным ЯЗЫI<ОМ Ilрограммирования, поскольку практически все профес­
сиональные программисты на том или ином уровне знакомы с С++. Изучив С++,
вы получите фундаментальные знания, которые позволят вам освоить любые
аспекты современиоI'О програММИРОВ3I1ИЯ.

Цель этой книги - помочь читателю овладеть базовыми элементами С++-про­


граммирования. Сначала, например, вы узнаете, как скомпилировать и выпол­
нить С++-l1рограмму, а затем шаг за шагом будете осваивать более сложные темы
(l<J1ючевые слова, языкоuые КОНСТРУКЦИИ, операторы и пр.). Текст книги подкре­
пляется многочислеllНЫМИ примерами программ, тестами для самоконтроля и

учебными проектами, поэтому, про работав весь материал этой книги, вы получи­
те глубокое понимание основ С++-программирования.
Я хочу подчеркнуть, что эта книга - лишь стартовая площадка. С++ - это
большой (по объему средств) и не самый простой язык программирования. Не­
обходимым условием успешного программирования на С++ является знание не
только ключевых слов, операторов и синтаксиса, определяющего возможностн

языка, но и библиотек классов и функций, которые существенно помогают в раз­


работке программ. И хотя HClcoTopble элементы библиотек рассматриваются в
этой книге, все же большинство нз них не нашло здесь своего отражения. Чтобы
стать псрвоклассным программистом на С++, необходимо в совсршенстве изу­
чить и С++-библиотеки. Знания, получснные при изучении этой книги, позволят
вам освоить не только библиотеки, но и все остальные аспекты С++.

Как организована эта книга


Эта книга представляет собой самоучитель, материал которого равномерно
распределен по разделам, причсм успешное освоение каждого следующего пред­

полагает знание всех предыдущих. Книга содержит 12 модулей, посвященных со­


ответствующим аспектам С++. Уникальность этой книги состоит в том, что она
включает несколько специальных элементов, которые позволяют закрепить уже

пройденный материал.
18 Введение

Практические навыки
Все модули содержат рюдслы (отмеченные n и KTO!l)aM мой" Важно! "), которые
важно не ПРОПУСТИТЬ при lIзучеllИИ материала юшги. ИХ ЗШ\ЧИМОСТI. ДЛЯ IJОСЛ~­
доватсльного освоения lюдчеркивается тем, что они про~умерованы и Ilеречис­

лены lJ начале каждого модуля.

Тест ДЛЯ самоконтроля


Каждый модуль завершается тестом для саМ.ЖОJlТРОЛЯ. Ответы можно наifЛI
lIа Web-странице этой КlНIПI по адресу: http://www . osborne. сот.

Вопросы для текущего контроля


в конце каждого значимого раздела содеРЖI1ТСЯ тест, пронеряющий степень
вашего ПОllимания ключевых момеитов, изложснных выше. Отвсты lIа эти 81)-

lIРОСЫ приводятся внизу соответствующей СТрalIIЩЫ.

Спросим у опытного программиста


в разЛИЧIIЫХ местах KНImi пы встретите спецнал bIlbl{' разДСЛЫ"СIiРОСНМ у ОПЫТIII}'
го програмМ\lста", которые содсржат ДОПОЛШIТе.7!I>\lУЮ IIнформацию ЮПI Itнтерссны('
комментарии по nанной теме. ЭП1 разделы преДСТ<lНJlСIIЫ в ~юр:..ta'rе "ВОП!)()С-ОТВС-'''

Учебные проекты
Каждый модуль включает ОДИН [lJ111 нескоЛ\.ко просктов, которые 1l0ЮlзЫВ;t­
ют, как Шl праКТlше можно прнмеНИТf> ИЗJlожеlIныi, здесь материал. ЭТII учебвые
Ilроекты представляют собой реальные примеры, КОТОРЫС можно IIСIЮЛI>ЗОВ"ТЬ JJ
качестве стартовых вариантов для ваших программ.

Никакого предыдущего опыта


в области программирования
не требуется
Для освоения IlредставлеlПЮГО здесь MaTeplI<1..I"IIIIK3KOrO предыдущего опыта
11 области програММНРОВaJlI!Я НС требуется. Поэтому, сели ГlЫ IIIlкогда раньше IIC
IIрограммировми. можете смело браться за эту юшгу. КОJj~ЧII() же. 13 JlаШII /lHII
IШlOгие Чlпат"JJIl уже имеlOТ хотя бы 1\1JшимаЛbllыii опыт 8 I1рограМ:"НI/ЮШШIll1.
Например. вам, возможно, уже приходилось писать IIpOгpaMMbl на .Ji1"a И.'lll С#.
Как вы скоро узнаете, С++ ~шляется "РОДlпеЛС~I" O(jUIIX <>ТИХ языков. Поэтому,
еСЛII вы уже JllaK01\1bI с Java или С#, то ГlaM не со( таlllП труда ОСГlЩIТЬ 11 С+ +.
С++: РУI<ОВОДСТВО ДМ, начинающих 19

Требуемое программное
обеспечение
Чтобы СКОМrII1;IIIРol),l1'Ь и 8ЫПОЛIIIIТl. программы, приведенные в этой книге,
вам ПОllадобится любой 1'13 таких современных компиляторов, как Visual С++
(MicrosQft) JI С++ Builder (Borland).

Программный код - из Web-


пространства
Помните, что IIСХОДНЫЙ код нсех примерОD ПРOJ·рамм 11 учебных IlРОСКТОВ,
Ilриведенных D этоjj книге. можно бесплатно загрузить с Wеl>-сайта по адресу:
httр://www.оSlюrпе.соm. Загрузка кода избавит вас от необходимости ово­
ДИ1Ъ тскп программ ОР}"I"УЮ.

Длt;l дальнейшего изучениt;l


программирования
Книга С#: Р./lКО(jО(kmtю для lta'lIJII(1ЮIЦUХ - это ваш "ключ" к серии книг по
lI(юграммированию, Щ1llнсаНIIЫХ It>p6epTu:\t ШИЛДТОМ. Ниже перечислены те из
них, которые MOI')'T "рсдставлять ДЛЯ вас IIнтерес.

Те, КТО желает подробнее изучить ЖIЫК С++, могут обратиться к следую­
ЩИМ книгам.

• ПОЛllЫU сnравоч"u" 110 С++,


• с + +: баЗО(Jыii курс,
• Освой самuсmоя тулы/() С + + ,

• sn Pro,!?,"IaтmingJmm tlle Grountl ир,


• СnравО1111l1К npOlpa~t.lmcma по С/С++.
Тем, кого JIIпсресует програММllрование на языке java, мы peJ<OMeAдyeM такие
издания.

• .Iozla 2: А Begill11e1"'S Gu;rJe, .


• ПО//1(ЫU cnpo(JO'lIIllK llO.!ava 2,

• .fava 2: РrОl:jnЩllllе1"'S R(jmnlCe.


• !1скуссmвр 1IjЮI]1а\f.'lUроааl/nlЯ I/и.!аиа.
20 Введение

Если вы желаете научиться ПРОq>aМмироваТl' на С#, обратитесь к следующим


книгам.

• С#: А Beginner's Guide,


• ПОЛНЫй справочник по С#.
Тем, кто интересуется Windоws-программированием, мы можем предложить
такие книги Шилдта.

• Windows 98 Pтogrammingfrom the Ground Ир,


• Windows 2000 Programmingjrom the Ground ир,
• MFC Programmingjrom the Ground Ир,
• The Windows Annotated Arcblves.
Если вы хотите поближе познакомиться с языком С, который является фунда­
ментом всех современных языков программирования, обратитесь к следующим
книraм.

• Полный сnравочнuк по С,
• Освой самостолmелыlO С.
Если вам нужны четкие ответы, обращ8ЙТеСЬ к ГербеJ7IY Шилдту, общепризнанво­
му авторитету в области ПрОf1>аммирования.

От издательства
Вы, читатель этой КIIИrn, и есть главный ее критик и комментатор. Мы ценим
ваше мнение и хотим знать, что было сделано нами правильно, что можно было сде­
лать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно УС.1Ы­
шать и любые другие замечания, которые вам хотелось бы высказать в наш адрес.
Мы ждем ваших комментариев и надеемся t: а них. Вы можете при слать нам
бумажное или электронное письмо, либо просто посетить наш Web-сервер и
оставить свои замечания там. Одним словом, любым удобным для вас способо.\{
дайте нам знать, нравится или нст вам эта книга. а также выскажите свое мнение
о том, как сделать наши книги более интересными ДЛЯ вас.
Посылая письмо или сообщение, не забудьте указать название книги и ее авто­
ров, а также ваш обратный адрес. Мы внимательно ознакомимся с вашим мнением
и обязательно учтем его при отборе и подготовке к изданию последующих книг.
Наши координаты:
E-таН: info@williamspublishing.c:)m
WWW: http://www.williamspublishing.com
Информация для писем из:
России: 115419, Москва, а/я 783
Украины: 03150, Киев, а/я 152
МОДУЛЬ 1
ОСНОВЫ С++

1.1. Из истории создания С++


1.2. Связь С++ с языками Java и С#
1.3. Объектно-ориентированное программирование
1.4. Создание, компиляция и выполнение С++-программ
1.5. Использование переменных
1.6. Использование операторов
1.7. Считывание данных с клавиатуры
1.8. Использование инструкций управления if и for
1.9. Использование блоков кода
1.10. Понятие о функциях
1.12. Ключевые слова С++
1.13. Идентификаторы
22 Глава 1. ОСНОВЫ С++

Если и существует один компьютерный язык, который определяет суп) со­


BpeMeHHoro программирования, то, безусловно, зто С++. Язык С++ предназна­
чен для разработки высокопроизводительного llрограммного обеспечения. ЕI"O
синтаксис стал стандартом для других профессиональных языков программи­
рова.ния, а его принципы ра.lработки отражают идеи развития вычислительной
техники в целом. С++ послужил фундаментом для разработки языков будущего.
Например, K3KJava, так и С# - прямые ПОТОМIСИ языка С++. СеГОДIIЯ быть про­
фессиональныM программистом высокого клас,:а означает быть компетентным в
С++. С++ - это ключ J( современному програм\iированию.
Цель этоro модуля - представить С++ в историческом аспекте, проследить
его истоки, проанализировuть его взаимоотношения с неrюсредствснным пред­

шественником (С), рассмотреть ero возможности (области ПРИМСНСIIИЯ) и ЩНШ­


ципы программирования, которые он поддерживает. Самым трудным 8 изучении
языка программирования, безусловно. является то, что НИ один его элемеlН не
существует изолировашю от других. Компоненты языка работают вместе, можно
сказать, в дружном "коллективе". Такая тесная взаимосвязь усложняет рассмо­
трение одного аспекта С++ без изучения других. Зачастую обсуждение одного
средства предусматривает предварительное знакомство с дрynlМ. Для преодоле­
ния подобных трудностей в этом модуле ПРИВОДIIТСЯ краткое описание таЮIХ эле­
ментов С++, как общий формат С++-программы, основные инструкции управле­
ния и операторы. При этом мы не будем пока УI'лубляться в детали, а сосредото­
чимся па общих концепциях создания С++-программы.

ВАЖНОI
,ММ ИЭИСТnРИId СО3ДnWИАu-С+* /

История создания С++ начинается с языка С. И немудреlfО: С++ построеllllа


фундаменте С. С++ и в самом деле представляе1" собой супермножество язЬ/ка С.
С++ можно назвать расширенной и улучшешю~r версией языка С. предназначен­
ной для поддержки объектно-ориентированного lIрограммирования (ero ПРИН­
ципы описаны ниже в этом модуле). С++ также включает ряд других усовершен­
ствований ЯЗЫI(а С, например расширенный набор библиотеЧIIЫХ функций. При
Э1'ОМ "вкус И запах" С++ унаследовал непосредственно из языка С. Чтобы до кон­
ца понять и оценить достоинства С++, необходимо понять все "как" и "почему" n
отношении языка С.
С++: PYI<OBOACтeO ДМ! начинающих 23

Язык С: начало эры современного


программирования +
+
Изобретение языка С отмстило начало новой эры COBpeMeHHoro программи­ u-
2i
рования. Его влияние нельзя было переоцеtlить, поскольку он коренным образом aI
О
:I:
изменил подход ~ программированию и его восприятие. Его синтаксис и принци­ U
О
I1Ы разработки оказали влияние на ВСС последующие компьютерные языки, по­
этому язык С можно назвать одной из основных революционных СИЛ в развитии
вычислительной техники.
Язык С изобрел Дэнис Ритчи (Dennis Ritchie) для компьютера РОР-ll (раз­
работка компании ОЕС - Digita) Equipment Corporation), который работал под
управлением операционной системы (ОС) UNIX. Язык С - это результат про­
цесса разработки, который сначала был связан с другим языком - BCPL, создан­
ным Мартином Ричардсом (Martin Riсlшгds). Язык BCPL ИНДУUИРОВaJI появле­
ние языка, получившего lIазвание В (его автор - Кен Томпсон (Кеп Thompson»,
который в свою очередь в lIачале 70-х годов привел к разработке языка С.
Язык С стал считаться первым современным "языком программиста", по­
скольку до его изобретения l(!Омпьютерные языки в основном разрабатывались
либо как учебные упражнения, либо как результат деятельности бюрократиче­
ских структур. С языком С все обстояло иначе. Он был задуман и разработан ре­
альными, практикующими программиста.\1И и отражал их подход к I1РОГРамми­

рованию. Его средства были многократно обдуманы, отточены и протестированы


людьми, которые действительно работали с этим языком. В результате этого про­
цесса появился язык, который понравилсяMHomM программистам-практикам и
быстро распространился по всему миру. Ero невиданный успех обусловило то,
что он был разработан программистами для программистов.
Язык С возник в результате реВОЛЮ1{ИИ cmpyкmypup08a,molO npozpa.AWupo-
вания БО-х годов. До появлеllИЯ структурироваююго программировзния 060-
значились проблемы в написания больших программ, поскольку программисты
пользовались логикой, которая ПРИ80ДИJlа к созданию так наЗЫI~аемого "cnareT-
ти-кода", состоящего из множества переХОДО8, последовательность которых было
трудно проследить. В структурированных ЯЗЫI<аХ программирования эта пробле­
ма реmалась путем Ifсполь:ювания хорошо определенных инструкций управле­
ния, подпрограмм, локальных персменных и других средств. Появление структу­
рированных языков позволило писать уже довольно большие nporpaмМbl.
Несмотря на то что в то время уже существовали другие структурированные
языки (например Pascal), С стал первым языком, в котором успешно сочеталисъ
мощь, изящество и выразительность. Ero лаконизм, простота синтаксиса и фило­
софия БЫЛII I-laСl'ОЛЬКО ПРI1Dлекательными, что в мире программирования непо-
24 !Лава 1. Основы С++

стижимо быстро образовалась целая армия его приверженцев. С точки зрения


сегодняшнего ДНЯ этот феномен даже трудно понять, но, тем не менее, язык С
стал своеl'ородадолrожданным "свежим ветром". который вдохнул в программи­
рование новую жизнь. В результате С был общепризнан как самый популярный
структурированный язык 1980-х годов.

ПреДПОСblЛКИ возникновения языка С++


Приведенная выше характеристика языка С может вызвать справедливое не­
доумение: зачем же тогда, мол, был изобретен я;tык С++? Если С - такой успеш­
ный и полезный язык, то почему возникла необходимость в чем-то еще? Оказы­
вается, все дело в сложности. На протяжении всей истории программирования
усложнение программ заставляло программистов искать пути, которые бы позво­
лили справиться со сложностью. Язык С++ можно считать одним из способов ее
преодоления. Попробуем лучше раскрыть взаимосвязь между постоянно возрас­
тающей сложностью программ и путями развития языков программирования.
Отношение к программированию резко измен flЛОСЬ с момента изобретения КО.\{­
пьютера. Например, программирование для первых вычислительных машин состоя­
ло в переключении тумблеров на их передней панели таким образом, чтобы их поло­
жение соответствовало двоичным кодам машинных команд. Пока длины программ
не превышали нескольких сотен команд, такой метод еще имел право на существо­
вание. Но по мере их дальнейшего роста был изобретен язык ассемблер, чтобы про­
граммисты могли использовать символическое представление машинных команд.

Поскольку программы продолжали расти в размерах, желание справиться с более


высоким уровнем сложности ВЫЗВало появление ЯЗЫКОВ высокого уровня, разработ­
ка которых дала программистам больше инструментов (новых и разных).
Первым широко распространенным языком программирования был, конечно
же, FORTRAN. Несмотря на то что это был очень значительный шаг на пути про­
гресса в области программирования, FORTRAN все же трудно назвать языком,
который способствовал написанию ясных и простых для понимания программ.
Шестидесятые годы двадцатого столетия считаются· периодом появления струк­
турированного программирования. Именно такой метод программирования и
был реализован в языке С. С помощью структурированных языков программи­
рования можно было писать программы средней сложности, причем без особых
героических усилий со стороны программиста. Но если программный проект до­
стигал определенного размера, то даже с использованием упомянутых структури­

рованных методов его сложность резко возрастала и оказывалась непреодолимой


для возможностей программиста. К концу 70-х к "критической" точке подошло
довольно много проектов.
С++: PYI<080ACTBO ДМl начинающих 25

Решить эту проблему "DЗЯЛИСЬ" новые технолоmи программирования. Одна из


них получила название объектно-ориентированного nрограм.мuрованuя (ООП).
Вооружившись методами 00 П, программист мог справляться с программами го­ :j:
u
раздо большего размера, чем прежде. Но язык С не поддерживал методов ооп.

Стремление получить объектно-ориентированную версию языка С в конце кон­ о
1:
()
цов и привело к созданию С++. О
Несмотря на то что язык С был одним из самых любимых и распространен­
ных профессиональных языков программирования, настало время, когда его
возможности по написанию сложных программ достигли своего предела. Же­
лание преодолеть этот барьер и помочь программисту легко справляться с еще
более объемными и сложными программами - вот что стало основной причи­
ной создания С++.

Рождение С++
Язык С++ был создан Бьерном Страуструпом (Bjame Stroustrup) в 1979 году
в компании ВеН Laboratories (г. Муррей-Хилл, шт. Нью-Джерси). Сиачала но­
вый язык получил имя "С с классами" (С with Classes), но в 1983 году он стал
называться С++.
Страуструп построил С++ на фундаменте языка С, включающем все его
средства, атрибуты и основные достоинства. Для него также остается в силе
принцип С, согласно которому программист, а не язык, несет ответственность
за результаты работы своей программы. Именно этот момент позволяет понять,
что изобретение С++ не было попыткой создать новый язык программирова­
ния. Это было скорее усовершенствование уже существующего (и при этом
весьма успешного) языка.
Большинство новшеств, которыми Страуструп обогатил язык С, было предна­
значено для поддержки объектно-ориентированного программирования. По сути,
С++ стал объектно-ориентированной версией языка С. Взяв язык С за основу,
Страуструп подготовил плавный переход к ооп. Теперь, вместо того, чтобы из­
учать совершенно новый язык, С-программисту достаточно было· освоить только
ряд новых средств, и он мог пожинать плоды использования 06ъектно-ориенти­
рованной технологии программироваиия.
Создавая С++, Страуструп понимал, иасколько важно, сохранив изначальную
суть языка С, Т.е. его зффективность, гибкость и принципы разработки, внести в
него поддержку объеКТlю-ориеllТИРОВaJlНОl"O орограммирования. К счастью, эта
цель была достигнута. С++ по-прежнему предоставляет программисту свободу
действий и власть над компьютером (которые были присущи языку С), значи­
тельно расширяя при этом его (программиста) возможности за счет использова­
ния объектов.
26 Глава 1. Основы С++

Несмотря на то что С++ изначально был I-I.щелен на поддержку очень БО.1Ь­


ПlИX программ, этим, Конечно же, его использование не ограничивалось. И 8 са­
мом деле, объектно-ориентированные средства С++ можно эффективно приме­
пять практически к любой задаче программирования. Неудивительно, что С++
используется для создания компиляторов, peДёJKTOpOB, компьютерных игр и про­

грамм сетевого обслуживания. Поскольку С++ обладает эффективностью языка


С, то программиое обеспечение многих высокоэффективных систем построено с
использованием С++. Кроме того, С++ - это язык, который чаще всего выбира­
ется для Wiпdоws-программирования.

Эволюция С++
С момента изобретения С++ претерпел т~,и крупные l1ереработки, причем
каждый раз язык как ДОПОJlllЯЛСЯ новыми средствами, так и в чем-то изменялся.
Первой ревизии он был подвергнут в 1985 году. второй - в 1990, а третья произо­
шла в процессе стандартизации, который актюшзировался в начале 1990-х. Спе­
циально для этого был сформирован объединенный АNSI/ISО-комитет (я был
его членом), который 25 января 1994 года принял первый проект предложенного
на рассмотрение стандарта. В этот проект БЫЛII включены все средства, впервые
определенные Страуструпом, и добавлены новые. Но в целом он отражал состоя­
ние С++ на тот момент времени.
Вскоре после завершения работы над первым проектом стандарта С++ про­
изошло событие, которое заставило значительно расширить существующий
стандарт. Речь идет о создании'Ллександром Степановым (Alexander Stepanov)
стандартной библиотеки шаблонов (Standar<\ Template Library - STL). Как
вы узнаете позже, STL - это набор обобщенных функций, которые можно ис­
пользовать для обработки данных. Он доволыio большой по размеру. Комитет
ANSI/ISO проголосовал за включение STL в спеllИфикauию С++. добавление
STL расширило сферу рассмотрения средств С++ далеко за пределы исходнuго
определения ЯЗblка. Однако ВlUlючение STL. помимо прочего, замедлило процесс
стандартизации С++, причем довольно сущест)}еllllO.
Помимо STL, в сам ЯЗblК было добавлено н('сколько новых средств и внесено
множество мелких изменений. Поэтому верси.il С++ после рассмотрения коми­
тетом по стандартизацЮ1 стала намного больше и сложнее по сравнению с ис­
ходным вариантом Страуструпа. Конечный результат работы комитета датиру­
ется 14 ноября 1997 года, а [lеалыlO ЛNSI/ISО-стандарт языка С++ увидел Сllет
в 1998 году. Именно эта спецификация С++ ОnЫЧJlО называется Standard С++.
и именно она описана в данной книге. Эта версия С++ поддерживается всеми
ОСНОВJlЫМИ C++-компилятораМII, включая Vistl<ll С-Н (Мiсrоsоft) и С++ Buildct·
С++: руководство для начинающих 27

(Borland). Поэтому код программ, приведенных в этой ЮiИге. полностью приме­


ним ко всем современным С++-средам.
+
u+
~
ВАЖНО! аз
О

IIII..C8S13hC** с ЯЗЬ'КQМИ 'nуо И с#. :J:


()
О
ПОМИМО С++, существуют два других современных языка программирования:
java н С#. Язык.Jаvа разработан компанией Sun Microsystems, а С# - компанией
Мicrоsоft. Поскольку иногда возникает путаница относительно того, какое ОТIЮ­
шеtlНС эти два языка имеют к С++, попробуем внести ясность о этот вопрос.
С++ является родительским языком· дляjаv~ и'С#. И хотя разработчикиjаvа
и С# добаВIIЛИ к псрвоисточнику, удалили из него или модифицировали различ­
ные средства, D целом синтаксис этих трех языков практически идентичен. Более
того, объектная модель, используемая С++, подобна объеК11iЫМ моделям языков
.Java и С#. Наконец, очень сходно общее впечатщшие и ощущение от использо­
вания всех этих языков. Это зиачит, что, зная С++, вы можете легко изучитьjаvа
или С#. Схожесть синтаксисов и объектных моделей - одна из причин быстрого
освоения (11 одобрения) этих двух языков многими опытными С++-программи­
стам". Обратная ситуация также имеет место: если вы знаетеjаvа или С#, изуче­
ние С++ не доставит вам хлопот.
Основное различие между С++, Java и С# заключается в типе вычислитель­
ной среды, для которой разрабатывался каждый из этих языков. С++ создавался
с целью lIаписания высокоэффективных программ, предназначеНJlЫХ для выпол­
нения под управлением Оllределенной операционной системы и в расчете на ЦП
конкретного типа. Например, если вы хотите написать высокоэффективную про­
грамму для выполнения на процессоре Intel Pentium под управлением операци­
онной системы Windows, лучше всего использовать для этого язык С + +.
Языки java и С# ра.lработаны в ответ на Уliикальные потребности сильно рас­
пределенной сетевой среды Internet. (При разработке С# также ставил ась цель
упростить СО~iДание программных компонентов.) Intemet связывает множество
различных типов ЦП и операционных систем. Поэтому возможность создания
межплатформеНIIОГО (совместимого с несколькими операционными средами)
переносимого программного кода для 1nternet при решеНИII некоторых задач ста­
ло определяющим УСЛОDием при выборе языка программирования.
IIерпым языком, отвечающим таким требованиям, был ]ava. Используя Java.
можно написать программу, которая будет выполняться в различных вычисли­
тельных средах, т.е. в широком диапазоне операционных систем и типов цп.
TaKIIM образом. Java-программа может свободно "бороздить просторы" Intemet.
Несмотря на то чтоjаvа позволяет создавать переиосимый ПРОJ"раммный код, ко-
28 Глава 1. Основы С++

торый работает в сильно распределенной среде, цена этой переносимости - эф­


фективность. )аvа-программы выполняются медленнее, чем С++-программы. То
же справедливо и дЛЯ С#. Поэтому, если вы хотите создавать высокоэффектив­
ные приложения, используйте С++. Если же вам нужны переносимые програм­
МЫ, используйте]аvа или С#.

Спросим у опытного программиста

Вопрос. каким образом. 1I3ы'"Java u С# позволяют созihuJamь м.eжnлoтфopм.ен.­


ные (coвм.ecmuм.ыe с неclcолышAIu оrrераЦUОННbllllll cpeдaмu) nepeнocu­
JltЫe npozpaJl4JllЫ u м..ем.у С nом.ощью С++ невО3АСОЖНО iJocmuчь mmc010
же риультamа?

Ответ. Все дело в типе объектного кода, создаваемого компиляторам


В результате работы С++-компилятора мы получаем машинный код, ко­
торый непосредственно выполняется конкретным центральным процеl;­
сором (ЦП). Друrими словами, С++-компилятор связан с конкретными
ЦП и операционной системой. Если нужно выполнить С++-проrpамму
под управлением другой операционной системой, необходимо пt>реком­
пилировать ее и получить машинный lCOn, подходящий для данной среды.
Поэтому, чтобы С++-программа могла выполняться в различных средах,
нужно создать несколько выполняемых версий этой проrpaммы.

Используя языкиjаvа и С#, можно добиться переносимости програММНО­


го кода за счет специальной компиляции проrpамм, позволяющей пере­
вести исходный проrpаммный код на промежуточный язык, именуемый
nсевдо"одом. Для java-проrpамм этI)т промежуточный язык называют
байт-"одом (bytecode), Т.е. машинно-независимым кодом, генерируемым
jаvа-комnилятором. В результате КОМНИЛЯЦИИ C#-nporpaммw получается
не исполняемый I(ОД, а файл, который содержит специальный псевдоко;х,
именуемый nромежуmочнbI.М. язы."ОА! Мicrosоft (Microsoft Intemlediate
Language - MSI L). В обоих случаях :IТOT псевдокод ВЫnОJПfяется специ­
альной операционной системой, которая дляjаvа называется вuртуШlЬ­
НОй машuнойjаvа Оауа Virtual MaclUne - jVM), а ДЛЯ С# - средством
Соттоп Language Runtime (CLR). Следовательно, java-программа
сможет выполняться D любой среде, содержащей JVM, а C#-проrpамма -
в любой среде, в которой реализовано средство CLR.
ПОСКОЛЬКУ специальные операционные системы ДЛЯ выполнения java-
и С#-кода занимают промежуточное местоположение между проrpаммой
и цп, выполнениеjаvа- и С#-программ сопряжено с расходом определен­
ных системных ресурсов, что совершеllНО излишне при ВЫПОJПfеНЮI C+~'­
программ. Вот поэтому С++-программы обbIЧНО выполняются быстрее,
чем эквивалентные ПРОllJ3.ммы, написанные Hajava и С#.
С++: руководство ДЛ)) начинающих 29

и последнее. Языки C++,java и С# предназначены для решения различных


классов задач. Поэтому вопрос "Какой язык лучше?" поставлен некорректно.
Уместнее сформулировать вопрос иначе: "Какой язык наиболее подходит для ре­ t
u
шения данной задачи?". :D
ID
О

~ПРОСЫ ДI\'iI текущего l<онтрОл'il _ _ _ _ _ _ __


I
U
О

1. От какого языка программирования произошел С++?

2. Какой основной фактор обусловил создание С++?


3. Верно ли, что С++ является предком языков Java и С#?·

ВАЖНОI
58 0 бьектнn-nр и еЫZИРОВQ нн ое
программирование
Основополагающими для разработки С++ стали принципы объектно-ори­
ентированного программирования (ООП). Именно ООП явилось толчком для
создания С++. А раз так, то, прежде чем мы напишем самую простую С++-про­
грамму, важно понять, что собой представляют принципы ООП.
Объектно-ориентированное программирование объединило лучшие идеи
структурированного с рядом мощных концепций, которые способствуют более
эффективной организации программ. В самом общем смысле любую программу
можно организовать одним из двух способов: опираясь на код (действия) или на
данные (информация, на l<ОТОРУЮ направлены эти действия). При использова­
нии лишь методов структурированного программирования программы обычно
опираются на код. Такой подход можно выразить в использовании "кода, дей­
ствующего на данные".
Механизм объектно-ориентированного программирования основан на выборе
второго способа. В этом случае ключевым принципом организации программ яв­
ляется использование "данных, управляющих доступом !( коду". В любом 06ъек­
тно-ориентированном языке программирования определяются данные и процеду­

ры, которые разрешается применить к этим данным. Таким образом, тип данных в
точности определяет, операции какого вида можно примешпь к этим данным.

1. Язык С++ произошел от С.


2. Основной фактор создания С++ - повышеIше сложности ПРОll>амм.
3. Верно: С++ действительно является родительским языком для Java и С#.
30 Глава 1. Основы С++

ДЛЯ поддержки принципов объектно-ориеtIтированноro программирования


все языки ООП, включая С++, характеризуются следующими общими свойства­
ми: иmcaпсуляцией, полиморфизмом н наследованием. Рассмотрим кратко каж­
дое из ЭТИХ свойств.

ИнкапсуМlЦИЯ
Инкапсуляция - это такой механизм программирования, который связывает
воедино код и данные, им обрабатываемые, чтобы обезопасить их как от внешне­
го вмешательства, так и от неправильного исrlOJlьзоваиия. В объекпю-ориеНnf­
рованиом языке код и данные могут быть связаны способом, при котором созда­
ется самодостаточный черный ЯЩlJК. В этом "яшике" содержатся все необходимые
(для обеспечения самостоятельности) данные If код. При таком связывании кода
и данных создается объект, Т.е. 06ьекm - это КО:iСТРУКЦИЯ, котuрая поддерживает
инка.псу ляцию.

Спросим у опытного программиста

Вопрос. Я СЛЫIIUVI, .,то mep.JtIUH ".JtIemoij" прlLNeшrетCR " noдnpozpa.J/l..Jtle. Тогда


прагда ли то, что .JtIe1JIOiJ - это то же Ctl.JtlOe, что и ФУJlJСЦU?

Ответ. В общем CMblCJIe ответ: да. Термин "метод" популяри:ювался t: появлени·


ем языка Java. То, что C++-програММIIСТ называет функцией, Jаvа-про·
граммист называет м('Тодом. С#-программисты также используют термltн
U метод ". В виду такой широкой популярности этот термин все чаще r-та.1И
применять и к C++-фУНКlIии.

Внутри объекта код, данные или обе эти составляющие могут быть закРЫТЫ\fИ
в "рамках" этогообъекта или открытыми. Закрытый код (или данные) известен и
доступен только другим частям того же объекта. Другими словами, к закрытому
коду или данным не может получить доступ та часть программы, которая суще­

ствует вне этого объекта. Открытый код (или данные) доступен любым другим
частям программы, даже если они определены [. других объектах. Обычно откры­
тые части объекта используются для предоставления управляемого интерфейса
с закрытыми элементами объекта.
Базовой единицей инкапсуляции является класс. Класс определяет новый тип
данных, который задает формат 06ьекта. Класс' включает как данные, так и код,
предназначенный ДЛЯ выполнения над этими данными. Следовательно, класс
связы8em данные с "адом. В С++ спецификация класса используется для по-
С++: PYI(OBOACTBO ДЛ~ начинающих 31

строения объектов. Обьекты - это экземпляры класса. По сути, lUIacc представ­


ляет собой набор планов, которые определяют, К3..I( Cl'pUlITb объект.
В lUIacce данные объявляются в виде nереме1lЯЫХ. а КОД оформляется в 8ИДt:
+
+
u
фуmщий (1I0Дl1рограмм). Функции и переменныс, СОС1'авляющнс класс, назьша­ 1i
CD
ются его членами. Таким образом, переменная, объявленная в КЛi!ссе, называется О
1:
U
Ч:lеIlОМ дml1lЫХ, а функция, объявленная в классе, назьшаеТС>1 ФуIt1Щllей-ч/leIIOАI. О
ИlIогда вместо термина I(лен дmmых используется теРМЮI lzере.ме//uйя эюеАmляра
(или nереАfеllНая реализации).

ПолиморфИЗМ
Полиморфизм (от греческого <:Л.ова polym01p11ism, означающего "много форм") -
это свойство, позволяющее исnользовать один интерфейс для целого lUIacca дей­
ствиii. В качестве простого примера полиморфизма можно привести руль автомоби­
ля. Лля руля (т.е. шперфейса) безразлично, какой пш рулеоого механизма исполь­
зуется u автомобиле. Другим словами, руль работает одинаково. не:iависимо от того,
оснащеli ли автомобиль рулевым управлением прямоro деЙСТВIIЯ (без усилителя),
рулевым управлением с УСИЛИТeJlем или механизмом реечной lIсредачи. Если вы
знаете, как обращаться с рулем, вы сможете вести автомоБИЛI, любого ТИП8.
ТОТ же IIРИНЦИП можно применить к программировClНИJO. Рассмотрим, напри­
мер, стек, или список, добавление и удаление элементов к !{оторому осуществля­
ется по ПРИIiЦИПУ "последним прибыл - пеРПl)IМ обслужен". У "ас может быть
программа, А которой используются три различных типа стека. Один стек пред­
назначен ДЛЯ цеЛОЧJ1СЛСIШЫХ значений, второй - для значений с плавающей точ­
кой и третий - для символов. AлrОРlfТМ реализации всех стеков - один и тот
же. несмотря на то, что 1:1 них хранятся данные различных типов. В необъектно­
ориентированном языке программисту пришлось бы создать три различных ,.. а­
бора подпрограмм обслуживания с.тека, причем подпрограммы должны были бы
иметь различные имена, а каждый набор - собственный интерфейс. Но благо­
даря полиморфизму в С++ можно создать одии общий набор подпрограмм (один
интерфейс), который подходит для всех трех конкретных СИ1'УациЙ. Таким обра­
зом, зная, как использовать один стек, вы можете использовать все остальные.

В более общем виде концепция полиморфизма выражается фРaзQй "один ин­


терфейс - много методов". Это означает, что для группы связанных действий
можно использовать один обобщенный интерфейс. Полиморфизм позволяет по­
низить уровень сложности за счет возможности применения одного и того же ИН­

терфейса ДЛЯ задания общею класса действий. Выбор же кою(рemuоzo деиС1llfJUЯ


(т.е. функции) примени.тельно к той или иной ситуации ложится "на плечи" ком­
пилятора. Вам, как программисту, не нужно делать этот выбор вручную. Ваша
задача - использовать общий mперфеЙс.
32 Глава 1. Основы С++

НаслеАование
Ншледованue - это процесс, благодаря которому один объект может при обретать
свойства дpyt·oгo. Благодаря наследованию ПОJl.ll.ерживается концепция иерархи­
ческой классификации. В виде управляемой иерархической (нисходящей) класси­
фикации организуется большинство областей знаний. Например, яблоки Красный
Делишее являются частью классификации яблоки, которая в свою очередь является
частью класса фрукты, а тот - частью еще большего класса пища. Таким образом,
класс пища обладает определеtШЫМИ качествами (съедобность, питательность и пр.),
которые применимы и к подклассу фрукты. Помимо этих качеств, класс фрукты
имеет специфические характеристики (сочность. сладость и пр.), которые отличают
их от других пищевых продуктов. В классе я6JЮКU определяются качества, специ­
фичные для яблок (растут на деревЬЯХ, не тропические и пр.). Класс Красныйде:ш­
шее наследует качества всех предыдущих классов и при этом определяет качес1'ва,

которые являются уникальными для этого сорта яблок.


Ecmf не использовать иерархическое представление признаков, для каждого объ­
екта пришлось бы в явной форме определить все присущие ему характеристики. Но
благодаря наследованию объекту нужно доопределить только те качества, которые
делают его уникальным внуч>и его класса, поскольку он (объект) наследует общие
атрибуты своего родителя. Следовательно, именно механизм наследования позво.1Я­
ет одному объекту представлять конкретный экземпляр более общего класса.

~ПРОСЫ ДЛЯ текущего КОНТРОЛЯ - _ _ _ _ _ __


1. Назовите принципы ООП.

2. Что является основной единицей инкапсуляции в С++?

з. Какой термин в С++ используется для обозначения подпрограммы?·

и выполнение C++-прОграмм
Пора приступить к программированию. Для этого рассмотрим первую про­
стую С++-программу. Начнем с ввода текста, а затем перейдем к ее компиляции
и выполнению.

1. Принщmами ООП являются инкапсуляция, полиморфизм И наследование.


2. Базовой единицей инкапсуля:ции в С++ является класс.
3. Для обозначен!fЯ подпрограммы в С++ используется теРМИН "функция".
С++: руководство для начинающих 33

Спросим у опытного программиста


Вопрос. Ilы gm8ерЖdаете, "сто olJъeкmнo-opиeнтиpo8aннoe пpotpaJllJtlrlpOeaHue
+
(ООН) 1Iредcmа8ЛЯUII собоа эффекти8НЫЙ cnoc06 ynpаuенш 60ЛЬ­
lIШ.Ми 110 объе",,!! npotpaAl.AlQ.МU. Но не ознаwuuml ли это, ....0 для от­
u+
]5
ID
носительно не60лыllш npolpaJtlJtl иcnоль:юеtUШe 000 AIOжem 8Нести О
:J:
неоnpавданкые затраты cuсте.мных ресурсов? И cnраеедлuео ли это
для С++?
8
Ответ. Нет. Ключевым моментом для понимания применения ООП в С++ явля­
ется то, что оно позволяет писать объектно-ориентированные програм'
МЫ, но не явл.яется 06язателыlI.м требованием. в э'гом и заключаетur
одно из важных отличий С++ от Я3blКОВ Java и С#, которые предnолara­
ют использование строгой объектной модели в каждой программе. С++
просто предоставляет программисту возможности ООП. Более того, по
большей части объектно-ориентированные свойства С++ при выполне­
нии программы совершекно неэаметны, поэтому вряд ли стоит говорить

о каких-то ощутимых ззтратах системных ресурсов.

1*
Это простая С++-программа.

Назовите этот файл Sample.cpp.


*1

#include <iostrearn>
using namespace std;

11 С++-программа начинается с функции main().


int main ()

cout « "С++-программирование - это сила!";

return О;
}
Итак, вы ДОЛЖНЫ выполнить следующие действия.

1. Ввести текст программы.

2. Скомпилировать ее.

3. Выполнить.
34 Глава 1. Основы С++

Прежде чем ПрИC1)'lIать к выполнению этих действий, необходимо определить


два термина: исходный код и объектный код. Исходный код - это версия программ ы,
которую может читать человек. Она сохраняется в текстовом файле. Приведенный
выше листинг - это пример исходного кода. Вьmолняемая версия программы (она
создается компилятором) называется 06ьeютmым или выnолн.яе.мым "одо.м.

ВВОД текста программы


Программы, представленные в этой книге, можно загрузить с Web-сзйта ком­
пании Osborne с адресом: www.osborne.com. При желании вы можете ввести
текст программ вручную (это иногда даже полезно: при вводе кода вручную мы
запоминаете ключевые моменты). В этом случае необходимо использовать ка­
кой-нибудь текстовый редактор (например WordPad, если вы работаете в ОС
Windows), а не текстовой процессор (word processor). Дело в том, что при 8воде
текста программ должны быть созданы исключительно текстовые файлы, а не
файлы, в которых вместе с текстом (при использовании текстового процессо­
ра) сохраняется информация о его форматировании. Помните, что информация
о орматировании помешает работе С++-компилятора.
Имя файла, который будет содержать исходный код программы, формально
может быть любым. Но С++-программы обычно хранятся в файлах с расшиrе­
нием . срр. Поэтому называйте свои С++-программы любыми именами, но в ка­
честве расширения используйте . срр. Например, назовите нашу первую про­
грамму Sample . срр (это имя будет употребляться в дальнейших инструкциях),
а для других программ (если не будет специаЛJ,НЫХ указаний) выбирайте имена
по своему усмотрению.

Компилирование программы
Способ компиляции программы Sample. срр зависит от используемого ком­
пилятора и выбранных ОIЩИЙ. Более того, многие компиляторы, например ViStlal
С++ (Мiсrоsoft) и С++ Builder (Borland), предоставляют два различных способа
компиляции программ: с помощью компилятора командной строки и интегриро­
ванной среды разработки (lntegrated Development Environment - IDE). Поэтому
для компилирования С++-программ невозможно дать универсальные инструк­
ции, которые подойдут для всех компиляторов. Это значит, что вы должны сле­
довать инструкциям, приведенным в СОПРОВОДIfТельной документации, прилага­
емой к вашему компилятору.
Но, если вы используете такие популярные компиляторы, как Visual С++
и С++ Builder, то проще всего в обоих случаях компилировать и выполнять про­
граммы, приведенные в этой книге, с использованием компиляторов командной
С++: PYI<OBOACTBO для начинающих 35

строки. Например, чтобы скомпилировать программу Sample. срр, используя


Visua1 С++, введите следующую командную строку:
+
С:\ ... >сl -GX Sample.cpp u+
]1
Опция -GX предназначена для повышения качества компиляции. Чтобы исполь­
!3:I:
зовать компилятор командной строки Visual С++, необходимо выполнить пакет­ U
ный файл VСVАRSЗ2. bat, который входит в состав Visual С++. (В среде Visual О

Studio .NET можно перейти в режим работы по приглашению на ввод команды,


который активизируется выбором команды Мiсrоsоft Visual Studio .NET<=>Visual
Studio .NETTools<=>Visual Studio .NET Command Prompt из меню Пуск<=>Программы).
Чтобы скомпилировать программу Sample. срр, используя С++ Bui1der, введи­
те такую командную строку:

С:\ ... >ЬссЗ2 Sample.cpp


В результате работы С++-компилятора получается выполняемый объектный
код. Для Wiпdоws-среды выполняемый файл будет иметь то же имя, что и ис­
ходный, но другое расширение, а именно расширение. ехе. Итак, выполняемая
версия программы Sample. срр будет храниться в файле Sample. ехе.

Выполнение программы
Скомпилированная программа готова к выполнению. Поскольку результатом
работы С++-компилятора является выполняемый объектный код, то для запу­
ска программы в качестве команды достаточно ввести ее имя в режиме работы
по приглашению. Например, чтобы выполнить программу Sample. ехе, исполь­
зуйте эту командную строку:

C:\ ... >Sample.cpp


Результаты выполнения этой программы таковы:

С++-лрограммирование - это сила!

Если вы используете интегрированную среду разработки, то выплнитъ про­


грамму можно путем выбора из меню команды Run (Выполнитъ). Безусловно, более
точные инструкции приведены в сопроводительной документации, прилагаемой к
вашему компилятору. Но, как упоминалось выше, проще всего компилироватъ и вы­
полнять приведенные в этой книге программы с помощью командной строки.
Необходимо отметить, что все эти програмМbI представляют собой консоль­
ные приложения, а не приложения, основанные на применении окон, т.е. они вы­

полняются в сеансе приглашения на ввод команды (Command Prornpt). При этом


вам, должно быть, извecmо, что язык С++ не просто подходит для WIпdоws-про­
граммирования, С++ - основной язык, применяемый в разработке WIпdоws-прило­
жений. Однако ни одна из программ, представленных в этой книге, не использует
36 Глава 1. Основы С++

графический И1lТерфейс пользователя (graphics user interface - GUI). Дело в 1'ОМ,


что Windows - довольно сложная среда для написания программ, включающая
множество второстепенных тем, не связанных напрямую с языком С++. ДЛЯ соз­
дания Wiпdоws-программ, которые демонстрируют возможности С++, потребо­
валось бы написать сотни строк кода. В то же время консольные приложения го­
раздо короче графических и лучше подходят для обучения программированию.
Освоив С++, вы сможете без ilроблем примешlТЬ свои знания в сфере создания
Wiпdоws-приложеНиЙ.

Построчный ··разбор полетов·· nepBoro примера


nporpaMMbI
Несмотря на то что программа Sarnple. срр довольно мала по размеру, она,
тем не менее, содержит ключевые средства, хара ктерные для всех С ++- програм м.
Поэтому мы подробно рассмотрим каждую ее строку. Итак, наша программа на­
чинается с таких строк.

/*
Это простая С++-программа.

Назовите этот файл Sarnple.cpp.


*/
Это - Kaм.мeнmapUЙ. Подобно большинству других языков программирования,
С++ позволяет вводить в исходный код программы комментарии, содержание ко­
торых компилятор игнорирует. С помощью комментариев описываются или разъ­
ясняются действия, выполняемые в программе, и эти разъяснения предназначают·
ся для тех, кто будет читать исходный код. В данном случае комментарий просто
идентифицирует программу. Конечно, в реальных приложениях комментарии ис­
пользуются для разъяснения особенностей работы отдельных частей программы
или конкретных действий программных средств. Другими словами, вы можете ис­
пользовать комментарии для детального описания всех (или некоторых) ее строк
В С++ поддерживается два типа комментариев. Первый, показанный в нача­
ле рассматриваемой программы, называется .м.НОlострочuым. Комментарий этого
типа должен начинаться символами /* (косая черта и "звездочка") и заканчи­
ваться ими же, но переставленными в обратном порядке (* /). Все, что находит­
ся между этими парами символов, компилятор игнорирует. Комментарий этого
типа, как следует из его названия, может занимать несколько строк. Второй тнп
комментариев (однострочный) мы рассмотрим чуть ниже.
Приведем здесь следующую строку программы.

#include <iostream>
С++: PYI<OBOACTBO Mt;\ начинающих 37

в языке С++ определеtI ряд зшоловков (header), которые обычно содержат ин­
формацию, необходимую для программы. В нашу программу включен зaroловок
<iostream> (он используется ДЛЯ поддержки С++-системы ввода-вывода), ко­
торый представляет собой внешний исходный файл, помещаемый компилятором
в начало программы с помощью директивы # incl ude. Ниже в этой книге мы

: +
: +
:U
::0
'111
:0
::1:
ближе познакомимся с заголовками и узнаем, почему они так важны.

:0
Рассмотрим. следующую строку программы:

using namespace stdi


Эта строка означает, что компилятор должен использовать пространство имен s td
Пространства имен - относительно недавнее дополнение к языку С++. Подробнее о
них мы поговорим позже, а пока ограничимся их кратким определеНием. ПрocmраН­
ство имен (namespace) создает декларативную область, в которой могут размещать­
ся различные элементы программы. Пространство имен позволяет хранить одно
множество имен отдельно от дрyroго. С помощью этого средства можно упростить
организацию больших программ. Ключевое слово using информирует компилятор
об использовании заяв~енного пространства имен (в данном случае std). Именно
в пространстве имен s td объявлена вся библиотека стандарта С++. Таким образом,
используя пространство имен s td, вы упрощаете доступ к стандартной библиотеке
языка. (Поскольку пространства имен - относительно новое средство, старые ком­
пиляторы могут его не ПОДll.ерживать. Если вы используете старый компилятор, об­
ратитесь к приложению Б, чтобы узнать, как быть в этом случае.)
Очередная строка в нашей программе представляет собой одuоcmрочный ком­
ментарий.

// С++-программа начинается с функции main().


Так выглядит комментарий второго типа, поддерживаемый в С++. Одностроч­
ный комментарий начинается с пары символов // и заканчивается в конце стро­
ки. Как правило, программисты используют МliоrocтРОЧJiые комментарии ДЛЯ
подробных и потому более пространНblХ разъяснений, а ОДНОСТРОЧНblе - для
кратких (построчных) описаний инструкций или назначения переменных. Во­
обще-то, характер использования комментариев - ЛИЧJiое дело программиста.
Перейдем к следующей строке.

int main ()
Как сообщается в только что рассмотренном комментарии, именно с этой строки
и начинается выполнение программы.

Все С++-программы состоят из одной или нескольких функций. (Как упоми­


н3Jюсь выше, под функцией мы понимаем подпрограмму.) Каждая С++-функция
имеет имя, И только одна из них (ее должна включать каждая С++-программа)
называется main (). Выполнение С++-программы начинается и заканчивается
38 Глава 1. Основы С++

(в большинстве случаев) выполнением функции main (). (Точнее, С++-про­


грамма начинается с вызова функции main () и обычно заканчивается возвра­
том из функции main ().) Открытая фигурная скобка на следующей (после int
main ( » строке указывает на начало кода функции main ( ). Ключевое слово
int (сокращеНJiе от слова integer), стоящее перед именем main (), означает тип
данных для значенJiЯ, возвращаемого функцией main (). Как вы скоро узнаете,
С++ поддерживает несколько встроенных типов данных, и int - один из них.
Рассмотрим очередную строку программы:

cout « "С++-программирование - это сила!";

это инtтp)'КЦl1Я вывода данных на консоль. При ее выполнении на экране компыо­


тера отобразlПСЯ сообщение С++-программирозание - это сила!. В этой ин­
струкции используется оператор вывода "«". Он обеспеЧJШaет вывод выражения,
стоящего с правой стороны, на устройство, указанное с левой. Слово сои t представ­
ляет собой встроеtшый JiДентификатор (составлеtmый из частей слов C01JSo/e output),
который в большинстве случаев означает экран компьютера. Итак, рассматриваемая
инструкция обеспечивает вывод заданноro сообщения на экран. Обратите внимание
на то, что эта инструкция завершается точкой с запятой. В действительности все вы­
полняеМhlе С++-инструкции завершаются точкой с запятой.
Сообщение "С++-программирование - это сила!" представляет собой
строку. В С++ под строкой понимается последовательность символов, заключен­
ная в двойные кавычки. как вы увидите, строка в С++ - это один из часто ис­
пользуемых элементов языка.

А этой строкой завершается функция main ():


return О;

При ее выполнении функцJiЯ main О возвращает вызывающему пРощ.оссу


(в роли которого обычно выступает операционная система) значение О. ДЛЯ боль­
шинства операционных систем нулевое значение, которое возвращает эта функ­
ЦИЯ, свидетельствует о нормальном завершеНIIИ програмМЫ. Другие значения
MOtyT означать завершение программы в связи с какой-нибудь ошибкой. Слово
return относится к числу ключевых и используется для возврата значения из

функции. При нормальном завершении (т.е. без ошибок) все ваши программы
должны возвращать значение О.
Закрывающая фиrypная скобка в конце програММbJ формально завершает ее.

Обработка синтаксических ошибок


Введите текст только что рассмотренной программы (если вы еще не сделали это),
скомпилируйте ее и выполните. Каждому програ.\tМИСТУ известно, насколько легко
С++: PYI<OBOACTBO ДI\~ начинающих 39

при вводе текста проrJ>аммы в компьютер вносятся случайные ошибки (опечатки). К


счастью, при попытке скомпилировать такую программу компилятор "просиmaлит"
+
сообщением о наличии cuнmaкt:UЧeCКUX ошибок. Большинство С++-компи.ляторов
u+
поDыаются "увИдеть" смысл в исходном коде пJюгрзммы' неэависимо от того, что
iR
вы ввели. Поэтому сообщение об ошибке не всегда отражает истинную причину про­ о
:I:
U
блемы. Например, если в предыдущей ПРОrJ>змме случайно опустить открывающую О
фигурную скобку после имени фymщии та in ( ) , компилятор укажет в качестве ис­
точника ошибки инструкцию сои t. Поэтому при получении сообщения об ошибке
просмотрите две-три строки кода. непосредственно предшествующие строке с "об­
наруженной" ошибкой. Ведь иногда компилятор начинает "чуять недоброе" только
через несколько строк после реального местоположения ошибки.

Спросим у опытноrо проrраммиста

Вопрос. ПОJICWlCО сообщений 06 Оlllибках JlCOU C++-кО.JtmWUUrJОР 8ьЮаem fJ Кalfе­


cmfJe резульmатОfJ CfJoeu работы и nреiJррежiJенUR. Уем же npeiJy-
npежiJенu.я оmлwшютс. от оllltlбок и ка слеiJyem petullpOfJa",. на них?
Ответ. действительно, мноrnе C++-КОМlIИЛЯТоры выдают В качестве результатов
своей работы не только сообщения о неисправиМhIX синтаксических ошиб­
ках, но и предупреждения (waming) различных типов. Если компилятор
"уверен" D некорректности программного кода (например, инструкция не
завершается точкой с запятой), он сиmа.лизирует об ошибке, а если у него
есть лишь "подозрение" на некорректностъ при видимой правильности с
точки зрения синтаксиса, то он выдает предупреждение. Тогда программист
сам должен оценить, насколько справедливы подозрения компилятора.

Предупреждения также можно использовать (по желанию программиста)


для информирования о применении в коде неэффекmвных конструкций
или устаревших средств. Компиляторы позволяют выбирать различные
опции, которые могут информировать об интересующих вас вещах. Про­
граммы, приведенные в этой книге, написаны в соответствии со стандар­
том С++ и при корректном вводе не должны генерировать никаких пред­
упреждающих сообщений.
Для примеров этой книги достаточно использовать обычную настрой­
ку компилятора. Но вам все же имеет смысл эаглянугь в прилагаемую к
компилятору документацию и поинтересоваться, какие возможности по

управлению процессом компиляции есть в вашем распоряжении. Многие


компиляторы довольно "интеллектуальны" и могут помочь в обнаруже­
нии неочевидных ошибок еще до того, как они перераС1)'Т в большие про­
блемы. Знание принципов, используемых компилятором при составлении
отчета об ошибках, .стоит затрат времени и усилий, которые потребуются
от программиста на их освоение.
40 Глава 1. Основы С++

~npocы МЯ текущего контроля - - - - - - - -


1. С чего начинается выполнение С++-программы?

2. Что такое cout?


З. Какое действие выполняет инструкция ~ include <iostream>?·
=

Возможно, самой важной конструкцией в любом языке программировання


является переменнcut. Пере.менная - это именованная область памяти, в которой
MOryт храниться различные значения. При этом значение переменной во время
выполнения программы можно изменить. Другими словами, содержимое пере­
менной изменяемо, а не фиксированно.
В следующей программе создается перемеиная с именем length, которой
присваивается значение 7, а затем на экране отображается сообщение Значение
переменной length равно 7.
// Использование переменной.

#include <iostream>
using namespace std;

int main ()
{
int length; // ~ Здесь об~.етCR переиеина•.

length = 7; / / Переиениой lenqth присваиааетс. чиcnо 7.

cout « "Значение переменной length равно ";


cout « length; // ОТображаетCR значение переиеиной
// lenqth, Т.е. чиcnо 7.

1. Любая С++-программа начинает выполнение с функции тШпО.


2. Слово cout представляет собой встроенный идентификатор, который имеет отно­
шение к выводу данных на консоль.

З. Она включает в код программы заголовок <iostream>, который lIоддерживает


функционирование системы ввода-вывода.
С++: руководство ДI\.~ начинающих 41

return О;

+
Как упоминалось выше, для С++-программ можно выбирать любые имена. +
u
Тогда при вводе текста этой программы дадим ей, скажем, имя VarDemo. срр. 2i
ID
О
Что же нового в этой программе? Во-первых, инструкция I:
(J
int length; // Здесь объявляется переменная. О

объявляет переменную с именем length целочисленного типа. В С++ все перемен­


ные ДОЛЖНЫ быть объявлены до их использования. В объявлении переменной по­
мимо ее имени необходимо указать, значения какого типа она может храниТЬ. Тем
самым объявляется тип переменной. В данном случае переменная length может
хранить цслочисленные значения, т.е. целые числа, лежащие в диапазоне -32 768-
32767. В С++ для объявления перемеllliОЙ целочисленного типа достаточно поста­
вить перед ее именем ключевое слово int. Ниже вы узнаете, что С++ поддерживает
широкий диапазон встроенных типов переменных. (Более того, С++ позволяет про­
граммисту определять co6cTBeHtrwe типы данных.)
Во-вторых. при выполнении следующей инструкции переменной присваива­
ется KOHKpeTHQe значение:

length = 7; // Переменной length присваивается число 7.


Как отмечено в комментарии, здесь переменной length присваивается число 7.
В С++ оператор nрисваuванuя представляется одиночным знаком равенства (=).
Его действие заключается в копировании значения, расположенного справа от
оператора, в переменную, указанную слева от него. После выполнения этой ин­
струкции присваивания переменная length будет содержать число 7.
Обратите внимание на использование следующей инструкции для вывода
значения переменной length:
cout « length; // Отображается число 7.
В общем случае для отображения значения переменной достаточно в инструк­
ции cout поместить ее имя спраоа от оператора "«". Поскольку в данном кон­
кретном случае переменная length содержит ЧИCJЮ 7, то оно и будет отображено
на экране. Прежде чем переходить к следующему разделу, попробуйте присвоить
пере мен ной length другие значения (в исходном коде) и посмотрите на резуль­
таты выполнения этой программы после внесения изменений.

Подобно большинству других языков программирования, С++ поддерживает


полный диапазон арифметических операторов, которые позволяют выполнять
42 Глава 1. Основы С++

действия над числовыми значениями, используемыми в программе. Приведем


самые элементарные.

+ Сложение
Вычитание
* Умножение
/ Деление

Действие этих операторов совпадает с действием аналогичных операторов в


алгебре.
В следующей программе оператор "*" исполъэуется для вычисления площади
прямоугольника, заданного его длиной и шириной.

// Использование оператора.

#include <iostream>
using narnespace std;

int rnain ()
{
int length; // Здесь объявляется п~ременная.
int width; / / Здесь объявляется В'l'орая переменная.
int area; // Здесь объявляется третья переменная.

length = 7; // Число 7 присваивается переменной length.


width = 5; // Число 5 присваивается переменной width.

area length * width; // ~ Здесь зычиcn.е~СR nnощад&


/ / DpJDIОУГОJIЪниха, а реЗУJIЪ~а~ Dpоизведевия:
// звачений aepeкeRRНX lenqth и vidth
// прксваиаае~~ аерекенной area.

cout « "ПЛощадь прямоугольника равна ";


cout « area; // Здесь отображается число 35.

return О;

в этой npoграмме сначала объявляются три перс~менные


length, width и area.
Затем переменной length присваивается число
7, а переменной width - число 5.
После этого вычисляется произведение значений переменных length и width
(т.е. вычисляется площадь прямоугольника), а результат умножения присваи­
вается переменной area. При выполнении программа отображает следующее.
С++: PYI<OBOACтaO для начинающих 43

ПЛощадь прямоугольника равна 35.


В этой программе в действительности нет никакой необходимости в исполь­
+
зовании переменной Поэтому предыдущую программу можно переписать
area.
u+
следующим образом. J5
11 Упрощенная версия программы вычисления площади
!3:I:
U
11 прямоугольника. О

#include <iostream>
using namespace std;

int main ()
{
int length; 11 Здесь объявляется переменная.
int width; /1 Здесь объявляется вторая переменная.

length = 7; /1 Число 7 присваивается переменной length.


width = 5; /1 Число 5 присваивается переменной width.

cout « "Площадь прямоугольника равна ";


cout « length * width; 1/ Здесь о~обраааетс. чиcnо 35
11 (реsуnьта~ 8wпоnвеИКR
11 операции lenqth * vidth
11 8JoI80,QИ'Ж'CJI на эхраи В&ПрJDlP) •
return О;

в этой версии площадь прямоугольника вычисляется в инструкции сои t пу­


тем умножения значений переменных length и width с последующим выводом
результата на экран.

Хочу 06ратИ1:Ь ваше внимание на то, что с помощью одной и той же инструк­
ции можно объявить не одну, а сразу несколько перемеНIiЫХ. Для этого достаточ­
но разделить их имена запятыми. Например, переменные length, width и area
можно было бы объявить таким образом.
int length, width, area; 11 Все переменные объявлены в
// одной инструкции.

В профессиональных С++-npограммах объявление нескольких переменных в


одной инструкции - обычная практика.
44 Глава 1. Основы С++

~просы мя текущего контроля --------


1. Необходимо ли переменную объявлять до ее использования?
2. Покажите, как переменной min ПРИСВОИ1Ъ значение о.

з. Можно ли в одной инструкции объявить сразу несколько переменных?-

в предыдущих при мерах действия ВЫПОЛНЯЛIIСЬ над данными, которые явным


образом были заданы в программе. Например, в программе вычисления площади
выполнялось умножение значений сторон прямоугольника, причем эти МIЮЖII­
тели (7 и 5) являлись частью самой программы. Безусловно, процесс вычисле­
ния площади прямоуголъника не зависит от размера ero сторон, поэтому наша

программа была бы гораздо полезнее, если бы при ее выполнении пользователю


предлагалось ввести размеры прямоугольника с клавиатуры.

Чтобы пользователь мог ввести данные в программу с клавиатуры, можно


применить оператор "»". Это С++-оператор ввода. Для считывания данных с
клавиатуры используется такой формат этого оператора.

cin » vpr;
Здесь cin - еще ОДИН встроенный идентификаl0Р. Он составлен из частей слов
console input и автоматически померживается средстоами С++. По умолчанию
идентификатор cin связывается с клавиатурой, хотя ero можно перенаправИ1Ь
и на другие устроЙства. Элемент var означает переменную (указанную с правой
стороны от оператора "»"), которая принимает вводимые данные.
Рассмотрим новую версию программы вычисления площади, которая позво­
ляет пользователю вводить размеры сторон прямоугольника.

/*
Интерактивная программа, которая вычисляет

площадь прямоугольника.

*/

iinclude <iostream>

1. Да. в С ++ переменные необходимо объявлять до их использования.


2. min ~O;
3. Да, в одной инструкции можно объявить сразу несколько переменных.
С++: pyr<OBOACтeO ДЛЯ начинающих 45

using namespace stdi

int main () +
{
u+
:JS
11:1
int length: // Здесь объявляется переыеииая. О
:I:
Здесь объявляется вторая переме~ная.
int width: // 8
cout « "Введите длину прямоугольника: ":
cin » length: // ВВОД sначеВИR дnивu прсиоyr~RИXа
// (~.e. значеИИR аерекеввой lenqth)
/ / с 1UIaвKa~yp...

cout « "Введите ширину прямоугольника: ":


cin »widthi / / ВвОД значеlUDl IIIКpИIВI пря:коyrom.виха
// (~.e. звачеНИR аеРек."оЙ vidth)
/ / с 1UIaвKa~...

cout « "Площадь прямоугольника равна ":


cout « length * width: // Отображение эначения площади.

return О:

Вот один из возможных результатов выполнения этой програмМЫ.

Введите длину прямоугольника: 8


Введите ширину прямоугольника: 3
Площадь прямоугольника равна 24
Обратите особое внимание на эти строки программы.

cout « "Введите длину прямоугольника: ";


cin » length: // Ввод значения длины прямоугольника

Инструкция cout приглашает пользователя ввести данные. Инструкция cin


считывает ответ пользователя, запоминая его в пере мен ной length. Таким обра­
зом, значение, введенное пользователем (которое в данном случае представляет
собой целое число), помещается в переменную, расположенную с правой сторо­
ны от оператора "»" (В данном случае переменную leFlgth). После выполнения
инструкции cin переменная length будет содержать значение длины прямоу­
roльника. (Если же пользователь введет нечисловое значение, переменная length
получит нулевое значение.) Инструкции, обеспечивающие ввод и считывание
значения ширины прямоугольника, работают аналогично.
46 Глава 1. Основы С++

Вариации на тему вывода данных


До сих пор мы использовали самые простые типы инструкций сои t. Однако в
С++ предусмотрены.и другие варианты вывода ;l:aнHblx. Рассмотрим два из них.
Во-первых, с помощью одной инструкции сои t можно выводить сразу несколь­
ко порций информации. Например, в программе вычисления площади для ото­
бражения результата использовались следующш: две строки кода.

cout « "ПЛощадь прямоугольника равна ";


cout « length * width;
Эти две инструкции можно заменить одной.

cout « "Площадь прямоугольника равна " « length * width;


Здесь в одной инструкции cout используется два оператора "«", которые по­
зволяют сначала вывести строку "Площадь лрямоугольника равна ", затем
значение площади прямоугольника. В общем случае в одной инструкции cout
можно соединять любое количество операторов зывода, предварив каждый эле­
мент вывода "своим" оператором "«".
Во-вторых, до сих пор у нас не было необходимости в переносе выводимых
данных на следующую строку, Т.е. в выплненIIии последовательности команд

"возврат каретки/перевод строки". Но такая необходимость может появиться до­


вольно скоро. В С++ упомянутая выше последовательность команд генерируется
с помощью CUМВOJllJ навой строки. Чтобы поместить этот символ в строку, исполь·
зуйте код \п (обратная косая черта и строчная буква "п"). Теперь нам остается на
практике убедиться, как работает последовательность \п.

/*
в этой программе демонстрируется и,::пользование кода \п,
который генерирует переход на новую строку.

*/
finclude <iostream>
using namespace std;

int main ()
{
cout « "один\п";
cout « "два\п";
cout « "три";
cout « "четыре";

return О;
С++: руководство ДЛЯ начинающих 47

При выполнении эта программа генерирует такие результаты.

один
+
два
u+
тричетыре :i5
~
Символ новой строки можно размещать в любом месте строки, а не только в :r
конце. Вам стоит на практике опробовать различные варианты применения кода 8
\n, чтобы убедиться в том, что вам до конца понятно его назначение.

~ПРОСЫ A/IЯ теl<ущего l<онтрОл'i1 _ _ _ _ _ _ __


1. Какой оператор используется в С++ дЛЯ вводаДaJШЫХ?
2. С каким устройством по умолчанию связан идентификатор cin?
3. Что означает код \n?*

Познакомимся еще с ОДНИМ


ТИПОМ данных
В предыдущих программах использавались переменные типа int. Однако
перемеlfные типа in t могут содержать только целые числа. И поэтому их нельзя
использовать для представления чисел с дробной частью. Например, персмеЮJая
типа int может хранить число 18, но не значение 18,3. К счастью, в С++, кроме
in t, определены и другие типы данных. Для работы с числами, имеющими дроб­
ную часть, в С++ предусмотрено два типа данных с плавающей точкой: float и
double. Они служат для представления значений с одинарной и двойной точ­
tlОСТЬЮ соответственно. Чаще в С++-программах используется тип double.
Для объявления переменной типа double достаточно написать инструкцию,
подобную следующей.

double result;
Здесь resul t представляет собой имя переменной, которая имеет тип
double.
Поэтому перемеииая resul t может содержать такие значеиия, как 88,56, 0,034
или -107,03.

1. Для ввода данных в С++ используется оператор "»".


2. По умолчанию идентификатор cin связан с клавиатурой.
З. Код \п означает символ ноnой строки.
48 Глава 1. Основы С++

Чтобы лучше понять разницу между типами дaJmыx iлt и double, рассмотрим
следующую npoграмму.

/*
Эта лрограмма иллюстрирует различия между типами

данных iлt и double.


*/

liлсludе <iostream>
using namespace std;

int mаiл () {
int ivar; // Здесь объявляется переменная типа int.
double dvar; // Здесь объявляется переменная типа double.

ivar 100; // Переменная ivar получает значение 100.

dvar = 100.0; // Переменная dvar получает значение 100.0.

cout « "Исходное значение переменной ivar: " « ivar


« "\п";
cout « "Исходное значение перемеНЕОЙ dvar: " « dvar
« "\п";

cout « "\п"; // ВМвОДИJI ПУС~ С!1'роху.

// А теперь делим оба значения на З.


ivar ivar / 3;
dvar = dvar / 3.0;

cout « "Значение ivar nOC.:tIe деления: " « ivar « "\п";


cout « "Значение dvar после деления: " « dvar « "\п";

return О;

Вот как ВЫГЛЯДЯТ результаты выполнения этоii программы.

Исходное значение переменной ivar: 10~


Исходное значеНие переменной dvar: 10)
С++: руководство ДЛ)! начинающих 49

Значение ivar после деления: 33


Значение dvar после деления: 33.3ЗЗЗ

Как видите, при делении значения переменной i var на 3 выполняется цело­ :t


u
численное деление, в результате которого (33) теряется дробная часть. Но при 2i
са
О
делении на 3 значения переменной dvar дробная часть сохраняется. :r
u
В этой ПРО'1>амме есть еще новая деталь. Обратите внимание на эту строку. О

cout « "\n"; // Выводим пустую строку.

При выполнении этой инструкции IlРОИСХОДИТ переход на новую строку. Ис­


пользуйте эту инструкцию в случае, когда нужно разделить выводиМые данн'ые
пустой строкой.

Спросим V опытного программиста


Вопрос. Почему в С++ существgют различные типы аанных дм npeдcmавле­
НIIR целых IfUсел и ЗNQlfeHUU с плавающей точкой? ПочеJfCУ бы все чис­
ловые зна.,енllR "е представлять с nОJfCощью оОного типа данных?

Ответ. В С++ предусмотрены различные типы даиных, чтобы программиеты


могли создавать эффективные про граммы. Например, вычисления с не­
ПОЛЬ."JО8aJ-lием Цt'ЛОЧИCJIенной арифметики выполняются гораздо быстрее,
чем вычислею1Я, производимые над значениями е плавающей точкой. Та­
ким образом, если оам не нужны значения с дробной частью, то вам и не
стоит зря расходовать системные ресурсы, соязанныс с обработкой таких
типов данных, как Ноа t или double. Кроме того, для хранения значе­
ний различных пmов требуются разные по размеру области пaмsrrи. Под­
держивая различные типы данных, С++ позволяет программисту выбрать
:;1
наилучший вариант использования системных ресурсов. Наконец, для не­
которых алгоритмов требуется использование данных конкретного nmа. i•
И потом, широкий диапазон встроенных С++-типов данных предоставля­
ет программисту проявнтъ максимальную гибкость при реализации реше­
ний разнообразиых задач программирования.
!

Проект 1.1. ',l r4l: ~~, rl~ФJ. : \' ~- 'I'J,.V,


1
, FtoM. срр Несмотря на то что в предыдущих примерах программ были
продемонстрированы важные средства языка С++, они полезны
больше с точки зрения теории. Поэтому важно закрепить даже те немногие све­
ден ия, которые вы уже почерпну ли о С + + , при создании практических программ.
В этом проекте мы напишем про грамму перевода футов в метры. Программа
50 Глава 1, Основы С++

должна предложить пользователю ввести значение в футах, а затем отобразить


значение, преобразованнос в метры.
Один метр равен приблизительно 3,28 футам. Следовательно, в программе мы
должны использовать представление данных с плавающей точкой. для выпол­
нения преобразования в программе необходимо объявить две переменные типа
double: одну для хранения значения в футах, а другую для хранения значения,
преобразованного в метры.

Последовательность действий
1. Создайте новый С++-фзйл с именем Ftc.M. срр. (Конечно, вы можете вы­
брать для этого файла любое другое имя.)

2. Начните программу следующими строками, которые разъясняют назначе­


ние программы, включите заголовок iозtrеаm и укажите пространство
имен std.
/*
Проект 1.1.

Эта про грамма преобразует фУ'ГЫ в метры.

Назовите программу FtoM.cpp.


*/

#include <iostream>
using namespace std;
3. Начните определение функции main () с объявления переменных f и!'.1.
int main () {
double f; / / содержит длину в футах
double т; // содержит результат преобразоваНИR в метрах

4. добавьте код, предназначенный для ввода значения в футах.

cout « "Введите длину в фута>!: ";


cin » f; 1/ считывание значения, выраженного в футах

5. добавьте код, которые выполняет преобраэование в метры и отображает


результат.

m = f 1 3.28; /1 преобразование в метры


cout « f « " футов равно n « m « " метрам.";
С++: PYI<OBOACTBO МЯ начинающих 51

6.

7.
Завершите программу следующим образом.
return О;

Ваша законченная программа должна иметь такой ВИД.


-
~
:U
:~
t
'1:11
:0
::1:
/* :U
Прое1(Т 1.1.
:0

Эта программа преобразует футы в метры.

Назовите про грамму FtoM.cpp.


*/

#include <iostrearn>
using narnespace std;

int rnain () {
double f; // содержит длину в футах
double т; // содержит результат преобразования в ме­
трах

cout « "Введите длину в футах: ";


cin » f; // считывание значения, выраженного в футах

rn = f / 3.28; // преобразование в метры


cout « f « " футов равно" « rn « " метрам.";
t-
return О;
!

I
8. СJ,(омпилируйте и выполните программу. Вот как выглядит один из воз­
можных результатов ее выполнения.

Введите длину в футах: 5


5 футов равно 1.52439 метрам.

9. Попробуйте ввести другие значения. Л теперь поnытайтесь изменить про­


грамму. чтобы она преобразовывала метры в футы.
52 Глава 1. Основы С++

~npocы для текущего контроля --------


1. Какое С++-ключевое слово служит ДЛЯ объявления данных целочислен­
ноготипа ?
2. Чтоозначаетсловоdоublе?

з. как вывести пустую строку (или обеспечить переход на новую строку)?·


-
управления if и for
Внутри функции выполнение инструкций происходит последовательно.
сверху вниз. Однако, используя различные инструкции управления, поддержи­
ваемые в С++, такой порядок можно измеНИ1Ь. Позже мы рассмотрим эти ин­
струкции подробно, а пока кратко представим их, чтобы можно было восполь:ю­
ваться ими для написания следующих примеров программ.

ИНСТРУКЦИЯ if
Можно избирательно выполнить часть программы, используя инструкцию
управления условием i f. Инструкция i f в С -\ + действует подобно инструкции
"IF", определенной в любом другом языке программирования (например C.Java
и С#). Ее простейший формат таков:

if(условие) инструкция;

Здесь элемент условие - это выражение, которое при вычислении может ока­
заться равным значению ИСТИНА ИЛИ ЛОЖЬ. В С++ ИСТИНА представля­
ется ненулевым значением, а ЛОЖЬ - нулем. Если условие, или условное вы­
ражение, истинно, элемент инструкция выполнится, в противном случае - нет.

НапрИМеР, при выполнении следующего фрагмента кода на экране отобразится


фраза 1 О меньше 11, потому что число 10 действительно ~еньше 11.
if(10 < 11) cout <~ "10 меньше 11";

1. Для объявления данных целочисленного ПIПa служит ключевое слово int.


2. Слово double является ключевым и используется для объявления данных с плавающей
ТОЧКОй двойной точности.

3. Для вывода пустой строки используется код \п.


С++: PYI(OBOACTBO для начинающих 53

Теперь рассмотрим такую строку кода.

if(10 > 11) cout « "Это соое5щение никогда не отое5раЭИТСЯ"i

В этом случае число 10 не больше 11, поэтому инструкция cout не выполнит­


t
u

ся. Конечно же, операнды в инструкции i f не должны всегда быть константами. 111
о
Они могут быть переменными. :J:
U
В С++ определен полныА набор операторов отношений, которые используют­ О

ся в условном выражении. Перечислим ИХ.

Равно
!= Не равно
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно

Обратите внимание на то, что для проверки на равенство используется двой­


ной знак "равно".
Теперь рассмотрим программу, в которой демонстрируется использование ин­
струкции Н.

// Демонстрация использования инструкции if.

#include <iostream>
using namespace stdi

int main ()
int а, Ь, С;

а = 2;
Ь З;

if (а < Ь) cout « "а меньше Ь\п"; / / +-(--

// Эта инструкция ничего не отобразит.


if(a Ь) cout « "Этого вы не увидите.\п";

cout « "\п";

с = а - Ь; // Переменная с содержит -1.

cout « "Переменная с содержит -1.\п";


54 Глава 1. OCl10BbI С++

if(c >= О) cout « "Значение с неотрицательно.\п";


if(c < О) cout « "Значение с отрицательно.\п";

cout « "\п";

с = ь - а; // Теперь переменная с содержит 1.


cout « "Переменная с содержит 1.\п";
if(c >= О) cout « "Значение с неотрицательно.\п";
if(c < О) cout « "Значение с отрицательно.\п";

return О;

При выполнении эта программа генерирует такие результаты.

а меньше Ь

Переменная с содержит -1.


Значение с отрицательно.

Переменная с содержит 1.
Значение снеотрицательно.

ЦИКЛfоr
Мы можем организовать повторяющееся выполнение одной и той же после­
дователыюсти инструкций с помощью специальной конструкции, именуемой
ЦUКЛОМ. В С++ предусмотрены различные ВИДЫ циклов, и одним из них являетея
цикл for. Для тех, кто уже не новичок в программировании, отмечу, что в С+ +
цикл for работает так же, как в языках С# или Java. Рассмотрим простейший
формат использования цикла for.
fоr(инициализация; условие; инкременr) инструкция;

Элемент инициализация обычно представляет собой инструкцию присваи­


вания, которая устанавливает управляющую nере.менную цuкла равной неко­
торому Ifачальному значению. Эта переменная действует в качестве счетчи­
ка, который управляет работой цикла. Элемент условие представляет собой
условное выражение, в котором тестируется значение управляющей пере­
мен ной цикла. По результату этого тестирования определяется, выполнится
цикл for еще раз или нет. Элемент инкремент - это вцражение, которое
определяет, как изменяется значение управляющей переменной цикла после
каждой итерации (т.е. каждого повторения элемента инструкция). Обратите
С++: РУКОВОДСТВО для начинающих 55

внимание на то, что все эти элементы цикла for должны отделяться точкой с
запятой. Цикл for б~/:tет выполняться до тех пор, пока вычисление элемента
+
условие дает истинный результат. Как только это условное выражение ста­
нет ложным, цикл завершится, а выполнение программы продолжится с ин­
u+
25.
m
струкции, следующей за циклом for. О
1:
()
Использование цикла for демонстрируется в следующей программе. Она вы­ О
водит на экран числа от 1 до 100.

11 Программа, иллюстрирующая ЦИКЛ for.

iinclude <iostream>
using hamespace std;

int main ()
{
int count:

for(count=l: count <= 100: count=count+1) // циxn ~Qr


cout « count « " ":

return О;

в этом цикле переменная count инициализируется значением 1. При каждом


повторении тела цикла проверяется условие цикла.

count <= 100;


Если результат проверки истинен, на экран будет выведено значение переменной
count, после чего управляющая переменная цикла увеличивается на единицу.

Когда значение переменной count превысит число 100, проверяемое в цикле


условие станет ложным, и цикл остановится.

В ПРофСССИОllальных С++-программах вы не встретите инструкции

count=count+1,
или подобной ей, поскольку в С++ существует специальный оператор инкре­
мента, который выполняет операцию увеличения значения на единицу более эф­
фективно. Оператор инкремента обозначается в виде двух знаков "плюс" (+ +).
Например, инструкцию for из приведенной выше программы с использованием
оператора "++" можно переписать так.

for(count=l; count <= 100; count++) // ЦИКЛ for


cout « count « " ";
56 Глава 1. Основы С++

в остальной части книги для увеличения значения переменной на едиmщу


омы будем использовать оператор инкремента.
В С++ также реализован оператор декремента, который обозначается двумя
знаками "минус" (--). Он применяется для уменьшения значения переменной
на единицу.

~ПРОСЫ ДI\<;1 текущего коН!Рол<;1----------


1. Каково назначение инструкции i f?
2. Для чего предназначена инструкция for?
З. Какие операторы отношений реализованы в С++?*

Одним из ключевых элементов С++ является бло" "ода. Блок - это логически
связанная группа программных инструкций (т.е. одна или несколько инс-rрук­
ций), которые обрабатываются как единое целое. В С++ программный блок соз­
дается путем размещения последоватеЛЬНОСТIf инструкций между фигурными
(открывающей и закрывающей) скобками, После создания блок кода становится
логической единицей, которую разрешено использовать везде, где можно приме­
нять одну инструкцию. Например, блок кода может составлять тело инструкций
if или for. Рассмотрим такую инструкцию if'.
i f (w < h) {
v w * h;
w = О;

Здесь, если значение переменной w меньше значения переменной h, будут вы­


полнены обе инструкции, заключенные в фИl'урные скобки. Эти две инструк­
ции (вместе с фигурными скобками) представляют блок кода. Они составля­
ют логически неделимую группу: ни одна из этих инструкций не может вы­
полниться без другой. Здесь важно понимап., что если вам нужно логически

1. Инструкция if - это инструкция условного выполнения части кода програмМЫ.


2. Инструкция {or - одна из инструкций цикла в С++, которая предназначена для
организации повторяющеroся выполнения некоторой последовательности ин­
струкций.
3. В С++ реализованы такие операторы отношеннй: =~, 1-, <, >, <- И >-.
С++: PYI<OBOACTBO для начинающих 57

связать несколько инструкций, это легко достигается путем создания блока.


Кроме того, с использованием блоков кода многие алгоритмы реализуются
более четко и эффективно.
+
u+
Рассмотрим программу, в которой блок кода позволяет предотвратить деле­ 21
111
ние на нуль. О
1:
U
// Демонстрация использования блока кода. О

#include <iostream>
using namespace stdi

int main ()
double result, n, di

cout « "Введите делимое: ";


cin » ni

cout « "Введите делитель: ";


cin » di

// Здесь инструкция if управляет целым блоком.


if (d ! = О) {
cout « "Звачение d не р_но О, дenеlDlе осущеС'1'llJDIО."

« "\n".i
result = n I di
cout « n « " I " « d « " Р".О " « resulti
}

return О;

Вот как Bblrлядят результаты выполнения этой программы.

Введите делимое: 10
Введите делитель: 2
Значение d не равно О, деление осуществимо.
10 / 2 равно 5
В этом случае инструкция i f управляет целым блоком кода, а не просто одной
ИliструкциеЙ. Если управляющее условие инструкции if истинно (как в нашем
примере), будут выполнены все три инструкции, составляющие блок. Поnробуй-
58 rлава 1. Основы С++

те ввести нулевое значение для делителя и узнайте, как выполнится наша про­
грамма в этом случае. Убедитесь,.что IIpИ вводе нуля if-блок будет опущен.
как будет показано ниже, блоки кода имеют дополнительные свойства и спо­
собы IIpименения. Но основная цель их существования - создавать логически
связанные единицы кода.

Спросим у опытного программиста

Вопрос. Не cmраОаem ли эффeIOtШВНоcmь выI'IлнeнIuI пpoгpa;tIМЫ от 1fl1IU'eнeNШI


блоКО8 "oiJa? Дpyzu.мu СЛО8QJff11, не тJJe6уemся ли дonoлнuтeльнoe врUIЯ
КОAmUЛRmору для 06рабoтIOl кода, заКЛНJlleюIOtо 8 фraypныe CfCобки?
Ответ. Нет. Обработка блоков кода не требует дополнительных затрат системных
ресурсов. Более того, использование блоков кода (благодаря возможнОС1"И
улросrnть кодирование некоторых aJlГОРИТМОВ) позволяет увеличить ско­
рость и эффективность выполнения n рограмм.

Точки с запятой и расположение


инструкций
в С++ точка с запятой означает конец инструкции. Другими словами, каж­
дая отдельная инструкция должна завершаться точкой с запятой. Как вы знаете,
блок - это набор логически связанных инструкций, которые заключены между
открывающей и закрывающей фигурными скобками. Бло}( не завершается точ­
кой с запятой. Поскольку блок состоит из инструкций, каждая из которых за­
вершается точкой с запятой, то в дополнителыюй точке с запятой нет никакого
смысла. Признаком же конца блока служит закрывающая фигурная скобка
Язык С++ не воспринимает конец строки в качестве признака конца инструк­
ции. Таким признаком конца служит только точка с запятой. Поэтому ДЛЯ компи­
лятора не имеет значения, в каком месте строки располагается инструкция. На­
IIpимер, с точки зрения С++-компилятора следующий фрагмент кода
• х = у:

у = y+l:
cout « х « " " « у;

аналогичен такой строке:

х = у; у = y+l; cout« х « " " « у;

Более того. отдельные элементы любой инструкции можно располагать на от­


дельных строках. Например, следующий вариант записи инструкции абсолютно
приемлем.
С++: РУКОВОДСТВО ДЛЯ начинающих 59

cout « "Это длинная строка. Сумма равна : "


« а + Ь + с + d + е + f;
+
Подобное разбиение на строки часто используется, чтобы сделать программу +
u
более читабельной. ..,
::о
о
:I:
U
Практика отступов О

Рассматривая предыдущие примеры, вы, вероятно, заметили, что некоторые


инструкции сдвинуты относительно левого края. С++ - язык свободной формы,
т.е. его синтаксис не связан позиционными или форматными ограничениями. Это
означает, что для С++-компилятора не важно, как будут расположены инструк­
ции по отношению друг к другу. Но у программистов с годами выработался стиль
применеНI1Я отступов, который значительно повышает чита6ельность программ.
В этой книге мы придерживаемся этого стиля и вам советуем поступать так же.
Согласно этому стилю после каждой открывающей скобки делается очередной
отступ вправо, а после каждой закрывающей скобки начало отступа возвращает­
ся к прежнему уровню. Существуют также некоторые определенные инструкции,
для которых предусматриваются дополнительные отступы (о них речь впереди).

~просы ДЛЯ текущего l<оНтролЯ ________


1. Как обозначается блок кода?
2. Что является признаком завершения инструкции в С++?

3. Все С++-инструкции должны начинаться и завершаться на одной строке .

Верно ли это? •

Проект 1.2.

' FtoMTable. срр : В этом проекте демонстрируется применение цикла for,


(
! инструкции i f и блоков кода на примере создания про-

1. Блок кода начинается с открывающей фигурной скобки ({), а оканчивается закры­


вающей фигурной скобкой (}).
2. Признаком завершения инструкции в С++ является точка с запятой.
3. Не верно. Отдельные элементы любой инструкции можно располагать на отдельных
строках.
60 Глава 1. Основы С++

граммы вывода таблицы результатов преобраювания футов в метры. Эта табли­


ца начинается с одного фута и заканчивается 100 футами. После каждых 1О фу­
тов выводится пустая строка. Это реализуется с помощью·переменноЙ counter,
в которой хранится количество выведенных с·грок. Обратите особое внимание на
использование этой переменной.

Последовательность действий
1. Создайте новый файл с именем Fto~Table. срр.
2. Введите 8 этот файл CJlед)'ющую программу.

/*
Проект 1.2.

При выполнении этой программы выводится таблица


результатов преобразования футов в метры.

Назовите эту программу FtoMTable.cpp.


*/

iinclude <iostrearn>
using narnespace std;

int rnain () {
double f; // содержит длину, выраженную в футах
double т; // содержит результат преобразования в метры
int counter; // счетчик строк

counter = О; // Вачanък" установка сче~чика C~K.

for(f = 1.0; f <= 100.0; f++) {


т = f / 3.28; // преобразуем в метры
cout « f « " футов равно" « m« " MeTpa.\n";

counter++; // Посае Х&8Дой итерации циxnа


/ / ивкрекев'1'Ир"Уем сче~UDC С'1'рок.

// После каждой 10-й CTPOK~ выводим пустую строку.


if(counter == 10) { // Еcnи сче~чик С'1'рок досчитал
// до 10, 8W80ДИК пус~ С'1'року.
cout « "\n"; // выводим пустую строку
С++: руководство ДЛЯ начинающих 61

counter О; // обнуляем счетчик строк

+
u+
1i
ID
return О; О
1:
U
О
3. Обратите внимание на то, как используется переменная counter для вы­
вода пустой строки после каждых десяти строк. Сначала (до входа в цикл
for) она устанавливается равной нулю. После каждого пре06разования
переменная counter инкРементируется. Когда ее значение становится
равным t О, выводится пустая строка, после чего счетчик ~ТPOK снова обну­
ляется и процесс повторяется.

4. Скомпилируйте и запустите программу. Ниже приведена часть таблицы,


которую вы должны получить при выполнении программы.

1 футов равно 0.304878 метра.


2 футов равно 0.609756 метра.
3 футов равно 0.914634 метра.
4 футов равно 1.21951 метра.
5 футов равно 1.52439 метра.
6 футов равно 1.82927 метра.
7 футов равно 2.13415 метра.
8 футов равно 2.43902 метра.
9 футов равно 2.7439 метра.
10 футов равно 3.04878 метра.

11 футов равно 3.35366 метра.


12 футов равно 3.65854 метра.
13 футов равно 3.96341 метра.
14 футов равно 4.26829 метра.
15 футов равно 4.57317 метра.
16 футов равно 4.87805 метра.
17 футов равно 5.18293 метра.
18 футов равно 5.4878 метра.
19 футов равно 5.79268 метра.
20 футов равно 6.09756 метра.

21 футов равно 6.40244 метра.


22 футов равно 6.70732 метра.
23 футов равно 7.0122 метра.
62 Глава 1. O~HOBЫ С++

24 футов равно 7.31707 метра.

25 футов равно 7.62195 метра.

26 футов равно 7.92683 метра.

27 футов равно 8.23171 метра.

28 футов равно 8.53659 метра.

29 футов равно 8.84146 метра.

30 футов равно 9.14634 метра.

31 футов равно 9.45122 метра.


32 футов равно 9.7561 метра.
33 футов равно 10.061 метра.
34 футов равно 10.3659 метра.
35 футов равно 10.6,707 метра.
36 футов равно 10.9756 метра.
37 футов'равно 11.2805 метра.
38 футов равно 11.5854 метра.
39 футов равно 11.8902 метра.
40 футов равно 12.1951 метра.
5. Самостоятельно измените программу так, чтобы она выводила пустую
строку через каждые 25 строк результатов.

'ШОI
:1111 []ablSlIId" а ф~ЫКI ~ldSl;l
Любая С++-программа составляется из "строительных блоков", именуемых
функциями. И хотя более детально мы будем рассматривать функции в MOДY:le 5,
сделаем здесь краткий обзор понятий и терминов, связанных с этой темой. Функ­
ция - это подпрограмма, которая содержит одну или несколько С++-инструк­
ций и выполняет одну или несколько задач.
Каждая функция имеет имя, которое используется для ее вызова. Чтобы вы­
звать функцию, достаточно в исходном коде l1porpaмMbl указать ее имя с парой
круглых скобок. Своим функциям программист может давать любые имена, за
исключением имени mаiл (), зарезервированного для функции, с которой начи­
нается выполнение программы. Например, мы назвали функцию именем МуРи­
лс. Тогда для вызова функции MyFunc достаточно записать следующее.
МуFuлс();

При вызове функции ей передается управление программой, в результате чего


начинает выполняться код, составляющий тело функции. По завершении функ­
ции управление передается инициатору ее вы:юва.
С++: РУI<ОВОДСТВО ДЛЯ начинающих 63

Функции можно передать одно или несколько значений. Значение, переда­


ваемое функции, называется аргументом. Таким образом, функции в С++ могут
принимать один или несколько аргументов. Аргументы указываются при вызове
функции между открывающей и закрывающей круглыми скобками. Например,
если функция MyFunc () принимает один целочисленный аргумент, то, исполь­
-
:+
:+
:U

CD
о
1:
'J
зуя следующую запись, можно вызвать функцию MyFunc () со значением 2. )
MyFunc(2);
Если функция принимает несколько аргументов, они разделяются запятыми.
В этой книге под термином список аргументов понимаются аргументы, разделен­
ные запятыми. Помните, что не все функции ПРИIIИМают аргументы. Если аргу­
менты не нужны, то круглые скобки после имеии функции остаются пустыми.
Функция может возвращать в вызывающий код значение. HelcoтopbIe фуик­
ции не возвращают никакого значения. Значение, возвращаемое функцией. мож­
но присвоить переменной в вызывающем коде, поместив обращение к функции
с праUОl1 стороны от оператора присваивaJШЯ. Например, если бы функция
MyFunc () возвращала значение, мы могли бы вызвать се таким образом.
х = MyFunc(2);
Эта инструкция работает так. Сначала вызывается ФУIIКЦИЯ MyFunc (). По
ее завершении возвращаемое ею значение ПРИСВa.IfВается персменной х. Вызов
функции можно также использовать в выражении. Рас~мо/'рим пример.
х = MyFunc(2) + 10;
В этом случае значение, возвращаемое функцией, суммируется с числом 1О,
а результат сложения присваивается переменной х. И вообще, если в какой-либо
инструкции встречается имя функции, эта функция автоматически вызывается,
чтобы можно было получить (а затем и использовать) возвращаемое ею значеЮiе.
Итак, вспомним: аргумент - это значение, передаваемое функции. Возвраща­
емое функцией значение - это данные, передаваемые назад в вызывающий код.
Рассмотрим короткую программу, которая демонстрирует использование функ­
ции. Здесь для отображения абсолютного значения числа используется стандарт­
ная библиотечная (т.е. встроенная) функция abs (). Эта функция принимает один
аргумент, преобразует его D абсолютное значение и возвращает результат.
// Использование функции abs().

#include <iostream>
#include <cstdlib>
using namespace std;

int main ()
64 Глава 1. Основы С++

int result;

result = abs(-lO)j // Вызыаае~ск фунхциа аЬа(), а


// воsвращаекое 8JD sначевие
// ПРИС8аивае~ск перемевной result.

cout « result;

return О;

Здесь функции abs () в качестве аргумента передается число -10. Фушщия


abs () , приняв при вызове аргумент, возвращает ero абсолютное значение. По­
лученное значение присваивается переменной resul t. Поэтому на экране ото­
бражается число 10.
Обратите также ВtIИмание на то, что рассматриваемая программа включает за­
ГОЛОВОК <cstdlib>. ЭТОТ заголовок необходим для обеспечения ВОЗМОЖНОСТИ
вызова функции аЬз ( ) . Каждый раз, когда вы используете библиотечную функ­
цию, в проrpамму необходимо включать соответствующий заголовок.
В общем случае в своих программах вы будеl е использовать функции двух ти пов.
К первому типу отнесем фушщии, нarшсанные программистом (т.е. вами), и в каче­
стве примера такой функции можно на.1вать функцию та i n ( ) . как вы узнаете ниже,
реальные С ++-программы содержат множество пользовательских функций.
Функции второго типа предоставляются компилятором. К этой категории Функ­
ций принадлежит функция аЬs ( ) . ОбbIЧНо программы состоят как из функций, на­
писанных программистами, так и из функций, предоставленных компилятором.
При обозначении функций в тексте этоii КНИЛi используется соглашение
(обычно соблюдаемое в литературе, посвященной языку программирования
С++), согласно которому имя функции завершается парой круглых скобок. На­
пример, если функция имеет имя getval, то ее упоминание в тексте обозначит­
ся как getval (). Соблюдение этого соглашения позволит легко отличать имена
переменных от имен функций.

Библиотеки С++
как упоминалось выше, функция аЬs () не является частью языка С++. но ее
"знает" каждый С++-компилятор. Эта функция, как и множестводругих, входит
в состав стандартной 6u6лuотeICU. В примерах этой книги мы подробно рассмо­
трим использование многих библиотечных функций С++.
С++: руководство для начинающих 65

в С++ определен довольно большой набор функций, которые содержатся в


стандартной библиотеке. 3ти функции предназначены для выполнения часто

u+
встречающихся задач, включая операции ввода-вывода, математические вы­

числения и обработку строк. При использовании программистом библиотечной


функции компилятор автоматически связывает объектный код этой функции с ~:J:
U
объектным кодом программы. О
Поскольку стандартная библиотека С++ довольно велика, в ней можно найти
много полезных ФУНКЦIIЙ, которыми действительно часто пользуются програм­
мисты. Библиотечные функции можно при менять подобно строительным бло­
кам, из которых возводится здание. Чтобы не "изобретать велосипед", ознакомь­
тесь с документацией на библиотеку используемого вами компилятора. Если вы
сами напишете функцию, которая будет "переходить" с вами из программы в про­
грамму, ее также можно поместить в библиотеку.
Помимо библиотеки функций, каждый С++-компилятор также содержит би­
блиотеку классов, которая является объеКТllо-ориеt}ТИРОВанной библиотекой.
Но, прежде чем мы сможем использовать библиотеку классов, нам нужно позна­
комиться с классами и объектами.

~просы для теl<ущего I<ОНТрОЛЯ


1. Что такое функция?
--------

2. Функция вызывается с помощью ее имени. Верно ли это?


3. Что понимается под стандартной библиотекой функций С++?·

ВАЖНОI
"'1 КАючевь'е САОва С++
В стандарте С++ определено 63 ключевых слова. Они показаны в табл. 1.1.
Эти ключевые слова (в сочетании с синтаксисом операторов и разделителей)
образуют определение языка С++. В ранних версиях С++ определено ключевое
слово overload, но теперь оно устарело. Следует иметь в виду, что в С++ раз­
личается строчное и прописное написание букв. Ключевые слова не являются ис­
ключением, Т.е. все они должны быть написаны строчными буквами.

1. Функция - это подпрограмма, которая содержит одну или несколько С++­


инcrpукциЙ.
2. Верно. Чтобы вызвать функцию, дocrnточно В исходном коде программы указать ее имя.
З. Стандартная библиотека фyнюntЙ С++ - это коллекция функций, поддерживаемая
всеми С + +-компиляторами.
66 Глава 1. Основы С++

Таблица 1.1. Ключевые слова С++


азm auto Ьооl break
case catch char class
const const class contint:e default
delete do double dinamic cast.
else enum explici t export
extern false float for
friend goto if inline
int long mutable, namespace
new operator private protected
public register reinterpret_cast return
short signed sizeof static
st'atic cast struct switch template
this, throw true try
typedef typeid typenanle union
unsigned using virtuaJ. void
volatile wchar t while

• Идентификаторы
в С++ идентификатор представляет собой имя, которое присваивается функ­
ции, переменной или иному элементу, 'определенному пользователем. 'Иденти­
фикаторы могут состоять из одного или нескольких символов. Имена перемен­
ных должны начинаться с буквы или символа подчеркивания. Последующим
символом может быть буква, цифра и символ подчеркивания. Символ подчер­
кивания можно использовать для улучшения читабельноети имени перемеююй,
IJ;lIlример line_count. В С++ прописные и строчные буквы воспринимаются
t-.:ш, различные символы, Т.е. myvar И MyVar - это разные имена. В С++ нельзя
использовать в качестве идентификаторов ключевые слова, а также имена стан­
дартных функций (например, abs). Запрещено также использовать в качестве
пользовательских имен встроенные идентификаторы (например, cout).
Вот несколько примеров допустимых идентификаторов.

Test х у2 Maxlncr
ир _top my_var simplelnterest23
Помните, что идентификатор не должен начинаться с цифры. Так, 980К­
недопустимый идентификатор. Конечно, вы вольны называть переменные и
другие программные элементы по своему усмотрению, но обычно идентифика­
тор отражает назначение или смысловую характеристику элемента, которому

он принадлежит.
С++: PYI<OBOACTBO ДЛЯ начинающих 67

~ПРОСЫ мя теl<ущего контром --------


+
u+
J!
1. Какой из следующих вариантов представляет собой ключевое слово: f о r, ~
:I:
For или FOR?
8
2. Символы какого типа может содержать С++-идентификатор?

3. Слова
катор?
..
index21 и Index21 представляют собой один и тот же идентифи­

• ТеСТ,ДАЯ СQМОКQНТрООЯ по МОАJlt'Ю 1


{. Выше отмечалось, что С++ занимает центральное место в области совре­
менного программирования. Объясните это утверждение.

2. C++-КОМПИJlЯтор генерирует объектный код, который непосредственно ис­


пользуется компилятором. Верно ли это?

З. Каковы три основных принципа объектно-ориентированного программи-


рования?

4. С чего начинается выполнение С++-программы?

5. Что такое заголовок?

6. Что такое <iostream>? Для чего служит следующий код?


#include <iostream>
7. Что такое пространство имен?

8. Что такое переменная?

9. какое (какие) из следующих имен переменных недопустимо (недопустимы)?

А. count
В. count
С. count27
D_ 67count
Е. if

1. Ключевым словом эдесь является вариант (от. В С++ все ключевые слова ПШlIутся
С ИСПОЛЪЗ0ванием СТРОЧНЫХ букв.
2. С++-идентификатор может содержать буквы, цифры И символ подчеркивания.
З. Нет, в С++ прописные и строчные буквы воспринимаются как раэличные симвоJIы.
68 Глава 1. Основы С++

10. как создать однострочный комментарий? Как создать мноrocтpoчный ком­


ментарий?

11. Представьте общий формат инструкции if. Представьте общий формат


инструкции for.
12. Как создать блок кода?

13. Гравитация Луны составляет около 17% от гравитации Земли. Напишите


программу, которая бы генерировала таблицу земных фунтов и эквива­
лентных ЗI-Iачений, выраженных в лунном весе. Таблица должна содержать
значения от 1 до 100 фунтов и включать пустые строки после каждых 25
строк результатов.

14. Год Юпитера (т.е. время, за которое Юпитер делает один полный оборот
вокруг СОЛf!.ца) составляет приблизительно 12 земных лет. Напишит(' [IPO-
грамму, которая бы выполняла преобразовани.я значений, выраженных в
годах Юпитера, в значения, выраженные в годах Земли. Конечно же, здесь
допустимо использование нецелых ЗliачениЙ.

15. Что происходит с управлением программой при вызове функции?


16. Напишите программу, которая усредняет абсолютные значения пяти значе­
ний, введенных пользователем. Програм\tа должна отображать результат.

~ Ответы на эти вопросы можно найти на Web-странице данной


На заметку " "' книги по адресу: http://www.osborne.com.
Модуль2
Типы данных и операторы

2.1. Типы данных С++


2.2. Литермы
2.3. Создание инициализированных переменных
2.4. Арифметические операторы
2.5. Операторы отношений и логические операторы
2.6. Оператор присваивания
2.7. Составные операТОрЫ присваивания
2.8. ПреобразОБание типов в операторах присваивания
2.9. Преобразование типов в выражениях
2.10. Приведеllие ТИПОlJ
2.11. Использование пробелов и круглых скобок
70 Модуль 2. Типы данных и операторы

Центральное место в описании языка ПРОГРJ.ммирования занимают типы дан­


ных и операторы. Эти элементы определяют ограничсния языка и виды задач, к
которым его можно примснить. Нетрудно предположить, что язык С++ поддер­
живает богатый ассортимент как типов данных, так и опсраторов, что позволяет
его использовать ДЛЯ решения широкого круга задач программирования.

Типы данных и операторы - это большая тема, которую мы начнем pacc:vla-


тривать с основных типов данных и наиболее часто используемых операторов. В
этом модуле мы также обсудим подробнее такие элементы языка, как перемен­
ные и выражения.

Почему ТИПbl данных столь ваЖНbI


Тип переменных определяет операции, которые разрешены для выполнения
над ними, и диапазон значений, которые могут быть сохранены с их помощью.
В С++ определеио несколько типов данных, 11 осе они обладают уникальными
характеристиками. Поэтому все переменные ДОЛЖНЫ быть определены до их ис­
пользования, а объявление переменной должно включать спецификатор типа.
Без этой информации компилятор не сможет сгеuерировать корректный код.
В С++ не существует понятия "переменной ОС-\ типа".
Типы данных важНЫ для С++-программирования еще и потому, что несколько
базовых типов тесно связанЫ со "строительными блоками", которыми оперирует
компьютер: байтами и словами. Другими словами, С++ позволяет программисту
задействовать те же самые ТИПЫ данных, которые использует сам централl>НЫЙ
процессор. Именно поэтому с помощью С++ можно писать очень эффективные
\
программы системного уровня.

Язык С++ поддерживает встроенные типы данных, которые позволяют ис­


пользовать в программах целые числа, СИМВОJlЫ, значения с плавающей точкой
и булевы (логические) значения. Именно использование КОНКРетных типов дан­
ных позволяет сохранять и обрабатывать в программах различные виды инфор­
мации. как будет показано ниже D этой книге, С++ предоставляет программисту
возможность самому создавать типы данных, а не только использовать встроен­

ные. Вы научитесь создавать такие типы данных, как классы, структуры и перс­
числения, 110 при этом помните, что все (даже очень сложные) новые типы дан­
ных состоят из встроенных.
С++: РУI<ОВОДСТВО ДNI начинающих 71

В С++ определено семь основных типов данных. Их названия и ключевые


слова, которые используются для объявления переменных этих типов, приведе­
ны в следующей таблице.

Тип Название
char Символьный
wchar t Символьный двубайтовый
int целочисленный
float С плавающей ТОЧКОЙ
double С плавающей точкой двойной точности
bool Лоrnческий (или булев)
void Без значения

В С++ перед такими типами данных, как char, int и double, разрешается
использовать .модифU1Шmоры. Модификатор служит для изменения значения ба­
зовоro типа, чтобы он более точно соответствовал конкретной ситуации. Пере­
чиелим возможные модификаторы типов.

signed
unsigned
long
short
Модификаторы signed, unsigned, long и short можно примеitять к це­
лочисленным базовым типам. Кроме того, модификаторы signed и unsigned
можно использовать с типом
char, а модификатор long - с типом double. Все
допустимые комбинации базовых типов и модификаторов приведены в табл. 2.1.
В этой таблице также указаны гарантированные минимальные диапазоны пред­
ставления для каждоro типа, соответствующие С++-стандарту ANSI/ISO.
Минимальные диапазоны, представленные в табл. 2.1, важно понимать именно
как .мUНUМШlЫtыe дuanаЭ()7fЫ и никак иначе. Дело в том, что С++-компилятор может
расширить один или несколько из этих минимумов (что и делается в большинстве
случаев). Это означает, что диапазоны представления С++-типов данных зависят от
конкретной реализации. Например, ДЛЯ компьютеров, которые используют арифме­
тику дополнительных кодов (т.е. почти все COBpeMeННhle компьютеры), целочислен­
НЫЙ тип будет иметь диапазоli представлеlШЯ чисел от -32 768 до 32 767. Однако во
всех случаях диапазон представления типа short int является подмножеством
диапазона типа in t, диапазон представления KOТOPOro в свою очередь является под­
множеством диапазона типа long int. Аналогичными отношениями связаны и
типы float, double и long double. В этом контексте термин noдмножеC11l8O озна­
чает более УЗЮiЙ или такой же диапазон. Таким образом, типы int и long int
MOryт иметь одинаковые диапазоны, но диапазон типа int не может быть шире диа­
пазона типа long int.
72 Модуль 2. Типы данных и операторы

Таблица 2.1. Все допустимые комбинации числовых типов и их


гарантированные минимальные диапазоны представления.

соответствующие С++-стандарту ANSI/ISO

~Ти~п~____________________~~~и~н~и~м~ал~ЬН~Ыf1~АИ~а_п
__о_эо_н
________________
char -128-127
unsigned char 0-255
signed char -128-127
int -32768-32767
unsigned int 0-65 535
signed int АналоrиЧентипу int
short int -32768-32767
unsigned short int 0-65 535
signed short int Аналоrnчентилу short int
10ng int -2147483648-2147483647
·signed 10ng int Аналоrnчен типу 10ng int
unsigned 10ng int 0-4294967295
floa t 1Е-37 -1 Е +37. с шестью значащими цифрами
double 1Е-37-1Е+З7. (" дссятьюзначащими цифрами
_1_0_п-=g__d_о_u_Ь_1_е______________1_Е_-_3_7_-_1_Е_+_37--'-,с десятью значащими циФрами

Поскольку стандарт С++ указывает только минимальный диапазон, который


обязан соответствовать тому или иному типу данных, реальные диапазоны, под­
держиваемые вашим компилятором, следует уroчнить в соответствующей доку­
ментации. Например, в табл. 2.2 указаны типи чные размеры значений в битах и
диапазоны представления для каждого С++-типа данных в 32-разрядной среде
(например, в Windows ХР).

Целочисленный тип
как вы узнали в модуле 1, переменные типа iI\ t предназначены для хранения lJ,e-
лочисленных значений, которые не содержат др06ной части. Переменные этого nma
часто используются для управления циклами и условными инструкциями. Опера­
ЦИИ с iпt-значениями (поскольку они не имеют дробной части) выполняются на­
много быстрее, чем со значениями с плавающей точкой (вещественного типа).
А теперь подробно рассмотрим каждый тип в отдельности.
Поскольку целочисленный тип столь важен для программирования, в С++
определено несколько его разJfовидностеЙ. Как показано в табл. 2.1, существуют
короткие (short int), обычные (int) и длинные (long int) целочисленные
значения. Кроме того, существуют их версии со знаком и без. Переменные uело­
численного типа со знаком MOryт содержать как положительные, так и отрица-
С++: PYI(OBOACTBO для начинающих 73

тельные значения. По умолчанию предполагается использование целочисленно­


го типа со знаком. Таким образом, указание модификатора s i gned избыточно (но
допустимо), поскольку объявление по умолчанию и так предполагает значение
со знаком. Переменные целочисленного типа без знака могут содержать только
положительные значения. Для объявления целочислеНIiОЙ переменной без знака
достаточно использовать модификатор unsigned.

Таблица 2.2. Типичные размеры значений в битах и диапазоны


представлениS1 С++-типов данных в 32-разрS1ДНОЙ среде

Тип Размер в битах Диапазон


char 8 -128-127
unsigned char 8 0-255
signed char 8 -128-127
int 32 -2 147483648-2 147483647
unsigned int 32 0-4 294 967 295
signed int 32 Аналогичен типу int
short int 16 -32 768-32 767
unsigned short int 16 0-65535
signed short int 16 -32 768-32 767
long int 32 Аналогичен типу int
signed long int 32 Аналогичен типу signed int
unsigned long int 32 АналОПfчен типу unsigned int
float З2 1,8Е-З8-ЗАЕ +З8
double 64 2,2Е-308-1,8Е+308
long double 64 2,2Е-308-1,8Е+308
Ьооl ИСТИНА или ЛОЖЬ
w char t 16 0-65535

Разли~ие между целочисленными значениями со знаком и без него заключа­


ется в иЙтерпретации старшего разряда. Если задано целочисленное значение
со знаком, С++-компилятор сгенерирует КОД с учетом того, что старший разряд
значения используется в качестве флazа знака. Если флаг знака равен О, число
считается положительным, а если он равен 1, - отрицательным. Отрицатель­
ные числа почти всегда представляются в дополнительном коде. Для получения
дополнительного кода все разряды числа берутся 8 обратном коде, а затем по­
лученный результат увеличивается на единицу. Наконец, флаг знака устанав­
ливается равным 1.
Целочисленные значения со знаком используются во многих алгоритмах, но
максимальное число, которое можно представить со знаком, составляет только
74 Модуль 2. Типы данных и операторы
................................................................................................................................

половину от максимального числа, которое можно представить без знака. Рас­


смотрим, например, максимально возможное 16-разрядное целое число (32 767):
0111111111111111
Если бы старший разряд этого значения со знаком был установлен равным 1. то
оно бы интерпретировалось как -1 (в дополнительном коде). Но если объявить
его как unsigned iпt-значение, то после установки его старшего разряда в 1 мы
получили бы число 65 535.
Чтобы понять различие в С++-интерпрет.щии целочисленныx значений со
знаком и без него, выполним следующую короткую программу.
#include <iostream>

/* Эта про грамма демонстрирует различие между


signed- и uпsigпеd-значениями целочисленного типа.

*/

using namespace std;

int main ()
{
short int i; // короткое iпt-значение со знаком
short unsigned int j; // короткое iпt-значение без знака

j = 60000; // Число 60000 попадает в диапазон


// представления типа short unsigned int, но
// не попадает в диапазон представления типа
// short signed int.
i j; // Поэтому после присваивания числа 60000
// переменной i оно будет интерпретироваться
// как отрицательное.
cout « i « " " « j;

return О;

При выполнении программа выведет два числа:

-5536 60000
Дело в том, что битовая комбинация, которая представляет число 60000 как короткое
(short) целочисленное значение без знака, интерпретируется в качестве короткого
iпt-значения со знаком как число -5536 (при 16-разрядном представлении).
С++: РУКОВОДСТВО ДЛЯ начинающих 75

в С++ предусмотрен сокращенный способ объявления unsigned-, short- и


lопg-значений целочисленноro типа. Это значит, что при объявлении iпt-зна­
чений достаточно использовать слова unsigned, short и long, не указывая ~ип
int, Т.е. тип int подразумевается. Например, следующие две инструкции объ­
являют целочисленные переменные без знака.
unsigned Х;
unsigned int У:

Символы
Переменные типа char предназначены для хранения ASCIl-СИМDОЛОВ (напри­
мер А, z или G) либо иных В-разрядных величин. Чтобы задать символ, необхо­
димо заключить ero в одинарные кавычки. Например, после выполнения следу-
I

ющих двух инструкций

char ch;
ch = 'х' i

переменной ch будет присвоена буква Х.


Содержимое сhаr-значения можно вывести на экран с помощью соut-ин­
струкции. Вот пример.

cout « "Это содержится в переменной ch: " « Chi


При выполнении этой инструкции на экран будет выведено следующее.
Это содержится в переменной ch: Х

Тип char может быть модифIщирован с помощью модификаторов signed и ип­


signed. Строго говоря, только конкретная реализация определяет по умолчанию,
каким будет сhаr-06ъявление: со знаком или без него., Но для большинства компи­
ляторов объявление типа char подразумевает значение со знаком. Следовательно,
в таких средах использование модификатора signed для char-обьяWlения также
избыточно. В этой книге предполагается, что char-значенИя имеют знак
Переменные типа char можно использовать не TOJIЫ(O для хранения ASсп­
символов, но и для хранения числовых значений. Переменные типа char могут со­
держать "небольшие" целые числа в диапазоне -128-127 и поэтому их можно ис­
пользовать вместо i nt -переменtlых, если вас устраивает такой диапазон представ­
ления чисел. Например, в следующей программе сhаr-переменная используется
для управления циклом, который выводит на экран алфавит английского языка.

,// Эта про грамма выводит алфавит.

tinclude <iostream>
76 МОДУЛЬ 2. Типы данных и операторы

using паmезрасе std;

int main ()
{
char letter; // Перекеква_ letter ~a char
// иcnоnьзуетса дn_ упрaanекия цихпок for.
J.
for(letter = 'А'; letter <= 'Z'; letter++)
cout « letter;

return О;

Если ЦИКЛ for вам покажется несколько cTpaнным. то учтите. что символ' А'
представляется в компьютере как число 65. а значения от 'А' до 'Z' являются
последовательными и расположены в возрастающем порядке. При каждом про­
ходе через ЦИКЛ значение переменной let ter инкрементируется. Таким обра­
зом. после первой итерации переменная lette:r будет содержать значение I Е'.
ТИП wchar_ t предназначен для хранения символов. входящих в состав t:юль­
ших символьных наборов. Вероятно. вам известно, что в некоторых естественных
языках (например китайском) определено очень большое количество символов,
для которых В-разрядное представление (обеспечиваемое типом char) вес[.ма
недостаточно. Для решения проблем такого рода в язык С++ и был добавлен тип
wchar _ t. который вам пригодится. если вы планируете выходить со своими про­
граммами на международный рынок.

~npocы дIIя текущего контроля - - -_____


1. Назовите семь основных типов данных в С++.

2. Чем различаются целочисленные значения со знаком и без?


З. Можно ли использовать переменные типа char для представления не­
больших целых чисел?·

1. Семьосновиыхтипов: char.wchar_t. in1:. float, double.bool иvоid.

2. Целочисленный тип со знаком позволяет хр;uпггь как положительные, так и отри­


цательные значения, а с помощью целочислеlПfОro типа без знака можно хранить
только положительные значения.

3. Да, переменные типа char можно ИСПОЛЬЗОЕ·атъ для представления небольmиx це­
лыхчисел.
С++: РУКОВОДСТВО ДЛЯ начинающих 77
..................................................................................................................................

Спросим у опытного программиста

Вопрос. Почему сltUlндарт С++ определяет только .минuмальн.,е диапазоны


дм tlcmpoeHIIых типов, не ycmaHa&llutllVl их более 1IUJIIнo?

Ответ. Не задавая точных размеров, язык С++ позволяет каждому компилятору


оптимизировать типы данных для конкретной среды выполнения. В этом
частично и состоит причина того, что С ++ предоставляет возможности для
создания высокопроизводительных I1рограмм. Стандарт ANSIIISO про­
сто заявляет, что встроенные тип bJ должны отвечать определенным требо­
ваниям. Например, в нем сказано, что тип in t "должен иметь естествен­
ный размер, предлагаемый архитектурой среды выполнения". это значит,
что R 16-раэрядных средах ДЛЯ хранения значений типа i nt должно выде­
ляться 16 бит, а в 32-разрядиых - 32. При этом наименьший допустимый
размер для целочисленных значений в любой среде должен составлять 16
бит. Позтому, есЛи вы будете Ш1Сать программы, не "заступая" за пределы
минимальных диапазонов, то они (программы) будут переносимы в дру­
гие среды. Одно замечание: каждый С++-компнлятор указывает диапазон
базовых типов в заголовке <cl imi t S>.

Типы данных с плавающей точкой


к переменным типа flоа t JI double обращаются либо для обработки чисел с
дробной частью, либо при необходимости выполнения операций над очень боль­
шими или очень малыми числами. ТИIIЫ float и double различаются значением
наибольшего ( и наименьшего) числа, которые можно хранить с помощью пере­
менных этих типов. Обычно тип double в С++ позволяет хранить число, при­
близительно в десять раз превышающее значение типа float.
Чаще всего в профессиона.льных программах используется тип double. Дело
IJ том, что большинство математических функций из С++-библиотеки исполь­
зуют dоublе-значения. Например, функция sqrt () возвращает dоublе-зна­
чение, которое равно квадратному корню из ее double-аргумента. Для примера
рассмотрим программу, в которой функция sqrt () используется для вычисле­
ния длины гипотенузы по заданным длинам двух других сторон треугольника.

/*
Здесь используется теорема Пифагора для вычисления
длины гипотенузы по заданным длинам двух других

сторон треугольника.

*/
78 МОДУЛЬ 2. Типы данных и операторы
.................................................................................................................................

finclude <iostream>
#include <cmath> // Это~ saroловох аеобходим дn_
/ / 8101S08. ФУRJC.ЦИИ 8qrt () .
using namespace std;

int main () (
double х, у, z;

х = 5;
У 4;

z = sqrt(x*x + у*у); // ФУВXЦИR 8qrt() RВnRe~c_ час~


// ма~ека~есхой С++-бибnио~ехи.

cout « "Гипотенуза равна " « z;

return О;

Вот как выглядят результаты выполнения згой программы.

Гипотенуза равна 6.40312


Необходимо отметить, что поскольку ФунюпiЯ sqrt () является частью стан­
дартной С++-библиотеки функций, то для ее вызова в программу нужно вкJlю­
читьзаroловок <cmath>.
Тип long double позволяет работать с O~JeHb большими или очень малень­
кими числами. Он используется в программах научно-исследовательского харак­
тера, например, при анализе астрономических данных.

Тип данных Ьооl


Тип bool (относительно недавнее дополнение к С++) предназначен для хра­
нения булевых (т.е. ИСТИНNЛОЖЬ) значений. В С++ определеliЫ две бу.lевы
константы: true и false, являющиеся единственными значениями, которые
могут иметь переменные типа bool.
Важно понимать, как значения ИСТИНNЛОЖЬ определяются в С++. Один
из фундаментальных принципов С++ состоит в том, что любое ненулевое значе­
ние интерпретируется как ИСТИНА, а нуль - как ЛОЖЬ. Этот принцип пол­
ностью согласуется с типом данных bool, поскольку любое ненулевое значение,
используемое в булевом выражении, автоматически преобразуется в значение
true, а нуль - в значение false. Обратное утверждение также справедливо: при
С++: руководство для начинающих' 79

использовании в небулевом выражении значение true преобразуется в число 1,


а значение fa1se - в число О. Как будет показано в модуле З, конвертируемость
нулевых и ненулевы.х значений в их булевы эквиваленты особенно важна при ис­
пользовании инструкций управления.
Использование типа bool демонстрируется в следующей программе.

// демонстрация использования boo1-значениЙ.

tinc1ude <iostream>
using nаmеэрасе std;

int main ()
Ьоо1 Ь;

ь = fa1se;
cout« "Значение переменной Ь равно" « Ь « "\n";

ь = true;
cout« "Значение переменной Ь равно" « Ь « "\п";

// Одно Ьооl-sиачевие мо.ет упрaanкт. if-ивструхциеЙ.


if(b) cout« "Это ВЫПОЛНИМО.\n";

ь fa1se;
=
if(b) cout« "Это не выполнимо.\n";

// Результатом применения оператора отношения


// являеТСR true- или fа1sе-значение.
cout« "10 > 9 равно" « (10 > 9) « "\п";

return О;

Результаты выполнения этой программы таковы.

Значение переменной Ь равно О


Значение переменной Ь равно 1
Это выполнимо.

10 > 9 равно 1
В этой программе необходимо отметить три важных момента. Во-первых, как
вы могли убедиться, при выводе на экран Ьоо1-значения отображается число О
80 МОДУЛЬ 2. Типы данных и оперспоры

или 1. Как будет показано ниже в этой книге, вместо чисел можно вывести слова
"false" и "true".
Во-вторых, для управления i f-инструкциеj;( вполне достаточно самого значе­
ния Ьооl-переменной, Т.е. нет необходимости в испольэовании инструкции, 110-
добной следующей.
if (Ь == true) ...
В-третьих, результатом выполнения оперг.ции сравнения. Т.е. применсния
оператора отношения (например, "<") являетСJl6улево значение. Именно поэ'~о­
му вычисление выражения 10 > 9 дает в результате число 1. При этом необхо­
димость в дополнителыilхx круглых скобках в чнструкции вывода
cout« "10 > 9 равно" « (10 > 9) « "\п";

объясняется тем, что оператор "«" имеет более высокий приоритет, чем опера­
тор "<".

Типvоid
Тип void испольэуется для задания выражений, которые не возвращают зна­
чений. Это, на первый взгляд, может показаlЬСЯ странным, но подробнее 1ИП
void мы рассмотрим ниже о этой книге.

~ПРОСЫ ДЛЯ текущего КОНТРОЛЯ - _______


_

1. Каковы основные различия между типами float и double?


2. Какие значения может иметь переменная типа bool? В какое булево зна­
чение преобраэуется нуль? .

................------------------_..------------------------..
3. Что такое void?·

1. Основное различие между типами п0а t и double заключается в величине значе­


НИЙ, которые они позволяют Xpaнm1>.

2. Переменные типа Ьооl могут иметь значения tгue либо false. Нуль преобразу­
ется в значение false.
3. void - это ТШI данных, характеризующий ситуацию отсутствия значения.
С++: руководство ДЛ~ начинающих 81

Проеl<Т 2.1.
- . - --1 В положении, когда Марс tlаходится к Земле ближе всего, рассто-
Mars.cpp ,
яние между ними составляет приблизительно 34000000 миль.
Предположим, что на Марсе есть некто, с кем вы хотели бы ПОI'()ворить. Тогда
возникает вопрос, какова будет задержка во времени между моментом отправ­
ки радиосигнала с Земли и моментом его прихода на Марс. Цель следующеro
проскта - создать программу, которая отвечает на этот вопрос. Для этоro вспом­
ним, что радиосигнал распространяется со скоростью света, т.е. приблизительно
186000 миль в секунду. Таким образом, чтобы вычислить эту задержку, необхо­
димо расстояние между планетами разделить на скорость света. Полученный ре­
зультат следует отобразить в секундах и минутах.

Последовательность действий
1. Создайте новый файл с именем Mars. срр.
2. Для вычисления змержки необходимо использовать значения с плавающей
. точкой. Почему? Да просто потому, что искомый временной интервал будет
иметь дробную часть. Вот переМСliные, которые используются в программе.

double distancei
double lightspeed;
double delaYi
double delay_in_min;
3. Присвоим перемеНIlЫМ distance и lightspeed начальные значения.

distance = 34000000.0; // 34 000 000 миль


ligl1tspeed = 18'6000.0; // 186 000 миль в секунду 1
u
4. Для получения задержки во времени
менtlОЙ di stалсе на значение
ное переменной

delay = distance /
(8 секундах) разделим значение
1 igh tspeed. Присвоим вычисленное част­
delay и отобразим результат.
lightspeedi
пере­

1.
cout« "Временная задержка при разговоре с Марсом: "
«
delay « " секунд.\п";
5. Для получения задержки n минутах разделим значение переменной delay
на 60 и отобразим результат.

delay_in_min = delay / 60.0;


82 МОДУЛЬ 2. Типы данных и операторы

6. Приведем программу Mars . срр целиком.

/*
Проект 2.1.

"ПоговорИЪо! с Марсом"

*/

#include <iostream>
using namespace std;

int main () {
double distance;
double lightspeed;
double delay;
double delay_in_min;

distance = 34000000.0; // 34 000 000 миль


lightspeed = 186000.0; 11 186 000 миль в секунду

delay = distance 1 lightspeed;

I cout« "Временная задержка ери разговоре с Марсом: n

«
delay « " секунд.\п";

cout« "Это составляет" « delay_in_min « " минут.";

return О;

7. Скомпилируйте и выполните программу. Вы должны получить такой ре­


зультат.

Временная задержка при разговоре с Марсом: 182.796


ceKYH.~.

Это составляет 3.04659 минут.

8. Самостоятельно отобразите временную задержку, которая будет иметь ме­


сто при ожидании ответа с Марса.
С++: руководство ДЛ~ начинающих 83

ЛитерШlЫ - это фиксированные (предназначеl-Illые для восприятия челове­


ком) значения, которые не могут быть изменены программоЙ. Они называются
также константами. Мы уже использовали литералы во всех предыдущих при­
мерах программ. А теперь настало время изучить их более подробно.
Литералы в С++ могут иметь любой базовый тип данных. Способ представле­
ния каждого литерала зависит от его типа. Символьные литерШlЫ заключаются в
одинарные кавычки. Например, 'а ' и ' %' являются символьными литералами.
Целачисленные литералы задаются как числа без дробной части. Например, 1О
и -1 О О - целочисленные литералы. Вещественные лиmepШlЫ должны содержать
десятичную точку, за которой следует дробная часть числа, например 11.123.
Для вещественных констант можно также использовать экспоненциальное пред­
ставление чисел.

Все литеральные значения имеют некоторый тип данных. Но, как вы уже зна­
ете, есть несколько разных целочисленных типов, например int, short int и
unsigned 10ng int. Существует также три различных вещественных типа:
f1oat, double и 10ng double. Интересно, как же тогда компилятор определяет
тип литерала? Например, число 123.23 имеет тип float или double? Ответ на
этот вопрос состоит из двух частей. Во-первых, С++-компилятор автоматически
делает определенные предположения насчет литералов. Во-вторых, при желании
программист мож~т явно указать ~п литерала.

По умолчанию компилятор связывает целочис.ленныЙ литерал с совместимым


и одновременно наименьшим по занимаемой памяти типом данных, начиная с
типа int. Следовательно, для 16-разрядных сред число 10 будет связано с типом
int, а 103000 - с типом 10ng int.
\ Единственным исключением из правила "наименьшего типа" являются веще­
ственные (с плавающей точкой) константы, которым по умолчанию присваива­
ется тип double.
Во многих Случаях такие стандарты работы компилятора вполне приемлемы.
Однако у программиста есть возможность точно определить нужный тип. Чтобы
задать точный тип числовой константы, используйте соответствующий суффикс.
Для вещественных типов действуют следующие суффиксы: если вещественное
число завершить буквой F, оно будет обрабатываться с использованием типа
П0а t, а если буквойL, подразумевается тип 10ng double. Для целочисленных
типов суффикс U означает использование модификатора типа unsigned, а суф­
фикс L - 10ng. (Для задания модификатора unsigned 10ng необходимо ука­
зать оба суффикса u и L.) Ниже приведеllЫ некоторые примеры.
~....... ~~~Y~~.~:.!~~.':'!.~~.':~~~~.I:'!.~.~~.~9.~<?'P'~I............................................

ТипдоннbIX Примеры констант


int 1,123,21000,-234
long int 35000L, -34L
unsigned int 10000U, 987U, 40000U
unsigned long 12323UL,900000UL
float 123.23F,4.34e-3F
double 23.23,123123.33,-0.9876324
long double 1001.2L

Шестнадцатеричные и восьмеричные литералы


Иногда удобно вместо десятичной системы счисления использовать восьме­
ричную или шестнадцатеричную. В вось.мерuч//оЙ системе основанием служит
число 8, а для выражения всех чисел используются цифры от О до 7. В восьме­
ричной системе число 1О имеет то же значеНИI~. что число 8 в десятичной. Си­
стема счисления по основанию 16 называется шеcmнадu,aтеpuчной и использует
цифры от О до 9 плюс буквы от А до F. означающие шестнадцатеричные "цифРhl"
10, 11, 12, 13, 14 и 15. Например, шестнадцатеричное число 10 равно числу 16
в десятичной системе. Поскольку эти две системы счисления (шестнадцатерич­
ная и восьмеричная) используются в программах довольно часто, в языке C~
разрешено при желании задавать целочислеННI,rс литералы не в десятичной, а в
шестнадцатеричной или восьмеричной системе. Шестнадцатеричный литерал
должен начинаться с префикса Ох (нуль и буква х) или ох, а восьмеричный­
с нуля. Приведем два примера.

int hex OxFFi 11 255 в десятичной системе


int oct = 011; // 9 в десятичной системе

Строковы е литералы
Язык С++ поддерживает еще один встроенный тип литерала, именуемый
строковым. Строка - это набор символов, заключенных в двойиые кавычки,
например "это тест". Вы уже видели примеры строк в некоторых соut-ии­
струкциях, с помощью которых мы выводили текст на экран. При этом обра­
тите внимание вот на что. Хотя С++ позволяет определять строковые литера­
лы, он не имеет встроенного строкового типа данных. Строки в С++, как будет
показано ниже в этой книге, поддерживаются в виде символьных массивов.
(Кроме того, стандарт С++ поддерживает строковый тип с помощью библио­
течного класса string.)
С++: РУКОВОДСТВО МЯ начинающих 85

Спросим у ОПЫТНОГО программиста

Вопрос. Вы nоказШlIl., как определить СШlВlJЛ6НЫ;; литерал (дmr эmozо СIIJНВlJЛ нео6хо­
дllJtlо JQключит6 tI одинарные IШtlычки). Надо nOHlIJНtlln6,
"mo pe116 ш)ет О ли­
тep~ax тим char. А КJU(:Jaдamь лumeptlll (m.е. константу) "'II11II W_ char?

Ответ. Для определения двубайтовой константы, Т.е. константы типа w char, не-
обходимо использовать префикс L. ВОТ пример. - .
w char wc;
~ = L'A';
Здесь переменной WC присваивается двубайтовая константа, эквивалеlП­
ная символу А. Вряд ли вам придется часто использовать "широкие" сим­
волы, но в этом мОЖет возникнуть необходимость при выходе ваших за­
казчиков на меЖдУНародный рынок.

Управляющие символьные последовательности


с выводом большинства печатаемых символов прекрасно справляются сим­
вольные константы, заключенные в одинарные кавычки, но есть такие "экзем­
пляГ'ы" (например, символ возврата каретки), которые невозможно ввести в ис­
ходный текст программы с клавиатуры. Некоторые символы (например, одинар­
ные и двойные кавычки) D С++ имеют специальное назначение, поэтому иногда
их нельзя ввести напрямую. По этой причине в языке С++ разрешено использо­
вать ряд специальных символьных последовательностей (включающих символ
"обратная косая черта"), которые также называются уnравляющимu nоследова­
mелЬ7ЮСmя.м.u. Их список привсден в табл. 2.3.

Таблица 2.3. Управляющие символьные последовательности


КОА Значение
\Ь Возврат-н-а-о-д-ну-п-о-з-ицию
\ f Подача страницы (для перехода к началу следующей страницы)
\n Н оная строка
\r Возврат каретки
\ t Горизонтальная табуляция
\" Двойная кавычка
\ ' Одинарная кавычка (апостроф)
\\ Обратная косая черта
\v Вертикальная табуляция
\а Звуковой сигнал (звонок)
\? Вопросительный знак
\N ВОСI,меричная константа (где N - это сама восьмеричная константа)
\xN Шестнадцатеричная константа (где N - это сама шестнадцатеричная константа)
86 Модуль 2. Типы данных и операторы

ИСПО.1JЬэоваиие управляющих последовательностей демонстрируется на при­


мере следующей программы.

// Демонстрация использования управляющих


// последовательностей.

Jinclude <iostrearn>
using narnespace std;

int rnain ()
{ ,
cout « "one\ttwo\tthree\n";
cout « "12З\Ь\Ь45"; // Последовательность \Ь\Ь
// обеспечит возврат на две позиции
// назад. Поэтому цифры 2 и 3 будут
// "стерты".
return О;

Этапрограмма генерирует такие результаты.

опе two three


145
Здесь в первой соut-инструкции для позиционирования слов "two" и "th-
ree" используется символ горизонтальной табуляции. Вторая соut-инструк­
ция сначала отображает цифры 123, затем после вывода двух управляющих
символов возврата на одну позицию цифры 2 и 3 удаляются. Наконец, выво­
дятся цифры 4 и S.

Спросим у опытного программиста

Вопрос. Если строка состоит из одного символа, то она эквuвалентна символьному


литералу? Например, ~k· и 'k' - это оти u то же? -
Ответ. Нет. Не нужно путать СТРОЮi с символами. СИМВОЛЬНЫЙ литерал пред­
ставляет одну букву, Т.е. значение типа cha r. В то же время строка (пусть
и состоящая всего из ОДНОЙ буквы) - все равно строка. Хотя строки со­
стоят из символов, это - разные типы данных.
С++: РУКОВОДСТВО дl\fI начинающих 87

~ПРОСЫ ДЛЯ текущего l<онтролЯ _ _ _ _ _ _ __


1. Какой тип данных используется по умолчанию для литералов 1О и
10.0?
2. как задать константу 100, чтобы она имела тип данных long int? Как
задать константу 100, чтобы она имела тип данных unsigned int?
з. Что такое \Ь?·

tH Соэдание ини' ~иаАиэироВаннh'х


переменных
с переменными мы познакомились в модуле 1. Теперь настало время рассмо­
треть их более подробно. Общий формат инструкции объявления переменных
выглядит так:

тип имя_переменной;

Здесь элемент тип означает допустимый в С++ тип данных, а элемент имя_пе­
ременной- имя объявляемой переменной. Создавая переменную, мы создаем
экземпляр ее типа. Таким образом, возможности переменной определяются ее
типом. Например, переменная типа Ьоо 1 может хранить только логические зна­
чения, и ее нельзя использовать для хранения значений с плавающей точкой. Тип
переменной невозможно изменить во время ее существования. Это значит, что
iпt-переменную нельзя превратить в dоublе-переменную.

Инициализацияпеременной
При объявлении переменной можно присвоить некоторое значение, Т.е. ини­
циQJluзироваmь ее. Для этого достаточно записать после ее имени знак равенства

1. Для литерала 10 по умолчанию используется тип int, а для литерала 10.0 - тип
double.
2. Для того чтобы константа 1О О имела тип данных lопg int, необходимо записать
100L. ДЛЯ того чтобы константа 1 О() имела тип данных unsigned int, необ­
ходимо записать 1 О О U.
З. Запись \Ь представляет собой управляющую последовательность, которая означа­
ет возврат на одну позицию.
88 Модуль 2. Типы данных и операторы

и присваиваемое начальное значение. Общий формат инициализацuи nepeJll(!HHoU


имеет следующий вид:

тил имя_леременной = значение;

Вот несколько примеров.

int count = 10; // Присваиваем переменной count начальное

1/ значение 10.
char ch = 'х' ; // Инициализируем nE!ременную ch буквой х.
float f = 1.2F // Переменная f инициализируется

// значением 1,2.
При объявлении двух или больше переменных одного типа в форме списка
можно одну из них (или несколько) обеспечигь начальными значениями. При
этом все элементы списка, как обычно, разделяются запятыми. Вот пример.
int а, Ь = 8, с = 19, d; // Переменные Ь и с
// инициализируются при объявлении.

Динамическаяинициализацияпеременных
Несмотря на то что переменные часто инициализируются-константами (как
показано в предыдущих примерах), С++ ПОЗВОllяет инициализировать перемt'н­
ные динамически, т.е. с помощью любого выраж ения, действительного на момент
инициализации. Например, рассмотрим не60лыuую программу, которая вычис­
ляет объем цилиндра по радиусу его основания и высоте.

// Демонстрация динамической инициализации nepeMeHHblX.

#include <iostream>
using namespace std;

int main () {
double radius = 4.0, height = 5.0;

/ / ДинUlИЧесltИ ИКJЩИanизируек перекени)'lO volume.


double volume = 3.1416 * radius * radius * height;

cout « "Объем цилиндра равен " « volume;

return О;
С++: руководство ДЛ'il начинающих 89

Здесь объявляются три локальные переменные radius, height и volume.


Первые две (radius и height) инициализируются константами, а третья (vo-
lume) - результатом выражения, вычисляемым во время ВЫПОЛllения програм­
мы. Здесь важно то, что выражение инициализации может использовать любой
элемент, действительный на момент инициализации, - обращение к функции,
другие переменные или литералы.

Операторы
в С++ определен широкий набор операторов. Оператор (operator) - это
символ, который указывает компилятору на выполнение конкретных матема­
тических действий или логических манипуляций. В С++ имеется четыре общих
класса операторов: арифметические, поразрядные, логuческие и операторы отно­
шений. Помимо них определены другие операторы специального назначения. В
этом модуле мы уделим внимание арифметическим. логическими и операторам
отношений, а также оператору присваивания. Поразрядные и операторы специ­
ального назначения рассматриваются ниже в этой книге.

;В Арифмехические~ПQРnТОРЫ
в С++ определены следующие арифметические операторы.

Оператор Действие -------_._-----_._----


+ Сложение
Вычитание, а также унарный минуо

* Умножение
/ Деление
% деление по модулю
Декремент
++ Инкремент

Действие операторов +, -, ~ И / совпадает с действием аналогичных опера­


торов в алгебре. Их можно при менять к данным любого встроенного числового
типа, а также типа char.
После применения оператора деления (/) к целому числу остаток будет отбро­
шен. Например, результат целочислеиного деления 10/3 будет равен 3. Остаток
от деления можно получить с помощью оператора деления по .модулю (%). На­
пример, 10 % 3 равно 1. Это означает, что в С++ оператор "%" нельзя при менять к
90 МОДУЛЬ 2. Типы данных и операторы

типам с плавающей точкой (float или double). Деление по модулю применимо


только к операндам целочисленного типа.

Использование оператора деления по модулю демонстрируется в следующей


программе.

// Демонстрация использования операrора деления по модулю.

#include <iostrearn>
using narnespace std;

int rnain ()
(
int х, У;

х =,10;
У = 3;
cout « х « " / " « у « " равно " « х / у «
" с остатком" « х % у « "\п";

х = 1;
У = 2;
cout « х « " / " « у « " равно " « х / у « "\п" «
х « " % " « у « " равно " « х % У;

return О;

Результаты выполнения этой программы таковы.


10 / з равно 3 с остатком 1
1 / 2 равно О
1 % 2 равно 1

Инкремент и декремент
Операторы инкремента (++) и декремента (--). которые уже упоминались в
модуле 1. обладают очень интересными свойствами. Поэтому им следует уделить
особое внимание.
Вспомним: оператор инкремента выполняет сложение опер~нда с числом 1. а
оператор декремеftrа вычитает 1 из своего операнда. Это значит. что инструкция

х = х + 1;
С++: руководство дI\'iI начинающих 91

аналогична такой инструкции:

++Х;

а.
А инструкция о

х=х...,/l;
6
а.
ф
с:
аналогична такой инструкции: о
:s:
--Х; х

1:
Операторы инкремента и декремента могут стоять как перед своим операн­ 1:

дом (nрефU1€СНая форма), так и после него (постфиксная форма). Например, ин­ ~
J5
с:
струlЩИЮ
~
Х = Х + 1;
можно переllИсать в виде префиксной

++Х; // Префиксная форма оператора инкремента.


или постфиксной формы:

Х++; // Постфиксная форма оператора инкремента.

В предыдущем примере не имело значения, в какой форме был применен опе­


ратор инкремента: префиксной или постфиксной. Но если оператор инкремента
или декремента используется как часть большего выражения, то форма его при­
менения очень важна. Если такой оператор применен в префиксной форме, то
С++ сначала выполнит эту операцию, чтобы операнд получил новое значение,
которое затем будет использовано остальной частью выражения. Если же опера­
тор применен в постфиксной форме, то С# использует в выражении его старое
значение, а затем выполнит операцию, в результате которой операнд обретет но­
вое значение. Рассмотрим следующий фрагмент кода:

Х = 10;
У = ++Х;

В этом случае переменная у будет установлена равной 11. Но если в этом коде
прсфиксную форму записи заменить постфиксной, переменная у будет установ­
лена равuой 10:
Х = 10;
У = Х++;
В обоих случаях переменнэя Х получит значение 11. Разница состоит лишь в том,
в какой момент она станет равной 11 (до присвоения ее значения перемеиной у
или после). Для программиста очень важно иметь возможность управлять време­
нем выполнения операции инкремента или декремеlПа.
92 МОДУЛЬ 2. Типы данных и операторы

Арифметические операторы ПОДЧИНЯЮТСЯ следующему порядку выполнения


действий.

Приоритет Операторы
Наивысший ++ --
- (унарный минус)
* / %
Низший + -
Операторы одного уровня старшинства вычисляются компилятором CJJeBa
направо. БезуCJJОВНО, ДЛЯ изменения порядка ВЫЧИCJJений можно использовать
круглые скобки, которые обрабатываются D С1-+ так же, как практичеСЮi во всех
других языках программирования. Операции IIЛИ набор операций, заключенных
в круглые скобки, приобретают более высокий приоритет по сравнению с други­
ми операциями выражения.

Спросим у опытного программиста

Вопрос. Не сtlRзан ли оператор инкремен",й "++" с uмelleм ЯЗ6'ка С++?

Ответ. Даl как вы знаете, С++ построен на фу.даменте языка С, к которому добав­
лено множество усовершенствований, tiольшинство из которых предна;ша­
чено ДЛЯ поддержки объектно-ориеНТlfJЮВС1ННОro Iтрограммирования. Та-'
ким образом, С++ представляет собой '''''''крем.ентное усовершенствовани~
языка С, а результат добавления симвслов "++" (оператора инкремеНТd) к
имени С оказался вполне подходящим liМeHeM для нового языка.

Бьерн Страусгрyn сначала назвал свой язык иС С классами" (С with Class-


es), но позже он изменил это название на С++. и хотя успех нового языка
еще только преДIlОЛaraлся, принятие нового названия (С++) практически
гарантировало ему видное место в истории, поскольку это имя было узна­
ваемым для каждого С-программиста_

и логические операторы
Операторы отношений и логические (булевы) операторы, которые часто
идут ирука 06 руку", ИСПОЛЬЗУЮТСЯ для получения результатов в виде значений
ИСТИНА/ЛОЖЬ. Операторы отношений ОU,енивают по "двухбалльной систе­
ме" отношения Между ДВУМЯ значениями, а лоrnческие определяют различные
С++: РУКОВОДСТВО ДЛЯ начинающих 93

способы сочетания истинных и ложных значеtlИЙ. Поскольку операторы отно­


шений генерируют ИСТИНА/ЛОЖЬ-результаты, то они часто выполняются с
логическими операторами. Поэтому мы и рассматриваем их в одном разделе.
Операторы отношений и логические (булевы) операторы перечислены в
табл.2.4. Обратите внимание на то, что в языке С++ в качестве оператора отноше­
ния "не равно" используется символ"! =", а для оператора "равно" - двойной сим­
вол равенства (==). Согласно стандарту С++ результат выполнения операторов от­
ношений и логических операторов имеет тип bool, Т.е. при выполнении операций
отношений и логических операций получаются значения true или false.

-tc При использовании более старых компиляторов результаты вы-


Назаметку n. . . полнения этих операций имели тип int (О или 1). Это различие в
интерпретации значений имеет в основном теоретическую осно­
ву, поскольку С ++, Kal< упоминал ось выше, автоматически преоб­
разует значение true в 1, а значение false - в О, и наоборот.
Операнды, участвующие в операциях "выяснения" отношений, могут иметь
практически любой тип, главное, чтобы их можно было сравнивать. Что касает­
ся логических операторов, то их операнды должны иметь тип bool, и результат
логической операции всегда будет иметь тип bool. Поскольку в С++ любое не­
uулевое чuсло оценивается как истишюе (true), а нуль эквивалентен ложному
значению (false), то логические операторы можно ис.польэовать в любом вы­
ражении, которое дает нулевой или ненулевой результат.
,
Таблица 2.4. Операторы отношений и логические операторы

Операторы отношений Значение


> Больше
< Меньше
>= Больше или рапно
<= Меньше или равно
Равно
!= Неравно
Логические операторы Значение
&&
11

Логические операторы используются для поддержки базовых логических опе­


раций И, или и НЕ в соответствии со следующей таблицей истинности.
94 Модуль 2. Типы данных и операторы

р q р И q р или q НЕ Р
ложь ЛОЖЬ ложь ЛОЖЬ ИСТИНА
ЛОЖЬ ИСТИНА ЛОЖЬ ИСТИНА ИСТИНА
ИСТИНА ИСТИНА ИСТИНА ИСТИНА ЛОЖЬ
ИСТИНА ЛОЖЬ ЛОЖЬ ИСТИНА ЛОЖЬ

Рассмотрим программу, в которой демонстрируется использование несколь­


ких логических операторов и операторов отношений.

// Демонстрация использования ЛОГИЧI~СКИХ операторов и


// операторов отношений.

#include <iostream>
using патезрасе std;

int main () (
int i, j;
bool Ы, Ь2:

i = 10:
j = 11:
if(i < j) cout « "i < j\n";
if(i <= j) cout « "i <= j\n":
if(i != j) cout « "i != j\n";
if(i j) cout « "Это не вьmолняется!\п";
if(i >= j) cout « "Это не выполняется!\п":
if{i > j) cout « "Это не выполняется!\п":

Ы = true:
Ь2 = false:
if(bl && Ь2) cout « "Это не выполняется!\п":
if(! (Ы && Ь2» cout «
" ! (Ы & & Ь2) равно значению ИСТИНА \п" ;
if (Ы I I Ь2) cout « "Ы I I Ь2 равно значению ИСТИНА \п";

return О:

При выполнении эта программа генерируе1 такие результаты.

i < j
i <= j
С++: PYI(OBOACтaO д.л~ начинающих 95
.................................................................................................................................

i != j
! (Ы && Ь2) равно значению ИСТИНА
л
Ы 1 1 Ь2 равно значению ИСТИНА а.
о
>-
Как операторы оmошений, так и логические операторы имеют более низкий Q

приоритет по сравнению с арифметическими операторами. Это означает, что та­ at


[:
о
кое выражение, как 10> 1+12, будет вычислено так, как ссли бы оно было запи­ s
сано n виде 10> (1+12). Результат этого выражения, КОJit.'ЧlJO же, равен значению х
А
:r::
ложь. :r::
Q
С помощью логических операторов можно объединить в одном выражении ~
А
любое количество операций отношений. Например, в этом выражении объеДИllе­ :~
:""'
но сразу три такие операции.

var > 15 11 ! (10 < count) && 3 <= item


Приоритет операторов отношений и логических операторов I1Ul,азан в следу­
ющей таблице.
-.-_._--_._----','-_._---------, - - - - - -.. ---,-- ----=---
[7риор'-'-и~rе'_'_r_ ___=О'_'_п__=еLР__=а::..:...ТО.::.JрL:.Cы"-'---_ _ _ _ _ _ _ _ . _ ________________ . __
Наивысший
> >= < <=
!=
&&
Низший
"
Проект

...... ._",
2.2.

_.. __ ...... - - .. ,
"исключающее ИЛИ"

XOR. срр I В языке С++ не определен логический оператор "исключающее


(XOR)
• g
Q

..
s
~

ИЛИ" (XOR), хотя это - довольно популярный у программистов


бинарный оператор. Операция "исключающее ИЛИ" генерирует значение ис­
ТИНА, если это значение ИСТИНА имеет один и только один из его операндов.
Это утверждение можно выразить в виде следующей таблицы истинности. .1
р

ЛОЖЬ
q
ЛОЖЬ
р

ЛОЖЬ
XOR q
Ig
ЛОЖЬ ИСТИНА ИСТИНА
ИСТИНА ЛОЖЬ ИСТИНА
ИСТИНА ИСТИНА ЛОЖЬ

Отсутствие реализации логического оператора "исключающее ИЛИ" как


встроенного элемента языка одни программисты считают досадным изъяном
96 Модуль 2. Типы данных и опероторы

С++. Другие же придерживаются иного мнеьия: мол, это избавляет язык 01 из­
быточности, поскольку логический оператор "исключающее ИЛИ" (XOR) не­
ТРУДНО "создать" на основе встроенных операторов. И, ИЛИ и НЕ.
В настоящем проекте мы создадим операпию "исключающее ИЛИ", исполь­
зуя операторы &&, II и !. После этого вы са.\fИ рещите, как относиться к тому
факту, что этот оператор не является встроеЮIЫМ элементом языка С++.

Последовательность действий
1. Создайте новый файл с именем XOR. ср::).

2. Будем исходить из того, что логический оператор "исключающее ИЛИ"


(XOR), применяемый к двум значениям булевоro типа р и q, строится так.

(р II q) && ! (р && q)
Рассмотрим это выражение. Сначала над операндами р и q выполняется
операция логического ИЛИ (первое подвыражеН1-\f), результат которой
будет равен значению ИСТИНА, если по крайней мере один из операн­
дов будет иметь значение ИСТИНА. Загем над операндами р и q ВЫПОЛ­
няется операЩIЯ логического И, результат которой будет равен значению
ИСТИНА, если оба операнда будут иметь значение ИСТИНА. Последний
результат затем инвертируется с помощью оператора НЕ. Таким обра­
зом, реэультат второго подвыражения !:р && q) будет равен значению
ИСТИНА, если один из операндов (или оба сразу) будет иметь значение
ЛОЖЬ. Наконец, этот результат логичеСl:И умножается (с помощью опера­
цИИ И) на результат вычисления первого подвьrpажения (р II q). Таким
образом, полное выражение даст значение ИСТИНА, если один из операн­
дов (но не оба одновременно) имеет знач.~ние J:fСТИНА.

3. Ниже приводится текст всей программы XOR. срр. При ее выполнении де­
монстрируется результат применения ош~рации "исключающее ИЛИ" дДЯ
всех четырех возможных комбинаций ИСТИНА/ЛОЖЬ-значений к двум
ее операндам.

1*
Проект 2.2.

Соэдание оператора "ИСКJ1ючаЮЩЕ!е ИЛИ" (XOR) на основе

встроенных логических С++-операторов.

*/
С++: руководство ДЛЯ начинающих 97

#include <iostrearn>
#include <crnath>

using narnespace std:



:25
~g.
....
~8.

:~
int rnain () :0
:S
:)(
:25
.:1:
bool р, q: ::1:
:0
:<t
р true: ~
~
q true;

cout « р « " XOR " « q « " равно " «


( (р II q) && ! (р && q) ) « "\п":

р false;
q true;

cout « р « " XOR " « q « " равно " «


( (р 1 1 q) && ! (р && q) ) « "\п":

р true:
q false;

cout « р « " XOR " « q « " равно " « ~
( (р 11 q) && ! (р && q) ) « "\n":

q
false:
false:
!
cout «
( (р
р

11
«
q)
" XOR " «
&& ! (р
q
&& q)
«
) «
" равно

"\п";
" « I
.~

I
J
return О:
98 МОДУЛЬ 2. Типы данных и операторы
................................................................................................................................

4. Скомпилируйте и выполните программу. Вы должны получить такие ре­


зультаты.

1 XOR 1 равно О
О XOR 1 равно 1
1 XOR О равно 1
О XOR О равно О

5. Обратите внимание на внешние круглые скобки, в которые заключены вы­


ражения о инструкции cout. Без них здесь обойтись нельзя, поскольку
операторы & & И II имеют более низкий приоритет, чем оператор вывода
данных «<). Чтобы убедиться в этом, попробуйте убрать внешние круглые
скобки. При попытке скомпилировать программу вы получите сообщение
об ошибке.

~ПРОСЫ ДЛЯ текущего КоНтролЯ--------


1. Каково назначение оператора "%"? К значениям какого типа его можно
применять?

2. Как объявить переменную index типа int с начальным значением 10?


3. Какой тип будет иметь результат логического выражения или выражения,
построенного с применением операторов отношении
U?

Начиная с модуля 1, мы активно используем оператор присваивания. Настало


время для "официального" с ним знакомства. В языке С++ оператором npuсва­
U6ШlUЯ служит одинарный знак равенства (=). Его действие во многом подобно
действию аналогичных операторов в других языках программирования. Его об­
щий формат имеет следующий вид.

леременная = выражение;

1. Оператор "%" предназначен для выполнения операции деления по моДУJIЮ и воз­


вращает остаток от деления нацело. Его можно применять только к значениям
целочисленных типов.

2. int index = 10;


3. Результат логического выражения или выражения, построенного с применением

операторов отношений, будет иметь тип bool.


С++: руководство для начинающих 99

Эта запись означает, что элементу леременная присваивается значение элемен­


та выражение.

Оператор присваивания обладает очень интересным свойством: он позволя­ !


ет создавать цепочку присваиваниЙ. Другими словами, присваивая "общее" зна­
чение сразу нескольким переменным, можно "связать воедино" сразу несколько
присваиваниЙ. Рассмотрим, например, следующий фрагмент кода.
I
о
:s
х
int ~, у, z; 25
:r
х = у = z = 100; 11 устанавливаем х, у и z равными 100 :r
~
При выполнении этого фрагмента кода с помощью одной инструкции сразу 25
с:
трем переменным х, у и z присваивается значение 100. Почему возможна такая ~
форма записи? Дело в том, что результатом выполнения оператора "=" является
значение выражения, стоящего от него с правой стороны. Таким образом. резуль­
татом выполнения операции z = 1О О является число 100, которое затем при­
сваивается переменной у и которое в свою очередь присваивается переменной
х. Использование цепочки присваиваний - самый простой способ установить
сразу несколько переменных равными "общему" значению.

присваивания
в С++ предусмотрены специальные cocmaвHыe операторы nрuсвauванu,Я. в
которых объединено присваивание с еще одной операцией. Начнем с примера и
рассмотрим следующую инструкцию.

х = х + 10:
Используя составной оператор присваивания, ее можно переписать в таком виде.

х += 10;
Пара операторов += служит указанием компилятору присвоить переменной х
сумму текущего значения переменной х и числа 10.
А вот еще один пример. Инструкция

х = х - 100;
аналогична такой: .
х -= 100:
Обе эти инструкции присваивают переменной х ее прежнее значение, уменьшен­
ное на 100.
100 Модуль 2. Типы данных и операторы
................................................................................................................................

Составные версии операторов присваивания существуют для всех бинарных


операторов (т.е. для всех операторов, которые работают с двумя операндами). Та­
ким образом, при таком общем формате бинарных операторов присваивания
переменная = переменная ор выражение;

общая форма записи их составных версий выглядит так:


переменная ор = выражение;

Здесь элемент ор означает конкретный арифметический или логический опера­


тор, объединяемый с оператором присваивания.
Поскольку составные операторы присваиваllИЯ короче своих "несоставных"
эквивалентов, составные версии иногда называются сокращеннbLМU операторами

присваивания.

Составные операторы присваивания обладают двумя достоинствами по срав­


нению со своими "полными формами". Во-первых, они компактнее. Во-вторых,
они позволяют компилятору сгенерировать более эффективный код (поскольку
операнд в этом случае вычисляется только один раз). Именно поэтому их можно
часто встретить в профессионально написанных С++-программах, а это значит,
что каждый С++-программист должен быть с ними на "ты".

ВАЖНОI
'1:8 "реобрnзовпние ТИEJав
воператорахприсваивания
Если переменные одного типа смешаны с переменными другого типа, про­
исходит nрео6разованuе тиnов. При выполнении инструкции присваивания
действует простое правило преобразования типов: значение, расположенное с
правой стороны от оператора присваивания, преобразуется в значение типа, со­
ответствующего переменной, указанной слева от оператора присваивания. Рас­
смотрим пример.

int Х;
char ch;
f10at f;

ch = Х; /* строка 1 */
Х = f; /* строка 2 */
f ch; /* строка 3 */
f Х; /* строка 4 */
С++: PYI<OBOACтeO д,лfl начинающих 101

в строке 1 старшие биты целочисленной переменной х отбрасываются, остав­


ляя переменной ch младшие 8 бит. Если значение переменной х лежит в пре­
делах от -128 до 127, то оно будет идентично значению переменной ch. В про­
тивном случае значение переменной ch будет отражать только младшие биты
переменной х. При выполнении следующей инструкции (строка 2) переменная
х получит целую часть значения, хранимого впеременной f. Присвзивание, за­
писанное в строке 3, обеспечит пре06разование 8-разрядного целочисленного
значения переменной ch в формат значения с плавающей точкой. Аналогичное
преобразование произойдет и при выполнении строки 4 за исключением того,
что в формат вещественного числа в этом случае будет пре06разовано не char-,
а iпt-значение.
При пре06разовании целочисленных значений в символы и длинных int- в
обычные iпt-значения отбрасывается соответствующее количество старших би­
тов. Во многих 32-разрядных средах это означает, что при переходе от целочис­
ленного значения к символыюму будут утеряны 24 бит, а при переходе от int- к
короткому iпt-значению - 16 бит. При пре06разовании вещес.твенного значе­
ния в целочисленное теряется дробная часть. Если тип переменной-приемника
недостаточно велик для сохранения присваиваемого значения, то в результате

этой операЦI1И в ней будет сохранен "мусор".


Несмотря на то что С++ автоматически преобразует значение одного встро­
енного типа данных 8 значение другого, результат не всегда будет совпадать с
ожидаемым. Чтобы избежать неожиданностей, нужно внимательно относиться к
использованию смешанных типов в одном выражении.

Вblражения
Операторы, литералы и переменные - это все составляющие выражений. Ве­
роятно, вы уже знакомы с выражениями по предыдущему опыту npoграммиро­

вания или из школьного курса алгебры. В следующих разделах мы рассмотрим


аспекты выражений, которые касаются их использования в языке С++.

в Вblражениях
Если в выражении смешаны различные типы литералов и переменных, ком­
пилятор преобразует их в один тип. Во-первых, все char- и short iпt-значе­
ния автоматически преобразуются (с расширением "типоразмера") в тип int.
102 Модуль 2. Типы данных и операторы

Этот процесс называется ЦeJZочисленнЬLМ расширением (integral promotion). 80-


вторых, все операнды преобразуются (также с расширением "типоразмера") в
тип caмoro БОЛЬJlIOГО операнда. Этот процесс называется расширением типа
(type promotion), причем он выполняется пооперационно. Например, если один
операнд имеет тип int, а другой - long int, то тип int расширяется в тип
long int. Или, если хотя бы один из операндов имеет тип double, любой
другой операнд приводится к типу double. Это означает, что такие преобразо­
вания, как из типа char в тип double, вполне допустимы. После преобразова­
ния оба операнда будут иметь один и тот же тип, а результат операции - тип,
совпадающий с типом операндов.

Прео6разования, связаННblе с типом bool


Как упоминалось выше. значения типа bool при использовании в выражении
целочисленного типа автоматически преобразуются в целые числа О или 1. При
преобразовании целочисленного результата в пrп bool нуль превращается в fa-
1 se, а ненулевое значение - в true. И хотя тип bool относительно недавно был
добавлен в язык С++, выполнение автоматических преобразований, связанных с
типом bool, означает, что его введение в С++ не имеет негативных последствий
для кода, написанного для более ранних версий С++. Более того, автоматические
преобразования позволяют С++ поддерживать исходное определение значений
ЛОЖЬ и ИСТИНА в виде нуля и ненулевого значения.

ВАЖНОI
tll•1 ПРИВРАрние типов L •
в С++ предусмотрена возможность установить для выражения заданный тип.
Для этого используется операция nриведенuя типов (cast). С++ определяет пять
видов таких операций. В этом разделе мы рассмотрим только один из них (самый
универсальный и единственный, который поддерживается старыми версиями
С + + ), а остальные четыре описаны ниже в этой книге (после темы создания объ­
ектов). Итак, общий формат операции приведеllИЯ типов таков:
(тип) выражение

Здесь элемент тип означает тип, к которому необходимо привести выржение•.


Например, если вы хотите, чтобы выражение х /2 имело тип floa t, необходимо
написать следующее:

(float) х / 2
С++: РУКОВОДСТВО ДМ! начинающих 103

Приведение типов рассматривается как унарный оператор, и поэтому он име­


ет такой же приоритет, как и другие унарны.е операторы.
Иногда операция приведения типов оказывается очень полезной. Например,
в следующей программе для управления циклом используется некоторая цело­
численная переменная, входящая в состав выражения, результат вычисления ко­

торого необходимо получить с дробной частью.


11 Демонстрация операции приведения типа.

i1nclude <1ostream>
us1ng патеэрасе std;

1пt та1п () (
1nt i;

for(i=l; 1 <= 10; ++1 )


cout « 1 « "1 2 равно: " « (float) 1 1 2 « '\п';
11 i
11 Приведение к типу fioat
11 обеспечит обязательный
11 вывод дробной части.
rеturп О;

Вот как выглядят результаты выполнения этой программы.

11 2 равно: 0.5
21 2 равно: 1
ЗI 2 равно: 1.5
41 2 равно: 2
51 2 равно: 2.5
61 2 равно: 3
71 2 равно: 3.5
81 2 равно: 4
91 2 равно: 4.5
101 2 равно: 5
Без оператора приведения типа (floa t) выполнилось бы только целочислен­
ное деление. Приведение типов в данном случае гарatПирует, что на экране будет
отображена и дробная часть результата.
104 Модуль 2. Типы данных и операторы

ВАЖНОI
"1' Использование прОбелов
и круглых скобок
Любое выражение в С++ дЛЯ повышения читабельности может включать про­
белы (или символы табуляции). Например, следующие два выражения совер­
шенно одинаковы, но второе прочитать гораздо легче.

х=10/у*(127/х);
х = 10 / у * (127/х);

Круглые скобки (так же, как в алгебре) повышают приоритет операций, содер­
жащихся внутри них. Использование избыточных или дополнительных круглых
скобок не приведет к ошибке или замедлению вычисления выражения. Другими
словами, от них не будет никакого вреда, но зато сколько пользыl Ведь они помо­
гут прояснить (для вас самих в первую очередь, не говоря уже о тех, кому придет­
ся разбираться в этом без вас) точный порядок вычислений. Скажите, например,
какое из следующих двух выражений легче понять?

х = у/З-З4*tеmр+127;
Х = (у/З) - (З4*tеmр) + 127;

Проект 2.3.
IВ_Э1"ОМ проекте ~ы создадим программу, которая вычисляет размер регулярных
RegPay. срр ! платежей по займу, скажем, на покупку автомобиля. По таким
данным, как основная сумма займа, срок займа, количество вы­
плат в год и процентная ставка, программа вычисляет размер платежа. Имея дело
с финансовыми расчетами, нужно использовать типы данных с плавающей точ­
кой. Чаще всего о подобиых вычислениях применяется тип double, вот и мы не
будем отступать от общего правила. Заранее отмечу, что в этом проекте исполь­
зуется еще одна библиотечная С++-Функция pow ( ) .
Для вычисления размера регулярных платежей по займу мы будем использо­
вать следующую формулу.

IntRate * (рnnсфаl/ PayPerYear)

Здесь intRate - процентная ставка, principa1 -начальное значение остатка


(основная сумма займа), PayPerYear - количество выплат в год, NumYears -
срок займа (в годах). Обратите внимание на то, что для вычисления знаменателя
С++: РУКОВОДСТВО ДЛf1 начинающих 105

необходимо выполнить операцию возведения в степень. Для этоro мы будем ис­


пользовать функцию pow ( ) . Вот как выглядит формат ее выэова.

а.
result = pow(base, ехр):
g
Функция pow () возвращает значение элемеита base, возведеиное в степень
ехр. Функция pow () принимает аргументы типа double и возвращает значение
~
с:
о
типа double. :s:
х
25
~
Последовательность действий
~

1. Создайте файл с именем RegPay. срр. с:
~
2. Объявите следующие перемеНJiые.

double Principal: 11 исходная сумма займа


double IntRate: 11 процентная ставка в виде
11 числа (например, 0.075)
double PayPerYear: 11 количество выплат в год
double NumYears; 11 срок займа(в годах)
double Payment; 1/ размер регулярного платежа
double numer, denom; 1/ временные переменные
double Ь, е; /1 аргументы для вызова функции
Pow ()
Обратите внимание на то, что после объявления каждой переменной дан
комментарий, в котором описывается ее назначение. И хотя мы не снабжа­
ем такими подробными комментариями большинство коротких программ
в этой книге, я советую следовать этой практике по мере тоro, как ваши
программы будут становиться все больше и сложнее.

З. Добавьте следующие строки кода, которые обеспечивают ввод необходи­


мой для расчета информации.

cout « "Введите исходную сумму займа: ":


cin » Principal;

cout « "Введите процентную ставку (например, 0.075): ";


cin » IntRate;

cout « "Введите количество выплат в год: ";


cin » PayPerYear;

cout « "Введите срок займа(в годах): ":


cin » NumYears:
106 МОДУЛЬ 2. Типы данных и операторы
.................................................................................................................................

4. Внесите в программу строки, которые выполняют финансовые расчеты.

numer = IntRate * Principal / PayPerYear;

е = -(РауРеrУеаr * NumYears);
Ь (IntRate / PayPerYear) + 1;

denom 1 - pow(b, е);

Payment numer / denom;


5. Завершите программу выводом результата.
cout« "Размер платежа по займу составляет "
« Payment;
6. Теперь приведем полный текст программы RegPay. срр.
/*
Проект 2.3.

Вычисление размера регулярных платежей по займу.

Назовите этот файл RegPay.cpp.


*/

#include <iostream>
iinclude <cmath>
using namespace std;

int main () {
double Principal; // исходная сумма займа
double IntRate; // процентная ставка в виде
// числа (например, 0.075)
double PayPerYear; // количество выплат в год
double NumYearsi // срок займа(в годах)
double Payment; // размер регулярного платежа
double numer, denom; // временные переменные
double Ь, е; // аргументы для вызова
11 функции Pow ( )

cout « "Введите исходную сумму займа: ";


cin » Principali
С++: РУКОВОДСТВО ДЛЯ начинающих 107
................................................................................................................................

cout « "Введите процентную ставку (например, 0.075): ";


cin » IntRate;

cout « "Введите количество выплат в год: ";


cin » PayPerYear;

cout « "Введите срок займа (в годах): ";


cin » NumYears;

numer = IntRate * Principal / PayPerYeari

е = -(РауРеrУеаr * NumYears);
Ь (IntRate / PayPerYear) + 1;

denom = 1 - pow(b, е);

Payment numer / denom;

cout« "Размер платежа по займу составляет "


« Payment;

return О;

ВОТ как выглядит один из возможных результатов выполнения этой про­


граммы.
-1g
Введите исходную сумму займа: 10000 '1
Введите процентную
Введите количество
ставку
выплат в
(например,
год: 12
0.075): 0.075
I
7.
Введите
Размер
срок займа
платежа по
(в годах):
займу составляет
5

Самостоятельно внесите изменения в программу, чтобы она отображала


200.379
Ii
также общую сумму, выплаченную по npоцентам за весь период займа.

I
108 Модуль 2. Типы данных и операторы
............................................................................................................................. - ..

"жеС Т АО9 самоконтроля по МОА)l'Ю 2


1. Какие типы целочисленных значений поддерживаются в С++?

2. Какой тип по умолчанию будет иметь константа 12. 2?


3. Какие значения может иметь переменная типа bool?
4. Что представляет собой длинный целочисленный тип данных?
5. Какая управляющая последовательность обеспечивает табуляцию? Какая
управляющая последовательность обеспечивает звуковой сю'пал?

6. Строка должна быть заключена в двойные кавычки. Это верно?

7. Что представляют собой шестнадцатеРИЧlfые цифры?


8. Представьте общий формат инициализации переменной при ее объявлении

9. Какое действие выполняет оператор "%"? Можно ли его применять к значе­


ниям с плавающей точкой?

10. Поясните различие между префиксной и постфиксной формами оператора


инкремента.

11. Какие из перечисленных ниже символов являются логическими операто-


рами в С++? •
А. &&
В. ##
С. 11
о. $$
Е. !
12. Как по-другому можно переписать следующую инструкцию?

х = х + 12;
13. Что такое приведение типов?

14. Напишите программу. которая находит все простые числе в интервале


от 1 до 100.

-tc Ответы на эти вопросы можно найти на Web-странице данной


На заметку n. . книги по адресу: http://www . osborne. сот.
МОДУЛЬ3
ИНСТРУI<ЦИИ управления

3.1. Инструкция if
3.2. Инструкция switch
3.3. Цикл for
3.4. Цикл while
3.6. ИСПОЛl>Зование инструкции break для выхода ШJ цикла
3.7. Использование ИIIСТРУКЦИИ continue
3.8. Вложенные llИКJIbl
3.9. Инструкция goto
110 Модуль З. ИНСТРУКЦИИ управления
..................................................................................................................................

В этом модуле вы узнаете, как управлять ходом выплнения С++-программы.


Существует три категории управляющих инструкций: инструкции 8ыбора (if,
swi tch), итерационные инструкции (СОСТОЯЩИf' из
for-, while- и do-while-
циклов) и инструкции перехода (break, continue, return и goto). За исклю­
чением return, все остальные перечисленные выше инструкции описаны в этом

модуле.

Инструкция if была представлена в модуле 1, но здесь мы рассмотрим ее бо·


лее детально. Полный формат ее записи таков.

if(выражение) ИНСТРУКЦИЯ;
else инструкция;

Здесь под элементом ИНСТРУКЦИЯ понимается одна инструкция языка С++.


Часть else необязательна. Вместо элемента ИНСТРУКЦИЯ может быть исполь­
зован блок инструкций. В этом случае формат записи if-инструкции принимает
такой вид.

i f (выражение)
{
последовательность инструкций

else

последовательность ИНСТРУКЦИЙ

Если элемент выражение, который представляет собой условное выражение,


при вычислении даст значение ИСТИНА, будет выполнена i f-ИНСтрукция; в
противном случае - еlsе-инструкция (если таковая существует). Обе инструк­
ции никогда не выполняются. Условное выражение, управляющее выполнением
if-ИНСтрукции, может иметь любой тип, действительный для С++-выражений,
но главное, чтобы результат его вычисления можно было интерпретировать как
значение ИСТИНА или ЛОЖЬ.
Использование if-инструкции рассмотрим на примере npoграммы, которая
представляет собой упрощенную версию игры "Угадай мarnческое число". Про­
грамма генерирует случайное число и предлагает вам его угадать. Если вы угадыва­
ете число, программа выводит на экран сообщение одобрения ** Правильно **.
в этой программе представлена еще одна библиотечная функция rand (), кото-
С++: РУКОВОДСТВО дl\Я начинающих 111
................................................................................................................................

рая возвращает случайным образом выбранное целое число. Для использования


этой функции необходимо включить в программу эaroловок <сз tdlib>.
// Про грамма "Угадай магическое число".

iinclude <iostream>

finclude <cstdlib>
using namespace std;

int main ()
{
int magic; // магическое число
int guess; // вариант пользователя

magic rand(); // Получаем случайное число.

cout « "Введите свой вариант магического числа: ";


cin » guess;

// Есик зна.ение перекенвой чuе88 со.пад.~ С


// "кarRЧ8СКИК .иcnок", буде~ .МВ.Д_НО сооб~•.
if(guess == magic) cout « "** Правильно **";

return О;

в этой программе для проверки TOro, совпадает ли с "магическим числом" ва­


риант, предложенный пользователем, используется оператор отношения "==".
При совпадении чисел на экран выводится сообщение ** Правиль но * *.
Попробуем усовершенствовать нашу программу и в ее новую версию вклю­
чим else-вeтвb для вывода сообщения о том, что предположение пользователя
оказалось неверным.

// Про грамма "Угадай магическое число":


// l-e усовершенствование.

'include <iostream>
iinclude <cstdlib>
using namespace stdi

int main ().


112 МОДУЛЬ З. ИНСТРУКЦИИ управления
................................................................................................................................

int magic; 11 магическое число


int guess; 11 вариант пользователя

magic = rand(); 11 Получаем случайное число.

cout « "Введите свой вариант магического числа: ";


cin » guess;

if(guess == magic)
cout « "** Правильно **".,
else
cout « " ... Очень жаль, но вы ошиблись."; 11 Индикатор
11 неправильного ответа.

return О;

Условное выражение
Иногда новичков в С++ сбивает с толку тот факт, что для управления if-ин­
струкцией можно использовать любое деЙствите.'IЪное С++-выражение. Другими
словами, тип выражения необязателыю ограничивать операторами отношений и
логическими операторами или операндами типа bool. Главное. чтобы результат
вычисления условного выражения можно было интерпреrnровать как значение
ИСТИНА или ЛОЖЬ. Как вы пьмните из предыдущего модуля, нуль автомати­
чески преобраэуется в false, а все ненулевые значения - в true. Это означает,
что любое выражение, которое дает в результате нулевое или ненулевое значе­
ние, можно использовать для управления if-ИlfструкциеЙ. Например, следую­
щая про грамма считывает с клавиатуры два целых числа и отображает частное
от деления первого на второе. Чтобы не допустить деления на нуль, в программе
используется if-инструкция.

11 Использование iпt-значения для управления


// if-инструкциеЙ.

#include <iostream>
using namespace std;

int main ()
С++: РУКОВОДСТВО ДМl начинающих 113

int а, Ь;

cout « "Введите числитель: ";


cin » а;

cout « "Введите знаменатель: ";


cin » Ь;

if(b) // ДnR упрaanевик


cout « "Результат: " « а / Ь // этой if-ИВСтру1ЩИ8Й
«'\п'; // достаточно ОД80Й
// переllенвой Ь.
e1se
cout « "На нуль делить нельзя.\п";

return О;

Возможные результаты выполнения этой программы выглядят так.

Введите числитель: 12
Введите знаменатель: 2
Результат: 6

Введите числитель: 12
Введите знаменатель: О
На нуль делить нельзя.

Обратите внимание на то, что значение переменной Ь (делимое) сравнивается


с нулем с помощью инструкции i f (Ь), а не инструкции if (Ь != О) . Дело в том,
что, если значение Ь равно нулю, условное выражение. управляющее ИНСТРУКЦИ­

ей if, оценивается как ЛОЖЬ, что приводит К выполнению е1sе-ветви. В про­


тивном случае (если Ь содержит ненулевое значение) условие оценивается как
ИСТИНА, и деление благополучно выполняется. Нет никакой необходимости
использовать следующую i f -ИI:IСТРУКЦИЮ. которая к тому же не свидетельствует
о хорошем стиле программирования на С++.

if(b != О) cout « а/Ь « '\п';

Эта форма if-ИНСТрукции считается устаревшей и потенциал[.но неэффек­


тивtюЙ.
114 Модуль З. ИНСТРУКЦИИ управления
................................................................................................................................

Вложенные if-ИНСТРУКЦИИ
Вложенные if-ИНСТРУКЦИИ образуются в том случае, если в качестве элемен­
та ИНСТРУКЦИЯ (СМ. полный формат записи) используется другая if-ИНСТРУК­
ция. Вложенные if-инструкции очень поnyлярн'ы в программировании. Главное
здесь - помнить, что еlsе-инструкция всегда относится к ближайшей if-ин­
струкции, которая находится внутри того же программноro блока, но еще не свя­
зана ни с какой другой else-инструкциеЙ. Вот пример.

if(i) (
if(j) result = 1;
if(k) result = 2:
else result = 3; // Эта else-ветвь связана с if(k).

else result = 4; // Эта else-ветвь связана с if(i).


Как отмечено в комментариях, последняя еlsе-инструкция не связана с ин­
струкцией i f (j ) , поскольку они не находятся в одном блоке (несмотря па то,
что эта if-инструкция - ближайшая, которая не имеет при себе "еlsе-пары").
Внутренняя еlsе-инструкция связана С инструкцией if (k), поскольку она­
ближайшая и находится внутри того же блока.
Продемонстрируем использование вложенных инструкций с помощью оче­
редного усовершенствования прогрзммы "Угадай магическое число" (здесь игрок
получает реакцию программы на неправильный ответ).

// Про грамма "Угадай магическое число":


// 2-е усовершенствование.

finclude <iostream>
#include <cstdlib>

using паmезрасе std;

int main ()
(
int magic; // магическое число
int guess; // вариант пользователя

magic = Land(); // Получаем случайное число.

cout « "Введите свой вариант магического числа: ";


cin » guess;
С++: руководство ДЛ~ начинающих 115
................................................................................................................................

if (guess == magic)
cout « ,,** Правильно **\n";
cout « magic
« " и есть то самое магическое число.\n";

else
cout « " ... Очень жаль, но вы ошиблись.";
if(guess > magic)
cout « " Ваш вариант больше магического числа.\n";
else
cout « " Ваш вариант меньше магического числа.\n";

return О;

"Лестничная" конструкция if-else-if


Очень распространенной в программировании конструкцией, в основе кото­
рой лежит вложенная if-ИНСтрукция, является "лестница" if-else-if. Ее мож­
но представить D следующем виде.

if (условие)

инструкция;

else if (условие)

инструкция;

else if(условие)

инструкция;

else
инструкция;

Здесь под элементом условие понимается условное выражение. Услов­


ные выражения вычисляются сверху вниз. Как только в какой-нибудь ветви
обнаружится истинный результат, будет выполнена инструкция, связанная с
этой ветвью, а вся остальная "лестница" опускается. Если окажется, что ни
одно из условий не является истинным, будет выполнена последняя еlsе-ин­
струкция (можно считать, что она выполняет роль условия, которое действует
116 Модуль З. ИНСТРУКЦИИ управления

по умолчанию). Если последняя еlsе-инструкция не задана, а все остальные


оказались ложными, то вообще никакое деiiствие не будет выполнено.
Работа if-еlsе-if-"лестницы" демонстрируется в следующей программе.

// Демонстрация использования "лестницы" if-else-if.

tinclude <iostream>
using namespace std;

int main ()
(
int Х;

for(x=Q; х<6; х++)


if(x==l) cout « "х равен единице.\п";
else if(x==2) cout « "х равен двум.\п";
else if(x==3) cout « "х равен трем.\п";
else if(x==4) cout « "х равен четырем.\п";
else cout « "х не попадает в диапазон от 1 до 4.\п";

return О;

Результаты выполнения этой программы iaKoBbl.

х не попадает в диапазон от 1 до 4.
х равен единице.

х равен двум.

х равен трем.

х равен четырем.

х не попадает в диапазон от 1 до 4.
Как видите, последняя еlsе-инструкция выполняется только в том случае,
если все предыдущие i f-условия дали ложный результат.
С++: PYI(OBOACTBO ДЛЯ начинающих 117

~просы МЯ текущего контроля - - - - - - - -


1. Верно ли то, что условие, управляющее if-инструкциеЙ. должно обяза-
тельно использовать оператор отношения?

2. С какой if-инструкцией всегда связана еlsе-инструкция7

3. Ч то такое ~. f -е 1 se-~. f -..лестница "7*

ВАЖНОI

Теперь познакомимся с еще одной инструкцией выбора - swi tch. Инструк­


ция switch обеспечивает многонаправленное ветвление. Она позволяет делать
выбор одной из множества альтернатив. Хотя многонаправленное тестирование
можно реализовать с помощью последовательности вложенных if-ИНструкциЙ.
во многих ситуациях mfСТРУКЦИЯ sw i tch оказывается более эффективным реше­
нием. Она работает следующим образом. Значение выражения последовательно
сравнивается с константами из заданного списка. При обнаружении совпадения
для одного из условий сравнения выполняется последовательность инструкций.
связанная с этим условием. Общий формат записи инструкции sw i tch таков.
switсh(выражение)

case KOHCTaHTal:

последовательность инструкций
break;
саsе константа2:
последовательность инструкций
break;
case константа3:
последовательность инструкций
break;

1. Не верно. Условие, управляющее if-инструкцией, должно при вычислении давать ре­


зультат, который можно расценивать как одно из значений ИСТИНА или ЛОЖЬ.

2. Ветвь else всегда связана с ближайшей i f-ветвью, которая находится в том же про­
граммном блоке, но еще не связана ни с какой другой else-инструкциеЙ.
3. ~Лес1'НИца" if-else-if - это последовательность вложенных i f-еlsе-инстрYJЩИЙ.
118 Модуль З. ИНСТРУКЦИИ управлени~

default:
последовательность инструкций

Элемент выражение инструкции switch должен при вычислении давать


целочисленное или символьное значение. (Выражения, имеющие, например, тип
с плавающей точкой, не разрешены.) Очень часто в качестве управляющего swi-
tсh-выражения используется одна переменная.
Последовательность инструкций defaul t-ветви выполняется в том случае,
если ни одна из заданных case-Констант не совпадет с результатом вычисления I

swi tсh-выражения. Ветвь defaul t не06язательна. Если она отсутствует, то


при несовпадении результата выражения ни с одной из case-констант никакое
действие выполнено не будет. Если такое совпадение все-таки обнаружится, бу­
дут выполняться инструкции, соответствующие данной саsе-ветви, до тех пор,
пока не встретится инструкция break или не будет достигнут конец swi tсh-ин­
струкции (либо в default-, либо в последней саsе-ветви).
Итак, для применения swi tch-ИНСтрукции необходимо знать следующее.
• Инструкция switch отличается от инструкции if тем, что switch-Bbl-
ражение можно тестировать только с использованием условия равенетва

(т.е. на совпадение switсh-выражеюш с одной из заданных case-KOH-


стант), в то время как условное if-выражение может быть любого ПIпа.
• Никакие две case-константbl в одной s',."i tch-ИНСтрукции не могут иметь
идентичных значений,

• Инструкция switch обычно более эффективна, чем вложенные if-ин­


струкции.

• Последовательность инструкций, связанная с каждой саsе-ветвью, не являет­


ся блоком, Однако полная swi tch-инструкЦIIЯ определяет блок. Значимость
этого факта станет очевидной после того, как вы больше узнаете о С++.

Использование swi tch-ИНСТрукции демонстрируется в следующей програм­


ме. После запуска программа предлагает пользователю ввести число от 1 до 3, а
затем отображает пословицу, связанную с выбранным числом. При вводе числа,
не входящего в диапазон 1-3, выводится сообщение об ошибке.
/*
Простой "генератор" пословиц, который демонстрирует

использование инструкции swi~ch.


*/

#include <iostream>
С++: PYl<OBOACтeo ДЛЯ начинающих 119

using namespace std;


с;:
int main () s:
:I:
( ~ID
int num;
8.
с::
>-
cout « "Введите число от 1 до 3: "; s:
s:
::r
cin » пит;
5.
е-
()
switch(num) // Значение переиенвой пum опредanRе~ :J:
:s:
// awпоnниекую ca8e-.e~.ъ.
case 1:
cout « "Один пирог два раза не съешь.\п";
break;
сазе 2:
cout « "Двум головам на одних плечах TecHo.\n";
break;
case 3:
cout « "Три раза прости, а в четвертый прихворости.\п";
.break;
default:
cout « "Вы должны ввести число 1, 2 или 3.\n";

return О;

Вот как выглядят возможные варианты въmолнения этой программы.

Введите число от 1 дО З: 1
Один пирог два раза не съешь.

Введите число от 1 дО З: 5
Вы должны ввести число 1, 2 или з.
Формально инструкция break не06язательна, хотя в большинстве случаев
использования switсh-КОНСТРУКЦИЙ она присутствует. Инструкция break, сто­
ящая в последовательности инструкций любой саsе-ветви, приводит к выходу
из всей 5wi tсh-КОНСТРУКЦИИ и передает управление инструкции, расположен­
ной сразу после нее. Но если инструкция break в саsе-ветви отсутствует, будут
выполнены все инструкции, связанные с данной саsе-ветвью, а также все после­
дующие инструкции, расположенные за ней, до тех пор, пока все-таки не встре-
120 МОДУЛЬ З. ИНСТРУI<ЦИИ управления

тится инструкция break, относящаяся к одной из последующих саsе-ветвей,


или не будет достигнут конец swi tсh-конструкции. Рассмотрите внимательно
текст следующей программы. Попробуйте предугадать, что будет отображено на
экране при ее выполнении.

// конструкция switch без инструкций break.

*include <iostrearn>
using паrnезрасе std;

int rnain ()
{
int i;

for(i=O; i<5; i++) {


switch (i)
case О: cout « "меньше 1 \п"; // ИRС'1'рYIЩИИ break нет
сазе 1: cout « "меньше 2\п"; //
сазе 2 : cout « "меньше З\п"; //
сазе 3: cout « "меньше 4\п"; //
сазе 4 : cout « "меньше 5\п" ; // инструхции break вет

cout « '\п' ;

return О;

При выполнении программа генерирует такие результаты.

меньше 1
меньше 2
меньше 3
меньше 4
меньше 5

меньше 2
меньше 3
меньше 4
меньше 5

меньше 3
С++: PYI<OBOACTBO ДЛЯ начинающих 121

меньше 4
меньше 5

меньше 4
меньше 5

меньше 5
Как ВИДНО по результатам, если инструкция break в одной саsе-ветви отсут­
ствует, выполняются инструкции, относящиеся к следующей саsе-ветви.
Как показано в следующем примере, в swi tсh-КОНСТРУКЦИЮ можно включать
"пустые" саsе-ветви.

switch(i)
case 1:
саsе 2: // +- Пустые саsе-ветаи.
саsе З:
cout « "Значение переменной i меньше 4.";
break;
саsе 4:
cout « "Значение переменной i равно 4.";
break;
Если переменная i в этом фрагменте кода получает значение 1,2 или З, ото­
бражается одно сообщение.
Значение переменной i меньше 4.
Если же значение переменной i равно 4, отображается другое сообщение.
Значение переменной i равно 4.
Использование "пачки" нескольких пустых са 5 е-ветвей характерно для случаев,
когда при выборе нескольких констант необходимо выполнить один и тот же код .

. Вложенные ИНСТРУКЦИИ swi tch


Инструкция swi tch может бьnъ использована как часть саsе-последовате.лъно­
сти внешней ШlСТРУКЦИ:И swi tch. В этом случае она называется вложенной инструк­
цией swi tch. Необходимо отметить, что case-конcт3нты внутренних И внешних
инструкций swi tch MOryr иметь одинаковые значения, при этом никаких конфлик­
тов не возникает. Например, следующий фрагмент кода вполне допустим.

switch(chl) (
case 'А': cout « "Эта д-ветвь - часть внешней switch";
switch(ch2) { // +- ВnО8евкая ИRструхци. switch.
122 Модуль З. ИНСТРУI<ЦИИ управлени~

case 'А':
cout « "Эта А-ветвь - часть внутренней switch";
break;
case '8': // _.,

break;
саБе '8': // __ .

~npocы МЯ теl<ущеro контроля - - - - - - - -


1. Какого типа должно быть выражение, управляющее инструкцией sw i tch?
2. Что происходит в случае, если результат вычисления swi tсh-выражения
совпадает с саsе-константой?

3. Что происходит, если саsе-последователъность не завершается инструк­


цией break?*

Спросим у опытного программиста

Вопрос. В каких случаях кодированu.я мноzонаnравленноzо вemвленu.я лучше исполь­


зовать i f -е 1 5 e-i f - Uлестницу", а не инструкцию 5 wi tch2'
Ответ. Используйте i f-еlsе-if-"лестиицу" в том случае, когда условия, управ­
ляющие процесс ом выбора, нельзя определить ОДЮlм значением. Рассмо­
трим, например, следующую i f -е 1 5 е- i f -последовательность.
If(x < 10) // _ ..
else Н(у > О) / / _ ••
else I f (! done) / / ' "
эту последовательность нельзя переllрограммировать с помощью инсгрук­
ции swi tch, поскольку все три условия вКJIIOчaIOТ различные перемеюwе
и различные пmы. какая переменнзя управляла бы инсгрукцией switch?
Кроме тoro, if-е1sе-if-"лестницу" придется использовать и в случае, когда
нужно проверять значения с плавающей точкой или другие объекты, 'IИПbI
которых не подходят для ИСП01IЬЭ08aI!ИЯ В swi tсh-вьrpaжении.

1. Выражение. управляющее инструкцией swi1:ch, должно иметь целочисленный или


символьный тип.

2. Если резулътзт вычисления swi tсh-выражения совпадает с саsе-константой, то вы­


полняется последователыlOСТЪ инструКЦИЙ, связанная с ЭТОЙ case-константоЙ.
З. Если са 5 е-последовательность не завершается инструкцией break, то ВЪП101ПlЯется
следующая case-последовательнос1Ъ инструкций.
С++: руководство ДМ! начинающих 123

"'ЩеЙ"'II" '_IIИМ·'. _,мм t


Г H;i~.~p;- Этот проект создает простую справочную систему, которая под-
--- . ....; сказывает синтаксис управляющих С++-инструкций_ После за-
пуска наша программа отображает меню, содержащее инструкции управления, и
переходит в режим ожидания до тех пор, пока пользователь не сделает свой вы­
бор. Введенное пользователем значение используется в инструкции swi tch для
отображения синтаксиса соответствующей инструкции_ В первой версии нашей
простой справочной системы можно получить "справку" только по i f- и s wi t -
сh-Инструкциям. Описание синтаксиса других управляющих С++-инструкuий
будет добавлено в последующих проеКтах_

ПослеАовательность Аействий
1. Создайте файл Help. срр_
2. Выполнение программы должно начинаться с отображения следующего
меню_

Справка по инструкциям:

1. if
2.switch
Выберите вариант справки:

Для реализации этого меню используйте следующую последователыюсть


инструкций.

cout « "Справка по инс'!'рукциям:\п";


cout « " 1. if\n";
cout « " 2. switch\n";
cout « "Выберите вариант справки: ";
3. Затем программа должна принять вариант пользователя.

cin » choice;
4. Получив вариант пользователя, программа применяет инструкцию swi t-
ch для отображения синтаксиса выбранной инструкции_
switch(choice)
саsе '1':
cout « "Синтаксис инструкции if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "е1sе инструкция;\п";
break;
124 Модуль З. Инструкции управления

саэе '2':
cout « "Синтаксис инструкции switch:\o\o":
cout « "switсh(выражение) (\о":
cout « " саэе константа:\о":
cout « " последовательность инструкций\о":
cout « " break:\o";
cout « " // ~ .. \o";
cout « "}\о":
break:
defau1t:
cout « "Такого варианта нет.\о";

5. Приведем полный лис'РИНГ программы Не1р. срр.

/*
Проект 3.1.

Простая справочная система.

*/

#ioc1ude <iostream>
usiog оатезрасе std:

iot maio ()
char choice:

cout « "Справка по инструкциям:\о":


cout « " 1. if\o":
cout « " 2. switch\n":
cout « "Выберите вариант справки: ":
cin » choice:

cout « "\n":

switch(choice)
саэе '1':
cout « "Синтаксис ИНСТРУКЦИИ if:\n\n":
cout « "if (условие) ин(:трукция: \0":
cout « "е1зе инструкция;\п";
С++: руководство ДЛЯ начинающих 125

break;
case '2':
cout « "Синтаксис инструкции switch:\n\n";
cout « "switсh(выражение) (\n";
cout « " case KOHcTaHTa:\n";
cout « " последовательность инструкций\n";

cout « " break; \n" ;


cout « " // ... \n";
cout « "}\n";
break;
default:
cout « "Такого варианта HeT.\n";

return О;

Вот один ИЗ возможных вариантов вьmолнения этой программы.

Справка по инструкциям:

1. i f
2. switch
Выберите вариант справки: 1

Синтаксис инструкции if:

if(условие) инструкция;

else инструкция;

ВАЖНОI

'18' 'ИК. fg;r;


в модуле 2 мы уже использовали простую форму цикла for. Здесь же мы рас­
смотрим его более детально, и вы узнаете, насколько мощным и гибким средством
программирования он является. Начнем с традиционных форм его использования.
Итак, общий формат записи цикла for для многократного выполнения одной
инструкции имеет следующий вид.

fоr(инициализация; выражение; инкремент) инструкция;

Если цикл for предназначен для многократного выполнения не одной ин­


струкции, а программноro блока, то его общий формат выглядит так.
126 МОДУЛЬ З. ИНСТРУКЦИИ управлениS1

fоr(инициализация; выражение; инкремент)


{
последовательность инструкций

Элемент инициализация обычно представляет собой инструкцию присваивания,


которая устанавливает ynpa6Л1UOlJJUЮ nepeмeн1l1JЮ ЦUЮUl равной начальному значе­
нию. эта переменная действует в качестве счетчика, который управляет ра(ютой
цикла Элемент выражение представляет собой условное выражение, в кО'Тором
тестируется значение управляющей переменной цикла. Результат этого тecrирова­
ния определяет, выполнится цикл for еще ра;} или нет. Элемент инкремент - это
выражение, которое определяет, как изменяется значение управляющей переменной
цикла после каждой итерации. Обраnпе ВНИМiUfие на то, что все эти элементы цикла
for должны отделяться точкой с запятой. Цикл for будет выполняться до тех пор,
пока вычисление элемента выражение дает истинный результат. как только это
условное выражение станет ложным, цикл завершится, а выполнение программы

продолжится с ииструкции, следующей за циклом for.


В следующей программе цикл for используется для вывода значений ква­
дратного корня, извлеченных из чисел от 1 до 99. В этом при мере управляющая
перемеtlНая цикла называется пит.

// ВЫВОД значений квадратного корня из чисел от 1 до 99.

#inc1ude <iostream>
#inc1ude <cmath>
using namespace std;

int main ()
{
int пит;
double sq_root;

for(num=l; пит < 100; пит++) (


sq_root = sqrt«double) пит);
cout « пит « " " « s~root « '\п';

return О;

в этой программе использована стандартная С++-функция sqrt (). как


разъяснялось в модуле 2, эта функция возвращает значение квадратного корня
С++: РУКОВОДСТВО М9. начинающих 127

изCBoero аргумента. Аргумент должен иметь тип double, и именно поэтому при
вызове функции sqrt () параметр пит приводится к типу double. Сама функ­
г­
ция также возвращает значение типа double. Обратите внимание на то, что в :s:
:r
Ф
программу включен заголовок <ста th>, поскольку этот заголовочный файл обе­ .;;:

спечивает подцержку функции sqrt (). '"о


Q.
Управляющая переменнЗS\ цикла for может изменяться как с положитель­
ны,' так и с отрицательным приращением, причем величина этого приращения

также может быть любой. Например, следующая программа выводит числа в диа­
пазоне от 50 до -50 с декрементом, равным 10.
// Использование в цикле for отрицательного приращеНИR.

#include <iostrearn>
using narnespace std;

int main()

int i;

for(i=50; i >= -50; i i-10) cout « i « I 1.

return О;

При выполнении программа генерирует такие результаты.

50 40 за 20 10 О -10 -20 -зо -40 -50


Важно понимать, что условное выражение всегда тестируется в начале выпол­
нения цикла for. Это значит, что если первая же проверка условия даст значение
ЛОЖЬ, код тела ЦИJUIа не выполнится ни разу. Вот пример:

for(count=10; count < 5; count++)


cout « count; // Эта инструкция не выполнится.

Этот цикл Iiикогда не выполнится, поскольку уже при входе в Hero значение его
управляющей переменной count больше пяти. Это делает условное выражение
(count < 5) ложным с самого начала. Поэтому даже одна итерация этоro цикла
не будет выполнена.

Вариации на тему цикла for


Цикл for - одна из наиболее гибких инструкций в С#, поскольку она по­
зволяет получить широкий диапазон вариантов ее применения. Например, для
128 Модуль З. ИНСТРУКЦИИ управления

управления циклом for можно использовать несколько переменных. Рассмо­


трим следующий фрагмент кода.

for(x=O, y=lO; х <= у; ++х, --у) // Сразу несколько


/ / упрааna:IЦИХ nepeкellJWX.
cout « х « ' , « у « '\п';

Здесь запятыми отделяются две инструкции инициализации и два инкремеНТНbJХ


выражения. Это делается для того, чтобы компилятор "понимал", что существует
две инструкции инициализации и две инструкции инкремента (декремента). В
С++ запятая представляет собой оператор, I<ОТОрЫЙ, по сути, означает "сделай
это и то". Другие применения оператора "запятая" мы рассмотрим ниже в этой
книге, но чаще всего он используется в цикле for. В разделах инициалИЗ311ИИ и
инкремента цикла for можно использовать любое количество инструкции, но
обычно их число не превышает двух.

Спросим у опытного программиста

Вопрос. Поддерживает ли С++, помимо !щrt(). другие мате..mтuческие функции?


Ответ. Даl Помимо функции sqrt (), С++ поддерживает широкий набор мате­
матических функций, например sin () , cos (), tan (). 10g (), cei1 ()
и floor ( ). Если вас интересует программирование в области матема1'И­
КИ, вам стоит ознакомиться с составом С++-библиотеки математических
функций. Это сделать нетрудно, поскольку все С++-компиляторы их под­
держивают, а их описание можно найти в документации, прилагаемой к
вашему компилятору. Помните, что все математичеСЮlе функции требуют
включения в программу заголовка <ста th>.

Условным выражением, которое управляет циклом f о r, может быть любое допу­


стимое С++-выражение. При этом оно не06язательно должно включать управляю­
щую переменную ЦИЮIа. В следующем примере цикл будет выполняться до тех пор,
пока функция rand () не сгенерирует значение, которое превышает число 20 000.
/*
Выполнение цикла продолжается здесь до тех пор, пока

случайное число не превысит 20000.


*/

tinclude <iostream>
tinclude <cstdlib>
using namespace std;
С++: PYI<OBOACTBO ДЛ91 начинающих 129

int main ()
{
int i;
int r;
• 10:
:s:
:1:
Ф
<
са

R
5-
:s:
r = rand () ; ~
~
>-
е-
u
for(i=O; r <= 20000; i++) // В этом уcnо.вом awp. . . вии .:1:
:S:
/ / уа:рaJШ-nцe- переllеlUl&Jl ЦИIUI&
// не ИCDоnъsуетс •.
r = rand () ;

cout « "Число равно " « r «


" Оно сгенерировано при попытке " « i « " . " ,.

return О;

ВОТ ОДИН из возможных результатов выполнения этой программы.

Число равно 26500. Оно сгенерировано при попытке з.

При каждом проходе ЦИlUIа с помощью функции rand () генерируется новое


случайное число. Когда оно превысит число 20 000, условие продолжения ЦИlUIа
станет ложным, и ЦИIUI прекратится.

Отсутствие элементов в определении цикла


в С++ разрешается опустить любой элемент заголовка цикла (инициализа­
ция, условное выражение, инкремент) или даже все сразу. Например, мы хотим
написать ЦИIUI, который должен выполняться до тех пор, пока с lUIавиатуры не
будет введено число 123. Вот как выглядит такая программа. .
// Цикл for без инкремента в заголовке.

#include <iostream>
using паmеsрасе std;

int main ()
130 МОДУЛЬ З. ИНСТРУJ<ЦИИ управлени~

int Х;

for(x=O; х != 123; { // ~ Зl.zpахе_ JUUCPeKeR~a He~.


cout « "Введите число: ";
ci'n » Х;

return О;

Здесь в заголовке цикла for отсутствует выражение инкремента. Это означает,


что при каждом повторении цикла выполняется только оДJЮ действие: значение
переменной х сравнивается с числом 123. Но если ввести с клавиатуры число 123,
условное выражение, проверяемое в цикле, станет ложным, и цикл завершится.

Поскольку выражение инкремента в заголовке цикла for отсутствует, управля­


ющая переменная цикла не модифицируется.
Приведем еще один вариант никла for, в заголовке которого, как показано в
следующем фрагменте кода, отсутствует раздел инициализации.

х = О; / / +- Переиенная: х инициanизируе~CJI вне ЦИlCJlа.

for ( ; х<10;

cout « х « I ,
1.

++Х;

Здесь пустует раздел инициализации, а управляющая переменная 1< инициализи­


руется до входа о цикл. К размещению выражения инициализации за пределами
цикла, как правило, прибегают только в том случае, когда начальное значение
генерируется сложным процессом, который неудобно поместить в определение
цикла. Обратите внимание также на то, что в :пом при мере элемент инкремента
цикла for расположен не в заголовке, а в теле цикла.

Бесконечный цикл for


Оставив пустым условное выражение цикла for, можно создать бесконечuый
цикл (цикл, К01'ОрЫЙ никогда не заканчивается). Способ создания такого цикла
показан на примере следующей конструкции цикла for.
for(;;)

// ...
С++: руководство ДЛЯ начинающих 131

Этот ЦИКЛ будет работать без конца. Несмотря на существование некоторых за­
дач программирования (например, KOMaMHbIX процессоров операционных си­
стем), которые требуют наличия бесконечного цикла, большинство "бесконеч­
ных циклов" - это просто циклы со специальными требованиями к завершению.
Ближе к концу этого модуля будет показано, как завершить ЦИКЛ тaкoro типа.
(Подсказка: с помощью инструкции break.)

Циклы без тела


в С++ тело цикла for может быть пустым, поскольку в С++ синтаксически
допустима пустая инструкция. "Бестелесные" циклы не только возможны, но за­
частую и полезны. Например, в с.ледующеЙ программе один из таких циклов ис­
пользуется для суммирования чисел от 1 до 10.
// Пример использования цикла for с пустым телом.

#include <iostream>
#include <cstdlib>
using namespace std;

int main ()

int i;
int зит О;

// Суммируем числа от 1 до 10.


for(i=l; i <= 10; sum += i++) ; // ~ "БеСlJ.lеnесRЫЙ" ЦИ1CJI.

cout « "Сумма чисел равна " « sum;

return О;

Результат выполнения этой программы выглядит так.

Сумма чисел равна 55


Обратите внимание на то, что процесс суммирования полностью реализуется
в заголовке J:lНСТРУКЦИИ f О r, и поэтому в теле цикла нет никакой неоБХОДИМОСnt.
Обратите также в}lИмание на следующее выражение инкрементирования.

зит += i++
132- Модуль З. ИНСТРУI(ЦИИ управлени~

Не стоит опасаться инструкций, подобных этой. Они часто встречаются в


профессиональных С++-программах. Если разБИТI> такую инструкцию на части,
то она окажется совсем несложной. Русским языком выполняемые ею действия
можно выразить так: "При6авим к значению переменной sum значение перемен­
ной i и сохраним результат сложения в переменной эит, а затем инкремеllТИ­
руем переменную i ". Другими словами, приведенную выше инструкцию можно
переписать так.

sum = эит + i;
i++;

Объявление управляющей переменной цикла


в заrоловке инструкции for
Зачастую переменная, которая управляет циклом for, нужна только для это­
го цикла и ни для чего больше. В этом случае такую переменную можно оБЪЯflИТЬ
прямо В разделе инициализации заголовка инструкции for. Например, в следу­
ющей программе, которая вычисляет как сумму чисел от 1 до 5, так и их факто­
риал, управляющая перемеtlНая цикла i объявляется в самом заголовке инструк­
ции for.
// Объявление управляющей переменной цикла в заголовке
// инструкции for.

#include <iostream>
using патеэрасе stdi

int main () {
int эит = О;
int fact = 1;

// Вычисляем факториал чисел от 1 до 5.


for(int i = 1; i <= 5; i++) { // ПеремеВВaR i оБЪRВnена
// в самОК цикnе Eor.
эит += i i / / Переменная i изв(~стна в рамках цикла,
fact *= i;

// но здесь она уже не существует.

cout « "Сумма чисел равна" « sum « "\п";


С++: РУКОВОДСТВО дл9t начинающих 1ЗЗ

cout « "Факториал равен " « fact;

return О;

При выполнении эта программа генерирует такие результаты.

Сумма чисел равна 15


Факториал равен 120
При объявлении переменной в заголовке цикла важно помнить следующее:
эта переменная известна только в рамках инструкции for. В этом случае гово­
рят, что область видимости такой переменной ограничивается циклом for. Вне
его она прекращает свое существование. Следовательно, в предыдущем примере
переменная i недоступна вне цикла f о r. Если же вы собираетесь использовать
управляющую переменную цикла в другом месте программы, ее не следует объ­
являть в заголовке цикла f о r.

в ранних версиях С++ переменная, объявленная В разделе инициа­

Нозаметку~ лизации заголовка инструкции fo r, была доступна и вне цикла fo r.


Но в результате процессастандартизации положение вещей измени-
лось. В настоящее время стандарт ANSI/ISO ограничивает область
ВИДИМОСТИ такой переменной рамками цикла for. Однако в неко­
торых КОМIШЛЯторах это требование не реализовано. Поэтому вам
имеет смысл прояснить этот момент в своей рабочей среде.

~просы ДЛЯ текущего КОНТРОЛЯ --------

t. Могут ли некоторые разделы заголовка инструкции for быть пустыми?

2. Покажите, как на основе инструкции for можно создать бесконечный


цикл?

3. Какова область видимости переменной, объявленной в заголовке ин­


струкции f о r? •

1. да. Пустыми MOryr быть все три раздела заголовка инструкции for: ииициалиэация,
выражение и инкремент.

2. for ( ; ; )
3. Область видимости переменной, объявленной в заголовке инструкции for, ограничи­
вается рамками цикла for. Вне его она неизвестна.
134 Модуль З. ИНСТРУI<ЦИИ управления

ВАЖНОI

Познакомимся с еще одной инструкцией циклической обработки данных: wh-


ile. Общая форма цикла while имеет такой вид:
whilе(выражение)инструкция;

Здесь под элементом ИНСТРУКЦИЯ понимается либо одиночная инструкция,


либо блок инструкций. Работой цикла управляет элемент выражение, который
представляет собой любое допустимое С++-выражение. Элемент ИНСТРУКЦИЯ
выполняется до тех пор, Пока условное выражение возвращает значение ИСТИ­
НА. Как только это выражение становится ложным, управление передается ин­
струкции, которая следует за этим циклом.

Использование цикла wh i 1 е демонстрируе"гся на примере следующей неболь­


шой ПРОll>аммы. Практически все компиляторы поддерживают расширенный
набор символов, который не ограничивается символами АSСП. В расширенный"
набор часто включаются специальные символы и некоторые буквы из алфавитов
иностранных языков. АSСII-символы исполь.зуют значения, не превышающие
число 127, а расширенный набор символов - значения из диапазона 128-255.
При выполнении этой программы выводятся все символы, значения которых
лежат в диапазоне 32-255 (32 - это код пробела). Выполнив эту программу, вы
должны увидеть ряд очень интересных символов.

/* Эта про грамма выводит все печатаемые символы,


включая расширенный набор символов, если
таковой существует.

*1

#include <iostrearn>
using narnespace std;

int main ()
{
unsigned char ch;

ch = 32;
while(ch) ( 1/ Начало цикла while.
cout « ch;
ch++;
С++: руководство ДЛЯ начинающих 135

return О;

Рассмотрим whilе-выражение из предыдущей программы. Возможно, вас уди­


вило, что оно состоит всего лишь из одной переменной ch. Но "ларчик" открывается
просто. Поскольку переменная ch имеет эдесь тип unsigned char, она может со­
держать значения от О до 255. Если ее значение равно 255, то после инкрементирооа­
ния оно "сбрасывается" в нуль. Следовательно, факт равенства значения переменной
ch нулю служит удобным способом завершить whilе-цикл.
Подобно циклу for, условное выражение проверяется при входе в цикл wh-
ile, а это значит, что тело цикла (при ложном результате вычис~ения УСЛОВJЮI'О
выражения) может не выполниться IШ разу. Это свойство цикла устраняет не­
обходимость отдельного тестирования до начала цикла. Следующая программа
выводит строку, состоящую из точек. Количество отображаемых точек равно зна­
чению, которое вводит пол ьзовате.IJ ь. Программа не позволяет "рисовать" строки,
если их длина превышает 80 символов. Проверка на допустимость числа выводи­
мых точек вьmолняется внутри условного выражения ЦИlUlа, а не снаружи.

#include <iostream>
using namespace std;

int main ()

int len;

cout « "Введите длину строки (от 1 до 79): ";


сiл » lел;

while(len>O && len<80)


cout « '.';
len--;

rеturл О;
}
Если значение переменной len не "вписывается" в заданный диапазон
(1-79), то цикл while не выполнится даже один раз. В противном случае его
тело будет повторяться до тех пор, пока Зliачение переменной len не станет
равным нулю.
136 Модуль З. ИНСТРУI<ЦИИ управления

Тело whilе-цикла может вообще не содержать ни одной инструкции. Вот


пример:

while(rand() != 100)
Этот цикл выполняется до тех пор, пока случайное число, генерируемое функци­
ей rand (), не окажется равным числу 100.

ВАЖНОI
&. Ilиl(Adn-wЬjlе

Настало время рассмотреть последний из С++-циклов: do-while. В отличие
от циклов for и while, в которых условие проверяется при входе, цикл do-wh-
ile проверяет условие при выходе из цикла. Это значит, что цикл do-while
всегда выполняется хотя бы один раз. Его общий формат имеет такой вид.
do {
инструкции;

} while (выражение);

Несмотря на то что фигурные скобки необязательны, если элемент ИНСТР~IlЩИИ


состоит только из одной инструкции, они часто используются для улучшения
читабельности конструкции do-whi le, не допуская тем самым путаницы с ци­
клом while. Цикл do-while выполняется до тех пор, пока остается истинным
элемент выражение, который представляет собой условное выражение.
В следующей программе цикл do-while 8ыполняется до тех пор. пока поль­
зователь не введет число 100.
#include <iostrearn>
using narnespace std;

int rnain ()
{
int пит;

do { // цихп do-while всегда выпоnняеТСR


// ХОТИ бы один раз.
cout « "Введите число (100 - для выхода): ";
cin » пит;
while(nurn != 100);

return О;
С++: РУКОВОДСТВО дl\Я начинающих 137

Используя цикл do-while, мы можем еще более усовершенствовать про­


грамму "Угадай магическое число". На этот раз программа "не выпустит" вас из
цикла угадывания, пока вы не угадаете это число.

// Про грамма "Угадай магическое число":


// З-е усовершенствование.

#include <iostream>
#include <cstdlib>
using паmеsрасе std;

int main ()
{
int magic; // магическое число
int guess; // вариант пользователя

magic = rand(); // Получаем случайное число.

do (
cout « "Введите свой вариант магического числа: ";
cin » guess;
if(guess == magic)
cout « "** Правильно ** ".,
cout « magic «
" и есть то самое магическое число.\п";

else
cout « " ... Очень жаль, но вы ошиблись.";
if(guess > magic)
cout «
" Ваше число больше магического.\п";
else cout «
" Ваше число меньше магического.\п";

while(guess != magic);

rеturп О;

вот как выглядит один ИЗ возможных вариантов выполнения этой программы.


138 Модуль З. ИНСТРУI(ЦИИ управлени~

Введите свой вариант магического числа: 10


... Очень жаль, но вы ошиблись. Ваше число меньше магического.

Введите свой вариант магического чис~а: 100


-... Очень жаль, но вы ошиблись. Ваше число больше магического.
Введите свой вариант магического числа: 50
... Очень жаль, но вы ошиблись. Ваше число больше магического.

Введите свой вариант магического числа: 41


** Правильно ** 41 и есть то самое магическое число.

И еще. Подобно циклам for и while, тело цикла do-while может быть пу­
стым, но этот случай редко применяется lIa практике.

~просы ДII<;t текущего контроля ________


1. Какое основное различие между циклами while и do-while?
2. Может ли тело цикла while быть пустым?·

Спросим у опытного программиста


-------------
Вопрос. Уt4итыва.я "tlрождеllНУЮ" щбкосmь всех С++-ЦUКЛ08, какай критерий CJlf'-
дует uсnальзotю1Л/, при выборе цикла? ДРУlими словами, ка" выбрать ЦИКЛ,
lIаи60лее подходящий для данной конкретной задачи?

Ответ. Используйте цикл for ДJ1Я выполнеНIIЯ известного количества итераций.


Цикл do-while пuдойдет для тех ситуаЩfЙ. когда неоБХОJDlМО, чтобы тело
цикла выподннлось хотя бы ОДИН раз. llllКЛ wh i le лучше всего использовать
в случаях, когда его тело должно повторi1ТЬСЯ неизвестное количество раз.

l,iЩ·МSЕ,,меовеpuJВJlC1В08ОНИrмm,.вQЮICi)Й
системыС++

Help2. срр Этот проект - попытка усовершенствовать справочную систему


С++, которая была создаиа в проекте 3.1. В этой версии добав­
лен синтаксис для циклов for, while и do-while. Кроме того, эдесь проверя-

1. В цикле while условие проверяется при входе, а в цикле do-while - при выходе.
Это значит, что цикл do-wh i 1 е всегда выполняется хотя бы ОДЮI раз.
2. Да, тело цикла while (как и любоl·О другого С++-цикла) может быть пустым.
С++: руководство ДЛ~ начинающих 139
................................................................................................................................

ется значение, вводимое пользователем при выборе uарианта меню, и с помощью


цикла do-while реализуется прием только допустимого варианта. Следует от­
метить, что для обеспечения функционирования меню цикл do-wh i 1 е применя­
ется очень часто, поскольку именно при обработке меню необходимо, чтобы цикл
ВЫПОЛIiИЛСЯ хотя бы один раз.

Последовательность действий
1. Создайте файл Help2 . срр.
2. Измените ту часть программы, которая отображает команды меню, причем
так, чтобы был использован цикл do:....while.
do
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";
cout « " 2. switch\n";
cout « " З. for\n";
cout « " 4. whilе\л";
cout « " 5. dо-whilе\л";
cout « "Выберите вариант справки: ";

cin » choice;

while ( choice < '1' I I choice > '5');


3. Расширьте инструкцию swi tch, включив реализацию вывода синтаксиса
по циклам for, while и do-while.
switch(choice)
case '1':
cout « "Инструкция if:\л\л":
cout « "if(условие) инструкция;\л":
cout « "else инструкция:\л":
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) {\л":
cout « " case константа:\л":
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \п":
cout « "}\п":
140 МОДУЛЬ З. ИНСТРУКЦИИ управления

break;
case 131:
cout « "Инструкция for:\n\n";
cout « "fоr(инициалиэация; условие; инкремент)";
cout « 11 инструкция;\п";
break;
case 141:
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case 151:
cout « "Инструкция do-while:\n\n";
cout « "do {\n";
cout « " инструкция;\п";
cout « "} while (условие);\п";
break;

Обратите внимание на то, что в этой версии swi tсh-ИНСТРУКЦИИ отсут­


ствует ветвь defaul t. Поскольку цикл по обеспечению работы меню ('а­
рантирует ввод допустимого варианта, то для обработки некорректных flа­
риантов инструкция defaul t больше не нужна.
4. При ведем полный текст программы Help2. срр.
/*
Проект 3.2.

Усовершенствованная версия справочной системы С++,


в которой для обработки вариантов меню используется
цикл do-while.
*/

#include <iostream>
using namespace std;

int main ()
char choice;

do {
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";
С++: PYI<OBOACTBO ДЛЯ начинающих 141

cout « " 2. switch\n";


cout « " З. for\n";
cout « " 4 • while\n";
cout « " 5. do-while\n";
cout « "Выберите вариант справки: " ,.

cin » choice;

while ( choice < '1' I I choice > '5');

cout « "\п\п";

switch(choice)
саsе '1':
cout « "Инструкция if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) I\п";
cout « " case KOHcTaHTa:\n";
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \п";
cout « "}\п";
break:
case '3':
cout « "Инструкция for:\n\n";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкuия;\п";
break:
case '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case '5':
cout « "Инструкция do-while:\n\n":
cout « "do {\n":
cout « " инструкция;\п";
142 МОДУЛЬ З. ИНСТРУ1<ЦИИ управлени~
............................................................................................................................................................

cout « "} while (услоВи~);\п";


break;

return О;

Спросим у OnbITHoro проrраммиста

Вопрос. Ра1tьше было nоказаuо, 'tтo nеремен.ную можно объявить в разделе uнициа­
лuзацuи ЗOlоловка цикла for. Можно ли оБЬSUJJUlmь neре.меНII.ые внутри лю­
бых других управляющux С + + -инструкций?

Ответ. Да. В С++ можно объявить nepeMeHH)lO в условном выражении if- ИJПI
sw i tсh-ииструкции, в УСЛОВНОМ выражеllИИ цик.ла whi le либо в разделе
инициализации цикла for. Область видимости переменной, объявлеююй
в ОДНОМ ИЗ таких мест. ограничена блоком кода, управляемым соответ·
ствующей инс.трукциеЙ.

Вы уже видели "ример объявления переменной в цикле for. Теперь рас­


смотрим пример uбъявлеlfИЯ перемеююй в условном выражеюш if-ин­
струкции,

if (int х = 20)
х = х - У;

Н(х > 10) У О;

Здесь в if-ЮiСТРУКЦИИ объявляется перемеиная х, которой присваивает­


ся число 20, ПОСКОJJЬКУ результат ПРИС\lаиван~ (20) рассматривается как
значение ИСТИ НА, то будет вьmолнеl1 блок' кода, заключенный в фигур­
ные скобки. Так как область видимости перемеllНОЙ, объявленной в услов­
ном выражении i f-ИнстрYJЩИИ, ограничена блоком кода, управляемым
,пой инструкцией, то перемениая х неизвестна вне зтого if-блока.

Как упомииалОСЬ в разделе, посвященном циклу f or, В раэных компилято­


рах по-разному реализовано требование uграничения доступа к перемен­
ной, объявленной в управляющей инструкции, рамками этой инструкции.
Поэтому, хотя D стандарте ANSI/ISO оговорено, что областъ видимос"..
такой переменной должна бытъ ограничена рамками соответствующей
управляющей инструкции, этот MOMem' необходимо уточнить в докумен­
Т'dЦИИ, ПРИЛaI'аемой к вашему компилятору.

Большинство про грамм истов не объявляет переменные в управляющих


инструкциях, за исключением цикла [or. И в самом деле, объявление
перемеНIIОЙ в дрynlХ инструкциях - довольно спорный момент, и МНОЛfе
прогр,lммисты считают это "дурным 1'О/ЮМ· программирования.
С++: руководство ДМI начинающих 143

ВАЖНО!

ДМt выхода из цикла


с ПОМОЩЬЮ инструкции brea k можно организовать немедленный ВЫХОД из
цикла, опустив выполнение кода, оставшеrocя в его теле, 11 проверку условного

выражения. При обнаружении внутри цикла инструкции break цикл заверша­


ется, а управление передается инструкции, следующей после цикла. Рассмотрим
простой пример.

iinclude <iostream>
using namespace std;

int main ()

int t i

// Цикл работает для значений t от О до 9, а не до 100!


for(t=O; t<100; t++)
if(t==10) break; 1/ При t = О цихп for npехращаетс •.
cout « t « I 1 ;

return О;

Результаты выполнения этой программы таковы.

О 123 4 5 б 7 8 9
как подтверждают эти результаты, программа выводит на экран числа от О
до 9, а не до 100, поскольку инструкция break при значении t, равном 10, обе­
спечивает немедленный выход из цикла.
При использовании вложенных циклов (т.е. когда один цикл включает дру­
гой) инструкция break обеспечивает выход только 113 наиболее глубоко вложеll­
ного цикла. Рассмотрим пример.

#include <iostream>
using namespace std;

int main ()
144 МОДУЛЬ З. ИНСТРУКЦИИ управления

int t, count;

for(t~O; t<10; t++) (


count = 1;
for(ii) (
cout « count « ' ';
count++i
if(count==10) break;

cout « '\п';

return О;

Вот какие результаты генерирует эта программа.

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
1 2 3 4 5 б 7 8 9
1 2 3 4 5 б 7 8 9
1 2 3 4 5 6 7 8 9
Как видите, эта программа вьшодит на экран числа от 1 до 9 десять раз. Каж­
дый раз, когда во внутреннем цикле встречается инструкция break, управление
передается во внешний ЦИКЛ for. Обратите внимание на то, что внутренняя for-
инструкция представляет собой бесконечный ЦИКЛ, для завершения которого ис­
пользуется инструкция break.
Инструкцию break можно использовать для завершения любого цикла. Как
правило, она предназначена для отслеживания специального условия, при насту­

плении которого необходимо немедленно прекратить цикл (чаще всего в таких


случаях используются бесконечные циклы).
И еще. Если инструкция break применяется в инструкции 5witch, которая
вложена в некоторый ЦИКЛ, то она повлияет только на данную инструкцию 5W i-
tch, а не на цикл, Т.е не обеспечит немедленный выход из этого цикла
С++: PYI<OBOACTВO ДЛЯ начинающих 145

ВАЖНОI
. . ИСПОАЬЗQвание иwструк'",ии"
continue
в С++ существует средство "досрочного" выхода из текущей итерации цикла.
Этим средством является инструкция continue. Она принудительна выполня­
ет переход к следующей итерации, опуская выполнение оставшегося кода в те­
кущей. Например, в следующей программе инструкция continue используется
для "ускоренного" поиска четных чисел в диапазоне от О до 100.
linclude <iostream>
using narnespace std;

int main ()
(
int Х;

for(x=O; х<=100; х++) (


if(x%2) continue; // Еcnи значение х Rече~вое,
11 сразу перехоДJDC на cneД)'ltlQll)
1/ итер8ЦIШ ЦИ1Ш&.
cout « х « I 1;

return О;

Здесь выводятся только четные числа, поскольку при обнаружении нечетного


числа происходит преждевременный переход к следующей итерации, и сои t-
инструкция опускается. (Вспомним, что с помощью оператора "%" мы получаем
остаток при выполнении целочисленного деления. Это значит, что при нечетном
х остаток от деления на два будет равен единице, которая (при оценке в качестве
результата условного выражения) соответствует истинному значению. В про­
тивном случае (при четном х) остаток от деления на доа будет равен нулю, т.е.
значению ложь.)
В циклах while и do-while инструкция continue передает управление
непосредственно инструкции, проверяющей условное выражение, после чего
циклический процесс продолжает "идти своим чередом". А в цикле for после
выполнения инструкции continue сначала вычисляется инкрементное выра­
жение, а затем - условное. И только после этого циклический процесс будет
продолжен.
146 М·одуль З. ИНСТРУI(ЦИИ управления

~ПРОСЫ ДIIЯ текущего КОНТРОЛЯ --------


1. Что происходит при выполнении инструкции break в теле цикла?
2. В чем состоит назначение инструкции continue?
З. Если цикл включает инструкцию switch, то приведет ли к завершению
этого цикла инструкция break, принад.71ежащая инструкции sw i tch?·

н~iрЗ ~ срр -1, Этот проект завершает построение справочной системы С++ .
. _-_._- ..! Настоящая версия пополняется описанием синтаксиса инструк­
ций break, continue и goto. Здесь пользователь может делать запрос уже не
только по одной инструкции. Эта возможность реализована путем использова­
ния внешнего цикла, который работает до тех пор. пока пользователь не введет
для выхода из программы команду меню q.

Последовательность действий
1. Скопируйте файл Help2 . срр в новый файл и назовите его НеlрЗ. срр.

2. Заключите весь программный код в бесконечный цикл for. Обеспечые


выход из этого цикла с помощью ИНСТРУ1ЩИИ break при вводе пользова­
телем команды меню q. Прерывание ЭТОI"О цикла приведет к завершению
всей программы в целом.

З. Измените цикл меню следующим образом.


do {
cout « "Справка по инструкциям:\п";
cout « " 1. if\n";

1. При выполнении инструкции ЬгеаК в теле цикла происходит немедлеЮfOе эавершение


этого Wfкла. ВЬПIOJПlение программы продолжается с инструкции, следующей cpaJY
после цикла.

2. Инструкция continue npинудительно выполняет переход К следующей итерации ЦИК­


ла, опуская выполнение оставшеrося кода в текущей.

3. Нет, если инструкция break применяется в инструкции switch, которая вложена в не­
КОТОРЫЙ ЦИХЛ. ТО она повлияет только на данную инструкцию ~witch. а не на цикл.
С++: PYI<OBOACTBO ДМI начинающих 147

cout « " 2 . switch\n";


cout « " 3 . for\n";
cout « " 4 . while\n";
cout « " 5 . do-while\n";
cout « " б. break\n";
cout « " 7 . continue\n";
cout « " 8. goto\n";
cout « "Выберите вариант справки (q для выхода) : " , .
cin » choice;
while( choice < '1' II choice> '8' && choice !=
'q') i

Обратите внимание на то, что меню сейчас включает дополнительные ин­


струкции: break, continue и goto. Кроме того, 8 качестве допустимой
команды меню принимается вариант q.
4. Расширьте инструкцию swi tch, включив реализацию вывода синтаксиса
по инструкциям break, continue и goto.
саэе '6':
cout « "Инструкция break:\n\n"i
cout « "break;\n";
break;
case '7':
cout « "Инструкция continue:\n\n";
cout « "continue;\n";
break;
саэе '8': ~
cout «
cout «
"Инструкцияgoto:\n\n";
"goto MeTKai\n"i I
u

1
break;
5. Вот как выглядит полный текст программы Help3. срр.
/* 5

I
Проект 3.3.

Окончательная версия справочной системы С++, которая

1
. позволяет обрабатывать несколько запросов пользователя.

*/

#include <iostream>
using namespace stdi
148 Модуль З. ИНСТРУКЦИИ управления

int rnain ()
char choice;

for(;;)
do {
cout « "Справка по ииструкциям:\п";
cout « " 1. if\n";
cout « " 2 . switch\n";
cout « " З. for\n";
cout « " 4 • while\n" ;
cout « " 5. do-while\n";
cout « " 6. break\n" ;
cout « " 7 • continue\n";
cout « " 8 . goto\n";
cout « "Выберите вариант еправки (q для выхода) : " ,.
cin » choice;
while( choice < '1' II choice > 'В' && choice !=
'q' ) ;

if(choice == 'q') break;

cout « "\n\n";

switch(choice)
case '1':
cout « "ИНСТРУ1Щия if:\n\n";
cout « "if(условие) инструкция;\п";
cout « "else инструкция;\п";
break;
case '2':
cout « "Инструкция switch:\n\n";
cout « "switсh(выражение) {\n"i
cout « " case KOHcTaHTa:\n"i
cout « " последовательность инструкций\п";
cout « " break;\n";
cout « " // ... \n";
cout « "}\n";
breaki
С++: РУКОВОДСТВО ДI\~ начинающих 149

сазе '3':
cout « "Инструкция for:\n\n";
cout « "fоr(инициализация; условие; инкремент)";
cout « " инструкция;\п";
break;
сазе '4':
cout « "Инструкция while:\n\n";
cout « "whilе(условие) инструкция;\п";
break;
case '5':
cout « "Инструкция do-while:\n\n";
cout « "do {\n";
cout « " инструкция;\п";
cout « "} while (условие);\п";
break;
сазе '6':
cout « "Инструкция break:\n\n";
cout « "break;\n";
break;
сазе '7':
cout « "Инструкция continue:\n\n";
cout « "continue;\n";
break;
case '8':
cout « "Инструкция goto:\n\n";
cout « ~goto MeTKa;\n";
~
]1

break;
I
u
'3:
О
cout « "\n"; :о:

§
8.
с
u

I
return О;

6. Вот как выглядит один ИЗ возможных вариантов выполнения этой про­ с

граммы.
!r
:о:
CD

Справка

1. if
2. switch
по инструкциям:

1
з. for
150 МОДУЛЬ З. ИНСТРУКЦИИ управления

4. while
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): 1

Инструкция if:

if(условие) инструкция;
e1se инструкция;

Справка по инструкциям:

1. if
2. switch
З. for
4. whi1e
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): б

Инструкция break:

break;

Справка по инструкциям:

1. if
2. switch
З. for
4. while
5. do-while
б. break
7. continue
8. goto
Выберите вариант справки (q для выхода): q
С++: РУКОВОДСТВО ДМI начинающих 151

ВАЖНОI
G:
:s:
:J:
Как было продемонстрировано на примере одной из предыдущих программ, ф
<
ID
один цикл можно вложить в другой. Вложенные циклы используются для реше­
ния задач самого разного профиля и часто используются в программировании.
8.
~
Например, в следующей программе вложенный цикл for позволяет найти мно­ :s:
~
жители для чисел в диапазоне от 2 до 100.
/* ~u
:J:
Использование вложенных циклов для отыскания s:
множителей чисел в диапазоне от 2 до 100
*/

#include <iostream>
using namespace std;

int main ()

for(int i=2; i <= 100; i++)


cout « "Множители числа" « i « ". ";

for(int j = 2; j < i; j++)


if«i%j) == О) cout « j « " ";

cout « "\n";

return О;

Вот как выглядит часть результатов, генерируемых этой программоЙ.

Множители числа 2:
Множители числа З:
Множители числа 4: 2
Множители числа 5:
Множители числа 6: 2 3
Множители числа 7:
Множители числа 8: 2 4
Множители числа 9: 3
152 Модуль З. ИНСТРУf<ЦИИ управления

Множители числа 10: 2 5


Множители числа 11:
Множители числа 12: 2 3 4 6
Множители числа 13:
Множители числа 14: 2 7
Множители числа 15: 3 5
Множители числа 16: 2 4 8
Множители числа 17:
Множители числа 18: 2 3 6 9
Множители числа 19:
Множители числа 20: 2 4 5 10
В этой программе внешн ий ~икл изменяет свою управляющую переменную i
от 2 до 100. Во внутреннем цикле тестируются все числа от 2 до значения пере­
мен ной i и выводятся те из них, на которые без остатка делится значение пере­
менной i.

ВАЖНОI
",.• 14WCTPVK' ,ИЯ got-o
Инструкция goto - это С++-инструкция безусловного перехода. При ее вы­
полнении управление программой передается и НСТРУIЩИИ, указанной с помощью
метки. Долгие годы эта инструкция находилась в немилости у программистов,
поскольку способствовала, с их точки зрения, созданию "спагетти-кода". Однако
инструкция goto по-прежнему используется, 11 иногда даже очень эффективно.
В этой книге не делается попытка "реабилитаиии" законных прав этой инструк­
ции в качестве одной из форм управления программой. Более того, необходимо
отметить, что в любой ситуации (о области программирования) можно обойтись
без инструкции goto, поскольку она не является элементом, обеспечивающим
полноту описания языка программирования. Вместе с тем, в определенных ситу­
ациях ее использование может быть очень полезным. В этой книге было решено
ограничить использование инструкции goto рамками этого раздела, так как, по

мнению большинства программистов, она вносит в программу лишь беспорядок


и делает ее практически нечитабелыюЙ. Но, поскольку использование инструк­
иии goto в некоторых случаях может сделать намерение программиста яснее, ей
стоит уделить некоторое внимание.

Инструкция goto требует наличия в программе метки. Метка - это действи­


тельный в С-Н- идентификатор, за которым поставлено двоеточие. При выполне­
нии инструкции goto управление программой передается инструкции, указан­
ной с помощью метки. Метка должна находиться D одной функции С инструк-
С++: руководство ANil ночи'ноющих 153

цией goto, которая ссылается на эту метку. Например, с помощью инструкции


goto и метки можно организовать следующий цикл на 100 итераций.
х = 1;
loopl:
х++;

if(x < 100) goto loop1; // 8wпопнение проrpaииw будет


// ПРОД0П89но с метки loopl.
Иногда инструкцию goto стоит использовать для выхода из глубоко вложен­
ных инструкций цикла. Рассмотрим следующий фрагмент кода.

for ( ... ) {
for ( ... )
while ( ... )
if( ... ) goto stop;

stop:
cout « "Ошибка в программе.\п";

Чтобы заменить инструкцию goto, пришлось бы выполнить ряд дополнитель­


ных про верок. В данном случае инструкция goto существенно упрощает про­
граммный код. Простым Ilрименением инструкции break здесь не обошлось, по­
скольку она обеспечила бы выход лишь из наиболее глубоко вложенного цикла.

. ..1еС Т А 4 Я C auOKOHrp04S ПО МОАУ'Ю 3


1. Напишите программу, которая считывает с клавиатуры символы до тех
пор, пока не будет введен символ "$". Организуйте в программе подсчет
количества введенных точек. Результаты подсчета должны выводиться по
окончании выполнения программы.

2. Может ли кодовая последовательность одной саsе-ветви в инструкции


swi tch переходить в последовательность инструкций следующей case-
ветви? Поясните свой ответ.

3. Представьте общий формат if-еlsе-if-"лестницы".


4. Рассмотрите следующий фрагмент кода.
154 МОДУЛЬ З. Инструкции управления

if(x < 10)


i f (у > 100) (;
if (!done) х := Z;
else у = Z;

else cout « "Ошибка!": // С 'какой if-инструкцией,


// СВЯЗdна эта еlsе-ветвь?

Вопрос: с какой if-инструкцией связана последняя еlsе-ветвь?

5. Напишите fоr-инструкцию для цикла, который считает от 1000 до О с ша­


rOM -2.
6. Допустим ли следующий фрагмент кода?

for(int i = О; i < пит; i++)


sum += i;

count = i;
7. Поясните назначение инструкции break.
8. Что будет отображено на экране после выполнения инструкции break в
следующем фрагменте кода?

for(i = О; i < 10; i++)


while (running) {
if (х < у) break;
// ...
cout « "После инструкции whi~e.\n";

cout « "После инструкции for.\Il";


9. Что будет выведено на экран при выполнении следующего фрагмента кода?
for(int i = О; i < 10; i++) {
cout « i « " ";
if(! (1%2» continue;
cout « "\п";

10. Инкрементное выражение в цикле for не обязательно должно изменять


управляющую переменную цикла на фиксированную величину. Псременная
цикла может изменяться произвольным образом. С учетом этого замечания
напишите программу, которая использует ЦИКЛ for, чтобы сгенерировать и
отобразить прогрессию 1.2,4,8. 16,32 и Т.Д,
С++: руководство ДЛfil начинающих 155

11. Код строчных букв ASCII отличается от кода прописных на 32. Таким об­
разом, чтобы преобразовать строчную букву в прописную, необходимо вы­
честь из ее кода число 32. Используйте эту информацию при написании
программы, которая бы считывала символы с клавиатуры. Перед отобра­
жением результата обеспечьте преобразование всех строчных букв в про­
писные, а всех прописных - в строчные. Другие же символы никаким из­
менениям подвергаться не должны. Организуйте завершение программы
после ввода пользователем символа "точка". Перед завершением програм­
ма должна отобразить количество выполненных преобразований (измене­
ний регистра).

12. В чем состоит назначение С++-инструкции безусловного перехода7

-tc Ответы на эти вопросы можно найти на Web-странице данной


НазаметкуП"'" книгипоадресу:httр://www.оsЬоrпе.соm.
Модуль 4
Массивы, СТРОI<И
и УI<азатели

4.1. Одномерные массивы


4.2. Двумерные массивы
4.3. Многомерные массивы
4.4. Строки
4.5. Некоторые библиотечные функции обработки строк
4.6. ИIIИЦИ<U1И:J<lЦИЯ массивов
4.7. Массивы строк
4.8. Укааатсли
4.9. Операторы, используемые с УКa:Jателями
4.10. ИСI10JII)ЗО8aIlие указателей в выражениях
4.11. Указатели и массивы
4.12. Многоуровневая lIепрямая адресаl~ИЯ
158 МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели

в этом модуле мы рассматриваем массивы, строки и указатели. Эти темы


могут показаться совсем не связанными одна с другой, но это только на первый
взгляд. В С++ они тесно переплетены между собой, и, что важнее всего, если хо­
рошо разобраться в одиой из них, у вас не будет проблем и с остальными.
Массив .(апау) - это коллекция переменных одинакового типа, обращение
к которым происходит с применением общего для всех имени. В С++ массивы
могут быть одно- или мно,'омерными, хотя в основном используются одномер­
ные массивы. Массивы представJ1ЯЮТ собой удобное средство создания списков
(группирования) связанных переменных.
Чаще всего в "повседневном" программировании используются символьные
массивы, в которых хранятся строки. как упом:иналось выше, в С++ не опреде­
лен встроенный тип данных для хранения строк. Поэтому строки реализуются
как массивы символов. Такой подход к реализации строк дает С++-программи­
сту больше "рычагов" управления по сравнению с теми языками, в которых 11С­
пользуется отдельный строковый rnп данных.
Указатель - это объект, который хранит адрес памяти. Обычно указатель ис­
пользуется для доступа к значению другого объекта. Чаще всего таким объектом
является массив. В действительности указатели и массивы связаны друг с другом
сильнее, чем можно было бы ожидать.

ВАЖНОI

Одномерный массив - это список связанных переменных. Такие СПИСки до­


вольно популярны в программировании. Например, одномерный массив можно
использовать для хранения учетных номеров активных пользователей сети или
результатов игры любимой бейсбольной КОМ'шды. Если потом "ужно усреднить
какие-то данные (содержащиеся в массиве), 'Го это очень удобно сделать по­
средством обработки значений массива. Одним словом, массивы - незаменимое
средство современного программирования.

Для объявления одномерного массива ИСПОЛl>зуется следующая форма записи.


тип имя_массива[размерJ;

Здесь с помощью элемента записи тип объявляется базовый тип массива. Базо­
вый тип определяет тип данных каждого элемента, составляющего массив. Коли­
чество элементов, которые будут храниться в массиве, определяется элементом
размер. Например, при выполнении приведеиной ниже инструкции объявляет­
ся iпt-массив (состоящий из10 элементов) с именем sample.
int sample[lOJ;
С++: РУКОВОДСТВО для начинающих 159

доступ к отдельному элементу массива осуществляется с помощью индекса.


Индекс описывает позицию элемента внутри массива. В С++ первый элемент мас­
сива имеет нулевой индекс. Поскольку массив sample содержит 10 элементов,
его индексы изменяютси 01'0 до 9. Чтобы получить доступ к элементу массива по
индексу. достаточно указать нужный номер элемента в квадратных скобках. Так,
первым элементом массива sample является sample l О) • а последним - заm­
ple [9). Например, следующая программа помещает в массив sample числа от
Од09.

#iлсludе <iostream>
usiлg лаmеsрасе std;

iлt mаiл ()
(
iлt sample[10); // Эта инструкция резервирует область
// памяти для 10 элементов типа iлt.
iлt t;

// Помещаем в массив значения.


for(t=O; t<10; ++t) 888ple[t] ti / / Обратите внимание
// на то, как индексируется
// массив 8ample.

// Отображаем содержимое массива.


for(t=Qi t<10i ++t)
cout « "Это элемент sample[" « t « "]: 11

« sample[t] « "\л";

rеturл О;

Результаты выполнения этой npoграммы выглядят так.

Это элемент sample[O]: О


Это элемент sample[l]: 1
Это элемент sample[2]: 2
Это элемент sаmрlе[З): 3
Это элемент sample[4]: 4
Это элемент sample[5]: 5
Это элемент sаmрlе[б): 6
Это элемент sample[7]: 7
160 Модуль 4. Массивы, СТРОI<И и УI<азатели

Это элецент sample[8]: 8


Это элемент sample[9]: 9
В С++ все массивы занимают смежные ячейки памяти. (Друmми словами,
элементы массива в памяти расположены последовательно друг за другом.)
Ячейка с наименьшим адресом относится к первому элементу массива, а с наи­
большим - к последнему. Например, после выполнения этого фрагмента кода
int nums[5];
int i;

for(i=O; i<5i i++) nums[i] = i;


массив nums будет выглядеть следующим образом.

nums[O] nums(1] nums[2] пums[З] nums[4]

О 2 з
4 I
Массивы часто используются в программировании, поскольку ПQ.1ВОЛЯЮТ
легко обрабатывать большое количество СВЯ;Iанных переменных. Например, в
следующей программе создается массив из де(:яти элементов, каждому элементу
присваивается некоторое число, а затем вычисляется и отображается среднее от
этих значений, а также минимальное и максимальное.

/*
Вычисление среднего и определение минимального

и максимального из набора эначенИЙ.

* 1-

#include <iostream>
using namespace std;

int main ()

int i, avg, min_val, max_val;


int nums[10);

nums[O] 10;
nums[l) 18;
nums[2) 75;
пums[З) О;

nums[4] 1;
С++: руководство ДЛЯ начинающих 161

nums[S] 56;
nums[6] 100;
nums[7] 12;
nums[8] -19;
nums [9] 88;

// Вычисление среднего значения.


avg = О;
for(i=O; i<10; i++)
avg += nums[i); // +- Суммируем звачеиих иаССИ8а numa.

avg /= 10; /1 +- Вwчиcn_еи среднее значение.

co.ut « "Среднее значение равно " « avg « '\п';

// Определение минимального и максимального значений.


min_val = max_val = nums[O];
for(i=1; i<10; i++) (
if(nums[i] < min_val) min val nums[i]; // КИНИИУИ
if(nums[i] > max_val) mах val nurns[i]; // каксииуи
,
cout « "Минимальное значение: .. « min val « '\n';
cout « "Максимальное значение: "« mах val « '\п';

return О;

При выполнении эта программа генерирует такие результаты.

Среднее значение 34равно


Минимальное значение: -19
Максимальное значение: 100

Обратите внимание на то, как в программе опрашиваются элементы массива


nums. Тот факт, что обрабатываемые здесь значения сохранены в массиве, значи­
тельно упрощает процесс определения среднего, минимального и максимального

значений. Управляющая переменная цикла for используется в качестве индекса


массива при доступе·к очередному его элементу. Подобные циклы очень часто
применяются при работе с массивами.
162 МОДУЛЬ 4. Массивы, строки и УI<аэатели

Используя массивы, необходимо иметь в виду одно очень важное ограни­


чение. В С++ иельзя присвоить один массив другому. В следующем фрагменте
кода, например, присваивание а = Ь i недопустимо.

int a[lO), Ь[10] i

1/ ...

а = Ь; // Ошибка!!!

Чтобы поместить содержимое одного массива в другой, необходимо отдельно


выполнить присваивание каждого значения. Вот пример.

for(i=Q; i<10; i++) a[i] = b[i];

На границах массивов беэ пограничников


в С++ не выполняется никакой про верки "нарушения границ" массивов, Т.е.
ничего не может помешать програ.ммисту обратиться к массиву за его пределами.
Другими словами, обращение к массиву (размером Nэлементов) за границей N-
го элемента может привести к разрушению програ.ммы при отсутствии каких­

либо замечаний со стороны компилятора и без выдачи сообщений об ошибках


во время работы программы. Например, С++-компилятор "молча" скомпилирует
и позволит запустить следующий код на выполнение, несмотря на то, что в нем
происходит выход за границы массива crash.
int crash[10], i;

for(i=Qi i<100; i++) crash[i]=ii


В данном случае цикл for выполнит 100 итераций, liесмотря на то, что мас­
сив crash предназначен для хранения лишь десяти элементов. При выполнении
этой программы возможна перезапись важной информации, что может привести
к аварийному останову программы.
Если "нарушение границы" массива происходит при выполнении инструкции
присваивания, могут быть изменены значения 8 ячейках памяти, выделенных не­
которым другим переменным. Если границы массива наРУlUаются при чтении дан­
ных, то неверно считанные данные могут попросту разрушить вашу програ.мму.

В любом случае вся ответственность за соблюдение границ массивов лежит толь­


ко на програ.ммистах, которые должны гарантировать корректную работу с мас­
сивами. Другими словами, программист обязан использовать массивы достаточно
большого размера, чтобы в них можно было беэ осложнений помещать данные, но
лучше всего в программе предусмотреть проверку пересечения границ массивов.
С++: PYI(OBOACTBO ДМI начинающих 163

Спросим у опытного программиста

Вопрос. Если нарушение zpаниц массива может привести к "атастрофичsсКWl no-


следствиям, то nоче.му в С + + 1и! предусмотрено операции "ZPШluчной" nро­
верки доступа" массиву 7

Опет. Вероятно, такая "непреДУСМОТРIГГелъность" С++, которая выражается в


отсутствии встроенных средств динамической проверки на "неnpикосно-
~
венность" границ массивов, может кого-то удивить. Напомню, однако, что
язык С + + предназначен для П)юфеССИОНaJlЬНЫХ программисгов, и его зада­
ча - предо(:тавить им возможность создавать максимально эффективный
код. Любая проверка корреКТНОСПI доступа средствами С++ существенно
замедляет выполнение программы. Поэтому подобные дейсгвия оставле­
ны на рассмотрение программистам. Кроме ТОГО, при необходимости про­
граммист может сам определить тип массива и заложить в него проверку

нерушимосги границ.

~ПРОСЫ ДЛЯ текущего КОНТРОЛЯ --------


1. Что представляет собой одномерный массив?

2. Индекс первого элемента массива всегда равен нулю. Верно ли это?

З. Предусмотрена ли в С++ проверка "нерушимости" границ массива? •

ВАЖНОI
с'· дs.yмермымассивbl
в С++ можно использовать многомерные массивы. Простейший многомер­
ный массив - двумерный. Двумерный массив, по сути, представляет собой спи­
сок одномерных массивов. Чтобы объявить двумерный массив целочисленных
значений размером 10х20 с именем twoD, достаточно записать следующее:
int twoD[10] [20];
Обратите особое внимание на это объявление. В отличие от MHomx других
ЯЗЫI<ОВ программирования, в которых при объявлении массива значения раз-

1. Одномерный массив - это список связанных значений.

2. Верно. Первый элемент массива всегда имеет нулевой индекс.


З. Нет. В С++ встроенные средства динамической проверки на "неnpикосновенность·
границ массивов отсутствуют.
164 МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели

мерностей отделяются запятыми, в С++ каждая размерность заключается в соб­


ственную пару квадратных скобок. Так, чтобы получить доступ к элементу мас­
сива twoD с координатами 3,5, необходимо использовать запись twoD (3] [5].
в следующем примере в двумерный массив помещаются последовательные
числа от 1 до 12.
#include <iostream>
using namespace std;

int main ()

int t, i, nums [3] [4] ;

for(t=O; t < 3; ++t)


for(i=O; i < 4; ++i)
nums[t] [i] = (t*4)+i+l; // Для индексации массива
// nums требуется два
// индекса.
cout « nums[t] [i] « ' ';

cout « '\n';

return О;

в этом примере элемент numS [О] [О] получит значение 1, элемент nums I О]
[ 1] - значение 2, элемент n uтз [ О] [2] - значение 3 и т.д. Значение элемента
nums [2] [3] будет равно числу 12. Схематически этот массив можно предста­
вить следующим образом.

о 2 з ~ Прааый индекс

о 1 2 3 4

1 5 6 ;!) 8

2 9 10 ~1 12

t
Левый индекс nums(1)[2)
С++: PYI(OBOACTBO дl\fI начинающих 165

В двумерном массиве позиция любого элемента определяется двумя индекса­


ми. Если представить двумерный массив в виде таблицы данных, то один индекс
означает строку, а второй - столбец. Из этого следует, что, если к доступ элемен­
там массива предоставить в порядке, в котором они реально храиятся в памяти,

то правый индекс будет изменяться быстрее, чем левый.


Необходимо помнить, что место "'"Ранения для всех элементов массива опреде­
ляется во время компиляции. Кроме того, память, выделенная для хранения мас­
сива, используется в течение всего времени существования массива. Для опреде­
ления количества байтов памяти, занимаемой двумерным массивом, используйте
следующую формулу.

число байтов = число строк х число столбцов х размер типа

в байтах

Следовательно, двумерный целочисленный массив размериостью 10х5 за­


нимает в памяти 10х5х4, Т.е. 200 байт (если целочисленный тип имеет размер
4 байт).

ВАЖНОI
рв МWQtQМepNЫ8..I4QCCИВЫ
В С++, помимо двумерных, можно определять массивы трех и более измере­
ний. Вот как объявляется многомерный массив.

тип имя[размерl] [раэмер2] ... [раэмерN];

Например, с помощью следующего объявления создается трехмерный цело­


численный массив размером 4хl0хЗ.

int multidim[4] [10] [3];


Массивы с числом измерений, превышающим три, используются нечасто,
хотя бы потому, что для их храиения требуется большой объем памяти. Ведь,
как упоминалось выше, память, выделенная для хранения всех элементов

массива, используется в течение всего времени существования массива. На­


пример, хранение <)лементов четырехмерного символьного массива размером

10х6х9х4 займет 2 160 байт. А если каждую размерность увеличить в 10 раз,


то занимаемая массивом Ш:l.мяТl, возрастет до 21 600 000 байт. Как видите,
большие многомерные массивы способны "съесть" большой объем памяти, а
программа, которая их использует, может очень быстро столкнуться с пробле­
мой нехватки памяти.
166 МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели

~ПРОСЫ I'>/IЯ текущего КОНТРОЛЯ --------


1. Каждая размерность многомерного массива в С++ заключается в соб­
ственную пару квадратных скобок. Верtlо ли это?
2. Покажите, как объявить двумерный целочисленный массив с именем
list размерностью 4х9?
3. Покажите, как получить доступ к элементу 2,3 массива 1 i 5 t из npeдыду­
щего вопроса? •

.,.•t8i§ЦI'Мс'РDJpQII&8IUIOCGИ8a
BUbble. ~~p I Поскольку одномерный массив обеспечивает организацию дан­
L ных в виде индексируемого линейного списка, то он представляет
собой просто идеальную структуру ланных для сортировки. В этом проекте вы по­
знакомитесь с одним из са\1ЫХ простых способоfl сортировки массивов. Существует
много различных алгоритмов сортировки. Широко применяется, например, сорти­
ровка перемешиванием и сортировка методом Шелла. Известен также алгоритм t)w-
строй сортировки. Однако самым пpocrым счит ..ется алгоритм сортировки ny3bl,CJb-
КOбbI.М методо,м. Несмотря на то что пузырьковая сортировка не отличается высокой
эффективностью (его производительность неприемлема для больших массивов), его
вполне успешно можно применять для сортировки массивов малого размера.

Последовательность действий
1. Создайте файл с именем ВиЬЫе. срр.

2. Алгоритм сортировки пузырьковым методом получил свое название от CI1O-


соба, используемого для упорядочивания элементов массива. Здесь выпол­
няются повторяющиеся операции сравнения и при необходимости меняются
местами смежные элементы. При этом элементы с меньшими значениями по­
степеllНО перемещаются к одному концу мзссива, а злсменты с большими зна­
чениями - к дрyroму. этот npoпесс напОМIIНает поведение пузырьков воздуха

1. Верно: в С++ каждая размерность многомерного массива заключается в собствен­


ную пару квадратных скобок.

2. int list[4] (9].


З. l i 5 t ( 2] [З].
С++: PYI(OBOACTBO длr;1 начинающих 167

в резервуаре с водой. Пузырьковая сортировка выполняется пyreм нескольких


проходов по массиву, во время которых при необходимости осущестВJIЯется
перестановка элеМСIПОВ, оказавшихся "не на своем месте". Количество прохо­
дов, гарантирующих получение отсортированного массива, равно количеству

элементов в массиве, уменьшенному на единицу.

Рассмотрим код, составляющий ядро пузырьковой сортировки. Сортируемый


массив носит имя nums.
// Реализация алгоритма пузырьковой сортировки.
for(a=l; a<size; а++)
for(b=size-1; Ь>=а; Ь--) {
if(nums[b-l] > nums[b) { // Если значения элементов
// массива расположены не
// по порядку,
// то меняем их местами.
t "" nums[b-1]i
nums[b-1] = nums[b];
nums[b) = t;

Обратите внимание на то, что сортировка реализована на двух циклах for.


Во внутреннем цикле организовано сравнение двух смежных элементов
массива. Если окажется, что они располагаются "не по порядку", т.е. не по
возрастанию, то их следуст поменять местами. При каждом проходе вну­
треннего цикла самый маленький элемеит помещается на "свое законное"
место. Внешний цикл обеспечивает повторение этого процесса до тех пор,
пока не будет отсортирован весь массив. I
I
3. Вот полный текст программы ВuЬЫе. срр.

/*
Проект 4.1.
Демонстрация метода пузырьковой сортировки
(ВuЬЫе sort).
*/
#include <iostre~m>
'include <cstdlib>
using namespace std;

int main ()
168 Модуль 4. Массивы, строки и указатели

int пиmз[10];
int а, Ь, t;
int size;

size = 10; // Количество сортируемых злементов.

// Помещаем в массив случайные числа.


for(t=O; t<size; t++) nums[t] = rand();

// Отображаем исходный массив.


cout « "Исходный массив:\п ";
for(t=O; t<size; t++) cout « nums[t] « ' ';
cout « '\п';

// Реализация алгоритма пузырьковой сортировки.


for(a=1; a<sizei а++)
for(b=size-1i Ь>=а; Ь--) (
if(nums[b-1] > пиmз[Ь]) ( // Если значения з~ементов
// массива расположены не
// по порядку,
// то меняем их местами.
t = nums[b-1]i
nums[b-1] = nums[b]i
nums[b] = ti

// Отображаем отсортированный массив.


cout « "\пОтсортиt>ованный массив:\п " ,.
for(t=Oi t<sizei t++) cout « nums[t] « I ';

return О;

Результаты выолнения этой программы таковы.

Исходный массив:
41 18467 6334 26500 19169 15724 11478 29358 26962 24464

Отсортированный массив:
41 6334 11478 15724 18467 19169 24464 26500 26962 29358
С++: руководство дl\t;J начинающих 169
.................................................................................................................................

ХОТЯ алгоритм пузырьковой сортировки пригоден ДЛЯ не60ЛЬШи.х масси­


4.
вов, для массивов большого размера он становится неэффективным. Более I
универсальным считается алгоритм Quicksort.
пользует средства С++, которые мы еще не изучили. Кроме того, в стан­
дартную библиотеку С++ включена Функция
Однако этот алгоритм ис­

qsort (), которая реализует


! 1')

~
>-
одну из версий этого алгоритма. Но, прежде чем использовать ее, вам не­ :s:
:s:
обходимо изучить больше средств С++. ~
о
е-
u
]j
ВАЖНОI 111
:s:
u
u
.0
Чаще всего одномерные массивы используются для создания символьных строк. :~
В С++ поддерживается два типа строк. Первый (и наиболее популярный) пред­
полагdет, что строка определяется как символьный массив, который 3аВершается
нулевым символом ( , \ О '). Таким образом. строка с завершающим нулем состоит
из символов и конечного нуль-символа. Такие строки широко используются в про­
граммировании, поскольку они позволяют программисту выполнять самые разные

операции над строками и тем самым обеспечивают высокий уровень эффективности


программирования. Поэтому, используя термин cmpoкa, С++-программист обычно
имеет в ВИДУ именно (."троку с завершающим нулем. Однако существует и ДРУГОЙ
тип представлеl"IИЯ строк в С++. Он заключается в применении объектов класса
string, который является частью библиотеки классов С++. Таким образом, класс
string - не встроенный тип. Он подразумевает объектно-ориентироВанный под­
ход к обработке строк, 110 используется не так IlIИJЮКО, как строки с завершающим
нулем. В этом разделе мы рассматриваем строки с завершающим нулем.

ОСНОВЫ преАставления СТРОК


Объявляя символьный массив, предназначенный для хранения строки с за­
вершающим нулем, необходимо учитывать признак ее завершения и задавать
длину массива на единицу больше длины самой большой строки из тех, которые
предполагается хранить в этом массиве. Например, при объявлении массива s tr,
в который предполагается поместить 10-символьную строку, следует использо­
вать следующую инструкцию.

char str[l1];
Заданный здесь размер (11) позволяет зарезервировать место для нулевого сим­
вола в конце строки.
170 Модуль 4. Массивы, СТРОI(И и УI<азатели
................................................................................................................................

Как упоминалось выше в этой книге, С++ позволяет определять строковые


константы (литералы). Вспомним, что cmpOKoвblй лumepШl- это список симво­
лов, заключенный в двойные кавычки. Вот несколько примеров.
11.,
"Привет" "Мне нравится с++" "Mars"
Строка, приведенная последней (" "), называется "улевОЙ. Она состоит только
из одного нулевого символа (признака завершения строки). Нулевые строки ис­
пользуются для представления пустых строк.

Вам не нужно вручную добавлять в конец строковых констант нулевые симво­


лы. С++-компилятор делает это автоматически. Следовательно, строка "Mars" в
памяти размещается так, как показано на этом рисунке:

м 8 r s о I
Считывание строк с клавиатуры
Проще всего считать строку с клавиатуры, создав массив, который примет эту
строку с помощью инструкции cin. Считывание строки, введенной пользовате­
лем с клавиатуры, отображено в следующей программе.
11 Использование сiп-инструкции для считывания строки
11 с клавиатуры.

#include <iostream>
using namespace stdi

int main ()
{
char str[80]i

cout « "Введите строку: ";


сi n >> 5t r ; 11 +- СЧИ'nlВаек с'2!рОКУ с JUliUtиаlJ!УРIo1
11 с ПОМО~ инс'2!рУКЦИИ cin.
cout « "Вот ваша строка: ";
cout « stri

rеturп О;

Вот возможный результат выполнения этой программы.


Введите строку: Тестирование

Вот ваша строка: Тестирование


С++: PYI<OBOACTBO ANil начинающих 171

Несмотря на то что эта программа формально корректна, она не лишена недо­


статков. Рассмотрим следующий результат ее выполнения. I
Введите строку: Это проверка

Вот ваша строка: Это

Как видите, при выводе строки, введенной с клавиатуры, программа отображает


только слово "Это", а не всю строку. Дело в том, что оператор "»" (в инструкции
cin) прекращает считывание строки, как только встречает символ пробела, табу­
ляции или новой строки (будем называть эти символы nробельным) •.
Для решения этой проблемы можно использовать еще одну библиотечную
функцию gets () . Общий формат ее вызова таков.

gеts(имя_массива);

Если в программе необходимо считать строку с клавиатуры, вызовите функ­


цию gets () , а D качестве аргумента передайте имя массива, не указывая индек­
са. После выполнения этой функции заданный массив будет содержать текст,
введенный с клавиатуры. Функция gets () считывает вводимые пользователем
символы до тех пор, пока он не нажмет клавишу<Enter>. Для вызова функции
gets () в программу необходимо включить заголовок <cstdio>.
В следующей версии предыдущей программы демонстрируется использова­
ние функции gets (), которая позволяет ввести в массив строку символов, со­
держащую пробелы.
11 Использование функции gets() для считывания строки

1/ с клавиатуры.

#include <iostream>
#include <cstdio>
using патеэрасе std;

int main ()
{
char str[80);

cout « "Введите строку: ";


gets(str); 11 ~ C~aeк c~oxy с xnаака'У.РЫ
11 с помощью фунхции qet8().
cout « "Вот ваша стр6ка: ";
cout « str;
172 МОДУЛЬ 4, Массивы, строки и УI<азатели
.................................................................................................................................

return О;

На этот раз после запуска новой версии программы не! 8ыполнеtlие и ввода с
клавиатуры текста "Это простой тест" строка считывается полностью, а затем 1ак
же полностью и отображается.

Введите строку: Это простой тест


Вот ваша строка: Это простой тест

В этой программе следует обратить внимание на следующую инструкцию.

cout « str;
Здесь (вместо привычного литерала) используется имя строкового массива. Эа­
помните: имя символьного массива, который содержит строку, можно испоЛ1,:Ю­
вать везде, где допустимо применение строкового литерала.

При этом имейте в DИДУ, что ни оператор "»" (В инструкции cin), нифункщ{я
gets () не выполняют граничной проверки (на отсутствие нарушения границ
массива). Поэтому, если пользователь введет строку, длина которой преВЫШilСТ
размер массива, возможны неприятности, о которых упоминалось выше. Из cl\a-
занного следует, что оба описанных здесь варианта считьшания строк с клавиату­
ры потенциально опасны. Однако ПOCJIе подробного рассмотрения С++-возмож­
ностей ввода-вывода мы узнаем способы, позволяющие обойти эту проблему.

~ПРОСЫ ДЛЯ текущего КОНТРОМ --------


1. Что представляет собой CTpOl<a с завершающим нулем?
2. Какой размер должен иметь символьный массив, предназначенный для
хранения строки, состоящей из 8 символов?
З. Какую Фуикцию можно использовать для считывания с клавиатуры СТРО-
u про б.
ки, содержащеи елы? '
_____________...__

1. Строка с завершающим нулем представляет собой массив сиr.mолов, который :Ja-


вершается нулем.

2. Если сиr.moльный массив предназначен для хранения строки, состоящей из 8 сим­


волов, то его размер должен быть не меньше 9.
З. ДЛЯ считывания. с клавиатуры строки, содеР)l.:ащеЙ пробелы, можно использовать
функцию gets () .
С++: руководство дl\f1 начинающих 173

ВАЖНОI
, • • HeKOlOpb1e бибАиQxQчныe
функции обработки строк
Язык С++ подцерживает множество функций обработки строк. Самыми рас­
пространенными из них являются следующие.

strcpy ()
strcat ()
strlen()
strcmp ()
Для вызова всех этих функций в программу необходимо включить заголовок
<cs tr ing>. Теперь познакомимся с каждой ФУlfкцией в отдельности.

ФУНКЦИЯ s trcpy ( )
Общий формат вызова функции strcpy () TalCOB:

strcpy(to, from);
ФУНlщия s t rcpy () кuпирует содержимое строки from о строку to. Помните,
что массив, используемый для хранения строки to, должен быть достаточно
БОЛЬUIIlМ, чтобы в него МОЖIIО было пом~стнть строку из массива from. В про­
ТlШIЮМ случае массив to IIсреI10ЛНИТСЯ. ·Г.С. произойдет выход за его границы,
что может привести к разрушению програММhI.

ФУНКЦИЯ s trca t ( )
Обращение к функции s t rca t () имеет следующий формат.

strcat(sl, s2);
ФУНlщия strcat () присоеДИlJяет строку s2 к концу строки з1; при этом строка
s2 не изменяется. Обе строки должны завершаться нулевым символом. Резуль­
тат lJЫ30lJa этой ФУНКIIИИ, T.~. результирующая строка s1 также будет завершать­
ся нулевым символом. Программист должен позаботиться о том, чтобы строка 51
была достаточно большой, и в нее поместилось, кроме ее исходного содержимого,
содержимое строки 52.

ФУНКЦИЯ strcmp ()
Обращение к функции s t rcmp () имеет следующий формат:

strcmp(sl, 52);
Функция strcmp () сравнивает строку 52 со строкой 51 и возвращает значение О,
если они равны. Если сч>ока s 1 лексикографичесlСИ (т.е. в соответствии с алфаJJИТ-
174 Модуль 4. Массивы, строки и указатели

HbIМ порядком) больше строки 52, возвращается положительное число. Если строка
51 лексикографичесЮf меньше строки 52, возвращается отрицательное число.
При использовании функции strcmp () важно помнить, что она В03вращает
число О (т.е. значение fa1se), если сравниваемые строки равны. Следовательно,
если вам необходимо выполнить определенные действия при условии совпаде­
ния строк, вы должны использовать оператор НЕ (!). Например, условие, управ­
ляющее следующей if-инструкцией, даст истинный результат, если строка str
содержит значение "С++".

if(!strcmp(str, "С++"» cout « "Строка str содержит С++";

Функция strlen ( )
Общий формат вызова функции str1en () таков:
str1en(5);
Здесь 5- строка. Функция 5tr1en () возвращает длину строки, указанной ар­
гументом 5.

Пример использования строковых функций


в следующей программе показано использование всех четырех строковых
функций.

// Демонстрация использования строковых ФУНКЦИЙ.


#inc1ude <io5tream>
#inc1ude <cstdio>
#inc1ude <c5tring>
using narnespace std;

int rnain ()
{
char 51[80], 52[80];

5trcpY(51, "С++");
5trcpY(52, " - это мощный язык.");

cout « "Длины строк: " « 5tr1en(sl);


cout « I I « 5trlen (з2) « I \п 1;

if(!strcrnp(sl, 52»
cout « "Эти строки равны.\п";
С++: Pyt<OBOACTBO ДЛЯ начинающих 175

else cout « "Эти строки не равны.\п":

5trcat(51, 52):
cout « 51 « '\п':

5trcpY(52, 51):
cout « 51 « " и n « 52 « "\п":

if(!5trcmp(51, 52»
cout « "Строки 51 и 52 теперь одинаковы.\п":

return О:

ВОТ как выглядит результат выполнения этой программы.

Длины строк: 3 19
Эти строки не равны.

С++ - это мощный язык.


С++ - ЭТО мощный ЯЗЫК. и С++ - это мощный ЯЗЫК.
Строки 51 и 52 теперь одинаковы.

Использование признака завершения строки


Факт завершения нулевыми символами всех С++-строк можно использовать
для упрощения различных операций над ними. Следующий пример позволяет
убедиться в ТОМ, насколько простой код требуется для замены всех символов
строки их прописными эквивалентами.

// Преобразование символов строки


// в их прописные эквиваленты.
#include <io5tream>
#include <c5tring>
#inc1ude <cctype>
u5ing namespace 5td:

int main ()

char 5tr[BO]:
int i:

5trcpY(5tr, "abcdefg");
176 Модуль 4. Массивы, строки и указатели

/ / Э'l'О'r цmtn saвe~CR, Kor"a :шекек'r s tr [i] бу"е'r


// co"ep.a'1'lo признак заверше_ сo:rpоlCИ.

// J.
for(i=Oi str[i]i i++) str[i] = toupper(str[i])i

cout « str;

return О;

Эта проrpамма генерирует такой результат.

ABCDEFG
Здесь используется библиотечная функция t.oupper (), которая возвращает
прописной эквивалент своего символьного аргумента. Для вызова функции t)-
upper () необходимо включить в проrpамму заголовок <cctype>.
Обратите внимание на то, что в качестве УСЛQf:ИЯ завершения цикла for ИСПО."IЬ­
зуется массив s tr, индексируемый управляющей переменноil i (str [i ) ). Такой
способ управления циклuм вполне приемлем, IJOCKOJ1bKY за истинное значение в
С++ прюrимается любое ненулевое значение. Вспомните, что все печатные СЩf[-:Q­
лы представляются значениями, не равными нулю, Ii только символ, завершаЮIlНfЙ
строку, равен нулю. Следовательно, этот цикл работает до тех пор, пою\ Иlщекс не
укажет на нулевой признак конца строки, Т.е. пока значение str [i ) не станет ну­
левым. Поскольку нулевой символ отмечает конец строки, цикл останавлинаеl::Я
в точности там, где нужно. В дальнеiiшем вы УВИ.НJТе множество примеров, в КО10-
ph1X нулевой признак КOllЦа строки используется подобным образом.

~просы ДЛЯ текущего I<ОНТРОЛЯ ---------


1. Какое действие выполняет функция strca t () ?
2. ~TO возвращает функция strcmp () при сравнении двух эквивалентных
строк?

.........................................................
3.
--.......-
Покажите, как получить длину строки с именем туэ tr?

1. Функция strcat () конкатенирует (т.е. СDeД;.fняет) две СТРОЮ-l.

2. Функция strcmp (). сравнивая двс эквивалСlIтные строки, возвращает о.

З. strlen (mystr) .
С++: PYI<OBOACTBO ДЛЯ начинающих 177·

Спросим у опытного программиста

~
Вопрос. J/оддержuвает Лll С++ другие, nOMIL'fO фующuu toupper (), фунxцuu 06-
работ"" сu.мволов?
о
м
Ответ. Да. ПОМИМО ФУНКЦИИ toupper () , стандартная библиотека С++ содер­
жит много ДРУПlХ фуюсций обработки символов. Например, ФУНКЦИЮ ~S
toupper () дополняет ФУНКЦИЯ tolower (), КОТQРая 80ЗВращает строч­ S
:..:
О
ный эквивалент своего символьного арryмеlПа. Например, perncтp буквы а.
(т.е. ПРОШIСНая она ИЮI строчная) можно определить с ПОМОЩЬЮ функции u
isupper (), которая возвра.щпет значение ИСТИНА, если исследуемая iCII
буква является прописной, и функции islower (), которая возвращает s
u
значение ИСТИНА, если исследуемая буква является строчной. Часто
u
о
используются такие функции, как isalpha (), isdigi t (), isspace () ~
и ispunct (), которые ПРИlшмаlOТ символьный аргумент и определяют,
принадлежит ли он к соответствующей категории. Например, функция
isalpha () во:шращает значение ИСТИНА, если ее аргумеlПОМ является
буква алфавита.

ВАЖНОI
а. ИНI4ЦI4ШИ3С1ЦkU1.,.м,QCСИВОВ
в С++ предусм()трена возможность инициализации массивов. Формат ини­
циализании массивов подобен формату ининиализации других переменных.
тип имя_массива [раэмер) = {список_значений};

Здесь элемент список_ эна чений представляет собой список значений ишщиа­
ЛИ:l3ЦИИ элемеНТОIJ массива, разделенных запятыми. Тип каждого значения ини­
циализаЩIII должен быть совместим с базовым типом массива (элементом тип).
Первое значение инициализации будет сохранено в первой позиции масс~ша,
IJторое значение - во второй 11 т.Д. ОбраТlIте внимание на то, что точка с запятоii
ставится после закрывающей фюурной скобки ( ) ).
Например, в следующем примере 10-элементнblЙ целочисленный массив ини­
циализируется числаМJI от 1 до 10. '
int i[lO) = {l, 2, 3, 4, 5, б, 7, 8, 9, lO}i
После вьшолнения этой инструкции элемент i [О) получит значение 1, а эле­
мент i [9) - значение 10.
Для символьных маССИRОП, предназначенных для хранеllИЯ строк, предусмо­
трен сокращенный вариант Iшициализации, который имеет такую форму.
char имя_массива[размер) = "строка";
178 МОДУЛЬ 4. Массивы, строки и указатели

Например, CJlедующий фрагмент кода инициализирует массив str фра.'ЮЙ


"привет".

char str[7] = "привет";

Это равнозначно поэлементной инициализации.

char str[-7] = {'п', 'р', 'и', 'в', 'е', 'т', '\О'};

Поскольку в С++ строки должны завершаться нулевым символом, у6едитесь,


<по при объявлении массива его размер указан с учетом признака конца. Именно
поэтому в предыдущем примере массив str объявлен как 7-элемевтный, несмо­
тря на то, что в слове "привет" только шесть букв. При использовании строкового
литерала компилятор добавляет нулевой признак конЦа строки автоматически.
Многомерные массивы инициализируются по аналогии с одномерными. На­
пример, в следующем фрагменте программы массив sqrs инициализируется
числами от 1 до 10 и квадратами этих чисел.
int sqrs [10] [2] = {
1, 1,
2, 4,
3., 9,
4, 16,
5, 25,
6, 36,
7, 49,
8, 64,
9, 81,
10, 100
};
Теперь рассмотрим, как элементы массива sqrs располагаются в памяти
(рис. 4.1).
При инициализации многомерного массива список инициализаторов каж­
дой размерности (подгруппу инициализаТОРОIJ) можно заключить в фигурные
скобки. Вот, например, как выглядит еще однн вариант записи предыдущего
объявления.
int sqrs[10] [2] = {
{1, 1} ,
{2, 4} ,
{ 3, 9} ,
{ 4 , 16} ,
{ 5, 25} ,
С++: РУКОВОДСТВО дl\fI начинающих 179

{ 6, 36} ,
(7, 49) ,
{8, 64 } ,
{9, 81) ,
{10, 100}
};

о 1 + - - ПраВblА индекс

о 1 1
1 2 4
2 Э 9
Э 4 16
4 5 25
5 6 эв

6 7 49
7 8 64
8 9 81
9 10 100

i
Левый индекс

Рис. 4.1. СХeJflатuческое npедcmasлeнue


UНUЦUaJ/uзuроваННОlOмассuва sqrs

При использопании подгрупп инициализаторов недостающие члены подгруп­


пы будуг инициализированы нулевыми значениями автоматически.
В следующей программе массив sqrs используется для поиска квадрата чис­
ла, введенного пользователем. Программа сначала выполняет поиск заданного
числа в массиве, а затем DЫВОДИТ соответствующее ему значение квадрата.

#include <iostrea~>
using narnespace std;

int sqrs [10] [2]


{ 1 , 1} ,
{2, 4} ,
{З, 9} ,
{4, 16} ,
{ 5, 25} ,
{ 6, 36} ,
{7, 49} ,
180 Модуль 4. Массивы, СТРОI<И и УI<азатели

(8, 64},
{9, 81},
{10, 100}
};

int main ()

int i, j;

cout « "Введите число от 1 до 10: ";


cin » i;

// Поиск значения i.
for(j=O; j<10; j++)
if(sqrs[j] [O]==i) break;
cout « "Квадрат числа" « i « " равен ";
cout « sqrs[j] [1];

return О;

Вот один из возможных результаты ВЫПOJшения этой I1poгpaMMы.

Введите число от 1 до 10: 4


Квадрат числа 4 равен 16

Инициализация "безразмерных" массивов


Объявляя инициализированный массив, можно позволить С++ автоматиче­
СЮi определять его длину. Для этого не нужно задавать размер массива. Компи­
лятор в этом случае определит его путем ПОl\счета количества инициализаторов,

после чего создаст массив, размер которого будет достаточным для хранения всех
значений инициализаторов. Например, при вы полнении этой инструкции

int nums[] = { 1, 2, З, 4 };
будет создан 4-элементный массив nums, соД(·ржащиЙ значеШiЯ 1,2, 3 и 4. По­
скольку здесь размер массива nums не задан явным образом, его можно назвать
"безразмерным" .
Массивы "безразмерного" формата иногда бывают весьма полезными. Напри­
мер, предположим, что мы используем следующий вариант инициализации мас­
сивов для построения таблицы !пtегпеt-адресов.
С++: РУI<ОВQДСТВО Mt;I начинающих 181

char е1[16] "www.osborn.com";


char е2[16] "www.weather.com";
char е3[15] "www.amazon.com";
НетрудtlО предположить, что вручную неудобно подсчитывать символы в каж­
дом сообщении, чтобы определить корректный размер массива (при этом всегда
можно допустить ошибку в подсчетах). Но поскольку в С++ предусмотрена 80Э­
можность автоматического определения длины массивов путем испольэования

их "безразмерного" формата, то предыдущий вариант инициализации массивов


для построения таблицы Iпtегпеt-адресов можно переписать так.
char е1[] "www.osborn.com";
char е2[] "www.weather.com";
char е3 [] "www.amazon.com";
Помимо удобства в первоначалъном оnpеJ(елении массивов, метод "безраз­
мерной" инициализации позволяет изменить любое сообщение без пересчета его
длины. Тем самым устраняется возможность внесения ошибок, вызванных слу­
чайным просчетом.
"Безразмерная" инициализация не ограничивается одномерными массивами.
При инициализации многомерных массивов вам необходимо указать все данные,
за исключением крайней слева размерности, чтобы С++-компилятор мог долж­
ным образом индексировать массив. Используя "безразмерную" ииициализацию
массивов, можно создавать таблицы различной длины, позволяя компилятору
автоматически выделять область памяти, достаточную для их хранения.
В следующем примере массив sqrs объявляется как "безразмерный".
int sqrs[] [2] = (
1, 1,
2, 4,
3, 9,
4, 16,
5, 25,
6, 36,
7, 49,
8, 64,
9, 81,
10, 100
) ;

Преимущество такой формы объявления перед "габаритной" (с точным указа­


нием всех размерностей) состоит в том, что программист может удлинять или уко­
рачивать таблицу значений инициализации, не изменяя размерности массива.
~82 Модуль 4. Массивы, СТРОI(И и указатели

ВАЖНО!
, . . МаССИВbI строк
Существует специальная форма двумерного символьного массива, которая
представляет собой массив строк В использовании массивов строк нет ничего
иеобычного. Например, в программировании баз данных для выяснения кор­
ректности вводимых пользователем команд входные данные сравниваются с со­

держимым массива строк, в котором записаны допустимые в данном приложе­

нии команды. Для создания массива строк используется двумерный символьный


массив, в котором размер левого индекса определяет количество строк, а размер

правого - максимальную длину каждой строки. Например, при выполнении


следующей ИНСТРУIЩИИ объявляется массив, предназначенный ДJIЯ хранения
30 строк длиной ВО символов.
char str'_array [ЗО] [80] ;
ПОЛУЧIПЬ доступ К отдельной строке довольно просто: достаточно указать
только левый индекс. Например, следующая инструкция вызывает функцию
gets () для записи тpeTbeii строки массива str_array.
gets(str_array[2]);
Для получения доступа к конкретному символу третьей строки используйте
инструкцию, подобную следующей.
cout « str array[2] [3];
При выполнении этой инструкции на экран будет выведен четвертый символ
третьей строки.
В следующей программс демонстрируется использование массива строк пу­
тем реализации очень простого компьютеризированного телефонного каталога.
Двумерный массив питье r s содержит пары значений: имя и телефонный номер.
Чтобы получить телефонный иомер, нужно ввести имя. После этого искомая ИН­
формация (телефонный номер) отобразится НiI экране.
// Простой компьютеризированный телэфоннЬ!Й каталог.
#include <iostream>
#include <cstdio>
using паmезрасе std;

int main ()
{
int i;
char str[80];
char nuтbers[10] [80]
С++: РУКОВОДСТВО ДМ) начинающих 183

"Том", "555-3322",
"Мария", "555-8976",
"Джон" , "555-1037",
"Раиса", "555-1400",
"Шура", "555-8873"
};

cout « "Введите имя: ";


cin » str;

for(i=O; i < 10; i += 2)


if(!strcmp(str, numbers[i]))
cout « "Телефонный номер: " « numbers[i+1] « "\п";
break;

i f (i 10) cout « "Отсутствует в каталоге.\п";

return О;

Вот пример выполнения программы.

Введите имя: Джон

Телефонный номер: 555-1037


Обратите внимание на то, как при каждом проходе цикла for управляющая
переменная i инкрементируется на 2. Такой шаг инкремента объясняется тем,
что имена и телефонные номера чередуются в массиве.

ВАЖНОI

Указатели - один из самых важных и сложных аспектов С++. Вместе с тем


они зачастую являются источниками потенциальных пр06лем. В значительной
степени мощь многих средств С++ определяется использованием указателей. На­
пример, благодаря им обеспечивается поддержка связных списков и механизма
динамического вьщеления памяти, и имен~о они позволяют функциям изменять
содержимое своих аргументов. Однако об этом мы поroворим в последующих мо­
дулях, а пока (т.е. в этом модуле) мы рассмотрим основы применения указателей
и покажем, как с ними обращаться.
184 Модуль 4. Массивы, СТРОI(И и указатели

~просы ДЛ',1 текущего l<ОНТРОЛЯ _ _ _ _ _ _ __

1. Покажите, как инициализировать четырехэлементный. массив list ir~ t-


значениями 1, 2, 3 и 4.

2. как можно переписать эту инициализаuию?

char 5 tr [6] = { ' П', 'р', 'и', 'Б', 'е', 'т', '\ О' } ;
з. Перепишите следующий вариант инициализации в "безразмерном"
формате.

int nums[4] = { 44, 55, 66, 77 };.

При рассмотрении темы указателей нам прИ.':1:СТСЯ ИСПО~ЬЗ0вать такие понятия,


как размер базовых С++-тшюв данных. В этой l'лаве мы предположим, что симво­
лы занимают в памяти один байт, целочислеНlIые значения - четыре. значения
с плавающей точкой типа floa t - четыре, а значения с плавающей точкой пша
double - восемь (эти palMcpbI характерны для ПШИЧflОЙ 32-разрядной среды).

Что представляют собой указатели


Указатель - это объект, который содержит некоторый адрес памяти. Чаще
псего этот адрес обозначает местоположение 11 памяти другого объекта, такого,
как переменная. Например, если х содержит адрес переменной у, то о перемен­
ной х говорят, что она "указывает" на у.
Переменllьre-указатели (или nеремеuные типа указатель) должны быть соот­
ветственно объявлены. Формат объявления переменной-указателя таков:
тип *имя_переменной;

Здесь элемент тип означает базовый тип указателя, причем он должен быть до­
пустимым С++-типом. Базовый тип определяет, на какой тип данных будет ссы­
латься объявляемый указатель. Элемент имя_ переменной представляет собой
имя переменноЙ-указателя. Рассмотрим пример. Чтобы объявить переменную
ip указателем на iпt-зна'lеиие, используйте следующую инструкцию.

int *ip;

1. int 1ist(] = { 1, 2, 3, 4 };
2. char str(] "Привет";

3. int nums (] = ( 44, 55, бб, 77 );


С++: РУКОВОДСТВО для начинающих 185

Для объявления указателя на floa t -значение используйте такую инструкцию.


float *fPi
В общем случае использование символа "звездочка" (*) перед именем перемен­
ной в инструкции объявления превращает эту переменную в указатель.

ВАЖНОI

с указателями
с указателями используются два оператора: "*" и "&". Оператор "&" - унар­
ный. Он возвращает адрес памяти, по !<оторому расположен его операнд. (Вспом­
ните: унарному оператору требуется только один операнд.) Например, при вы­
полнении следующего фрагмента кода

ptr = &totali
в переменную ptr помещается адрес переменной total. Этот адрес соответству­
ет области во внутренней памяти компьютера, которая принадлежит переменной
total. Выполнение этой инструкции uuкак не повлияло на значение переменной
total. Назначение оператора" &" можно "перевести" на русский язык как "адрес
переменной", перед которой он стоит. Следовательно, приведенную выше инструк­
цию присваивания можно выразить так: "перемеllная ptr получает адрес пере­
менной total". Чтобы лучше понять суть этого присваивания, предположим, что
переменная total расположена в области памяти с адресом 100. Следовательно,
после выполнения этой инструкции переменная ptr получит значение 100.
Второй оператор работы с указателями (*) служит дополнением к первому
(&). Это также унарный оператор, но он обращается к значению переменной, рас­
положенной по адресу, заданиому его операндом. Другими словами, он ссылается
на значение переменной, адресуемой заданным указателем. Если (продолжая ра­
боту с предыдущей инструкцией присваивания) перемеНl:lая ptr содержит адрес
переменной total, то при выполнении инструкции

val = *ptri
переменной val будет присвоено значение переменной total, на которую ука­
зывает переменная ptr. Например, если переменная total содержит значение
3200, после выполнения последней инструкции переменная val будет содержать
значение 3200, поскольку это как раз то значение, которое хранится по адресу
100. Назначение оператора "*" можно выразить словосочетанием "по адресу".
В данном случае предыдущую инструкцию можно прочитать так: "переменная
val получает значение (расположенное) по адресу ptr".
I
186 Модуль 4. Массивы, СТРОI<И и YI<аэатели

Последовательность только что описанных операций выполняется в следую­


щей программе.

#include <iostrearn>
using narnespace std;

int rnain ()
{
int total;
int *ptri
int val;

total = 3200; // Переменной total присваиваем 3200.

ptr &totali // Получаем адрес переменной total.

val = *ptr; // Получаем значение, хранимое


// по этому адресу.
cout « "Сумма равна: " « val « '\n';

return О;

к сожалению, знак умножения (*) и оператор со значением "по адресу" обо­


значаются одинаковыми символами "звездочка", что иногда сбивает с то.1КУ
новичков В языке С++. Э111 операции никак не связаны одна с другой. Имейте
в виду, что операторы "*" и "&" имеют более высокий приоритет, чем любой из
арифметических операторов, за исключением унарного минуса, приоритет кото­
рого такой же, как у операторов, применяемых для работы с указателями.
Операции, выполняемые с помощью указателей, часто называют оneрацшu.cu
нenрямоzо доступа, поскольку оии позволяют получить доступ к одной перемеи­
ной посредством некоторой другой перемениой.

о важности базового типа указателя


На примере предыдущей программы была ноказана возможность присвоения
переменной val значения переменной total посредством операции непрямого
доступа, т.е. с использованием указателя. Возможно, при этом у вас возник во­
прос: "Как С++-компилятор узнает, сколько необходимо скопировать байтоо в
переменную val из области памяти, адресуемой указателем pt r?". Сформулиру­
ем тот же вопрос в более общем виде: как С++-компилятор передает надлежащее
С++: PYI(OBOACTBO для начинающих 187

количество байтов при ВhIЛолнении операции присоаивания с ИСПОЛЬ30ванием


указателя? Ответ звучит так. Тип данных, адресуемый указателем, определяется
базовым типом указателя. В данном случае, поскольку ptr представляет собой
указатель на целочисленный тип, С++-компилятор скопирует впеременную va1
из области памяти, адресуемой указателем ptr, 4 байт информации (что спра­
ведливо для 32-разрядной среды), но если бы мы имели дело с dоublе-указате­
лем, то в аналогичной ситуации скопировал ось бы 8 байт.

~ПРОСЫ дl\Я текущего I<ОНТРОЛЯ --------


1. Что такое указатель?

2. Как объявить указатель с именем va1Ptr, который имеет базовый тип


10ng int?
3. Каково назначение операторов" ,*" и .. & n при использовании с указателями? •
Переменные-указатели должны всегда указывать на соответствующий тип
данных. Например, при объявлении указателя типа int КОМШ1JlЯТОР "предпо­
лагает", что все значения, на которые ссылается этот указатель, имеют тип int.
С++-компилятор попросту не позволит выполнить операцию присваивания
с участием указателей (с обеих сторон от оператора присваивания), если тюTh!
этих указателей нссопмсстнмы (по сути, не одинаковы). Например, следующий
фрагмент кода некорректен.

int *р;
double f;
11 ...
р = &f; 11 ОШИБКА!

Некорректность ЭТОl'О фрагмента состоит в недоnyстимости присваивания


dоublе-указателя iпt-указателю. Выражение &f генерирует указатель на do-
ublе-значение, ар - указатель на целочислеиtlый тип int. Эти два типа несо­
вместимы, поэтому компилятор отметит эту инструкцию как ошибочную и не
скомпилирует программу.

1. Указатель - это объект, который содержит адрес памяти некотороro другого 06ъекта.
2. long int *valPtr;
3. Оператор" * " позволяет получить значение, хранимое по адресу, заданному ero
операндом. Онератор \\ &" возвращает адрес объекта. по которому расположен етО
операнд.
188 'МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели

Несмотря на то что, как было заявлено выше, при присваивании два указателя
должны быть совместимы по типу, это серьезное ограничение можно преодолеть
(правда, на свой страх и риск) с помощью операции приведения типов. Напри­
мер, следующий фрагмент кода теперь формально корректен.

int *р
double f;
/ / ...
р = (int *) &f; // Теперь формально все ОК!

Операция приведения к типу (int *) вызовет преобразование dоulйе­


к iпt-указателю. Все же использование операции приведения в таких целях
несколько сомнительно, поскольку именно базовый тип указателя определяет,
как компилятор будет обращаться с даННЫМI1, на которые он ссылается. В дан­
ном случае, несмотря на то, что р (после выполнения последней инструкции)
в действительности указывает на значение с IIлавающей точкой, компилятор по­
прежнему "считает", что он указывает на целОЧИСЛСНlIое значение (поскольку р
по определению - iпt-указатель).
Чтобы лучше понять, 110<leMY использование операции I1риведения типов при
присва.ивании одного указателя другому не всегда приемлемо, рассмотрим сле­

дующую I1рограмму.

// Эта про грамма не будет выполняться правильно.


#include <iostream>
using namespace std;

!i.nt main ()
{
double Х, У;
int *р;

Х = 123.23;
Р (int *) &Х; / / Используем операцию приведения типов
// для присваивания dоublе-указателя
// iпt-указателю.
// CnеД)'DЦИе две инструкции не ДaJD'J.' _enаекых резуm.'2!а'2!ОВ.
У = *р; // Что происходит при выполнении этой инструкции?
cout « У; // Что выведет эта инструкция?

return О;
С++: РУI<ОВОДСТВОДЛЯ начинающих 189

Вот какой результат генерирует эта программа. (У вас может ПОЛУЧ~ТI3СЯ дру­
гое значение.)

1.31439е+ОО9

Как видите, в этой программе переменной р (точнее. указателю на целочис­


ленное значение) присваивается адрес переменной х (которая имеет тип dou-
-
bl е). Следовательно, когда переменной у присваивается значение, адресуемое
указателем р, переменная у получает только 4 байт данных (а не все 8, требуемых
ДЛЯ dоublе-значения), поскольку р - указатель на целОЧl1сленный тип iлt. Та­
ким образом, при выполнении соut-Инструкции на экран будет выведено не чис­ :iS
са
:s:
ло 123 .23, а, как говорят программисты, "мусор". u
u
·0
j~
Присваивание значений с ПОМОЩЬЮ указателей
При присваивании значения области памяти, адресуемой указателем, сго
(указатель) можно использовать с левой стороны от оператора присваивания.
Например, при выполнении следующей инструкции (если р - указатель на це­
лочисленный тип)

*р = 101;
число 101 присваивается области памяти, адресуемой указателем р. Таким об­
разом, эту инструкцию можно прочитать так: "по адресу р помещаем значение
101". Чтобы инкрементировать или декрементировать значение, расположенное
в области памяти, мресуеМШI указателсм, можно ИСfIOЛI>ЗО[]ЗТЬ IШСТРУIЩIfIО, по­
добную следующей.

(*р)++;

Круглые скобки здесь обязательны, поскольку оператор "." имеет более низкий
приоритет, чем оператор "++".
Присваивание эначений с использованием указателей демонстрируется D сле­
дующей программе.

#include <iostream>
иsiлg namespace std;

int rnain ()
{
int *р, пurn;

р = &пит;

*р = 1 О О; 11 r Присваиваек перекениой D\DI1 чиcnо 100


190 Модуль 4. Массивы, СТРОI(И и указатели

// через ухазатen. р.
cout « пит « ' ';
(*р) ++; / / t- Ивхреиев'1'ИрУек значение переке_ой пшn
// через ухазатenъ р.
cout « пит « ' ':
(*р)--: // t- Дехрекеи'1'ИрУеи значение перекеиной num
// через указатеnь р.
cout « пит « '\n':

return О:

Вот такие результаты генерирует эта программа.

100 101 100

ВАЖНОI
1'111 ИСПОАIИQВQМ'48 ука3Qr8 А ей
в выражениях
Указатели можно использовать в большинстве допустимых в С++ выражений,
Но при этом следует при менять некоторые снециальные правила и не забывать,
что некоторые части таких выражений необходимо заключать в круглые скобки,
чтобы гарантированно получить желаемый [)(':'Iультат.

АрИфметические операции над указателями


с указателями можно использовать только четыре арифметических операто­
ра: ++, --, + и -. Чтобы лучше понять, что ПРI)ИСХОДИТ при выполнении арифме­
тических действий С указателями, начнем с примера. Пусть pl - указатель на
int-переменную с текущим значением 2000 (т.е. pl соДсржит адрес 2000). После
выполнения (в 32-разрядной среде) выражеНI1Я

pl++:
содержимое переменной-указателя р 1 станст равным 2 004, а не 2 0011 Дело в
том, что при каждом инкрементировании указатель pl будет указывать на сле­
дующее int-значение. Для операции декрементирования справедливо обратное
утверждение, Т.е. при каждом декрсментирооаш/И значение рl будет уменьшать­
ся на 4. Например, после выполнения инструкции

рl--:

указатель рl будет иметь значение 1 996, еслн до этого оно было равно 2000.
С++: руководство ДЛfJ начинающих 191

Итак, каждый раз, когда указатель инкрементирустся, он будет указывать на


область памяти, содержащую следующий элемент 6а'ЮВОГО пта этого указателя.
А при каждом декрементировании он будет указывать на область памяти, содержа­
~
о-
щую преДЫДУЩИЙ элемеlП базового типа этого указателя. Для Уlса;:штелей на сим­ О
м
О
вольные значения результат операций инкрементироваюlЯ и декрементироваJiИЯ :.:
>-
будет таким же. как при "нормальной" арифметике. поскольку символы занимают :s:
:s:
только один байт. Но при использовании любого дрyroго типа указателя при ИШЧJe­ :.:
О
ментировании или декрементировании значение переменноli-указателя будет уве­ ...u
Q.

личиваться или уменьшаться на величину, равную размеру его базового типа. :а


са
Арифметические операции над указателями не ограничиваются использова­ s
u
нием операторов инкремента и декремента. Со значениями ука.lателеЙ можно u
о
выполнять операции сложения и вычитания, используя D качестве второго опе­ ~

ранда целочислеННhlе значения. Выражение

рl = рl + 9;
заставляет рl ссылаться на девятый элемент 6азового типа указателя рl относи­
тельно элемента, на который рl ссылался до выполнения этой ИНСТРУКЦЮI.
Несмотря на то что складывать указатели нельзя, один указатель можно вы­
честь из другого (если они оба имеют один и тот же 6азовый тип). Разность пока­
жет количество элементов базового типа, которые разделяют эти два указателя.
Помимо сложения указателя с целочисленным значением и вычитания его из
указателя, а тЗI<ЖС вычисления разности двух указателей, над указателями ника­
кие другие аРИф~fетичеСКllе операции не выl1лняются•. Например, с указателями
нельзя складывать Цоа t - или dоublе-зиачения.
Чтобы понять, как формируется результат выполнения арифметических опе­
раций нал указателями, выполним следующую короткую программу. Она выводит
реальные физические адреса, которые содержат указатель 11а int-значение (i) и
указатель на dоublе-значение (f). Обратите внимание на каждое изменение адре­
са (зависящее от базового типа указателя), которое происходит при повторении
цикла. (Для большинства З2~разрядных компиляторов значение i будет увеличи­
ваться на 4, а значение f - на 8.) Отметьте также, что при использовании указателя
в соut-инструкции его адрес автоматически отображается 8 формате адресации,
применяемом для текущего процессора и среды выполнения.

#include <iostream>
using namespace std;

int main ()

int Ч, j [10];
192 Модуль 4. Массивы, СТРОI<И и УI(азатели
................................................................................................................................

double *f, ч(10];


int Х;

i j;
f Ч;

for(x=O: х<10: х++)


cout « i+x « ' , « f+x // Отобр. .аеи адреса,
« '\п': // попучеввые з рез~.та~е
/ / CJIo*elUUl зВ&че_ х
,,/ / с ха*дюс указатеnеи.
return О:

Вот один из позможных резуJIьтатов ВЫlIолнения этой ПР011>аммы (у вас М,ХУТ


быть другие значения).
0012F724 0012F74C
OO12F'728 0012F754
0О12Р72С 0012F75C
0012F730 0О12Р764
OO12F734 OO12F76C
0012F738 OO12F774
ОО12F7ЗС OO12F77C
0012F740 OO12F784
0012F744 0012F78C
OO12F748 0012F794

Сравнение указателей
Указатели можно сравнивать, использун операторы отношсния ==, < И >.
Однако для :roro, чтuGы результат сравнения указателей поддавался интер­
претации, сравниваемые указатели должны быть каким-то образом связаны.
Например, если указатели ссылаются на две отдельные и никак не связанные
переменные, то любое их сравнение в общем случае не имеет смысла. Но если
они указывают на пеrемснtIые, мсжду которыми существует не которая связь

(как, например, между элементами одного 11 того же массива), то реЗУ:lьтат


сраDнения этих указателей может иметь определенный смысл (пример такого
сравнения вы увидите в проекте 4.2). При этом можно говорить о еще ОДIIОМ
типе сравнения: любой указатель можно сравнить с нулевым указателем, ко­
торый, по сути, равен нулю.
С++: PYI<OBOACTBO для начинающих 193

~ПРОСЫ МЯ теl<ущего КоНТроля--------


t. С каким типом связаны все арифметические действия над указателями?
2. На какую величину возрастет значение dоublе-указателя при его инкре­
меlпировании, если предположить, что тип double занимает 8 байт?
3. В каком случае может иметь смысл сравнение двух указателей?·

ВАЖНОI
111' Vказ nТМ l4.14ма ссивы
в С++ указатели и массивы тесно связаны между собой, причем настолько,
что зачастую понятия "указатель" и "массив" взаимозаменяемы. В этом разделе
мы попробуем проследить эту связь. Для начала рассмотрим следующий фраг­
мент программы.

char str [80];


char *рl;

рl = str;
Здесь str предстапляе·г соБОl1 имя массива, содержащего 80 символов, а рl -
УI<а:1атель на ТIШ char. Особыii интерес представляет третья строка, при выпол­
неllИИ которой переменной р 1 присваивается адрес первого элемента массива
str. (Другими словами, после этого присваивания рl будет указывать на эле­
мент str [О].) Дело в том, что в С++ использование имени массива без индекса
генерирует указатель из первый элемеит этого массива. Таким обра.10М, при вы­
полнеllИИ присваивания

рl = str
адрес str[Q] присваивается указателю рl. Это и есть ключевой момент, кото­
рый необходимо четко понимать: неиндексированное имя массива, использован­
ное в выражении, означает указатель на начало этого массива.

Поскольку после рассмотренного выше присваивания рl будет указывать на


начало массива str, указатель рl можно использовать для доступа к элементам

1. Все арифметические действия над указаreлям:и связаны с базовым пшом указателя.


2. Значение указателя увеличится на 8.
з. Сравнение двух указателей может иметь смысл в случае, когда они ссwaJOТCЯ на
один И тот же объект (например, массив).
194 Модуль 4. Массивы, СТРОI<И и УI<азатели
................................................................................................................................

этого массива: Например, чтобы получить доступ к пятому элементу массива


str, используйте одно из следующих выражений:

str[4]
или

* (p1+4)
В обоих случаях будет выполнено обращение к пятому элементу. Помните,
что индексирование массива начинается с нуля, поэтому при индексе, равном че­

тырем, обеспечивается доступ к пятому элементу. Точно такой же эффект произ­


водит суммирование значения исходного указателя (рl) с числом 4, ПОСКОЛЬКУ
рl указывает на первый элемент массива str.
Необходимость использования круглых скобок, в которые заключено выра­
жение рl+4, обусловлена тем, что оператор "*" имеет более высокий приори'rет,
чем оператор "+". Без этих круглых скобок выражение бы свелось к получению
значения, адресуемого указателем рl, Т.е. значения первого элемента массива,
которое затем было бы увеличено на 4.
В действительности в С ++ предусмотрено два Сllособадоступа к элементам масси­
вов: с помощью U71дексuроваlllJ.Я J.IaСС/ЮО6 и аpuф.меmUIСU указателей. Дело в том, что
арифметические операции ШIД ука:ытCJlЯМИ ШЮГ/J:а выполняются быстрее. чем ИН­
дексирование массивов, особенно при доступе к элементам, расположение которых
отличается строгой упорядочешlOСТЫО. Поскольку быстродействие часто является
определяющим фактором при выборе тех или иных решений в программировании,
то использование )'1<азателей для досryпа!( элеМ('lIтам массипа - характерная осо­
бенность мноПiХ С++-программ. Кроме того, ИНOIда указатели позволяют написать
более компактный код по сравнению с использованием индексирования массивов.
Чтобы лучше понять различие между испольэованием индексирования масси­
вов и арифметических операций Над указателями, рассмотрим две версии одной
и той же программы, которая реверсирует регистр букв в строке (т.е. строчные
буквы преобразует в прописные и наоборот). В первой версии используется ИН­
дексирование массивов, а 80 второй - арифмеТlIка указателей. Вот текст первой
версии программы.

// Реверсирование регистра букв с по~ощью


// индексирования массивов.

_include <iostream>
_include <cctype>
using namespace stdi

int main ()
С++: PYI(OBOACTBO дl\Я начинающих 195

int i;
char 5tr[80] = "Thi5 I5 А Test";

cout « "Исходная строка: " « str « "\n";

for(i = О; str[i); i++) (


if(isupper(str[i))
str[i] = tolower(str[i);
else if(islower(str[i]»
str[i) = toupper(str[i);

cout « "Строка с инвертированным регистром букв: "


« str;

return О;

Результаты выполнения этой программы таковы.

Исходная строка: This Is А Test


Строка с инвертированным регистром букв: tHIS iS а tEST
Обратите внимание на то, что в программе для определения регистра букв
используются библиотеЧllые функции isupper () и islower (). Функция
i supper () возвращает значение ИСТИНА, если ее аргумент представляет собой
прописную букву, а функция islower () возвращает значение ИСТИНА, если
ее аргумент представляет собой строчную букву. На каждом проходе цикла for с
помощью индексирования массива 5t r осуществляется доступ к очередному его

элементу, который подвергается проверке регистра с последующим изменением


его на "противоположный". Цикл останавливается при доступе к завершающему
нулю массива str, поскольку нуль означает ложное значение.
А вот версия той же программы, пере писанной с использованием арифметики
указателей.

1/ Реверсирование регистра букв с помощью


// арифметики указателей.

#include <iostrearn>
tinclude <cctype>
using narnespac'e std;

int main ()
196 Модуль 4. Массивы, СТРОI<И и указатели

char *р;
char str[80] = "This I5 А Test";

cout « "Исходная строка: " « str « "\п";

р = str; // Присваиваем указателю р адрес начала массива.

while (*р)
if (isupper (*р))
*р = tolower(*p);
else if(islower(*p)) // Дoc~ к массиву _tr через
// указатeJП..
*р = toupper(*p);
р++;

cout « "Строка с инвертированным регистром букв: "


« str;

return О;

в этой версии указатель р устанавливается на начало массива str. Затем бук­


ва, на которую указывает р, проверяется и изменяется соответствующим обра­
зом в циклеwhile. Цикл остановится, когда р будет указывать на завершающий
нуль массива 5tr.

у этих программ может быть различное быстродействие, что обусловлено осо­


бешlOСТЯМИ генерирования кода С++-компиляторами. Как правило, при исполь­
зовании индексирования массивов генерируется более длинный код (с большим
количеством машинных команд), чем при выполнении арифметических действий
над указателями. Поэтому lIеудивительно. что в профессионально написанном
С++-коде чаще встречаются версии, ориентированные на обработку указателей.
Но если вы - начинающий программист, смело используйте индексирование
массивов, пока не научитесь свободно обращаться с указателями.

Индексирование указателя
Как было показано выше, доступ к массиву можно получить путем выполне­
ния арифметических действий над указателями. Интересно то, что в С++ указа-

С++: РУI<ОВОДСТВО ДЛЯ начинающих 197
....•...••....•.......••.••....•.................•............................................................................

тель, который ссылается tla массив, можно индексировать так, как если бы это
было имя массива (это говорит о тесной связи Между указателями и массивами).
Рассмотрим пример, который представляет собой третью версию программы из­
менения регистра букв.
// Индексирование указателя подобно массиву.
#include <iostream>
#include <cctype>
using namespace std;

int main ()
{
char *р;
int i;
char str[80] = "This Is А Test";

cout « "Исходная строка: " « str « "\п";

р = str; // Присваиваем указателю р адрес начала массива.

// now, index р
for(i = О; p(i); i++) (
if(isupper(p[i]))
p[i] = tolower(p[i)); / / f - Используем р !Сак еcnи бы
else if(islower(p[i])) // это было имя массива.
p[i) = toupper(p[i]);

cout « "Строка с инвертированным регистром букв: "


« str;

return О;

При выполнении эта программа создает сhаr-указатель с именем р, а затем


присваивает ему адрес первого элемента массива str. В цикле for указатель
р индексируется с использованием обычного синтаксиса индексирования мас­
сивов, что а6солюrnо допустимо, поскольку в С++ выражение р [ i] по своему
действию идентично выражению'" (р+ 1) . Этот пример прекрасно иллюстрирует
тесную связь Между указателями и массивами.
198 МОДУЛЬ 4. Массивы, СТРОI(И и указатели

~просы МЯ текущего КоНтрОЛS1 _ _ _ _ _ _ __


1. Можно ли к массиву пол)"lить доступ через указатель?

2. Можно ли индексировать указатель подобно массиву?


3. Какое назначение имеет само имя массива, использованное без индекса?·

Строковые константы
Возможно, вас удивит способ обработки С++-компиляторами строковых KOIf-
стант, подобных следующей. I
cout « strlеп("С++-компилятор");

Если С++-компилятор обнаруживает строковый литерал, он сохраняет его


в таблице строк программы и генерирует указатель на нужную строку. Поэто­
му следующая программа совершенно корректна и при выполнении RЫВОДИТ на

экран фразу Работа с указателями - сплошное удовольствие!.

#include <iostream>
using namespace std;

int main()

char *ptr;

// Указатеn~ ptr присваиваетск ацрес строковой


// константы.
ptr = "Работа с указателями - СПЛО:l1ное удовольствие!\п";

cout « ptr;

return О;

1. Да. к массиву можно получить доступ через указатель.

2. Да, указатель можно индексировать подобно массиву.

З. Само имя массива. использованное без индекса, генерирует указатель на первый


элемент этого массива.
С++: руководство ДМ) начинающих 199

Спросим у опытного программиста

Вопрос. Можно Jlи утверждать, что указатели и AlйCCU8Ы вэаWWЗtlAllШRe.AtЫ?


Ответ. Указатели и массивы очень тесно связаны и во многих случаях вэаимо­
заменяемы. Например, с помощью указателя, который содержит адрес
начала массива, можно получить доступ к элементам этого массива либо
I
а
5-
:s:
посредсгвом арифметических действий над указателем, либо посредс1'Вt)м
индексирования массива. Однако утверждать, что указатели и массивы
взаимозаменяемыми в общем случае нельзя. Рассмотрим, например, та­
кой фрагмент кода.
I
:зi
lr:I
:s:
int num[10]; u
u
int i; а
~

for (i=O; i<10; i++) {


*num = i; // Здесь все в порядке_
пит++; // ОШИБКА! Переменную num
// модифицировать нельзя.

Здесь используется масClШ целочислеНlIЫХ значений с именем num. как от­


мечено в комментарии, несмотря на то, что сопершеllllO приемлемо приме­

ИИ"IЪ к имени n um оператор" *" (который обычно ПРИМСllяется к указателям),


абсолюто недопуcntМо модифицировать значение num. дело в том, что
num - это константа, которая указьmает на начало массиnа. И ее, следооа­
телыю, инкреМСН11rронать НlJЮlК НеЛЪ.1Я. Дрyrnмli словами, несмотря на то,
что имя массиna (без индекса) леЙС11ШТелыlO генерирует указатель на начало
массива, его значение IIЗменениlO не подлежlff. Хотя имя массива генерирует
констa.my-указатсль, его, тем не менее, можно (подобно указателям) вклю­
чать в выражения, если, конечно, оно при этом не модифицируется. Напри­
мер, следующая ИНСТРУIЩИЯ, при вьmолнении которой элемеmy пит [ З]
присвaиnaется значение 100, вполне допустима.
* (num+3) = 100; // Здесь все в порядке,
// поскольку num не изменяется.

При выполнении этой программы символы, образующие строковую констан­


ту, сохраняются в таблице строк, а персменной ptr присваивается указатель H~
соответствующую строку в этой таблице.
Поскольку указатель на таблицу строк конкретной программы при использо­
вании строкового литерала генерируется автоматически, то можно попытаться

использовать этот факт для модификации содержимоro данной таблицы. Однако


такое решение вряд ли можно назвать удачным. Дело в том, ЧТО С++-компиля-
200 МОДУЛЬ 4. Массивы, СТРОI<И и указатели

торы создают оптимизированные таблицы. в которых один строковый литерал


может использоваться D двух (или больше) ра:JЛИЧНЫХ местах программы. По­
этому "насильственное" изменение строки мож('т вызвать нежелательные побоч­
ные эффекты.

:- s·"t-··R"- ..._""-
: r ev.cpp
1 Выше отмеча.lЮСЬ, что сравнение указателей имеет смысл,
06
если

---- сравниваемые указатели ссылаются на один и тот же ъект, на-

пример массив. Допустим, даны два указателя (с именами А и В), которые ссыла­
ются на один и тот же массив. Теперь, когда вы lIонимаете, как могут быть связа­
ны указатели и массивы, можно применить сравнение указателей для упрощения
некоторых алгоритмов. В этом проекте мы рассмотрим один такой пример.
Разрабатываемая здесь программа реверсирует содержимое строки, Т.е. меня­
ет на обратный порядок следования ее СИМВОЛОIl. Но вместо копирования СТРOJ~и
в обратном порядке (от конна к началу) о ltpyroii массиu, она реверсирует содер­
Жll:\юе строю! DHYTPIl JlI<\СС!ШU, l<UTopbli'( ее СОДСРЖIIТ. ДЛЯ реализации Dышеска­
заlllЮГО программа llспользует дnе переменныс типа у«азаТель. Одна указывает
на начало массива (еl"О пероыi1 элемент), а вторая - на еl"О конец (его последний
элемент). UИl<Л, используеl\(Ы(~1 в прогрuмме, продолжается до тех пор, пока ука­
затель на начало строки меньше указателя на ее конец. На каждой итерации ЦИК­
ла символы, на I<OTOPbIC ссылаются эти указатели, меняются местами, после че,'о

обеспечивается настройка указателей (путем инкрементирования и декрементн­


роваlШЯ) на слеДУЮЩllе СIfМnОЛЫ. Когда указатель на начало строки в результате
сравнения становится больше или paUIIbIM указателю на ее конец, строка OI<аЗlt\­
вается инвертированной.

Последовательность действий
1. Создайте файл с именем StrRev. срр.

2. Введите в этот файл следующие строки кода.

/*
Проект 4.2.
Реверсирование содержимого строки "по месту".

*/
tinclude <iostream>
tinclude <cstring>
using namespace stdi
С++: РУКОВОДСТВО дl\Я начинающих 201

int main ()
{
~
~
char str[] "это простой тест"; о
с')

char *start, *end; о

int len;
5-
s
s
char t; ::.::
О

Строка, подлежащая реверсированию, содержится в массиве str. Для до­


~
ступа к нему используются указатели start и end. J5
111
S
3. Введите в файл программы код, предназначенный для отображения исхо­ U
U
дной строки, получения ее длины и установки начальных значений указа­ .0
:~
телей start и end.
cout « "Исходная строка: " « str « "\п";

len = strlen(str);

start = str i
end = &str[len-l];
ОбраТIIТС ВШlМаllIlС lIа то, что у[(~атCJlЬ end ссылается на последний сим­
вол строки, а не на признак ее завершения (нулевой символ).


4. Добавьте код, который обеспечивает реверсирование символов в строке .

while(start < end)


// обмен символов
t = *starti
*start = *end;
*end = ti

// настройка указателей на следующие символы


start++i
end--;

Этот процесс работает следующим образом. До тех пор пока указатель


start ссылается на область памяти, адрес которой меньше адреса, содер­
жащегося в указателе end, цикл while продолжает работать, т.е. менять
местами символы, адресуемые указателями start и end. Каждая итерация
цикла заканчивается ин крементированием указателя s t а r t и декременти­
рованием указателя end. В тот момент, когда значение указателя start
202 Модуль 4. Массивы, строки и указатели

станет больше или равным значению указателя end, можно утверждать,


что все символы строки реверсированы. Поскольку указатели start и erld
ссылаются на один и тот же массив, их сравнение имеет смысл.

5. Приведем полный текст программы StrRev. срр.


/*
Проект 4.2.
Реверсирование содержимого строки "по месту".

*/
#include <iostream>
#include <cstring>
using namespace std;

int main () .
(
char str [) "это простой тест";
char *start, *end;
int len;
char t;

cout « "Исходная строка: " « str « "\п";

len = strlen(str);

start = str;
end = &str[len-l];

while(start < end)


// обмен символов
t = *start;
*start = *end;
*end = t;

// настройка указателей на следующие символы


start++;
end--;

cout « "Строка после реверсирования: " « str « "\


С++: РУКОВОДСТВО МfI начинающих 203

п" ;
rеtцrп О;

Результаты выполнения этой программы таковы.

Исходная строка: это простой тест


С.трока после реверсирования: тсет йотсорп отэ

Массивы указателей
Указатели, подобно ДillШЫМ других типов, могут храниться в массивах. Вот,
например, как выглядит объявление 10-элементного массива указателей на int-
значения.

int *pi[10];
Здесь каждый элемент массива pi содержит указатель на цело численное значение.
Чтобы присвоить адрес i n t -перемеи ной с именем v а r третьему элементу это­
го массива указателей, запишите следующее.

int var;
рН2] = &var;
Помннте, что здесь pi - массиu указатслсi1на цеЛО'lислеlШЫС значения. Элемен­
ты этого массива могут содержать только значения, которые представляют собой
адреса персмеНIIЫХ ЦСЛОЧИСЛСIl1IОГО ТllПа, а не сами значення. Вот поэтому пере­
меннаяvаr предваряется оператором" &".
Чтобы узнать Зllачение lIеремеНIIОЙ var С 110МО~ЬЮ массива pi, используйте
такой синтаксис.

*pi[2]
Поскольку адрес переменной var хранится n элементе pi [2] , применение опе­
ратора "*" к этой индексированиой персменной и позволяет получить значение
переменной var.
Подобно другим массивам, массивы указателей можно инициализировать.
Как правило, инициализированные массивы указателей используются для хра­
нения указателей на строки. Рассмотрим пример исполъзования двумерного мас­
сива символьных указателей для СОЗДilllИЯ неболъшого толкового словаря.

11 Использование двумерного массива указателей


11 для создания словаря.

#include <iostream>
#include <cstring>
204 МОДУЛЬ 4. Массивы, СТРОI<И и указатели

using namespace std;

int main ()
11 Д-укерlDlЙ ка.ссив chаr-укаsатenей ДnK адресации
1/ пар строк.
char *dictionary[] [2] = {
"карандаш", "Инструмент для письма или рисования.",
"клавиатура", "Устройство ввода данных.",
"винтовка", "Огнестрельное оружи.=.",
"самолет", "Воздушное судно с неподвижным крылом.",
"сеть", "Взаимосвязанная группа компьютеров.",
" " , ",.
};
char word[80];
int i;

cout « "Введите слово: ";


cin » word;

for (i = О; *dictionary[iJ [OJ; i++)


// Дл. отыскани. определени. в массиве dictionary
// выполи.етс. поиск Значения строки word. Если
// обнаруживаетс. совпадение, отображаетс. определение,
1/ соответстз~е найдекиоиу спОВУ.
if ( ! strcmp (dictionary [i J [О J, wor j) )
cout « dictionary[i] [1] « "\:1";
break;

if(!*dictionary[iJ [О])
cout « word « " не наЙдено.\п";

return О;

ВОТ пример выполнения ЭТОЙ програмМЫ.

Введите слово: сеть

Взаимосвязанная группа компьютеров.


С++: РУКОВОДСТВО ДЛЯ начинающих 205

При создании массив dictionary инициализируется набором слов и их зна­


чений (опред~ениЙ). Вспомните: С++ запоминает все строковые константы в
таблице строк, связанной с конкретной программой, поэтому этот массив нужен
только для хранения указателей на используемые в программе строки. Функци­
онирование словаря заключается в сравнении слова, введенного пользователем,

со строками, хранимыми в словаре. При обнаружении совпадения отображается


строка, которая является его определением. В противном случае (когда заданное
слово в словаре не найдено) выводится сообщение об ошибке ..
Обратите внимание на то, что инициализация массива dictionary заверша­
ется двумя пустыми строками. Они служат в качестве признака конца массива.
Вспомните, что пустые строки содержат только завершающие нулевые символы.
Используемый в программе цикл for продолжается до тех пор, пока первым
символом строки не окажется нуль-символ. Это условие реализуется с помощью
такого выражения.

*dictionary[i] [О]

Индексы массющ з<Щают указатель на строку. Оператор "*" по:зволяет получить


СIII\IВОЛ
110 указаllllОМУ адресу. Если ЭТОТ символ окажется нулевым, то выражение
*dictionary [i] [О] будеТОЦСllено каl(ЛОiЮЮе., 11 ЦИI<Л завершится. В П[ЮТИ!lНОМ
случае это выражеНllе (как истинное) дает "добро" на продолжение цикла.

Соглашение о нулевых указателях


Объявленный, но не инициализированный указатель буде·г содержать про­
IIЗIюлыюе значеНJlе. При попытке I1СПОJlUЗОIJа·1Ъ указателu дО ПРИСВОСIIНН ему
КOlШРСТlIOГО значения можно раЗРУIllIIТI> Ile толыш соБСТВСIiИУЮ программу, но
даже 11 операционную систему (отвратнтслыlйший,' надо сказатu, тип ошибки!).
Поскольку не существ уст гарантированного Сllособа избежать использования не­
I-!-нициализироваНIlОГО Уlшзателя, С++-программисты приняли процедуру, кото­
рая позволяет избегать таких ужасны·х ошибок. По соглашению, если указатель
содержит нулевое значение, считается, что он ни на что не ссылается. Это значит,
что, если всем неиспользуеМbIМ указателям присваивать нулевые значения и из­

бегать применения нулевых указателей, можно предотвратить случайное исполь­


зование неинициализированного указателя. Вам также следует придерживаться
этой практики программироваНIiЯ.
При объявлении указатель любого типа можно инициализировать нулевым
значением, например, как это делается в следующей инструкции.

float *р = О; /1 р теперь нулевой указатель.


206 Модуль 4. Массивы, СТРОI(И и УI(аэатели

Для тестирования указателя используется инструкция if (любой из следую­


ЩИХ ее вариантов).

if(p) // Выполняем что-то, если р - не нулевой указатель.

if(!p) // Выполняем что-то, если р - нулевой указатель.

~npocы ДЛЯ текущего контроля --------


1. Используемые в программе строковые константы сохраняются в _ __
2. Что создает объявление floa t * f ра [ 18] ;?
з. По соглашению указатель, содержащий нулевое значение, считается не­
используемым. Так ли это? •

ВАЖНОI
~_~~!LМног.о.v:РDвне,вая.иenр.ЯМая
адресация
Можно создать указатель. который будет ссылаться на другой указатеЛJ"
а тот - на коне'шое значсние. Эту ситуацию назыпают цепочкой указателей,
лmОlOУРО611саой lIеnрямой адресацией (multiple iJldirection) или использовани.ем
указа.теля иа указатель. Идея многоуровневой непрямой адресации схематично
ПРОIlЛЛЮСТрIlроn:ша на рис. 4.2. Как DНДIIТС, значение обычного указатсля (при
ОДllOУРОDIlспоilнспрямоii а..'l[)есаЦlШ) предстаDляет собой адрес псремешIOЙ, ко­
торая содсржнт HCI<OTOPOC Зllа'IСНИС. В случае применеиия УI<азателя на указа­
тель псрвыii содсржит адрес оторого, а тот ссылается на переменную, содержа­
щую определенное значение.

При использовании непрямой адресации можно организовать любое жела­


емое количество уровней, во, как правило, ограничиваются лишь двумя, по­
скольку увеличение числа уровней часто ведет IC возникновению концептуаль­
nыхоши60к.

1. Используемые в программе строковые KOHCТCUn'Ы сохраняются в таблице строК.

2. ЭТО объявление создает 18-элементный массив указателей на flоаt-значения.


з. Да, указатель, содержащий нулевое значение, считается неиспользуемым.
С++: руководство МЯ начинающих 207

свАоАТАЩ:"

1.%a[IA6 ...--+1.1 «I*-AI~ I


свАоАТАII*-

1.%aOA6 1 .1 l."IooiJA6 ·I«I*-AI~


АI6"оО,_~о,IА,*-IА6L]",*- ~IJAOr~

Рис.42. Одноуровневая и .мноzоуровневая 1IenрR.lllая


адресация

Переменную, которая является указателем на указатель, нужно объявить со­


ответствующим образом. Для этого достаточно перед ее именем поставить до­
полнительный символ "звездочка"( *). Например, следующее объявление сооб­
щает компилятору о TQM, что balance - это указатель на указатель на значение
типа int.
int **balance;
Необходимо по~шиТl), что перемснная halance здесь - не указатель на целочис­
ленное значеНIIС, а УКLlзаТСJII) lIа указатель на iпt-значснис.

Чтобы ПОЛУЧIIТЬ ДОСТУI1 К значению, адрссуемому указателем на указатель, не­


оБХОДIIМО ДВCtЖДI,I П[Ш\IСIlIIТЬ оператор" *", Kal( показано в следующем примере.

11 Использование многоуровневой непрямой адресации.


#include <iostream>
using паmезрасе std;

int main ()
{
int х, *р, **q;

х = 10;

р &х; / / Приса . . . .ек перемвнной р адрес п&рек8КВОЙ х.

q = &р; 11 присванаавк пврекеикой q адрес перемеиной р.

cout « **q; 11 Выводим значение переменной х. (Доступ х


11 sваче~ перекенной х иw получаем через
208 МОДУЛЬ 4. Массивы, строки и указатели

// ухазатen. q. Обра~. вникавие на


// испо.Jlltsовавие дар .CPJМВOJIOB "*".)

return О;

Здесь переменная р объявлена как указатеЛI. на iпt-значение, а переменная


q- как указатель на указатель на i n t -значение. При выполнении этой програм­
мы на экран будет выведено значение переменной х. Т.е. число 10.

Спросим V опытного программиста


Вопрос. "НеоcmОРОЖllое" 06раЩf!lIие с .tIказатеJlД~1U, учитывал Ш' огРОМlIы,е воз­
МOЖIЮсти, может иметь разруши11lt!лыlеe последствия для npozpaA4MIJi.
Как можно избежать ошибок при работе с указателями?

Ответ. Во-первых. перед ис.ПОJlЬзоваlЩем ука3<lтеля необходимо убедиться, что


он IIн~щиал}(з){ропан, Т.е. действительно указывает на сущеетuующее зна·
чение. Во-вторых, следует позаботиться о том, чтобы тип объекта, адре­
суемого указателем, совпадал с его баЗОJlЫМ ТИIIОМ. В-третьих, не ПЫПОJl­
НЯIlте опсраЦlI1I с IIСПОЛЬЗОllаНJlСМ IIУЩ~ВЫХ укаЗ<JтслеН. Не ззuьшайтс
нулевой указатель означает, что 011 ни на ЧТО lIе укзаыоает. Наконец, II~
l1ыполняйте с УК3З<tП'ЛЯМН опер3lЩИ ПРlll1едеllШI ТlIЩl "только для TOrU,
чтобы программз скомпилиропалась". О(JЫЧНО ошибки, связанные с lIесо­
ОТВСТСТВllем ТIIПОВ указателей, СDlщеП'!ll ствуют о яоноii недоработке про·
граммнста. Практика lIоказьшает, что IJр"веДСJIIIС одного пша УI,зэзтеля IC
другому требуется лишь в особых 06СТОI!П'ЛЬСтвах.

1. Покажите, как объявить массив h i gh t етр s для хранения 3 1 элемента типа


short int?
2. В С++ И14дексирование всех массивов начltиается с _ _О
3. Напишите программу, которая находит и' отображает значения-дубликаты
в 10-элементном массиве целочисленных значений (если таковые в нем
присутствуют).

4. Что такое строка с завершающим нулем?


5. Напишите программу, которая предлагает польЗователю ввести две строки.
а затем сравнивает их, игнорируя "реПtстрооые" различия, Т.е. прописные и
строчные буквы ваша программа должна 80спринимать как одинаковые.
С++: PYI(OBOACTBO M~ начинающих 209
...............................................................................................................................
-....

6. Какой размер должен иметь массив-приемник при использовании ФУНК-


цИИ strcat () ?
7.
8.
9.
Как задается каждый индекс в многомерном массиве?

Покажите, КaJ( инициалИЗИРОВЗTh iлt-массив лuтs значениями

Кш<Ое принципиальное преимущество имеет объявление безразмерного


массива?
5,66 и 887
I
>-
s
~
о
10. Что такое указатель? Какие вы знаете два оператора, используемые с ука­
зателями?
~
j
111
11. Можно ли индексировать указатель подобно массиву? Можно JЦI п(),лучить S
U
U
доступ к элементам массива посредством указателя? о
~
12. Напишите программу, которая подсчитывает и отображает на экране KOJm-
чество ПРОПИСIJЫХ букв В cTpol<e.
13. Как называется ситуация, когда используется указатель, который ссылает­
ся на другой?

14. Что означает нулевой указатель в С++?

i\.. OTU~Tbl Ш1 ЭПI ~UllpOC.bl МОЖllО lIаЙТll на Wcb-странице дашюй


На заметку Л ... ЮiИlИ по адресу. http./ /www.osborne.com.
Модуль5
Введение в фУНI<ЦИИ

5.1. Общий формат С++-функций


5.2. Создание функции
5.3. Использование аргументов
5.4. ИСПОЛЬЗ0Ианис инструкции return
5.5. Использование функций в выражениях
5.6. ЛокаЛl,Ная обласТl. видимости
5.7. Глобальная область видимости
5.8. Передача указателей и массивов в качестве аргументов функций
5.9. Возвращение функциями указателей
5.10. Передача аргументов командной строки функции rnain ()
5.11. Прототипы функций
5.12. Рекурсия
212 МОДУЛЬ 5. Введение в функции

в этом модуле мы приступаем к углубленному рассмотрению функций.


Функции - это строительные блоки С++, а потому без полного их понимания
невозможно стать успешным С++-программистом. Здесь вы узнаете, как создать
функцию и передать ей аргументы, а также о локальных и глобальных перемен­
ных, 06 областях видимости функций, их прототипах и рекурсии.

ОСНОВЫ использования функций


Функцuя - это подпрограмма, которая содержит одну или несколько С ++-
инструкций И выполняет определенную задачу. Каждая из приведенных выше
программ содержала одну функцию main (). Функции называются СТРОИТI~ЛЬ­
ными блоками С++, ПОСI<ОЛЬКУ программа в С++, как правило, представляС1 со­
бой коллекцию фУНlщий. Все действия программы реализованы в виде ФУНКI\ИЙ.
Таким образом, функция содержит инструкции, которые с точки зрения человека
составляют исполняемую часть программы.

Очень простые программы (как представлеНllые здесь до сих пор) содержат толь-
1<0 одну функцию main () , в то время I<aк большинство других включает нссю)лько
фуrшпнii. ОltJ[~ШО IЮ~fмсrЧl'СIClIС ПJюграммы ""СЧllТывают СОТНII ФУlllщшUr.

ВАЖНОI

~.,z,О_бщиЙ~ормаr_С:.+_~"фу,.нl{ЦИЙ
Всс С++-Функции имеют такой общий формат.
тил_возвращаемого_значения имя (список_лараметров)

11 тело функции

с помощью элемента тил_возвращаемого_эначенияуказывается тип значс­


ния, возвращаемого функцией. Это может быть практически любой тип, за ис­
ключением массива. Если функция не возвращает никакого значения, необхо­
димо указать тип void. Если функция действительно возвращает значение, оно
должно иметь тип, совместимый с указанным в определении функции. Каждая
функция имеет имя. В качестве имени можно использовать любой допустимый
идентификатор, который еще не был задействован в программе. После имени
функции в круглых скобках указывается список параметров, который представ­
ляет собой последовательность пар (СОСТОЯЩIIХ из типа данных и имени), раз­
деленных запятыми. Параметры - это, по сути, переменные, которые получают
С++: PYI<OBOACTBO ДМ! начинающих 213

значение аргументов, передаваемых функции при вызове. Если функция не име­


ет параметров, элемент список_параметро13 отсутствует, т.е. круглые скобки
s
остаются пустыми.
~
В фигурные скобки заключено тело функции. Тело функции составляют С++­
инструкции, которые определяют действия функции. ФУJIКЦИЯ завершается !
са

(и управление передается вызывающей процедуре) при достижении закрываю­ ф


S
щей фигурной скобки ИЛИ инструкции return.

J
111 Создание фVНКII.kU4
Функцию создать очень просто. Поскольку все функции исполъзуют один И
тот же общий формат, их структура подобна структуре фуикции main (), с кото­
рой нам уже приходилось иметь дело. Итак, начнем с простого примера, который
содержит две фушщии: main () и myfunc (). Еще до выполнения этой програм­
мы (или чтения послеДУlощего описания) внимательно изучите ее текст и попы­
тайтесь предугадать, что она должна отобра.lИТЬ иа экране.

/* Эта программа содержит две Функции: main()


и myfunc().
*/
#include <iostream>
using namespace std;

void myfunc(); // Прототип ФУНКЦИИ myfunc().

int main ()

cout « "В ФУНКЦИИ main(f.\n";

myfunc(); // Вызываем ФУНКЦИЮ myfunc().

cout « "Снова в ФУНКЦИИ main() .\п";

return О;

/1 Опредеnение фУНКЦИИ myfunc().


void myfunc () ,
214 Модуль 5. Введение в функции

cout « .. В ФУНКЦИИ myfunc () . \п";

Программа работает следующим образом. Вызывается функция main () и


выполняется ее первая cout-инстрУ1<ЦИЯ. Затем из функции main () вызыва­
ется функция myfunc ( ) . Обратите внимание на то, как этот вызов реализуется
в программе: указывается имя функции туft1ЛС, за которым следуют пара кру­
глых скобок И точка с запятой. Вызов любой функции представляет собой С ++-
ин:струкцию И поэтому должен завершаться точкой с запятой. Затем функция
myfunc () выполняет свою единственную соut-Инструкцию и передает управ­
ление назад функции та i n ( ) , причем той строке кода, которая расположена не­
посредственно за вызовом функции. Наконец, функция main () выполняет свою
вторую соut-инструкцию, которая завершает всю программу. Итак, на экране
мы должны увидеть такие результаты.

В ФУНКЦИИ main().
В ФУНКЦИИ myfunc().
Снова в функции main().

То, [(ш< организован ВЫЗОВ функции rnyfun с () 11 ее uозврат, предстаВЛЯt:1 со­


бой конкретный вариант общего процесса, который применястся ко всем фУНК­
циям. В общем случае, чтобы DЫЗDать ФУНКЦllЮ, достаточно указать се имя с па­
рой круглых скобок Послс вы;юва упраВЛСJlIJе ГIРOl'рам~ой переходит к ФУНКЦИИ.
ВЫ110ЛllСllllС ФУНКЦИИ продолжается до 06Н<lружеюrя заКРЫDающей фигурной
скобки. Когда функция заuершается, управление передается инициатору ее вы­
зова.

В этой программе обратите внимание на следующую инструкцию.


void rnyfunc(); 11 Прототип функции rnyfunc().
Как отмечено в комментарии, это - прототип функции rnyfunc (). Хотя под­
робнее прототипы будут рассмотрены ниже. без кратких пояснений здесь не
обойтись. Прототип функции объявляет функцию до ее определения. Прототип
позволяет компилятору узнать тип значения. возвращаемого этой функцией, а
также количество и тип параметров, которые она может иметь. Компилятору
нужно знать эту информацию до первого вызова функции. Поэтому прототип
располагается до функции main (). Единственной функцией, которая не требует
прототипа, является rnain (), поскольку она встроена в язык С++.
КЛючевое слово vo id, которое предваряет как прототип, так и определение
функции rnyfunc () , формально заявляет о том, что функция myfunc () не воз­
вращает никакого значения. В С++ функци,и, не возвращающие значений, объяв­
ляются с использованием ключевого слова void.
С++: руководство дl\Я начинающих 215

Функции можно передать одно ИJlИ несколько значений. Значение, передава­


емое функции, называется apZYMeнmaм.. Таким образом, аргументы представляют
собой средство передачи инициализации в функцию.
При создании функции, которая принимает один или несколько аргументов,
необходимо объявить переменные, которые получат значения этих аргументов.
Эти переменные называются nара.меmра.мu ФУН1щuu. Рассмотрим пример опре­
деления функции с именем Ьох ( ) , которая вычисляет объем параллелепипеда и
отображает полученный результат. Функция Ьох () принимает три параметра.

void box(int length, int width, int height)


(
cout « "Объем параллелепипеда равен "
« length * width * height « "\п";

в общем случае при каждом вызове функции Ьох () будет вычислен объем
параллелепипеда путем Уl\шожения значений, переданных ее параметрам leng-
th, width и height. Обратите внимание на то, что объявлены эти параметры в
виде списка, заключеНIIОГО в круглые скобки, расположенные после имени ФУНК­
ции. Объявление каждого параметра отделяется от следующего запятой. Так объ­
являются параметры для всех функций (если они их используют).
При вызове функции необходимо указать три аргумента. Вот примеры.
Ьох (7, 2 О, 4);
Ьох (50, З, 2);

Ьох (8, 6, 9);

Значения, заданные в круглых скобках, являются арryментами, передаваемы­


ми функции Ьох ( ) . Каждое из этих значений копируется в соответствующий па­
раметр. Так, при первом вызове функции Ьох () число 7 копируется в параметр
length, 20 - в параметр width, а 4 - в параметр height. При выполнении вто­
рого вызова 50 копируется в параметр length, 3 - в параметр width, а 2 - в па­
раметр height. Во время третьего вызова в параметр length будет скопировано
число 8, в параметр width - число 6ив параметр height - число 9.
Использование Функции Ьох () демонстрируется в следующей программе.

// Про грамма демонстрации использования ФУНКЦИИ Ьох().

#include <iostream>
using namespace std;
216 МОДУЛЬ 5. Введение в фУНI<ЦИИ
................................................................................................................................

void box(int length, int width, int height); // Прототип


// функции Ьох() .

int main ()
{
Ьох(7, 20, 4); // Передаем apryмeH~ ФУНКЦИИ Ьох().
Ьох (50, 3, 2);
Ьох (8, 6, 9);

return О;

// Вычисление объема параллелепипеда.


void box(int length, int width, int height) // Параметры
11 npииимают значении аргумен-
'1'08,
1/ переданкых фУНКЦИИ Ьох().

cout « "Объем параллелепипеда равен "

« length * width * height « "\п";

При вьшолнении программа генерирует Tat..:I1C результаты.

Объем параллелепипеда равен 560


Объем параллелепипеда равен 300
Объем параллелепипеда равен 432

• Помните, что термин apZYJtfeum ОТIIОСИТСЯ !( значеllИIO, !<оторое ис­


Узелок -""-
напамять n пользуется ПРli вызове функции. Переменная, которая принимает
значение аргумента, называется nарамemром. Функции, ПрЮiИ­
мающие аргументы! называются nара.мemрuзованны"мU.

return
в предыдущих примерах возврат из ФУНКЦIIИ к инициатору ее вызова проис­
ходил при обнаружении закрывающей фиryрной скобки. Однако это приемлемо
С++: PYI(OBOACTBO для начинающих 217

не для всех ФУНКЦИЙ. Иногда требуется более гибкое средство управления воз­
вратом из функции. Таким средством служит инструкция return.
:s:
~

~ПРОСЫ МЯ текущего контроля --------


!
111
Ф
:s:
1. Как выполняется программа при вызове Функции? j
2. Чем аргумент отличается от параметра? 111
CD

З. Если функция использует параметр, то где он объявляется?·

ИНСТРУКЦИЯ return имеет две формы применения. Первая позволяет возвра­


щать значение, а вторая - нет. Начнем с версии иltструкции return, которая не
возвращает значение. Если тип значения, возвращаемого функцией, определяет­
ся ключевым словом void (т.е. функция не возвращает значения вообще), то для
выхода из функции достаточно использовать такую форму инструкции return.
returni
При обнаружении инструкции return управление программой немедленно
передается lШИЦllaТОРУ ее вызова. Любой код в функции, расположенный за ин­
струкцией return, игнорируется. Рассмотрим, например, эту программу.

11 Использование инструкции return.

#include <iostream>
using namespace stdi

void f () i

int main ()
(
cout « "До вызова функции.\п";

f();

1. После вызова управление программой переходит к функции. Когда ВЫПOJUlение


функции завершается, управление передается инициатору ее вызова, причем той
строке кода, которая расположена непосредственно за вызовом функции.

2. Аргумент - это значение, передаваемое фymщии. Параметр - это переменная, ко­


торая принимает это значение.

З. Параметр объявляется в круглых скобках, стоящих после имени функции.


218 Модуль 5. Введение в функции

cout « "После вызова функции.\п";

return О;

// определение vоid-функции, которая использует


// инструкцию return.
void f ()
{
cout « "8 функции f () . \п";

return: // Beкeдneввoe 80S.р&Щ$ние х ивициа~ору .uзова


// без ВlШоJUl8IIИJI cnедyDЦей соut-ииструхции.

cout «"Этот текст не будет ОТОбражен.\п";.

Вот как выглядят результаты выполнения этой программы.

До вызова функции.

В функции f().
После вызова функции.

Как видно из результатов выполнения этой программЬ1, функция


f () возвра­
щается в функцию main () сразу после обнаружения ИНСТР)'1<ции return. Сле­
дующая после return инструкция cout никогда не выполнится.

Теперь рассмотрим более практичный пример использования инструкции


ret u rn. Функция powe r ( ) , показанная в следующей программе, отображает ре­
зультат возведения целqчисленноro значения в положительную целую степень.

Если покаэатель степени окажется отрицательным, инструкция return немед­


ленно завершит эту функцию еще до попытки вычислить результат.

#include <iostream>
using паmезрасе std;

void power(int Ьазе, int ехр);

int main ()
(
power(10, 2):
power(10, -2);
С++: PYI<OBOACTBO ДМ1 начинающих 219

return О;

// Возводим целое число в положительную степень.


void power(int Ьаэе, int ехр)
{
int i;

if(exp < О) return; /* Отрицательные показатели степени


не обрабатываются. */

i = 1;

for ( ехр; ехр--) i = base * i;


cout « "Ответ равен: " « i;

Результат Вblполнения этой программы Tal<OB.

Ответ равен: 100


Если значение параметра ехр отрицательно (как при втором вызове функции
power () ), вся оставшаяся часть функции опускается.
Функция может содержать несколько инструкциi:1 return. В этом случае воз­
врат из функции будет Вblполнен при обиаружении одной из иих. Например, сле­
дующий фрагмент кода вполне допустим.

void f ()
(
// ...
switch(c)
case 'а': return;
саэе 'Ь': // ...
саэе 'с': return;

if(count < 100) return;


// ...
Однако следует иметь в виду, что слишком большое количество инструкций
return может ухудшить ясность алroритма и ввести в заблуждение тех, кто бу-
220 Модуль 5. Введение в функции

дет в нем разбираться. Несколько инструкций return стоит использовать толь­


ко в том случае, если они способствуют ясности функции.

Возврат значений
Функция может возвращать значение инициатору своего вызова. Таким об­
разом, возвращаемое функцией значение - это средство получения информации
из фушщии·. Чтобы вернуть значение, используйте вторую форму инструкции
return.
return значение;

Эту форму инструкции return можно ИСПО;lьзовать только с не vоid-фун{(ци­


ями.

Функция. которая возвращает значение, должна определить тип этого значе­


ния. Тип возвращаемого значения должен БIJIТЬ совместимым с типом данных,
используемых в инструкции return. В противном случае неминуема ошиБЕа во
время компиляции программы. Функция может возвращать данные любого до­
ПУСТИМOI"о в С++ типа, за I\сключеtlИем масо:ва.
Чтобы проиллюстрировать процесс возврата функцией значений, переllишем
уже известную вам функцию Ьох () . в этой версии функция Ьох () возвращает
объем параллелеllипсда. Обратите внимание ШI расположение функции с правой
стороны от оператора присваивания. Это значит, что переменной, расположен­
ной с левой стороны, присваивается значенис:, возвращаемое функцией.

// Возврат значения.

#include <iostrearn>
using narnespace std;

int box(int length, int width, int height); // Функция


// возвращает объем параллелепипеда.

int main ()
(
int answer;

answer = Ьох(10, 11, 3); // Значение, .os.p~eкoe


// фувкцкей, DpИС3&И8ае'J.'CJI
// аереlсениоА &nswer.
cout « "Объем параллелепипеда равен "« answer;
С++: РУКОВОДСТВО МЯ начинающих 221

return О;

:s:
~
// Эта функция возвращает значение.
int box(int length, int width, int height)
(
1
ID
Ф
:s:
return length * width * height ; // Bos.p~e~CR об~и.

Вот результат выполнения этой программы.


J
Объем параллелепипеда равен 330
В этом при мере функция Ьох
() с помощью инструкции return возвращает
значение выражения length * width * height, которое затем присваивает­
ся переменной answer. Другими словами, значение, возвращаемое инструкцией
return, становlПСЯ значением выражения Ьох () в вызывающем коде.
Поскольку функция Ьох () теперь возвращает значение, ее прототип и опре­
деление не предваряется ключевым словом void. (Вспомните: ключевое слово
void используется только для функции, КОТQРая 1Ю возвращает значение.) На
этот раз функция Ьох () объявляется как возвращающая значение типа in t.
Безусловно, тип int - не единственный тип данных, которые может возвра­
щать функция. Как упоминалось выше, функция может возвращать данные лю­
бого допустимого в С++ типа, за исключеllием маССlша. Например, в следующей
программе мы переделаем функцию Ьох () так, чтобы она ПРИIIИМ<U1а параметры
типа double 11 возвращала значение типа double.

11 Возврат значения типа double.

#include <iostream>
using namespace stdi

// Использование параметров типа dovble.


double box(double length, double width, double height);

int main ()
{
double answer;

answer = Ьох(10.1, 11.2, 3.3);' // Присваиваем значение,


// возвращаемое функцией.
cout « "Объем параллелепипеда равен "« answer;
222 МОАУЛЬ 5. ВвеАение в функции

return О;

// Эта версия функции Ьох() использует данные типа double.


double box(double length, double width, double height)

return length * width * height

Вот результат выполнения этой программы.

Объем параллелепипеда равен 373.296


Необходимо также отметить следующее. Если lle vоid-функция завершается
из-за обнаружения закрывающейся фигурной скобки, инициатору вызова ФУНК­
цИИ вернется неопределенное (т.е. неизвеСТllое) значение. Из-за особенностей
формального синтаксиса С++ не vоid-функция не обязана в действительности
выполнять инструкцию return. Но, поскольку функция объявлена как возвра­
щающая значение, значение должно быть возпращсно "во что бы то ни стало" -
пусть даже это будет "мусор". Конечно, хорошая практика програмМИРОВаtlия
подразумевает, что не vo id-фуикция должна позвращать значение посредством
явно выполняемой инструкции return.

BA~
fJ ИС П О,дЫQВQНи еТФVI:JIЩI414
В выражениях
в предыдущем примере значение, возвращаемое функцией Ьох (), присваи­
валось переменной, а затем значение Этой перемешlOЙ отображалось с помощью
инструкции cout. Эту программу можно было бы переписать в более эффектив­
ном варианте, используя возвращаемое функцией значение непосредственно в
инструкции cout. Например, функциюmаiП () из предыдущей программы мож­
но было бы представить в таком виде.
int main ()
(
// Непосредственное использование значения,
11 возвращаемого функцией Ьох().
cout « "Объем параллелепипеда равен "
« box(lO.l, 11~2, 3.3); 11 Испonъзоваиие зкачеНИR,
С++: руководство дAr;) начинающих 223

// .оs.ращаеиоrо функцией
// Ьох(), непосредст••иво
:s:
// • инструхции cout.
~
х

return О; !
са
ф
:s:
При выполнении этой инструкции
ция Ьох ()
cout автоматически вызывается функ­
для получения возвращаемого ею значения, которое и выводится на I
со
a:I
экран. Выходит, совершенно необязательно присваивать его сначала некоторой
переменной.
В общем случае vоid-функцию можно использовать в выражении любоro
типа. При вычислении выражения функция, которая о нем содержится, автома­
тически вызывается с целью получения возвращаемого ею зна~ения. Например,
J] следующей программе суммируется объем трех параллелепипедов, а затем ото­
бражапся среднее значение объема.
//Использование функции Ьох() в выражении.

finclude <iostream>
using namespace std;

// Используем параметры типа double.


double box(double length, double width, double height);

int main ()
(
double sum;

sum = box(lO.l, 11.2,3.3) + Ьох(5.5, б.б, 7.7)


+ Ьох ( 4 . О, 5. О, 8. О) ;

cout « "Суммарный объем всех параллелепипедов равен "


« зшn« "\П,";
cout « "средний объем равен " « sum / з. О « "\п";

return О;

// в этой версии функции Ьох() используются параметры


// типа double.
224 МОДУЛЬ 5. Введение в функции

double box(double length, double width, double height)


{
return length * width * height

. При выполнении эта программа генерирует такие результаты.


Суммарный объем всех параллелепипедов равен 812.806
Средний объем равен 270.935

~просы для текущего контром ________


1. Покажите две формы применения инструкции return.
2. Может ли void-фУНКЦЮI возвращать ~:начение?

- 3. Может ли функция быть частью выражения?·


'J'i"'''' .
...."--
Правила действия областей
видимости фУНКЦИЙ
До сих пор мы использовали переменные, не рассматривая формально, rHI~ их
можtIO объявить, как долго они существуют и какие чаСТИ'програММbl могут по­
лучить I( ним доступ. Эти атрибуты определяются правилам\! дейстпия обла('тей
видимости, определенными" С++.
Правила действия областей OUaU.MOCmll ЛIобого языка программироваШIЯ­
это правила, которые поаволяют управлять i\OcтynOM к объекту из различных
частей программы. ДРУГИМII словами, правила действия областей DИДIIМС'СТИ·
определяют, I(акой КОД имеет доступ к той IЫI1 IfНОЙ перемеююЙ. Эти пралила
также определяют продолжительность "жизни" переменной. В С++ определено
две основные области видимости: локальные и lЛоБШIЬ7lые. В любой из них можно
оБЪЯWlЯТh переменные. В этом разделе мы рассмотрим, чем переменные, 06ъяв-

1. ВОТ как ВЫГJIЯДЯТ две формы инструкции ret:um.


returni
return значение;
2. Иет, vоid·функция не может воэвращать эначение.
3. Выэов не VOid-функции можно испольэовагь в выражении любого типа. В <.ТОМ
случае функция, которая в нем содержится. автоматически выполняется с целью
получения воэвращаемого ею эначения.
С++: PYI<OBOACTBO
.............................................................................. ДЛJ1 начинающих 225
,. ................................................... .

ленные 8 локальной области, отличаются от переменных, объявленных в гло­


бальной, и как каждая из них связана с функциями.

L. АQКQАьыая оБАQСТhВИДИМОСIИ
Локальная область видимости создается блоком. (Вспомните, что блок на­
чинается с открывающей фигурной скобки и завершается закрывающей.) Таким
ID
Ф
s

j
образом, каждый раз при создании нового блока программист создает новую об­
ласть видимости. Переменная, объявленная внутри любого блока кода, называет­
ся локалыюй (по отношению к этому блоку).
Локальную переменную могут ИСПОJII,зовать лишь ИНСТРУКЦИИ, включенные
D блок, в котором эта переменная объявлена. дРУЛ1МИ словами, локальная пере­
мснная нсизвестна за пределаl\Ш собствснного блока кода. Следовательно, ИН­
СТРУЮIИИ внс блока не могут получить доступ к объекту, определенному внутри
блока. По сути, объявляя локальную переменную. вы защищаете ее от несанкци­
онироваНIIОГО J1.0ступа н/или модифиющии. Более того, можно сказать, что пра­
вила действия областеii DIIДIIМОСТИ обеспечивают фундамент для инкапсуляции.
Важно понимать, что локальные переменные существуют только во время вы­
полнения программного блока, u котором они оБЪЯDлены. Это означает, что ло­
кальная псременная создается при входе в "свой" блок и разрушается при выходе
И;J него. А поскольку ЛОl<aJIЬНая пере~енная разрушается при выходе из "своего"
блока, се значеНllе теряется.
Самым распрос-граненным программным блоком является функция. В С++
каждая функция определяет блок кода, который начинается с ОТl<рьшающей фи­
гурной скоБЮI этой функции 11 заuершается ее закрывающей фигурной скобкой.
Код функнии и ее данные - это ее "частная собственность", и к ней не может по­
ЛУ'IИТЬ доступ liИ ОJl.на инструкция нз любой другой функции, за исключением
инструкции ее вызова. (Например, невозможно использовать инструкцию goto
для перехода в середину кода другой функции.) Тело ФУНКЦИИ надежно скрыто
от остальной части программы, и она не может оказать никакого влияния на дру­
гие части программы, равно, как и те на нее. Таким образом, содержимое одной
функции совершенно "езависимо от содержимого другой. Другими словами, код
и данные, определенные в одной функции, Jje могут взаимодействовать с кодом
и данными, определенными в другой, поскольку две функции имеют различные
области видимости.
Так как каждая функция определяет собственную область видимости, пере­
менные, объявленные в одной функции, не оказывают никакого влияния на пере-
226 Модуль 5. Введение в функции

менные, объявленные в другой, причем даже в том случае, если эти переменные
имеют одинаковые имена. Рассмотрим, например, следующую программу.

tinclude <iostream>
using namespace std;

void f1 () ;

int main ()
{
int val = 10; // ~a пsреке_. . val nОК8J'П8аа по

// о~вошеВRD к функции ша1п().

cout « "Значение переменной val J?, функции main(): "


« val « '\п';
f1 () ;
cout « "Значение переменной val Е функции main(): "
« val « '\п';

return О;

void fl ()

int val 88; 11 Э~а переИSКВ&R val nокanъна по

11 о~ношеВRD К фУНКЦИИ fl().

cout « "Значение переменной val в функции f1(): "


« val « "\п";

Вот результаты выполнения этой программы.

Значение переменной val в функции main(): 10


Значение переыенной va1 в функции f1(): 88
Значение переменной val в функции main(): 10
Целочислекная переменная val объявляется здесь дважды: первый раз в
функции main () и еще раз
- в функции f1 (). При этом переменная val, объ­
явленная в функции main ( ) , не имеет никакого отношения к одноименной пере­
менной из функции f 1 ( ) . как раэъяснялось выше, каждая переменная (в данном
случае val) известна только блоку кода, в котором она объявлена. Чтобы убе-
С++: руководство ДЛЯ начинающих 227

диться в этом, достаточно выполнить приведенную выше программу. как видите,


несмотря на то, что при выполнении функции f1 () переменная val устанавли­
вается равной числу 88, значение переменной val в функции main () остается
равным 10.
Поскольку локальные переменные создаются с каждым входом и разрушают­
ся с каждым выходом из программного блока, в котором они объявлены, они не
хранят своих значений между активизациями блоков. Это особенно важно пом­
нить в отношении функций. При вызове функции ее локальные переменные соз­
даются, а при выходе из нее - разрушаются. Это означает, что локальные пере­
менные не сохраняют своих значений между вызовами функций.
Если объявление локальной переменной включает инициализатор, то такая
переменная инициализируется при каждом входе в соответствующий блок. Рас­
смотрим пример.

/11
Локальная переменная инициализируется

при :каждом входе в "свой" блок.

*/

#include <iostream>
using namespace std;

void f();

int main ()

for (int i=O; i < 3; i++) f ();

return О;

// Переменная num инициализируется при каждом вызове


/ / функции f () .
void f ()
(
int num 99; / / При JCаидом вызове ФУКIЩИИ f ()
// перекеинаR num устаи8ll.JDlllаетCJI равной 99.

cout « пит « "\п";


228 МОДУЛЬ 5. Введение в фУНI<ЦИИ

num++; // Это действие не имеет устойчивого эффекта.

Результаты выполнения этой программы подтверждают тот факт, что пере­


менная пит заново устанавливается при каждом вызове функции f () .

99
99
99
Содержимое неинициализированной локальной переменной будет неизвест­
но до тех пор, пока ей не будет присвоено некоторое значение.

Локальные переменные можно объявлять


внутри любого блока
Обычно все переменныс, которые иеобходимы для выполнения функции,
объявляются в начале блока кода этой ФУНКЦIIИ. ЭТО делается, в основном, для
того, чтобы ;ГаМУ, кто станет разбираться в коде этой функции. было проще 110-
нять, какие переменные здесь используются. О,1.lIако начало блока функции - не
единственное место, где можно объявлять ЛОЮUlьные переменные. Локальные
переменные MOryT быть объявлены в любом месте блока кода. Локальная пере­
менная, объявленная внутри блока, локальна 110 отношению к этому блоку. ~)TO
означает, что переменная не сущсствует до входа в этот блок, 1I разрушается при
выходе из блока. Более TOl'O, никакой КОД вне этOI'О блока - включая иной код О,
той же ФУНКЦИИ - не может получить доступ к этой переменной. Чтобы убедить­
ся в вышесказанном, рассмотрим следующую программу.

11 Переменные могут быть локальными по отношению к блоку.

#include <iostream>
using namespace std;

int main ()
int х 19; // Переменная х иэвестна всему коду функции.

if(x == 19)
int у 20; 11 nepeкeRИa. у похan.ва дик if-бпоха.

cout « "х + у равно " « х + у « "\n";


С++: руководство ДЛЯ начинающих 229

11 у = 100; 11 Ошибка! Переменная у здесь неизвестна.


s
~
return О;
!
са

Переменная х объявляется в начале области видимости функции main () и ф

поэтому доступна ДЛЯ осеl"О последующего кода этой функции. В блоке if-ин­
струкции объявляется переменная у. Поскольку рамками блока и определяется
область видимости, переменная у видима только для кода внутри этого блока.
j
Поэтому строка

у = 100;
(она находится за пределами этого блока) отмечена как комментарий. Если
убрать символ коммснтария (/1) в началс этой строки, компилятор отреагирует
сообщением об ошибке, поскольку персменная у невидима вне этого блока. Вну­
три i f-блока вполне можно использовать переменную х, поскольку код в рамках
блока имеет доступ к переменным, объявленным во включающем блоке.
Несмотря на то что локальные ПСРСl\Iснные обычно объявляются в начале сво­
его блока, это не ЯАляется обязательным требованием. Локальная переменная
может быть объявлена в любом месте блока кода - главное, чтобы это произошло
до ее использования. Например, слеЛУЮIJЩЯ программа абсолютно допустима.
#include <iostrearn>
using namespace stdi

int rnain ()
{
cout « "Введите первое число: ";
int а; /1 Объявляем одну переменную.
cin » а;

cout « "Введите второе число: ";


int Ь; // Объявляем еще одну переменную.
cin » Ь;

cout « "Проиэведение равно: " « а*Ь « '\п';

return О;
230 Модуль 5. Введение в функции

в этом примере переменные а и Ь объявляЮТСЯ "по мере необходимости", т.е. не


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

Сокрытие имен
Если имя переменной, объявленной во внутреннем блоке, совпадает с именем
переменной, объявленной во внешнем блоке, то "внутренняя" переменная скры­
вает, или переопределяет, "внешнюю" в пределilX области видимости внутренне­
го блока. Рассмотрим пример.
linclude <iostream>
using namespace stdi

int main ()
{
int i, j;

i 10;
j 100;

if(j > О) {
int ii // Эта леременная i отделена от
// внешней леременной i.

i = j / 2;
cout « "Внутренняя леременная i: " « i « '\п';

cout « "Внешняя переменная i: " « i « '\п';

return О;

Вот как выглядят результаты выполнения этой программы.

Внутренняя леременная i: 50
Внешняя леременная i: 10
Здесь переменная i, объявленная внутри if~лока, скрывает внешнюю пере­
менную i. Изменения, которым подверглась внутренняя переменная i, не ока-
С++: руководство МЯ начинающих 231

зывают никакого влияния на внешнюю i. Более того, вне i f -блока виутреНВJUI


переменнCUI i больше не существует, и поэтому внешняя переменная i снова cтa~
:s
новитCR видимой.

параметры функции i
ID
Ф
:s
Параметры функции существуют В пределах области ВИДИМОСТИ функции.
Таким образом, они локзльны по отношению к функции. Если не считать получе­ !
ния значений аргументов при вызове функции, то поведение параметров ничем не !
отличается от поведения любых других локзльRых перемеlПlblX внутри функции.

Спросим V опытного программиста


Вопрос. Како.о lltullачеllllе /(.flюче80Z0 сло.а auto? Mlle U:J.ecmHO, что оно IIСnОЛ6-
:Jуеmся для оliыlлеllиRR локалЬ"ЬL~ nеремеllllЫх. Так ли ~mo?

Ответ. Действительно, язык С++ содержит ключевое слово а uto, которое можно
использовать для объявления локальных переменных. Но поскольку все
локальные переменные являются по умолчанию аutо-переменными, то

к этому ключевому слову практически никогда не прнбегают. Поэтому вы


не найдете в этой книге ни одного при мера с его использованием. Но сели
вы захотите все-таки примеЮ1ТЬ его в своей программе, то знайте. что раз­
мещать его нужно непосредственно перед типом переменной. как показа­
но ниже.

auto char ch;


Еще раз напомню, что ключевое слово auto ИСПОJIЬ.10Dать нео6язательно.
и в этой книге вы его больше не встретите.

Поскольку локальные псреМСIIНЫС известны только 8 прсделах функции. в ко­


торой они объявлены, то у вас может возникнуть вопрос: а как создать перемен­
ную. которую могли бы использовать сразу несколько функций? Ответ звучит
так: необходимо создать переменную в глобальной области видимости. Гло6шrь­
ная область видимости - это декларативная область, которая "заполняет" про­
странство вне всех функций. При объявлении переменной в глобальной области
видимости создается zл06шrьна.я nepeменная.
Глобальные переменные известны на протяжении всей программы, их можно
использовать в любом месте кода, и они поддерживают свои значения во время
232 Модуль 5. Введение в функции

выполнения всего кода программы. Следовательно, их область видимости рас­


ширяется до объема всей программы. Глобальная переменная создается путем ее
объявления вне какой бы то ни было функции. Благодаря их глобальности до­
ступ к этим переменным можно получить иэ любого выражения, независимо от
ФУНКЦИИ, в которой это выражение находится.
Использование глобальной переменной демонстрируется в следующей про­
грамме. Как видите, переменная count объявлена вне всех функций (в данном
случае до функции rnain ( ) ), следовательно, она глобальная. Из обычных прак­
тИческих соображений лучше объявлять глобальные переменные поближе к на­
чалу программы. Но формально они просто должны быть объявлены до их пер­
вого использования.

11 Использование глобальной леременной.

#include <iostream>
using narnespace std;

void funcl () ;
void func2();

int count; 11 Это rлобапьнак переменнак.

int rnain ()

int i; /1 Это лохanънак перемеинак.

for(i=O; i<10; i++)


count = i * 2; 11 Здесь результат сохраняетск
11 а глобальной перемеккой count.
funcl ();

return О;

void funcl ()

cout « "count: " « count; 11 Дос'l'YD х Ж'.nоб&Ju.НОЙ


11 перемеивой count.
cout « '\п'; 11 Вывод символа новой строки.
С++: руководство дl\Я начинающих 233

func2();

s
void func2() ~
!
са
int count; / / Э~О JlOKaJIIoBёUI пер....IUI&JI. ф
S

// Здеса. ИСПOJlloSУ.~CII .покап.... пер....вва. count. ~


са
CD
//
for(count=O; count<3; count++) cout « '.';

Результаты выполнения этой программы таковы.

count: О
... count: 2
... count: 4
... count: 6
... count: 8
· .. count: 10
· .. count: 12
· .. count: 14
... count: 16
· .. count: 18

При внимательном изучении этой программы вам должно быть ясно, что как
ФУНКЦИЯ main (), так и функция func1 () используют глоба.тIl:.НУЮ переменную
count. Но в ФУНКЦИИ func2 () объявляется локальная l1еременная соип t. Поэ­
ТОМУ здесь при использовании имени count подразумевается именно локальная,

а не глобальная переменная count. Помните, что, если в ФУНКЦИИ глобальная и


локальная l1ерсмеиные имеют ОДИНaI<Овые имена, то при обращеllИИ к ЭТОМУ име­
ии используется локальная переменная, не оказывая при ЭТО~11i1iJ(акого влияния

на глобалыlю •. Это и означает, что локальная перемеllНая CI<Pbll3<1CT глобальную


с таким же именем.

Глобальные переменные инициализируются при запуске программы на вы­


полнение. Если объявление глобальной переменной включает инициализатор, то
переменная инициализируется заданным значением. В противном случае (при от­
сутствии инициализатора) глобальная переменная устанавливается равной нулю.
Хранение глобальных переменных осуществляется 8 не которой определенной
области памяти, специально выделяемой программой для этих целей. Глобаль-
234 Модуль 5. Введение в функции

ные переменные полезны в том случае, когда в нескольких функциях программы


используются одни и те же данные, или когда переменная должна хранить свое

значение на протяжении выполнения всей программы. Однако без особой не­


обходимости следует избегать использования глобальных переменных' и на это
есть три причины.

• Они занимают память в течение всего времени выполнения программы, а не


только тогда, когда действительно необходимы.
• Использование глобальной переменной в "роли", с которой легко бы "спра­
вилась " локальная переменная. делает такую функцию менее универсальной,
поскольку она полагается на неоБХОДИМОС1"Ь определения данных вне этой
функции.

• Использование большого количества гл06алl,НЫХ переменных может привести


к появлению ошибок 1) работе программы, поскольку при этом возможно про­
явление Ifеизвестных и нежелательных побочных эффектов. Основная пробле­
ма, характерная для разработки больших С++-программ, - случайная модифи­
кация значения переменной в каком-то другом месте программы. Чем больше
глобальных переменных п программе, тем больше вероятность ошибки.

~ПРОСЫ МЯ текущего КОНТРОЛЯ ---------


1. Каковы основные различия между лока.1ЬНЫМИ и глобальными персмен­
ными?

2. Можно ли локальную переменную объявить в любом месте внутри БЛОК.l?


3. Сохраняет ли локальная переменная спое значение между вызовами
функции, в которой она объявлена?· _

1. Локальная переменная известна только внутри блока, в котором она определена.


Она создается при входе в блок и разрушается при выходе из него. Глобальная пе­
ремеЮlая объявляется вне всех функций. Ее могут использовать все функции, и
она существует на протяжении времеЮf выполнения всей программы.

2. да, локальную переменную можно объявить в любом месте блока, 110 до ее исполь­
зования.

3. Her, лок3льныe переменные разрушаются при выходе из функций, в которых они


объявлены.
С++: PYI(OBOACTBO дl\91 начинающих 235

ВАЖНОI
181;8 ПеРААача~азаlfМей.I4МOССИ808
••
в качестве аргументов функций
в предыдущих примерах функциям передавались значения простых перемен­
НhlX типа int или double. Но возможны ситуации, когда в качестве аргумен­
тов необходимо использовать указатели и массивы. Рассмотрению особенностей
передачи аргументов этого типа и посвящены следующие разделы.

Передача фУНКЦИИ указателя


Чтобы передать фующии указатель, необходимо объявить параметр типа ука­
затель. Рассмотрим пример.

// Передача функции указат~ля.

#inc1ude <iostream>
using namespace stdj

void f(int *j)j 11 Функция f() объявляет параметр-ухазатель

11 на iпt-значение.

int main ()

int ii
int *р;

р = &i i 11 Указатель р теперь ссьшается на переменную i.

f(p)i 11 Передаем ухазатель. Функция f() вызывается с


11 указателем на iпt-значение.

cout « i j 1/ Переменная i теперь содержит число 100.

return О;

11 Функция f() принимает указатель на iпt-значение.

void f(int *j)


236 МОДУЛЬ 5. Введение в фУНI<ЦИИ
............................................................................................................................- ...

*j 100; // Переменной, адресуемой указателем j,


// nрисваивается число 100.

как видите, в этой программе функция f () принимает один параметр: указа­


тель на целочисленное значение. В функции main () указателю Р. присваиваетея
адрес переменной i. Затем из функции main () вызывается функция f (), а ука­
затель р передается ей в качестве аргумента. После 1'01'0 как параметр-указаТСJlЬ
j получит значение аргумента р, он (так же, как и р) будет указывать на пере­
менную i, определенную в функции main (). Таким образом, при 8ыполпенi!И
операции присваивания

*j == 100;
переменная i получает значение 100. Поэтому программа отобразит на экране
число 100. В общем случае Ilрипеденная здесь функция f () присваипаеl' ЧИС.ilо
100 переменной, адрес «оторой был передан э10Й функции в качестве аргумента.
В предыдущем примере необяззтелыю было использовать переменную-ука­
затель р. Вместо нее при вызове функции f () ,'{остаточно взять переменную i.
предварив ее оператором" &" (при этом, как вы знаете, генерируется адрес пе­
рсмешюй i). После внесения огопорешюго ИЗI\IСНfrшя предыдущая программа
приобретает такой вид.
// Передача указателя функции (исправленная версия).
#include <iostream>
using namespace std;

void f(int *j);

int main ()

int i;

f(&i); // в переиекной-ухазатеnе р нет необхоАИМОСТИ.


// Тах напрRМ~ передаетск адрес перемениой i.

cout « i;

return О;

void f(int *j)


С++: PYI(OBOACTBO AMI начинающих 237

*j 100; 11 Переменной, адресуемой указателем j,


// присваивается число 100.

Передавая указатель фующии, необходимо понимать следующее. При выпол­


нении некоторой операции в функции, которая использует указатель, эта операция
фактически выполняется над переменной, адресуемой этим указателем. Это значит,
что такая функция может изменить значение объекта, адресуемого ее параметром.

Передача фУНКЦИИ массива


Если массив является аргументом функции, то необходимо понимать, что при
вызове такой функции ей передается только адрес первого элемента массива,
а не полная его копия. (Помните, что D С++ имя массива без индекса представ­
ляет собоi't указатель на первый элемент этого массива.) Это ОЗllачает, что обыш­
ление параметра должно иметь тип, совместимый с пшом аргумента. Вообще су­
ществует три способа объявить параметр, который ПРИlшмает укаэатсль на массив.
Во-первых, параметр можно объявить как массив, тип и размер которого совпадает
С типом и раэмером массива, ИСГЮЛКiУСМОГО при вызове функции. Этот вариант
объявлсния параметра-массива продемонстрирован 8 следующсм примерс.

#include <iostream>
using namespace stdi

void display(int num[lO])i

int main ()
{
int t[lO],ii

for (i=Oi i<lO; ++i) t [i] =i;

display(t); /1 Передаем функции массив t.

return О;

1/ Функция выводит все элементы массива.


void display(int num[10]) // Параие~ здесь об~nев как
11 массив заданно~о размера_
238 Модуль 5. Введение в фУНКЦИИ

int i;

for (i=O; i<10; i++) cout « num[i] « ' ';

Несмотря на то что параметр пит объявлен эдесь как целочисленный массив,


состоящий из 10 элементов, С++-компилятор автоматически преобразует его в
указатель на целочисленное значение. Необходимость этого преобразования объ­
ясняется тем, что никакой пара метр в действительности не может принять мас­
сив целиком. А так как будет передан один лишь указатель на массив, то функция
должна иметь параметр, способный принять этот указатель.
Второй способ объявления параметра-массипа состоит в его представлении в
виде безразмерного массива, как показано ниже.
void display(int num[]) 11 Параиетр здесь объявлен как
1/ безразмерный массив.

int i;

for (i=O; i<10; i++) cout « num[i) « ' ';

Здесь параметр пит объявляется как целочислеНIIЫЙ массив неизвестного pa:l-


мера. Поскольку С++ не обеспечивает Г!роперку нарушения границ массива, то
реальный размер масс"ша - JlерелеШШТНbJЙ фактор для подобного параметра
(но. безусловно, не для программы в целом). Целочисленный массив при таком
способе объявления также автоматичеСЮ1 прео6раэуется С++-компилятором в
указатель иа цеЛОЧИCJlенное значение.

Наконец, рассмотрим третий способ объявления параметра-массива. При пер<~­


даче массива фушщии ее параметр можно объящпь как указатель. Как раз ,пот вар' (-
ант чаще всею используется профессионалыiмии Ilрогра.Ммистами. ВОТ пример:
void display(int *num) 11 Параиетр здес. объявлен ~aK
11 указатель.

int i;

for(i=O; i<10; i++) cout « num[i] « ' ';

ВОЗМОЖНОСТЬ такого объявления параметра объясняется тем, что любой указа­


тель (подобно массиву) можно индексировать с ПОМОЩЬЮ символов квадратных ско­
бок ( [ ] ). Таким образом, все три способа объявления параметра-массива приводятся
к одинаковому результату, который можно выразить ОДНИМ СЛОВОМ - указатель.
С++: руководство для начинающих 239·

Важно помнить, что, если массив используется в качестве аргумента функции,


то функции передается адрес этого массива. Это означает, что код функции мо­
:s:
жет потенциально изменить реальное содержимое массива, используемого при

вызове функции. Например, в следующей программе функция сиЬе


зует значение каждого элемента массива в куб этого значения. При вызове ФУНК­
цИИ сиЬе () в качестве первого аргумента необходимо передать адрес массива
() прео6ра­
i
111
Ф
:s:
значений, подлежащих пре06разоваliИЮ, а в качестве второго его размер.
~
-
// Изменение содержимого массива с помощью функции. 111
ID

#include <iostream>
using namespace std;

void cube(int ·n, int пит);

int main ()
{
int i, nums[10];

for(i~O; i<10; i++) nums[i] = 1+1;

cout « "Исходное содержимое массива: ";


for(i=O; i<10; 1++) cout « nums[i] « ' , ,.
cout « '\п';

// Вычисляем кубы значений.


сиЬе(питз, 10); // Передаем фуихции cuЬe() адрес
// массива nums.
I

cout « "Измененное содержимое: ";


for (i=O; i<10; i++) cout « питэ [i] « ' ';

return О;

// Бсэводим в куб элементы массива.


void cube(int *п, int пит)

while(num)
·п = ·п * *п * *п; 11 Э~О в~а.евие измеRRе~
// зва.еиие эnеиеи~а массива,
240 Модуль 5. Введение в функции

// aдpecyeMO~O yxasa~eneм n.
цит--;

п++;

Результаты ВЫПОJПJения этой программы такОВЫ.

Исходное содержимо~ массива: 1 2 3 4 5 6 7 8 9 10


Измененное содержимое: 1 8 27 64 125 216 343 512 729 1000
как видите, после обращения к функции сuЬе () содержимое массива nums из­
менилось: каждый элемент стал равным кубу исходного значения. Другими слова­
ми, элементы массива nums бьvlИ модифицированы инструкциями, составляющими
тело функции сuЬе () , поскольку ее параметр n указывает на массив nums.

Передача фУНКЦИЯМ строк


Поскольку строки в С++ - это обычные симr:ольные массивы, которые завер­
шаются нулевым символом, то прп передаче фушщии CTPOKII реально передается
только указатель (типа cha r *) на начало этоi1 строки. Расс~lОТРИМ, например,
следующую программу. В ней определяется ФУIIКUИЯ strInvertCase (), KOTI)-
рая инвертирует рсшстр букв строки, Т.С. заменяет строчные СЮ1DОЛЫ прописны­
ми, а ПРОПИСIfЫС - строчными.

1/ Передача ФУНКЦИИ СТРОКИ.

tinclude <iostream>
#include <cstring>
#include <cctype>
using паmеsрасе stdi

void strInvertCase(char *str);

int main ()
(
char str[80];

strcpy(str, "ТЫs Is А Test");

strlnvertCase(str);

cout « stri // Отображаем модИфицированную строку.


С++: PYI(OBOACTBO ДЛSI начинающих 241

return О;

// Инвертируем регистр букв строки.


void strInvertCase(char *str)
1
(
while(*str) (

// Инверсия регистра букв.


if(isupper(*str» *str = tolower(*str);
else if(islower(*str») *str = toupper(*str);

str++; // Переход к следующей букве.

Вот как выглядит результат выполнения этой программы.

tНIS iS а tEST

~просы текущего l<оНтрОл'i1--------


M'i1
t. Покажите, как можно объявить vоid-Функцию с именем count, прини­
мающую один параметр pt r, который представляет собой указатель на
Зllачение типа long i n t.
2. Если функции перелается указатель, то может ли эта функция изменить
содержимое объекта, адресуемого этим указателем?
З. Можно ли функции 8 качестве аргумента передать массив? Поясните
ответ. •
-=

t. void count (long int *ptr)


2. Да, объект, адресуемый параметром-укаэатenем, может быть модифицирован ко­
дом функции.

3. Массивы как таковые функции передать нельзя. Но можно передать фyнI<ЦИи ука­
затель на массив. В этом случае изменения, вносимые в массив кодом функции,
окажут влияние на массив, испольэуемый в качестве apryмeнтa.
242 Модуль 5. Введение в функции

указателей
Функции могут возвращать указатели. Указатели возвращаются подобно эна­
чениям любых других типов данных и не соэдают при этом особых проблем. Но,
поскольку укаэатель представляет собой одно иэ самых сложных (или не6еэопас­
ных) средств яэыка С++, имеет смысл посвятить этой теме отдельный раздел.
Чтобы вернуть указатель, функция должна объявить ero тип в качестве типа
возвращаемого эначения. Вот как, например, объявляется тип воэвращаемого эна­
чения для функции f ( ) • которая должна воэвращать указатель lIа целое число.

int *f()i
ЕСЛIJ функция [юзвращает указатель, то зна'[ение, используемое 8 ее инструк­
[ЩИ return, тш,же должно быть указателем. (Как и ДЛЯ всех функций, rеt\lr-п­
значение ДОЛЖНО быть совместимым с типом возвращаемого значения.)
В следующей программе демонстрируется использование указателя в каче­
стве типа возвращаемого значения. Функция get_substr () возвращает ук.ыа­
тель на первую подстроку (найденную в строке), которая совпадает с заданной.
Если заданная подстрок<I. не найдена, возвращается нулевой указатель. Напри­
мер. если в строке "Я люблю С++" вьmолияется поиск подстроки "люблю", ТО
ФУНlЩИЯ get_substr () возвраnп укаэатель на букву л в слове "люблю".
11 Возвращение функцией указателя.

#include <iostream>
using namespace'stdi

char *get_substr(char *зиЬ, char *str)i

int main ()
(
char *substri

substr = gеt_SUЬstr("три", "один два три четыре");

cout « "Заданная подстрока наЙден.а: " « substr;

return О;
С++: руководство ДМ1 начинающих 243

// Возвращаем указатель на подстроку или нуль,


// если таковая не найдена.
char *get_substr(char *эиЬ, char *str) //
//
Эта фyиxциR
возвращает
/ / ужаза'l'enъ на
- 111
// char-.наЧ8НИ8 . • ф
:s;
:~
int t; :<l

char *р, *р2, *start; ~~

for(t=O; str[t); t++)


р = &str[t); // установка указателей
start = р;
р2 = suЬ;
while(*p2 && *р2==*р) ( // проверка на наличие подстроки
р++;

р2++;

/* Если обнаружен конец р2-строки (т.е. подстроки), то


искомая подстрока найдена. */
i f (! *р2)
return start; // Возвращаем указатель на начало
//, подстроки.

return О; // Совпадения не обнаружено.

Вот результаты выполнения этой программы,

Заданная подстрока найдена: три четыре

ФУНКЦИЯ main ( )
как вы уже знаете, ФУНlЩИЯ main () - специальная, поскольку это первая
I

функция, которая вызывается при выполнении программы. В отличие от некото-


рых других языков программирования, в которых выполнение всегда начинается

"сверху", Т.е. с первой строки кода, каждая С++-программа всегда начинается с


вызова функции main () независимо от ее расположения в программе. (Все же
обычно функцию rnain () размещают первой, чтобы ее было легко найти.)
В программе может быть только одна функция main (). Если попытаться
включить в программу несколько функций main (), она "не будет знать", с какой
244 МОДУЛЬ 5. Введение в функции

ИЗ них начать работу. В действительности БОЛl>ШИНСТВО компиляторов легко об­


наружат ошибку этого типа и сообщат о ней. Как ynоминалось выше, ПОСКОЛl.ку
ФУНКЦИЯ main () встроена в язык С++, она не требует прототипа.

ВАЖНОI
fl ll' Передача аргумрнтnвкомаWДNОЙ
строки функции main ( )
Иногда возникает необходимость передать информацию программе при ее
запуске. Как правило, это реализуется путем передачи аргументов ко.манд1l0Й
строки функции main () . ApГYMCIIT командной строки представляет собой IIH-
формацию, указываемую в команде (командной строке), предназначенной для
ВЫПОЛIIСIIIIЯ oIlepaЦllOllllOii системоii, после ЮlеШJ программы. (В Windo\vs 1<0-
манда "Rtш" (Выполнить) т.шже использует командную строку.) Например, с--+­
программы можно компилировать путем выполнения следующей команды.

cl рrоg_лате

Здесь элемент рrоg_лаmе - имя ПJЮI"раммы. которую мы хотим СКОМJlИЛЩЮ­


пап,. Имя I1рограммы JJередается С++-компилятору 8 качестве аргумента КО­
маНДIlОЙ строки.
В С++ дЛЯ функции mаiл () опредеJlенодва встроенных, но JJсоuязательных на­
раметра, argc и argv, которые получают свои значения от аргументов командной
строки. В конкретной операционной среде могут гюддеРЖJlваться и ДРУПJе ар'"у­
менты (такую информацию необходимо уточнип, по ДОI<ументащш, прилагаемой
к вашему компилятору). Рассмотрим параметры а rgc и argv более подробно.

"*'- Формально для имен парамеТрОll IсомаtJДJюi! СТРОКИ можно вы(iи-


На заметку n" рать любые идеНТИфИJ<аторы, ОЛШ\J<О имена argc 11 argv ИСПОЛbJУ­
ются по соглашеllИЮ уже в теЧСlillе нескольких Jlет, и поэтому И!VIеет

СМЬJСЛ не ПРIl6егать J( другим ИЩ'JПllфИIG1Торам, чтобы любой про­


граммист, которому придется разбираться R пашеii програмМС, смог
быстро идентифицировать их как парамстры командной строки.

Параметр argc имеет целочисленный тип н предназначен для хранения ко­


личества аргументов командной строки. Его значение всегда не меньше единиuы,
поскольку имя программы также является одним из учитываемых аргументов.

Параметр
argv представляет собой указатель на массив символьных указате­
лей. каждый указатель в массиве argv ссылается на строку, содержащую аргу­
мент командной строЮf. Элемент а rgv [ О) указывает на имя программы; элемент
argv [1] - на первый аргумент, элемент argv [2] - на второй и Т.д. Все аргументы
С++: PYI<OBOACTBO МЯ начинающих 245

командной строки передаются программе как строки, поэтому числовые аргумеlПbl


необходимо преобразовать в npoграмме в соответствующий внутренний формат.
Важно правильно объявить парrметр argv. Обычно это делается так.
char *argv[];
Доступ к отдельным аргументам командной строки можно получить путем
индексации массива argv. как это сделать, показано в следующей программе.
При ее выполнении на экран выводятся все аргументы командной строки.

// Отображение аргументов командной строки.

#include <iostream>
using namespace std;

int main(int argc, char *argv[])

for(int i = О; i < argc; i++)


cout « argv[i] « "\п";

return О;

Например, присвоим этой программе имя ComLine и вызовем ее так.

ComLine
one
two
three
В С++ точно не оговорено, как должны быть представлены аргументы команд­
ной строки, поскольку сред.ы выполнения (операционные системы) имеют здесь
большие различия. Однако чаще всего используется следующее соглашение:
каждый аргумент командной строки должен быть отделен пробелом или симво­
лом табуляции. Как правило, запятые, точки с запятой и тому подобные знаки не
являются допустимыми разделителями аргументов. Например, строка

один, два и три

состоит из четьтрех строковых аргументов, в то время как строка

один, два, три

включает только два, поскольку запятая не является допустимым разделителем.


246 МОДУЛЬ 5. Введение в функции

Если необходимо передать в качестве одного аргумента командной строки набор


символов. который содержит пробелы, то его нужно заключить в кавычки. Напри­
мер, этот набор символов будет воспринят как один аргумент командной строки:
"это лишь один аргумент"

Следует иметь в виду, что представленные здесь примеры применимы к широ­


кому диапазону сред, но это не означает, что ваша среда входит в их число.

Обычно аргументы argc и argv IIСПОЛЬЗУЮТСЯ для ввода в программу началь­


ных параметров, исходных значений, имен файлОВ или вариантов (режимов) работы
программы. В С ++ можно ввести столько аргументов командной строки, СКОЛЬКО до­
пускает операционная система. Использование аргументов командной строки при­
дает программе профессионалъный вид и позволяет использовать ее в командном
файле (исполняемом текстовом фai'tЛе, содержащем одну или несколько команд).

Передача ЧИСЛОВЫХ аргументов командной строки


При передаче программе числовых данных в качестве аргументов командной
СТРОКИ ::lТИ данные принимаются 8 строковой форме. В ПРOl-рамме должно быть
IIредусмотрсно их преобразование n соответствующий внутренний формат с по­
мощью одной из стандартных библиотечных функций, поддерживаемых С++.
ПеrС'lНСЛIJМ три из них.

а tof () Прсо6разует строку в значение типа double и возвращает результат.


а tal () Ilрсо6разует строку в значение типа long int и возвращает результат.
а to i () 11 реобразуст строку в значение Тlша i n t и возвращает результат.
Каждая из переЧИСЛСННblХ функций ИСПОЛЕ,зует при вызове 8 качестве аргу­
мента строку. содержащую числопое значение. для их вызова необходимо вклю­
чить в программу заГОЛОВОЧltый файл <cstdlib>.
Ilри UЫГlOлнении следующей программы выподится сумма двух чисел, кото­
рые указЫВilЮТСЯ в I(омандtюй строке после имени программы. Для пре06ра:ю­
uаllllЯ ilrryMeHTon командной строки во внутреннее предстапление здесь ИСПО.1Ь­
зуется стандартная библиотечная функция atof (). Она преобразует число из
строкового формата в значеннетипа double.
/* Эта про грамма отображает сумму двух числовых
аргументов командной строки.

*/

#include <iostream>
finclude <cstdlib>
using namespace std;

int main(int argc, char *argv[])


С++: руководство длr. начинающих 247

double а, Ь;
s
~
if(argc!=3) {
cout « "Использование: add
return 1;
число число\п"; !
CII
ф
S
}
1/
1/
Иcnonъзование функции atof() дпя: npеобразовaииR
строховых аргументов командной строки в чиcnовuе
~
CII
a::I
1/ звачения: типа double.
а = atof(argv[l]); // Преобразование второго аргумента
// командной строки.
Ь atof(argv[2]); // Преобраэование третьего аргумента
// командной строки.

cout « а + Ь;

return О;

Чтобы сложить два числа, используйте командную строку такого вида (пред­
полагая, что эта программа имеет имя add).
C>add 100.2 231

~просы ДЛЯ текущего КОНТРОЛЯ _ _ _ _ _ _ __

1. Как обычно называются дпа параметра функции main ()? Поясните их


назначение.

2. Что всегда содержит первый аргумент командной строки?

з. Числовой аргумент командной строки передается в виде строки. Верно


ли это?'"

1. Два naраметра функции main () обычно называются argc и argv. Параметр argc
предназначен для хранеЮUI количества apryмeНТOB командной строки. Параметр argv
представляет собой указатель на массив символьных указателей, а каждый указатель
в массиве argv ссылается на строку, содержащую соответствующий apryмeнт команд­
ноЙстроки.

2. Первый аргумент командной строки всегда содержит имя самой программы.

3. Верно, числовые apryMeиты командной строки принимаются в строковой форме.


248 Модуль 5. Введение в функции

в начале этого модуля мы кратко коснулись темы использования прототипов


функций. Теперь настало время поговорить о них подробно. В С++ все функции
должны быть объявлены до их использования. Обычно это реализуется с помо­
щью прототипа функции. Прототипы содержат три вида информации о ФУНКЦИИ:

• тип возвращаемого ею значения;

• тип ее параметров;

• количество параметров.
Прототипы позволяют компилятору выполнить следующие три важные опе­
рации.

I! 01111 сообщают КО:lШlIЛЯТОРУ, код какого типа неоБХОДIfМО генерировать при


вызове ФУНКЦИИ. Различия в типах парам('тров и значении, возвращаемом
функцией, обеспечивают разную обработку компилятором.

• Они позволяют С++ обнаружить недопустимые преобразования типов аргу­


ментов, используемых при вызове функции, в тип, указанный в объявлении ее
параметров, и сообщить о них.
• Они позволяют компилятору выявить различия между количеством аргум('Н­
тов, используемых при вызове функции, и количеством нараметров, заданШ)IХ
в определении функции.

Общая форма прототипа функции аналогична ее определению за исключени­


ем того, что в прототипе не представлено тело функции.

тип имЯ_фУНКЦИИ(ТИП имя_параметраl, тип имя_параметра2, ... ,


тип имя_параметраN);

Использование имен параметров в прототипе необязательно, но ПОЗВО,lяет


компилятору идентифицировать любое ltеСОВП<lдсние типов при ВОЗllИКIfОвении
ошибки, поэтому лучше имена параметров все же включать D прототип функwш.
Чтобы лучше понять полс:щость прототипов функций, рассмотрим следующую
программу. Если вы попытаетесь ее скомпилир()вать, то получите от компилято­
ра сообщение об ошибке, поскольку в этой программе делается попытка вызвать
функцию sqr i t () с целочисленным аргументом, а не с указателем на целочис­
леНlfое значение (согласно прототипу функции). Ошибка состоит в невозможно­
сти автоматическоro преобразоваиия целочислеllНОro значеtlИЯ в указатель.
/* Эта про грамма использует прототип функции для
осуществления контроля тиnов_

*/
С++: PYI<OВOACTBO ДМI начинающих 249

void sqr_it(int *i); // ПРОТОТИП фУНКЦИИ

int main ()
(
int х;

х = 10;
// Бnаrодари прототипу КОИПИnRТор вЫRВИТ иесоответствие
/ / типов. Ведъ функции sqr_ i t () дoпlCКa принимать
/ / yxasa'l'enь, а она IWSвана с ЦeJIОЧ:ИCneJUDal арryиен'1'ОК.
sqr_it(x); /1 Ошибка! Несоответствие типов!

return О;

void sqr~it(int *i)

*i = *i '* '*i;

Определение функции может также служить в качеСТDе своего прототипа,


если оно размещено до первого ее использования в программе. Например, следу­
ющая прor-рамма абсолютно допустима.

// Использование определения функции в качестве ее прототипа.

#include <iostream>
using namespace std;

// Определяем, является ли заданное число четным.


bool isEven(int пит) ( /1 ПОСКО.JIьку функция isEven()
// опреДeJIиетси до ее испо.пьsоваиии,
// ее определение одновреиеико
// слукит и ее ПРО'l'отипок.
if(! (пит %2» return true; // Значение пит - четное число.
return false;

int main ()
250 Модуль 5. Введение в функции

if(isEven(4» cout « "Число 4 четное.\п";


if(isЕvеп(З» cout « "Эта строка не будет выведена.";

return О;

Здесь функция isEven () определяется до ее использования в функции


main ( ). Таким образом, ее определение служит и ее прототипом, поэтому нет
необходимости в отдельном ВIUIючении прототипа в программу. .
Обычно все же проще объявить прототип для всех функций, используемых
в программе, вместо определения до их вызова. Особенно это касается больших
программ, в которых трудно отс.ледить последовательность вызова одних функ­
ций другими. Более того, возможны ситуации, ](огда две функции вызывают друг
друга. В этом случае необходимо использовать именно прототипы.

Стандартные заголовки содержат


прототипы функций
Ранее 8Ы узнали о существовании стандартных заголовков С++, которые со­
держат информацию, необходимую ДЛЯ ваших программ. Несмотря на то что все
8ышеСК3.1аююе - ИСТИllllая правда, это еще uе вся правда. Заголовки С++ содер­
жат прототипы стандартных библиотечных функций. а также различные значе­
ния и определения, используемые этими фушщиями. Подобно функциям, соз­
даваемым программистами, стандартные бибЛ/lотечные функции также должны
"заявить о себе" в форме прототипов до их ИС110льзования. Поэтому любая про­
грамма, в которой используется библиотечная функция, должиа включать заго­
ловок, содержащий ПрОТОТИll этой функции.
Чтобы узнать, какой заГОЛО80К необходим для той или ЮlOй библиотечной
функции, следует обратиться к справочному руководству, I1рилагаемому к ваше­
му компилятору. Помимо описания каждой функции, там должно быть указано
имя заголовка, который необходимо включить в программу для использоваttия
выбранной функции.

ВАЖНОI

flffРеКУРСИ9
Рекурсия - это последняя тема, которую мы рассмотрим в этом модуле. Ре­
"урсu.л, которую иногда называют ЦU1ClluчeCКUAC определением, представляет собой
процесс определения чего-либо на собственной основе. В области программиро­
вания под рекурсией пони мается процесс вызова функцией самой себя. Функ­
цию, которая вызывает саму себя, называют ре,,:урcuвноЙ.
С++: руководство #\S1 начинающих 251

~npocы д/lЯ текущего контроля ________


1. Что представляет собой прототип функции? Каково его назначение?
2. Все ли функции (за исключением main () ) должны иметь прототипы?

З. Почему необходимо включать в программу заголовок при использовании


стандартной библиотечной функции?· _

Классическим npимером рекурсии является вычисление факториала числа с


помощью функции factr (). Факториал числа N представляет собой произведе­
ние всех целых чисел от 1 до N. Например. факториал числа 3 paBell 1х2хЭ. или б.
Рекурсивный способ вычисления факториала числа демонстрируется в следую­
щей программе. Для сравнения сюда же вклю'/ен и его IIсрекурсиnный (rпера­
тивный) эквивалент.

// Демонстрация рекурсии.

#include <iostream>
using патеsрасе stdi

int factr(int п);


int fact(int п);

int main ()

// Использование рекурсивной версии.


cout « "Факториал числа 4 равен" « factr(4)i
cout « '\п';

// Использование итеративной версии.


cout « "Факториал числа 4 равен" « fact(4);
cout « '\п';

1. Прототип объяв.nяет имя функции. тип возвращаемого ею значения и параметры.


Прототип сообщает компилятору. как сгенерировать КОД при обращении к фующии.
и ~рантирует. ЧТО она будет вызвана Koppeкrno.

2. да. все ФУНКЦИИ, за исключением mшп(), должны иметь прототипы.

З. Помимо прочеro, заголовок включает прототип библиотечной функции.


252 Модуль 5. Введение в функции

return О;

// Рекурсивная версия.
int factr(int п)
{
int answeri

if(n==l) return(l)i
answer = factr(n-1)*ni // ВЫполнение рекурсивного вызова
// фунхции factr().
return(answer)i

// Итеративная версия.
int fact(int n)
{
int t, answer;

answer = 1;
for(t=li t<=ni t++) answer answer*(t);
return(answer) ;

Нерекурсивная версия функции fact () довольно проста и не требует рас­


ширенных пояснеиий. В ней используется uикл, в котором организовано пере­
множение последовательных чисел, начиная с 1и заканчивая числом, заЩlliНЫМ
в качестве параметра: на каждой итерации цикла текущее значение управляющей
переменной цикла умножается на текущее значение произведения, получеJflюе в
результате выполнения предыдущей итерации ЦИЮlа.
Рекурсивная функция factr () несколько сложнее. Если она вызывается с
аргументом. равным 1, то сразу возвращает эначение1. В пропшном случае она
возвращает произведение factr (n-1) * п. Для вычисления этого выражения
вызывается функция factr () с apryMeHTOM п-1. Этот процесс повторяется до
тех пор, пока аргумент не станет равным 1, после чего вызванные ранее функции
начнут возвращать значения. Например, при вычислении факториала числа 2
первое обращение к функции f act r () приведет ко второму обращению к той же
функции, но с аргументом, равным 1. Второй вызов функции factr () возвра­
тит значение 1, которое будет умножено на 2 (исходное значение параметра n).
Возможно, вам будет интересно вставить в функцию factr () инструкции cout,
чтобы показать уровень каждого вызова и промежуточные результаты.
С++: РУКОВОДСТВО для начинающих 253

Когда функция вызывает сама себя, в системном стеке выделяется память для
новых локальных переменных и параметров, и код функции с самого начала вы­
полняется с этими новыми переменными. При возвращении каждого рекурсив­
ного вызова из стека извлекаются старые локальные переменные и параметры, и

выполнение функции возобновляется с "внутренней" точки ее вызова. О рекур­


сивных функциях можно сказать, что они "выдвигаются" и "задвигаются" .
. Следует иметь в виду, что в большинстве случаев использование рекурсивных
функций не дает значительного сокращения объема кода. Кроме того, рекурсивные
версии многих ПрОllедур выполняются медленнее, чем их итеративные эквивален­
ты, из-за дополнительных затрат системных ресурсов, связанных с многократны­

ми вызовами функций. Слишком большое количество рекурсивных обращений к


функции может вызвать переполнение стека. Поскольку локальные переменные и
параметры сохраняются в системном стеке и каждый НОDЫЙ вызов создает IЮDУЮ
КOlШIO этих перемеJ1I1ЫХ, может настать момент, !(OJ'да память стека будет исчерпа­
на. В этом случае могут быть разрушены другие ("ни в чем не повинные") данные.
Но если рекурсия построена корректно, об этом вряд ли стоит волноваться.
Основное достоинство рекурсии состоит в том, что некоторые типы алгорит­
мов рекурсивно реализуются проще, чем их итеративные ЭКВНlJаленты. Напри­
мер, алгоритм СОРТИРОВJСИ QuickSort доволыlO трудно реализовать итеративным
способом. Кроме того, некоторые задаЧII (особенно те, которые связаны с искус­
ственным интеллектом) просто созданы для рекурсивных решений.
При написании рекурсивной функции необходимо включить в lIee инструк­
цию проверки условия (например, if-инструюJ,ИЮ), которая бы обеспеЧИDала
выход из функции без выполнения рекурсивного вызова. Если этого не сделать,
то, вызвав однажды такую функцию, из нее уже нельзя будет вернуться. При ра­
боте с рекурсией это самый распространенный тип ошибки. Поэтому при разра­
ботке программ с реКУРСIIDНЫМИ функциями не стоит скупиться на инструкции
сои t, чтобы быть в курсе того, что ПРОИСХОl\ИТ В конкретной функции, и иметь
возможность прервать ее работу в случае обнаружения ошиБКII.
Рассмотрим еще один пример рекурсивной функции. ФУНКIIИЯ reverse ()
использует рекурсию для отображения своего строкового аргумента 8 обратном
порядке.

1/ Отображение строки в обратном порядке с помощью рекурсии.

finclude <iostream>
using namespace std;

void reverse(char *s);


int main ()
254 МОДУЛЬ 5. Введение в функции

char str[] = "Это тест";

reverse(str) ;

return О;

11 ВЫВОД СТрОКИ'В обратном порядке.


void rever5e(char *5)

if{*s)
rever5e(s+1);
else
return;

cout « *э;

Функция reverse () проверяет, не передан ли ей в качестве параметра ука­


затель на нуль, которым заuершается строка. Если нет, то функция reverse ()
вызывает саму себя с указателем на следующий символ в строке. Этот "закручи­
вающийся" процесс повторяется до тех пор, пока той же функции не будет пере­
дан указатель на нуль. Когда, наконец, обнаружится символ конца строки. пой­
дет процесс "раСКРУЧИВ.ilIИЯ", Т.е. вызванные ранее ФУНКЦИИ начнуг возвращать
значения, и каждый возврат будет сопровождаться "довыполнением" метода, Т.е.
отображением символа в. В результате исходная строка посимвольно отобрС'зит­
ся В обратном порядке.
И еще. Создание рекурсивных функций часто вызывает трудности у начина­
ющих программистов. Но с приходом опыта использование рекурсии становится
для многих обычной праl<ТИКОЙ.

Проект 5.1. 8Ат.о~~ти.....


QuickSort
QSDerno. срр I В модуле 4 был рассмотрен простой метод пузырьковой со­
_ . ._ - - .-". ртировки, а также упоминалось о существовании гораздо
более эффективных методов сортировки. Здесь же мы запрограммируем вер­
сию одного из лучших алгоритмов, известного под названием QuickSort. Метод
С++: PYI<OBOACТВO ДМ! начинающих 255
.................................................................................................................................

QuickSort, изобретенный C.AR. Hoare, - лучший из известных ныне алгоритмоп


сортировки общего назначения. Мы не рассматривали этот а.i1ГОРИТМ в модуле
поскольку самая эффективная его реализация опирается на использование ре­
4.

курсии. Версия, которую мы будем разрабатывать, предназначена ДЛЯ сортироп·


ки символьного массива, но ее логику вполне можно адаптировать под сортиров­
- ш

ку объектов любого типа. Ф


.::>:
::1:
Алгоритм QuickSoit основан на идее разделений. Общая ПРОПСДУР<l состоит :Ф
:<i
в вы1оре значения. именуемого КОм'парандом (операнд в операции cpanHCl-lllЯ). с :~
последующим разделением массива на ДB~ части. Все ЭЛ~\I~НТЫ. lшторые бо.'1ЬШ~ :CLJ

компаранда или равны ему, помещаем в одну часть, OCTaJIblThle (которые меш,шс
компаранда) - в другую. Этот процесс затем повторяется для ка.ждоЙ части до
тех пор, пока массив не станет полностью отсортированным. Например, воз.ьмем
массив fedacb I{ используем значение dn качестве КОЫl1араl!да. Тогда в реЗУ"'lu'
тате первого прохода алгоритма QuickSогt lIаш массив Gудет реорганшюшш СЖ~­
дующим образом.

Исходный массив fedacb


Проход 1 bcadef
Этот процесс затем повторяется отдельно для частей Ьса и def. К31\ I3ИДНТС.
этот процесс по своей природе рекурсивен, поэтому его проще всего реалИЗОВ<1ТiJ
в виде рекурсивной функции.
Значение компаранда можно выбрать двумя способами: либо случайным об­
разом, либо путем усреднения не60льшого набора значений, взятых из исходного
массива. для получения оmимальноro результата сортировки следует взять ЗШ1.че­
ние, расположенное в середине массива. Но для большmtства наборов данных это
сделать нелегко. В самом худшем случае выбранное вами значение будет находиться
на одном из концов массива. Но даже о этом случае алгоритм QuickSort UЫПОЛIШТ­
ся корректно. В нашей версии в качестве компаранда мы выбираем среДlшii
элемент массива.

Следует отметить, что в библиотеке С++ содержится функция qsort () , ко­


торая также выполняет сортировку методом QlIickSort. Вам было бы И1lтересно
сравнить ее с версией, представленной в этом 1lроекте.

Последовательность действий
1. Создайте файл QSDemo. срр.

2. Алгоритм QuickSort реализуется здесь в виде двух функций. Одна нз них,


quicksort () , обеспечивает удобный интерфейс для пользователя и под­
готавливает обращение к другой, qs (), которая, по сути, и выполняет со­
ртировку. Сначала создадим функцию quicksort () .
256 Модуль 5. Введение в функции

11 Функция обеспечивает вызов реальной функции сортировки.


void quicksort(char *items, int len)
(
qs(items, О, len-l);

Здесь параметр i tems указывает на массив, подлежащий сортировке, а па­


раметр len задает количество элементов в массиве. Как показано в следу­
ющем действии (см. пункт 3), ФУНКЦИЯ qs () принимает область массива,
предлагаемую ей для сортировки функцией quicksort ( ) . Преимущества
испаЛЬЗ0ваЮfЯ функции quicksort () состоит D .ТОМ, что ее можно вы­
звать, передав ей n качестве параметров лишь указатель на сортируемый
массив и его длину. Из этих параметров затем формируются начальный и
конечный индексы сортируемой области.
З. Добавим в I1pограмму функцию реальной сортировки массива qs () о

1/ Рекурсивная версия алгоритма Quicksort для сортирсвки


// символов.
void qs(char *items, int left, int right)
(
int i, j;
char х, У;

i lefti j =oright;
х ~ items[( left+right) / 2 ];

do (
while«items[i] < х) && (i < right» i++;
while«k < items[j]) && (j > left» j--;

if(i o<= j)
у = items[i]i
items[i] = items[j];
items[j] = У;
i++; j--;

while (i <= j);

if(left < j) qs(items, left, j);


if(i < right) qs(items, i, right);
С++: PYI<OBOACтeO ДЛЯ начинающих 257

Эту функцию необходимо вызывать, передавая ей в качестве параметров


индексы сортируемой области. Параметр left должен содержать начало
:s:
(левую границу) области, а параметр right - ее конец (правую границу).
При первом вызове сортируемая область представляет собой весь массив.
~
е каждым рекурсивным вызовом сортируется все меньшая область. !
ID
Ф
4. Для использования алгоритма QuickSort достаточно вызвать функцию :s:
quicksort () , передав ей имя сортируемого массива и ero
вершении этой функции заданный массив будет отсортирован. Помните,
длину. По за­
~
ID
ID
эта версия подходит ТОЛhКО для символьных массипов, но ее логику можно

адаптировать ДЛЯ сортировки массивuв любого типа.


5. Приведем полную программу реализации алгоритма QuickSort.
/*
Проект 5.1.
Версия реализации алгоритма Quicksort ДЛЯ сортировки
символов.

*/

finclude <iostream>
#include <cstring>

using namespace std;

void quicksort(char *items, int len);

void qs(char *items, int left, int right);

int main () {

char str[] = "jfmck1doe1az1kper";

cout « "Массив в исходном порядке: " « str « "\n";

quicksort(str, strlen(str));

cout « "Отсортированный массив: " « str « "\п";

return О;
258 МОДУЛЬ 5. Введение в функции
.................................................................................................................................

11 Функция обеспечивает вызов реальной функции сортиров­


ки.

void quicksort(char *items, int len}


(
qs(items, О, len-l);

1/Рекурсивная версия алгоритма Quicksort ДЛЯ сорТироЬКИ


1/ символов.
void qs(char *items, int left, int right)
{
int i, j;
char х, У;

i left; j = right;
х = items [( left+right) / 2 ];

do (
while«items[i] < х) && (i < right» i++;
while«x < items[j]) && (j > left)) j--;

i f (i <= j)
у = items[i];
items[i] = items[j];
Неmз [j] = У;
i++; j--;

while (i <= j);

if(left < j) qs(items, left, j);


if(i < right) qs(items, i, right);

При выполнении эта программа генерирует такие результаТЬi.

Массив в исходном порядке: jfmckldoelazlkper


Отсортированный массив: acdeefjkklllmoprz
С++: РУКОВОДСТВО МЯ начинающих 259

Спросим у опытноrо проrраммиста

Вопрос. Н СЛWШIlJl, чmo сущесm.уеm nptl_ШlО "IID умолчаНIIЮ тlln in t ". В '101 0110
сосmoll", 11 пplLtleHlIelllCll ли к С++?

Ответ. В языке С (да и 8 ранних версиях С++), если в каком-нибудь объявлении


не был указан спецификатор типа, то подразумевался тип i n t. Например,
в С-коде (или в старом С++-коде) следующая функция была бы допусrn­
ма и возвращала результат типа int.
f() ( 1/ По умолчанию для значения,
11 возвращаемого функцией, подразумевается
11 тип int.
{
iлt Х;

1/ ...
return х;

Здесь Тlm значения, возвращаемого функцией f ( ) , ПРИllимается равным


int, поскольку никакой дрyrой rnп не задан. Однако правило "по умол­
чанию ТИI1 iлt" (или правило "неЯDНОГО типа iлt") "е поддеРЖИl!астся
совремеННЫМII веРСIIЯМИ С++. И хотя БОЛЬШIIНСТВО компиляторов долж­
но поддерживать это лрав~LЛО ради обратной совместимости, вам следует
явно задавать ПIП значений, возвращаемых всеми ФУНКЦИЯМII, которые вы
определяете. При модернизаЦИII старого кода это обстоятельство также
следует иметь в I!~IДY.

• JeCTnA4Sl.CQMQКD~OASI QQ.МОАJfAю.5
1. Представьте общий формат Функr.rIIИ.
2. Создайте функцию с именем hypot (), которая вычисляет длину гипоте­
нузы ПРЯМОУГОЛЬНОI'О треугольника, заданного длинами двух противопо­

ложных сторон. Продемонстрируйте применение этой ФУНКlIИИ n програм­


ме. ДЛЯ решения этой задачи воспользуйтесь стандартной библиотечной
функцией, которая возвращает значение квадратного корня из аргумента.
Вот ее прототип, '
double sqrt (double val) ;
Использование функции sqrt () требует включения в программу зarолов­
ка <cmath>.
260 МОДУЛЬ 5. Введение в функции
................................................................................................................................

3. Может ли функция возвращать указатель? Может ли функция возврawать


массив?

4. Создайте собственную версию стандартной библиотечной функции str-


len (). J:lазовите свою версию mystrlen () и продемонстрируйте ее при­
менение в программе.

5. Поддерживает ли локальная переменная свое значение между вызовами


функции, в которой Оllа объявлена?
6. Назовите одно достоинство и один недостаток глобальных перемснных.
7. Создайте ФУНКЦИЮ с именем byThrees () , которая возвращает ряд чисел,
в котором каждос следующее значение на 3 больше прсдыдущего. Пусть
этот ряд начинается с числа о. ТЮ<ИМ образом, первыми пятью членами
ряда, которые возuраш.ает функция byThrees () ,должны быть числа 0,3,
6,9 н 12. Создаi1те еще ОДIlУ ФУIIКЦИЮ С IIменсм reset ( ) , которая заставит
функцию byThrees () снова наЧИllать генерируемый ею ряд ~П.fсел с нуля.
Продемонстрируйте I1рименение этих функций D программе. Подскззка:
здесь нужно использовать глобальную леременную.

8. Напишите программу, которая запрашивает пароЛ1), задаваемый в команд­


ной строке. Ваша программа не ДОЛЖJlа реально выполнять какие-либо
действия, за исключением выдачи сообщения о том, корректно ли был вве­
дсн пароль или пст.

9. ПРОТОТИП функции предотвращает ее IIЫЗОВ с неверно задаНIiЫМ КQ;lН1че­


ством аргументов. Тш< ШI это?

10. Напишите реl<УРСIIВНУЮ функцию, которая выводит числа от 1 до 10.


Продемонстрируiiтс се применеПI1С в l1рограмме.

""- Ответы на эти вопросы можно найти на Web-странице данной


Наэаметку" " книги по адресу: http://www . osborne. сот.
Модуль6
о фУНI<ЦИЯХ подробнее

6.1. Два способа передачи аргументов


6.2. Как в С++ реали:ювана передача аргументов
6.3. ИСlIоль:ювание укаэателя для обеспечения вызова по ссылке
6.4. ССЫЛОЧII ыс параметры
6.5. Возврат ссылок
6.6. НезаВИСlfмые ссылки
6.7. Перегруэка Функний
6.8. Аргументы, передаllаемыс функции по умолчанию
6.9. Перегрузка функций и неодно:шаЧIlОСТЬ
262 МОДУЛЬ 6. О ФУНI<ЦИЯХ подробнее
..................................................................................................................................

в этом модуле мы продолжим изучение ФУНКЦИЙ, рассматривая три самые 8.1Ж­


ные темы, связанные с ФУНКЦИЯМИ С++: ссылки, перегрузка Функций и использова­
ние аргументов по умолчанию. Эти три средства в значительной степени расширяют
8О.."Iможности Функций. Как будет показано ниже, ссылка - это неявный указатель.
Перегрузка функций представляет собой средство, которое позволяет одну фyнкuию
реализовать несколькими способами. причем в каждом случае возможно выполне­
ние отдслыюй задачи. Поэтому есть все основания считать пере грузку ФУНКЦИЙ од­
ним ИЗ способов поддержки пuлиморфизма в С++. Используя возможность задания
apl)'MeHTOB 110 умuлчанию, можно определить Зllа'lеliие для параметра, которое бу­
лет аuтоматически применеllО в случае. если соотвеТСТВУ1QЩИЙ аргумент не задан.
Начнсм с рассмuтреllИЯ лвух способов передачи аргументов функциям и резуль­
'·<1ТОВ их ИСIIOЛЬЗОВalШЯ. Это важно для ПОIiИМ;ШИЯ назначения ссылок. поскольку
IIX IIPIIМCIICIIIIC lС парамстр:ш ФУll1ЩШI - uсновная причина их существования.

ВАЖНОI
f._йДsа..с,lX1COfSa.pереДШlI4,
аргументов
н языках I1рограММИРОВ<lIIИЯ. как правило, IIреДУсматривается два способа.
которыс l1031ЮЛЯЮТ персдавать аргументы в подпрограммы (функции, методы,
ПРОIIСДУf>Ы). Первыii назыrшстся оы.ЭООО_~1 по 31lаченuю (call-by-value). В этом
случае 31ЮЧС1luе аргумснта копируется в фОР\1алЫIЫЙ параметр подпрограммы.
Следовательно. изменения. внесенные в парамстры подпрограммы. не влияют на
аргументы. используемые при ее вызове.

Второй способ передач н аргумента ПОДПрOl·рамме Н<1зывается оызовОА( по С(;ЬИ­


ке (call-by-refel·ence). В этом случае в параметр l<OJшруется адрес аргумента (а не
сго значснис). В пределах вызываемой подпрограммы этот адрес используется
для доступа к реалыюму аргументу, задашlОМУ при ее вызове. Это значит, Чl'О из­
МСllСIШЯ. внесенные в l1араметр. Оl{ажут возд('iicтвие на аргумент. используемый
IlрИ ВЫЗОDС подпрограммы.

ВАЖНОI

~t_ КaJL8 С*ж.реоАИ30ВQИQ передача


аргументов
По умолчанию для передачи аргументов в С++ используется ~feтoa ОЫЗ08а
по 31ШчеIlUЮ. Это означает, что в общем случае код функции не может изменить
аргументы, используемые при вызове ФУНКЦИИ. ВО всех программах этой книги,
С++: PYI(OBOACTBO ДЛЯ начинающих 263
.................................................................................................................................................................................................. ...................................................... .
~

предстзвленных до сих пор, использовался метод вызова по значению. Рассмо­


трим, например, функцию reciprocal () в следующей программе.
// Изменение параметра при вызове по функции по значению
11 не влияет на аргумент.

#include <iostream>
using namespace stdi

double reciprocal(double Х);

int main ()
(
double t = 10.0;

cout « "Обратная величина от числа 10.0 равна»

« reciprocal(t) « "\n";

cout « "Значение переменной t по-прежнему равно: "


« t « "\n"i

return О;

// Функция возвращает обратную величину от


// заданного значения.
double reciprocal(double х)
{
х = 1 / х; 1/ ВЫЧИСЛReN обрат~ величину. (Это действие
11 иихах не отражается на значении перемекиой t
1/ из фунхции main().)

return х;

При выполнении эта программа генерирует такие результаты.

Обратная величина от числа 10.0 равна 0.1


Значение переменной t по-прежнему равно: 10
как подтверждают результаты выполнения этой программы. при выполнении
присваивания

Х = 1 / Х;
.~~.....~~~r~~.~:. <?P.Y.~~~~~~.~?~P.~~!:I.~~ .................................................

в ФУНКЦИИ reciprocal () модиФицируется только переменная х. Переменная


t, используемая в качестве аргумента, по-прежнему содержит значение 10 и не
подвержена влиянию действий, выполняемых в функции reciprocal ( ) .

ДЛЯ обеспечения вызова


по ссылке
Несмотря на то что в ю:\чеСТl:lе C++-соглашеIlИЯ о передаче параметров по УМОЛ­
чаниюдействуст DЫЗОD по ЗШlчеIUlIO, существует возможность "вручную"заменить
еl'O ВЬТЗ0ООМ по ССЫЛlCе. n этm.l случае фушщии будет передаnаться адрес аргумента
(т.е. указатель на apl)'MCHT). Это позволит внутреннему коду ФУllКЦИИ изменить
значение apryмcHTa, которое хранится оне функции. Пример такого "дистанцион­
ного" упраllления значениями перемеННblХ пре;tставлен в предыдущсм модуле rJРИ
рассмотрении возможнос.ти вызова функции с указателями. Как вы знаете, указа­
тели передаются функциям подобно значениям любого другого типа. БеЗУСЛОI\НО,
для этого необходимо оБЪЯDИТЬ параметры с типом указателей.
Чтобы понять, как передача указателя позволяет вручную обеспечить вызов
по ссылке, рассмотрим функцию swap (). (Она меняет значения двух перемен­
ных, на которые указывают ее apГYMeIlTbl.) Вот как выглядит один из способов ее
реализации.

/ / Обмен значениями двух nepeMeHHbI>:, адресуемых


// параметрами х и у.
void swap(int *х, int *у)
(
int temp;

temp == *х; // Временно сохраняем значение,


// расположенное по адресу х.
*х *у; // Помещаем значение, хранимое по адресу у,

// по адресу х.
*у temp; // Помещаем значение, которое раньше
// хранилось по адресу х, по адресу у.

Функция swap () объявляет два параметра-укаэателя (х и у). Она исполь­


зует эти параметры для обмена значений переменных, адресуемых аргумента-
С++: PYI(OBOACTBO дl\fI начинающих 265.
............. ,..................................................................................................................

ми (переданными функции). Не забывайте, что выражения *х и * у обозначают


переменные, адресуемые указателями х и у соответственно. Таким образом, при
ф
выполнении инструкции ф


*х = *у: о

значение объекта, aдpecyeMoro указателем у, помещается в объект, адресуемый g


с:::
указателем х. Следовательно, по завершеиии этой Фуикции содержимое пере­ х
t;;
менных, используемых при се вызове, "поменяется местами". S
:::r
:.:
Поскольку функцияswap () ожидает получить два ука.lателя, вы должны :I:

помнить, что функцию swap () необходимо вызьшзть с адресами переменных, ~


О
значения которых иы хотите обменять. Корректный вызов этой функции проде­
монстрирован в следующей ПРОГJflмме.

// Демонстрируется версия ФУНКЦИИ swap() с использованием


1/ указателей.

#include <iostream>
using namespace std:

// Объявляем ФУНКЦИЮ swap(), которая использует указатели.


void swap(int *х, int *у);

int main ()
{
int i, j;

i 10;
j 20;

cout « "Исходные значения переменных i и j: ";


cout « i « ' , « j « '\n';

swap(&j, &i); // swap() с


Вызываем адресами
// переменных i и j.

cout « "Значения переменных i и j после обмена: ":


cout « i « ' ',« j « '\п':

return О:
}
266 Модуль 6. О ФУНJ(ЦИ~Х подрОбнее

// Обмен значениями двух переменных, адресуемых


// параметрами х и у.
void swap(int *х, int *у)
{
int tempi

// Менкем звачеНИR переиевиых, адресуемых


// паракетраии х и у, испоnьзук явно задаиные
// операции с ухазателкми.
temp = *х; // Временно сохраняем значение,
// расположенное по адресу х.
*х *у; // Помещаем значение, хранимое по адресу у,
// по адресу х.
*у temp; // Помещаем значение, которое раньше
// хранилось по адресу х, по адресу у.

в функции main () переменной i было ПРИСlюено начальное значение 10, а п('ре­


менной j - 20. Затем была вызвана функция swap () с адресами переменныx i н j.
Для получеJiия адресов здесь используется унарный оператор "&". Следователt.tIО,
функции swap () при вызове были переданы адреса переменных i и j, а не их зна­
чения. После ВЫJJOлtlения функции swap () псрсменныс i и j ФбменЯJШСЪ своими
значения~и, что подтверждается результатами выполнения этой программы.

Исходные значения переменных i и j: 10 20


Значения переменных i и j после обмена: 20 10

~npocы ДМ1 текущего контроля _______._


1. Поясните суть передачи функции пара.\tетров по значению.

2. Поясните суть персдачи фушщии парамстров по ссылке.

- ч -
З. I<ц:ой механизм передачи параметров используется в С++ по умолчанию?·
..
1. При передаче функции параметров ПО значению (т.е. при вызове по значению) зна­
чения аргументов копируются 8 параметры подпрограммы.

2. При передаче фующии параметров по ссылке (т.е. при вызове по ссылке) в naраме­
тры подпроrpаммы КОШiРУIOТСЯ адреса аргументов.

З. По умолчанию в С++ используется вызов по значению.


С++: руководство ДМI начинающих 267

ф
ф
:I:
Несмотря на возможность "вручную" организовать вызов по ссылке с помощью \о
О
оператора получения адреса. такой подход не всегда удобен. Во-первых, он вынужда­
ет про~миста въmолнять все операции с использованием ука:iате..lеЙ. Во-вторых,
g
~

вызывая функцию, программист должен не забыть передать ей адреса apryMcHTOB, х


t;:
:s:
а не их значения. К счастью, в С++ МОЖIlО сориентировать l<ОМПЮl}lТОР на автома­ ::r
тическое использование вызова по ссылке (вместо вызова по значению) ДЛ}I ОДIIОГО '"
::r:
>-
-€т
или нескольких параметров l<ОШСрСТНОЙ функции. Такая возможность реализуется с О
помощью ссылочного параметра (гefeгence parameteг). При ИСПОЛf>30вании ссылоч­
ного параметра функции автоматически передается адрес (а не значение) аргумента.
При выполнении lсода фушщин (при выполнеllИИ опеР,Щllllllал: ссы./roчным паРй:\IС­
ТРОМ) обеспечивается его автоматическое раЭЫМСIlОl]аllие, н поэтому IIрограммнсту
не нужно при6егать к операторам, ИСПОЛЬ3УС:\IЫМ с указателями.
Ссылочный параметр объявляется с. помощью символа" & ", который должен
предшествовать имею~ параметра в объявлении функции. Операuии, выполняе­
мые Над ссылочным параметром, Оlшзывают влияние на аргумент, используемый
при вызове функции, а не на сам ссылоч.ный нараметр.
Чтобы лучше понять механизм дейспшя ссылочных параметров, рассмотрим
для начала простой пример. В следующей программе функция f () принимает
один ссылочный параметр типа int.
// Использование ссылочного параметра.

#inc1ude <iostream>
using namespace std;

void f(int &i); / / Здесь i объявnRеТСR как СCWnОЧRblЙ паракетр.

int main ()
(
int val = 1;
cout « "Старое значение переменной "а1: .. « va1
« '\п';
f(va1); 1/ Передаем адрес переменной va1 ФУНКЦИИ f().
cout « "Новое значение переменной val: " « val
« '\п';
return О;
268 МОДУЛЬ 6. О функциS1Х подробнее

void f(int &i)

i = 10; // МодифИХ&ЦИR арryи.в~а, sаданвоrо при ВНЗОВ •.

При выполнении этой программы получаем такой результат.

Старое значение переменной val: 1


Новое значение переменной val: 10
Обратите особое внимание на определение функции f ( ) .

void f(int &i)

i = 10; // Модификация аргумента, заданного при вызове.

Итак, рассмотрим оБЪЯllJlСllllе параметра i. Его IIмеllИ I1редшествует сиМl;ОЛ


"&", который "прсвращает"переменную i в ссылочный параметр. (Это обышле­
ние также используется в прототипе функции.) Инструкция

i = 10;
(в данном случае она одна составляет тело функции) не присваивает перемешюй
i значение 10. В действительности значение 10 присваивастся переменной, на
которую ссылается переменная i (в нашей ПрОl'раммс ею является персмсшшя
val). Обратите внимание на то, что в ЭТQJ':'I инструкции нет оператора "*". ко­
торый используется при работе с указателями. Применяя ссылочный llараМС1'Р,
вы тем caMbiM уведомляете С++-компилятор о передаче адреса (т.с. указателя),
и компилятор автоматически разымеиовьшаст его за вас. Более тоl'о. если бы вы
попытались '·помочь" компилятору, использовав оператор "*". то сразу же nO.'IY-
чили бы сообщение об ошибке.
Поскольку перемснная i была объявлена как ссылочный параметр, КОМПП.'Iя­
тор автоматически передает функции f () адрес аргумента, с которым вызывает­
ся эта функция. Таким образом, в фушщии та:..п () инструrщия

f(va1); // Передаем адрес переменно;'1 va1 функции f().


передает функции f () адрес переменной val (а не ее ЗШI.ЧСIIИС). Обратите 811И­
мание на то, что при вызове фуИIЩИИ f () не IfУЖНО предварять перемеюrую vа 1
оператором "&". (Более того, это было бы ошибкой.) ПОСКОЛЬКУ функция f () по­
лучает адрес переменной val в форме ссылки, она может модифицировать зна­
чение этой переменной.
Чтобы проиллюстрировать реальное применение ссылочных параметров
(и тем самым продемонстрировать их ДОСТШlllства), перепишем нашу старую
знакомую функцию swap () с использованием ссылок. В следующей программе
обратите внимание на то, как функция swap () объявляется и вызывается.
С++: руководство ДМl начинающих 269

// Использование ссылочных параметров в Функции swap().

iinclude <iostream>
using namespace stdi

// Объявляем функцию swap() с использованием ссылочных


// параметров.
void swap(int &х, int &У);

int main ()
{
il1t i, ji

i 10;
j 20;

cout « " Исходные значения переменных i и j: ";


cout « i « ' , « j « '\n';
swap(j, i); // Здесь фУНКЦИИ swap() автоматически
// передаатCR адреса перемевиwx i и j.

cout « " Значения переменных i и j после обмена: ";


cout « i « ' , « j « '\n';

return О;

/* Здесь функция swap() определяется с использованием


вызова по ссылке, а не вызова по значению. Поэтому она

может выполнить обмен значениями двух аргументов, с

которыми она вызывается.

*/
void swap(int &х, int &у)
[
int tempi

// Для обмена значениями аргументов используем


// ссылки на них.
temp = х;
х У; // Теперь обмен значениями происходит
// автоматически посредствоы ссылок.
у tempi
270 Модуль 6. О функци~х подробнее

Результаты выполнения этой программы совпадают с результатами запуска